Sync module files
This commit is contained in:
parent
0db69f901c
commit
6c8ca654bc
10 changed files with 1409 additions and 2171 deletions
|
@ -353,20 +353,20 @@ config OVMS_VEHICLE_CAN_RX_QUEUE_SIZE
|
|||
help
|
||||
The size of the CAN bus RX queue (at the vehicle component).
|
||||
|
||||
config OVMS_VEHICLE_RENAULTZOE_PH2_OBD
|
||||
bool "Include support for Renault Zoe PH2 vehicles via OBD port (read-only)"
|
||||
default y
|
||||
depends on OVMS
|
||||
help
|
||||
Enable to include support for Renault Zoe Ph2 vehicles via OBD port (read-only).
|
||||
|
||||
config OVMS_VEHICLE_RENAULTZOE_PH2_CAN
|
||||
bool "Include support for Renault Zoe PH2 vehicles via direct CAN access after Core Can Gateway"
|
||||
default y
|
||||
depends on OVMS
|
||||
help
|
||||
Enable to include support for Renault Zoe Ph2 vehicles via direct CAN access after Core Can Gateway.
|
||||
|
||||
config OVMS_VEHICLE_RENAULTZOE_PH2_OBD
|
||||
bool "Include support for Renault Zoe PH2 vehicles via OBD port (read-only)"
|
||||
default y
|
||||
depends on OVMS
|
||||
help
|
||||
Enable to include support for Renault Zoe Ph2 vehicles via OBD port (read-only).
|
||||
|
||||
config OVMS_VEHICLE_RENAULTZOE_PH2_CAN
|
||||
bool "Include support for Renault Zoe PH2 vehicles via direct CAN access after Core Can Gateway"
|
||||
default y
|
||||
depends on OVMS
|
||||
help
|
||||
Enable to include support for Renault Zoe Ph2 vehicles via direct CAN access after Core Can Gateway.
|
||||
|
||||
endmenu # Vehicle Support
|
||||
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ static const char *TAG = "boot";
|
|||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/xtensa_api.h"
|
||||
#include "rom/rtc.h"
|
||||
#include "rom/uart.h"
|
||||
#include "soc/rtc_cntl_reg.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_panic.h"
|
||||
|
@ -165,10 +166,18 @@ void boot_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc,
|
|||
writer->printf("\nLast crash: ");
|
||||
if (boot_data.crash_data.is_abort)
|
||||
{
|
||||
// Software controlled panic:
|
||||
writer->printf("abort() was called on core %d\n", boot_data.crash_data.core_id);
|
||||
if (boot_data.stack_overflow_taskname[0])
|
||||
writer->printf(" Stack overflow in task %s\n", boot_data.stack_overflow_taskname);
|
||||
else if (boot_data.curr_task[0].name[0] && !boot_data.curr_task[0].stackfree)
|
||||
writer->printf(" Pending stack overflow in task %s\n", boot_data.curr_task[0].name);
|
||||
else if (boot_data.curr_task[1].name[0] && !boot_data.curr_task[1].stackfree)
|
||||
writer->printf(" Pending stack overflow in task %s\n", boot_data.curr_task[1].name);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hardware exception:
|
||||
int exccause = boot_data.crash_data.reg[19];
|
||||
writer->printf("%s exception on core %d\n",
|
||||
(exccause < NUM_EDESCS) ? edesc[exccause] : "Unknown", boot_data.crash_data.core_id);
|
||||
|
@ -176,18 +185,29 @@ void boot_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc,
|
|||
for (int i=0; i<24; i++)
|
||||
writer->printf(" %s: 0x%08lx%s", sdesc[i], boot_data.crash_data.reg[i], ((i+1)%4) ? "" : "\n");
|
||||
}
|
||||
|
||||
for (int core = 0; core < portNUM_PROCESSORS; core++)
|
||||
{
|
||||
if (boot_data.curr_task[core].name[0])
|
||||
writer->printf(" Current task on core %d: %s, %u stack bytes free\n",
|
||||
core, boot_data.curr_task[core].name, boot_data.curr_task[core].stackfree);
|
||||
}
|
||||
|
||||
writer->printf(" Backtrace:\n ");
|
||||
for (int i=0; i<OVMS_BT_LEVELS && boot_data.crash_data.bt[i].pc; i++)
|
||||
writer->printf(" 0x%08lx", boot_data.crash_data.bt[i].pc);
|
||||
|
||||
if (boot_data.curr_event_name[0])
|
||||
{
|
||||
writer->printf("\n Event: %s@%s %u secs", boot_data.curr_event_name, boot_data.curr_event_handler,
|
||||
boot_data.curr_event_runtime);
|
||||
}
|
||||
|
||||
if (MyBoot.GetResetReason() == ESP_RST_TASK_WDT)
|
||||
{
|
||||
writer->printf("\n WDT tasks: %s", boot_data.wdt_tasknames);
|
||||
}
|
||||
|
||||
writer->printf("\n Version: %s\n", StdMetrics.ms_m_version->AsString("").c_str());
|
||||
writer->printf("\n Hardware: %s\n", StdMetrics.ms_m_hardware->AsString("").c_str());
|
||||
}
|
||||
|
@ -265,14 +285,12 @@ Boot::Boot()
|
|||
boot_data.boot_count++;
|
||||
ESP_LOGI(TAG, "Boot #%d reasons for CPU0=%d and CPU1=%d",boot_data.boot_count,cpu0,cpu1);
|
||||
|
||||
m_resetreason = boot_data.reset_hint;
|
||||
ESP_LOGI(TAG, "Reset reason %s (%d)", GetResetReasonName(), GetResetReason());
|
||||
|
||||
if (boot_data.soft_reset)
|
||||
{
|
||||
boot_data.crash_count_total = 0;
|
||||
boot_data.crash_count_early = 0;
|
||||
m_bootreason = BR_SoftReset;
|
||||
m_resetreason = ESP_RST_SW;
|
||||
ESP_LOGI(TAG, "Soft reset by user");
|
||||
}
|
||||
else if (boot_data.firmware_update)
|
||||
|
@ -281,6 +299,7 @@ Boot::Boot()
|
|||
boot_data.crash_count_early = 0;
|
||||
m_bootreason = BR_FirmwareUpdate;
|
||||
ESP_LOGI(TAG, "Firmware update reset");
|
||||
m_resetreason = ESP_RST_SW;
|
||||
}
|
||||
else if (!boot_data.stable_reached)
|
||||
{
|
||||
|
@ -288,24 +307,33 @@ Boot::Boot()
|
|||
boot_data.crash_count_early++;
|
||||
m_bootreason = BR_EarlyCrash;
|
||||
ESP_LOGE(TAG, "Early crash #%d detected", boot_data.crash_count_early);
|
||||
m_resetreason = boot_data.reset_hint;
|
||||
ESP_LOGI(TAG, "Reset reason %s (%d)", GetResetReasonName(), GetResetReason());
|
||||
}
|
||||
else
|
||||
{
|
||||
boot_data.crash_count_total++;
|
||||
m_bootreason = BR_Crash;
|
||||
ESP_LOGE(TAG, "Crash #%d detected", boot_data.crash_count_total);
|
||||
m_resetreason = boot_data.reset_hint;
|
||||
ESP_LOGI(TAG, "Reset reason %s (%d)", GetResetReasonName(), GetResetReason());
|
||||
}
|
||||
}
|
||||
|
||||
m_crash_count_early = boot_data.crash_count_early;
|
||||
m_stack_overflow = boot_data.stack_overflow;
|
||||
if (!m_stack_overflow)
|
||||
boot_data.stack_overflow_taskname[0] = 0;
|
||||
|
||||
boot_data.bootreason_cpu0 = cpu0;
|
||||
boot_data.bootreason_cpu1 = cpu1;
|
||||
boot_data.reset_hint = ESP_RST_UNKNOWN;
|
||||
|
||||
// reset flags:
|
||||
boot_data.soft_reset = false;
|
||||
boot_data.firmware_update = false;
|
||||
boot_data.stable_reached = false;
|
||||
boot_data.stack_overflow = false;
|
||||
|
||||
boot_data.crc = boot_data.calc_crc();
|
||||
|
||||
|
@ -351,6 +379,8 @@ const char* Boot::GetBootReasonName()
|
|||
|
||||
const char* Boot::GetResetReasonName()
|
||||
{
|
||||
if (m_stack_overflow)
|
||||
return "Stack overflow";
|
||||
return (m_resetreason >= 0 && m_resetreason < NUM_RESETREASONS)
|
||||
? resetreason_name[m_resetreason]
|
||||
: "Unknown reset reason";
|
||||
|
@ -448,6 +478,52 @@ bool Boot::IsShuttingDown()
|
|||
return m_shutting_down;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Direct UART output utils borrowed from esp32/panic.c
|
||||
*/
|
||||
static void panicPutChar(char c)
|
||||
{
|
||||
while (((READ_PERI_REG(UART_STATUS_REG(CONFIG_CONSOLE_UART_NUM)) >> UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT) >= 126) ;
|
||||
WRITE_PERI_REG(UART_FIFO_REG(CONFIG_CONSOLE_UART_NUM), c);
|
||||
}
|
||||
|
||||
static void panicPutStr(const char *c)
|
||||
{
|
||||
int x = 0;
|
||||
while (c[x] != 0)
|
||||
{
|
||||
panicPutChar(c[x]);
|
||||
x++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is called by task_wdt_isr function (ISR for when TWDT times out).
|
||||
* It can be redefined in user code to handle twdt events.
|
||||
* Note: It has the same limitations as the interrupt function.
|
||||
* Do not use ESP_LOGI functions inside.
|
||||
*/
|
||||
extern "C" void esp_task_wdt_isr_user_handler(void)
|
||||
{
|
||||
panicPutStr("\r\n[OVMS] ***TWDT***\r\n");
|
||||
// Save TWDT task info:
|
||||
esp_task_wdt_get_trigger_tasknames(boot_data.wdt_tasknames, sizeof(boot_data.wdt_tasknames));
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is called if FreeRTOS detects a stack overflow.
|
||||
*/
|
||||
extern "C" void vApplicationStackOverflowHook( TaskHandle_t xTask, signed char *pcTaskName )
|
||||
{
|
||||
panicPutStr("\r\n[OVMS] ***ERROR*** A stack overflow in task ");
|
||||
panicPutStr((char *)pcTaskName);
|
||||
panicPutStr(" has been detected.\r\n");
|
||||
strlcpy(boot_data.stack_overflow_taskname, (const char*)pcTaskName, sizeof(boot_data.stack_overflow_taskname));
|
||||
boot_data.stack_overflow = true;
|
||||
abort();
|
||||
}
|
||||
|
||||
void Boot::ErrorCallback(XtExcFrame *frame, int core_id, bool is_abort)
|
||||
{
|
||||
boot_data.reset_hint = ovms_reset_reason_get_hint();
|
||||
|
@ -495,8 +571,28 @@ void Boot::ErrorCallback(XtExcFrame *frame, int core_id, bool is_abort)
|
|||
boot_data.curr_event_runtime = 0;
|
||||
}
|
||||
|
||||
// Save TWDT task info:
|
||||
esp_task_wdt_get_trigger_tasknames(boot_data.wdt_tasknames, sizeof(boot_data.wdt_tasknames));
|
||||
// Save current tasks:
|
||||
panicPutStr("\r\n[OVMS] Current tasks: ");
|
||||
for (int core=0; core<portNUM_PROCESSORS; core++)
|
||||
{
|
||||
TaskHandle_t task = xTaskGetCurrentTaskHandleForCPU(core);
|
||||
if (task)
|
||||
{
|
||||
char *name = pcTaskGetTaskName(task);
|
||||
uint32_t stackfree = uxTaskGetStackHighWaterMark(task);
|
||||
if (core > 0) panicPutChar('|');
|
||||
panicPutStr(name);
|
||||
strlcpy(boot_data.curr_task[core].name, name, sizeof(boot_data.curr_task[core].name));
|
||||
boot_data.curr_task[core].stackfree = stackfree;
|
||||
if (!stackfree) boot_data.stack_overflow = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
boot_data.curr_task[core].name[0] = 0;
|
||||
boot_data.curr_task[core].stackfree = 0;
|
||||
}
|
||||
}
|
||||
panicPutStr("\r\n");
|
||||
|
||||
boot_data.crc = boot_data.calc_crc();
|
||||
}
|
||||
|
@ -514,6 +610,8 @@ void Boot::NotifyDebugCrash()
|
|||
// ,<curr_event_name>,<curr_event_handler>,<curr_event_runtime>
|
||||
// ,<wdt_tasknames>
|
||||
// ,<hardware_info>
|
||||
// ,<stack_overflow_task>
|
||||
// ,<core0_task>,<core0_stackfree>,<core1_task>,<core1_stackfree>
|
||||
|
||||
StringWriter buf;
|
||||
buf.reserve(2048);
|
||||
|
@ -557,6 +655,20 @@ void Boot::NotifyDebugCrash()
|
|||
buf.append(",");
|
||||
buf.append(mp_encode(StdMetrics.ms_m_hardware->AsString("")));
|
||||
|
||||
// Stack overflow task:
|
||||
std::string name = boot_data.stack_overflow_taskname;
|
||||
buf.append(",");
|
||||
buf.append(mp_encode(name));
|
||||
|
||||
// Current tasks:
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
name = boot_data.curr_task[i].name;
|
||||
buf.append(",");
|
||||
buf.append(mp_encode(name));
|
||||
buf.printf(",%u", boot_data.curr_task[i].stackfree);
|
||||
}
|
||||
|
||||
MyNotify.NotifyString("data", "debug.crash", buf.c_str());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,12 @@ typedef struct
|
|||
} bt[OVMS_BT_LEVELS];
|
||||
} crash_data_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char name[16];
|
||||
uint32_t stackfree;
|
||||
} task_info_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// data consistency:
|
||||
|
@ -83,6 +89,9 @@ typedef struct
|
|||
char curr_event_handler[16]; // … MyEvents.m_current_callback->m_caller
|
||||
uint16_t curr_event_runtime; // … monotonictime-MyEvents.m_current_started
|
||||
char wdt_tasknames[32]; // Pipe (|) separated list of the tasks that triggered the TWDT
|
||||
bool stack_overflow;
|
||||
char stack_overflow_taskname[16];
|
||||
task_info_t curr_task[portNUM_PROCESSORS];
|
||||
} boot_data_t;
|
||||
|
||||
extern boot_data_t boot_data;
|
||||
|
@ -128,6 +137,7 @@ class Boot
|
|||
bootreason_t m_bootreason;
|
||||
esp_reset_reason_t m_resetreason;
|
||||
unsigned int m_crash_count_early;
|
||||
bool m_stack_overflow;
|
||||
};
|
||||
|
||||
extern Boot MyBoot;
|
||||
|
|
|
@ -718,12 +718,25 @@ OvmsConfigParam* OvmsConfig::CachedParam(std::string param)
|
|||
return *p;
|
||||
}
|
||||
|
||||
/**
|
||||
* ProtectedPath: `true` if the path is protected (e.g. config)
|
||||
* - Note: path is canonicalized before comparison, in case the path
|
||||
* does not exist in the filesystem, the result will be `false`
|
||||
* (i.e.: not protected).
|
||||
*/
|
||||
bool OvmsConfig::ProtectedPath(std::string path)
|
||||
{
|
||||
#ifdef CONFIG_OVMS_DEV_CONFIGVFS
|
||||
return false;
|
||||
#else
|
||||
return (path.find(OVMS_CONFIGPATH) != std::string::npos);
|
||||
char canonical_path[PATH_MAX];
|
||||
char * result = realpath(path.c_str(), canonical_path);
|
||||
if (NULL == result) {
|
||||
// If error during path resolution, we consider it as not protected
|
||||
return false;
|
||||
}
|
||||
std::string resolved_path(canonical_path);
|
||||
return (resolved_path.find(OVMS_CONFIGPATH) != std::string::npos);
|
||||
#endif // #ifdef CONFIG_OVMS_DEV_CONFIGVFS
|
||||
}
|
||||
|
||||
|
|
|
@ -46,4 +46,8 @@
|
|||
#define ESP_LOGD( tag, format, ... ) esp_log_write(ESP_LOG_DEBUG, tag, LOG_FORMAT(D, format), esp_log_timestamp(), tag, ##__VA_ARGS__)
|
||||
#define ESP_LOGV( tag, format, ... ) esp_log_write(ESP_LOG_VERBOSE, tag, LOG_FORMAT(V, format), esp_log_timestamp(), tag, ##__VA_ARGS__)
|
||||
|
||||
#define ESP_BUFFER_LOGI( tag, buffer, buff_len) esp_log_buffer_hexdump_internal( tag, buffer, buff_len, ESP_LOG_INFO)
|
||||
#define ESP_BUFFER_LOGD( tag, buffer, buff_len) esp_log_buffer_hexdump_internal( tag, buffer, buff_len, ESP_LOG_DEBUG)
|
||||
#define ESP_BUFFER_LOGV( tag, buffer, buff_len) esp_log_buffer_hexdump_internal( tag, buffer, buff_len, ESP_LOG_VERBOSE)
|
||||
|
||||
#endif //#ifndef __OVMS_LOG_H__
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -38,6 +38,7 @@
|
|||
#include "ovms_events.h"
|
||||
#include "metrics_standard.h"
|
||||
#include "ovms_version.h"
|
||||
#include "esp_idf_version.h"
|
||||
|
||||
/**
|
||||
* chargestate_code: convert legacy chargestate key to code
|
||||
|
@ -628,3 +629,100 @@ bool get_buff_string(const uint8_t *data, uint32_t size, uint32_t index, uint32_
|
|||
strret.assign<const char *>(begin, end);
|
||||
return true;
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 0)
|
||||
/**
|
||||
* realpath: this implementation is from (a newer version of) newlib, in particular
|
||||
* https://github.com/espressif/esp-idf/commit/d83ce227aa987d648e1a0dddba32b88846374815#diff-91b1abbc42db113c5dcf9c11c594f0b3e75cae5f6dfba9ef8b68e26bbd422f4a
|
||||
* - Note: strchrnul has been replaced with strchr as it's also non implemented
|
||||
* in our versions.
|
||||
*/
|
||||
char * realpath(const char *file_name, char *resolved_name)
|
||||
{
|
||||
char * out_path = resolved_name;
|
||||
if (out_path == NULL) {
|
||||
/* allowed as an extension, allocate memory for the output path */
|
||||
out_path = (char *)malloc(PATH_MAX);
|
||||
if (out_path == NULL) {
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* canonical path starts with / */
|
||||
strlcpy(out_path, "/", PATH_MAX);
|
||||
|
||||
/* pointers moving over the input and output path buffers */
|
||||
const char* in_ptr = file_name;
|
||||
char* out_ptr = out_path + 1;
|
||||
/* number of path components in the output buffer */
|
||||
size_t out_depth = 0;
|
||||
|
||||
|
||||
while (*in_ptr) {
|
||||
/* "path component" is the part between two '/' path separators.
|
||||
* locate the next path component in the input path:
|
||||
*/
|
||||
const char* end_of_path_component = strchr(in_ptr, '/');
|
||||
if (NULL == end_of_path_component) {
|
||||
end_of_path_component = in_ptr + strlen(in_ptr);
|
||||
}
|
||||
size_t path_component_len = end_of_path_component - in_ptr;
|
||||
|
||||
if (path_component_len == 0 ||
|
||||
(path_component_len == 1 && in_ptr[0] == '.')) {
|
||||
/* empty path component or '.' - nothing to do */
|
||||
} else if (path_component_len == 2 && in_ptr[0] == '.' && in_ptr[1] == '.') {
|
||||
/* '..' - remove one path component from the output */
|
||||
if (out_depth == 0) {
|
||||
/* nothing to remove */
|
||||
} else if (out_depth == 1) {
|
||||
/* there is only one path component in output;
|
||||
* remove it, but keep the leading separator
|
||||
*/
|
||||
out_ptr = out_path + 1;
|
||||
*out_ptr = '\0';
|
||||
out_depth = 0;
|
||||
} else {
|
||||
/* remove last path component and the separator preceding it */
|
||||
char * prev_sep = strrchr(out_path, '/');
|
||||
assert(prev_sep > out_path); /* this shouldn't be the leading separator */
|
||||
out_ptr = prev_sep;
|
||||
*out_ptr = '\0';
|
||||
--out_depth;
|
||||
}
|
||||
} else {
|
||||
/* copy path component to output; +1 is for the separator */
|
||||
if (out_ptr - out_path + 1 + path_component_len > PATH_MAX - 1) {
|
||||
/* output buffer insufficient */
|
||||
errno = E2BIG;
|
||||
goto fail;
|
||||
} else {
|
||||
/* add separator if necessary */
|
||||
if (out_depth > 0) {
|
||||
*out_ptr = '/';
|
||||
++out_ptr;
|
||||
}
|
||||
memcpy(out_ptr, in_ptr, path_component_len);
|
||||
out_ptr += path_component_len;
|
||||
*out_ptr = '\0';
|
||||
++out_depth;
|
||||
}
|
||||
}
|
||||
/* move input pointer to separator right after this path component */
|
||||
in_ptr += path_component_len;
|
||||
if (*in_ptr != '\0') {
|
||||
/* move past it unless already at the end of the input string */
|
||||
++in_ptr;
|
||||
}
|
||||
}
|
||||
return out_path;
|
||||
|
||||
fail:
|
||||
if (resolved_name == NULL) {
|
||||
/* out_path was allocated, free it */
|
||||
free(out_path);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -351,7 +351,7 @@ INT sign_extend( UINT uvalue, uint8_t signbit)
|
|||
UINT signmask = UINT(1U) << signbit;
|
||||
if ( newuvalue & signmask)
|
||||
newuvalue |= ~ (static_cast<uint_t>(signmask) - 1);
|
||||
return reinterpret_cast<INT &>(uvalue);
|
||||
return reinterpret_cast<INT &>(newuvalue);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -365,7 +365,7 @@ INT sign_extend( UINT uvalue)
|
|||
uint_t newuvalue = uvalue;
|
||||
if ( newuvalue & ( UINT(1U) << SIGNBIT) )
|
||||
newuvalue |= ~((uint_t(1U) << SIGNBIT) - 1);
|
||||
return reinterpret_cast<INT &>(uvalue);
|
||||
return reinterpret_cast<INT &>(newuvalue);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -51,64 +51,145 @@ static const char *TAG = "vfs";
|
|||
#include "vfsedit.h"
|
||||
#endif // #ifdef CONFIG_OVMS_COMP_EDITOR
|
||||
|
||||
void vfs_ls(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
||||
{
|
||||
/**
|
||||
* This class is a kind of wrapper around dirent
|
||||
*/
|
||||
class Direntry {
|
||||
public:
|
||||
std::string basepath; // base path of the file / directory (parent dir) - always ending with '/'
|
||||
std::string name; // name of the file / dirctory
|
||||
bool is_dir; // `true`: is a directory, `false`: is a file
|
||||
bool is_protected; // `true`: this path is protected
|
||||
bool is_skip; // `true`: we should not analyze this entry
|
||||
int64_t size; // size
|
||||
time_t mtime; // modification time
|
||||
|
||||
/**
|
||||
* Default constructor, calls stat() on the file.
|
||||
*/
|
||||
Direntry(std::string basepath, struct dirent *direntry) {
|
||||
struct stat st;
|
||||
this->basepath = basepath + '/';
|
||||
name = direntry->d_name;
|
||||
is_protected = MyConfig.ProtectedPath(path());
|
||||
stat(path().c_str(), &st);
|
||||
is_dir = S_ISDIR(st.st_mode);
|
||||
is_skip = (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) || (direntry->d_name[0] == '.');
|
||||
size = st.st_size;
|
||||
mtime = st.st_mtime;
|
||||
}
|
||||
|
||||
std::string path() const {
|
||||
return basepath + name;
|
||||
}
|
||||
|
||||
bool operator<(const Direntry &other) const {
|
||||
static std::less<std::string> const less;
|
||||
return less(path(), other.path());
|
||||
}
|
||||
|
||||
void display(OvmsWriter* writer, bool show_full_path) {
|
||||
char bufsize[64], mod[64];
|
||||
|
||||
if (is_dir) {
|
||||
if (is_protected) {
|
||||
strcpy(bufsize, "[P][DIR]");
|
||||
} else {
|
||||
strcpy(bufsize, "[DIR] ");
|
||||
}
|
||||
} else {
|
||||
if (is_protected) {
|
||||
strcpy(bufsize, "[P] ");
|
||||
} else {
|
||||
if (size < 1024) {
|
||||
snprintf(bufsize, sizeof(bufsize), "%d ", (int) size);
|
||||
} else if (size < 0x100000) {
|
||||
snprintf(bufsize, sizeof(bufsize), "%.1fk", (double) size / 1024.0);
|
||||
} else if (size < 0x40000000) {
|
||||
snprintf(bufsize, sizeof(bufsize), "%.1fM", (double) size / 1048576);
|
||||
} else {
|
||||
snprintf(bufsize, sizeof(bufsize), "%.1fG", (double) size / 1073741824);
|
||||
}
|
||||
}
|
||||
}
|
||||
strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&mtime));
|
||||
|
||||
const char *slash = is_dir ? "/" : "";
|
||||
|
||||
writer->printf("%8.8s %17.17s %s%s\n", bufsize, mod, show_full_path ? path().c_str() : name.c_str(), slash);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fills a `std::set<Direntry>` with all possible entries starting at `startpath`.
|
||||
* if `recurse` is `true`, then all possible directories are recursively entered.
|
||||
*/
|
||||
static void read_entries(std::set<Direntry> &entries, std::string startpath, bool recurse = true) {
|
||||
DIR *dir;
|
||||
struct dirent *dp;
|
||||
char size[64], mod[64], path[PATH_MAX];
|
||||
struct stat st;
|
||||
|
||||
if (argc == 0)
|
||||
{
|
||||
if ((dir = opendir (".")) == NULL)
|
||||
{
|
||||
writer->puts("Error: VFS cannot open directory listing");
|
||||
return;
|
||||
if ((dir = opendir(startpath.c_str())) != NULL) {
|
||||
while ((dp = readdir(dir)) != NULL) {
|
||||
Direntry dirent(startpath, dp);
|
||||
if (dirent.is_skip) {
|
||||
continue;
|
||||
}
|
||||
entries.insert(dirent);
|
||||
if (recurse && dirent.is_dir && !dirent.is_protected) {
|
||||
read_entries(entries, dirent.path());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MyConfig.ProtectedPath(argv[0]))
|
||||
{
|
||||
writer->puts("Error: protected path");
|
||||
return;
|
||||
}
|
||||
if ((dir = opendir (argv[0])) == NULL)
|
||||
{
|
||||
writer->puts("Error: VFS cannot open directory listing for that directory");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while ((dp = readdir (dir)) != NULL)
|
||||
{
|
||||
snprintf(path, sizeof(path), "%s/%s", (argc==0) ? "." : argv[0], dp->d_name);
|
||||
stat(path, &st);
|
||||
|
||||
int64_t fsize = st.st_size;
|
||||
int is_dir = S_ISDIR(st.st_mode);
|
||||
const char *slash = is_dir ? "/" : "";
|
||||
|
||||
if (is_dir) {
|
||||
strcpy(size, "[DIR] ");
|
||||
} else {
|
||||
if (fsize < 1024) {
|
||||
snprintf(size, sizeof(size), "%d ", (int) fsize);
|
||||
} else if (fsize < 0x100000) {
|
||||
snprintf(size, sizeof(size), "%.1fk", (double) fsize / 1024.0);
|
||||
} else if (fsize < 0x40000000) {
|
||||
snprintf(size, sizeof(size), "%.1fM", (double) fsize / 1048576);
|
||||
} else {
|
||||
snprintf(size, sizeof(size), "%.1fG", (double) fsize / 1073741824);
|
||||
}
|
||||
}
|
||||
strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&st.st_mtime));
|
||||
|
||||
writer->printf("%8.8s %17.17s %s%s\n", size, mod, dp->d_name, slash);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
closedir(dir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills a `std::set<Direntry>` with all possible entries, and display the sorted result.
|
||||
* if `recurse` is `true`:
|
||||
* - all possible directories are recursively entered,
|
||||
* - the display shows the full file path
|
||||
* if `recurse` is `false`:
|
||||
* - only `startpath` is listed,
|
||||
* - the display shows only file / directory name
|
||||
*/
|
||||
static void list_entries(OvmsWriter* writer, std::string startpath, bool recurse = true) {
|
||||
std::set<Direntry> entries;
|
||||
|
||||
while((startpath.back() == '/') || (startpath.back() == '.')) {
|
||||
startpath.pop_back();
|
||||
}
|
||||
|
||||
if (MyConfig.ProtectedPath(startpath)) {
|
||||
writer->puts("Error: protected path");
|
||||
return;
|
||||
}
|
||||
|
||||
read_entries(entries, startpath, recurse);
|
||||
|
||||
for (auto it = entries.begin(); it != entries.end(); it++) {
|
||||
Direntry dirent = *it;
|
||||
dirent.display(writer, recurse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive listing of the files and directories starting at `argv[0]` (or '.')
|
||||
* - all possible directories are recursively entered,
|
||||
* - the display shows the full file / directory path
|
||||
* - protected directories are identified and not entered
|
||||
*/
|
||||
void vfs_rls(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) {
|
||||
list_entries(writer, (argc==0) ? "." : argv[0], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listing of the files and directories at `argv[0]` (or '.')
|
||||
* - the display shows only the file / directory name
|
||||
* - protected directories are identified
|
||||
*/
|
||||
void vfs_ls(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) {
|
||||
list_entries(writer, (argc==0) ? "." : argv[0], false);
|
||||
}
|
||||
|
||||
void vfs_cat(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
||||
{
|
||||
|
@ -505,6 +586,7 @@ VfsInit::VfsInit()
|
|||
|
||||
OvmsCommand* cmd_vfs = MyCommandApp.RegisterCommand("vfs","Virtual File System framework");
|
||||
cmd_vfs->RegisterCommand("ls","VFS Directory Listing",vfs_ls, "[<file>]", 0, 1);
|
||||
cmd_vfs->RegisterCommand("rls","VFS Recursive Directory Listing",vfs_rls, "[<file>]", 0, 1);
|
||||
cmd_vfs->RegisterCommand("cat","VFS Display a file",vfs_cat, "<file>", 1, 1);
|
||||
cmd_vfs->RegisterCommand("head","VFS Display first 20 lines of a file",vfs_head, "[-nrlines] <file>", 1, 2);
|
||||
cmd_vfs->RegisterCommand("stat","VFS Status of a file",vfs_stat, "<file>", 1, 1);
|
||||
|
|
|
@ -169,6 +169,17 @@ void test_watchdog(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc
|
|||
writer->puts("Error: We should never get here");
|
||||
}
|
||||
|
||||
void test_stackoverflow(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
||||
{
|
||||
uint8_t data[256];
|
||||
memset(data, verbosity & 255, sizeof data);
|
||||
writer->printf("Stack bytes remaining: %u\n", uxTaskGetStackHighWaterMark(NULL));
|
||||
vTaskDelay(pdMS_TO_TICKS(250));
|
||||
test_stackoverflow(verbosity+1, writer, cmd, argc, argv);
|
||||
// never reached, just to inhibit tail recursion optimization:
|
||||
writer->printf("%x\n", data[0]);
|
||||
}
|
||||
|
||||
void test_realloc(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
||||
{
|
||||
void* buf;
|
||||
|
@ -385,6 +396,7 @@ TestFrameworkInit::TestFrameworkInit()
|
|||
cmd_test->RegisterCommand("chargen","Character generator",test_chargen,"[<#lines>] [<delay_ms>]",0,2);
|
||||
cmd_test->RegisterCommand("echo", "Test getchar", test_echo);
|
||||
cmd_test->RegisterCommand("watchdog", "Test task spinning (and watchdog firing)", test_watchdog);
|
||||
cmd_test->RegisterCommand("stackoverflow", "Test stack overflow detection (crashes)", test_stackoverflow);
|
||||
cmd_test->RegisterCommand("realloc", "Test memory re-allocations", test_realloc);
|
||||
cmd_test->RegisterCommand("spiram", "Test SPI RAM memory usage", test_spiram);
|
||||
cmd_test->RegisterCommand("strverscmp", "Test strverscmp function", test_strverscmp, "", 2, 2);
|
||||
|
|
Loading…
Reference in a new issue