Compare commits

...

3 commits

15 changed files with 1996 additions and 2376 deletions

View file

@ -38,80 +38,18 @@
#include "ovms_netmanager.h" #include "ovms_netmanager.h"
#include "vehicle_renaultzoe_ph2_can.h" #include "vehicle_renaultzoe_ph2_can.h"
#include "ph2_poller.h"
void OvmsVehicleRenaultZoePh2CAN::CanInit() void OvmsVehicleRenaultZoePh2CAN::CanInit()
{ {
OvmsCommand* cmd; OvmsCommand* cmd;
OvmsCommand* obd; OvmsCommand* obd;
obd = cmd_xrt->RegisterCommand("can", "CAN tools"); obd = cmd_zoe_ph2->RegisterCommand("can", "CAN tools");
cmd = obd->RegisterCommand("request", "Send ISO-TP request, output response"); cmd = obd->RegisterCommand("request", "Send ISO-TP request, output response");
cmd->RegisterCommand("device", "Send ISO-TP request to a ECU", shell_can_request, "<txid> <rxid> <request>", 3, 3); cmd->RegisterCommand("device", "Send ISO-TP request to a ECU", shell_can_request, "<txid> <rxid> <request>", 3, 3);
cmd->RegisterCommand("broadcast", "Send ISO-TP request as broadcast", shell_can_request, "<request>", 1, 1); cmd->RegisterCommand("broadcast", "Send ISO-TP request as broadcast", shell_can_request, "<request>", 1, 1);
} }
void OvmsVehicleRenaultZoePh2CAN::shell_obd_request(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
uint16_t txid = 0, rxid = 0;
string request;
string response;
// parse args:
string device = cmd->GetName();
if (device == "device") {
if (argc < 3) {
writer->puts("ERROR: too few args, need: txid rxid request");
return;
}
txid = strtol(argv[0], NULL, 16);
rxid = strtol(argv[1], NULL, 16);
request = hexdecode(argv[2]);
} else {
if (argc < 1) {
writer->puts("ERROR: too few args, need: request");
return;
}
request = hexdecode(argv[0]);
// "broadcast"
txid = 0x7df;
rxid = 0;
}
// validate request:
if (request.size() == 0) {
writer->puts("ERROR: no request");
return;
} else {
uint8_t type = request.at(0);
if ((POLL_TYPE_HAS_16BIT_PID(type) && request.size() < 3) ||
(POLL_TYPE_HAS_8BIT_PID(type) && request.size() < 2)) {
writer->printf("ERROR: request too short for type %02X\n", type);
return;
}
}
// execute request:
int err = OvmsVehicleRenaultZoePh2CAN->CanRequest(txid, rxid, request, response);
if (err == -1) {
writer->puts("ERROR: timeout waiting for response");
return;
} else if (err) {
writer->printf("ERROR: request failed with response error code %02X\n", err);
return;
}
// output response as hex dump:
writer->puts("Response:");
char *buf = NULL;
size_t rlen = response.size(), offset = 0;
do {
rlen = FormatHexDump(&buf, response.data() + offset, rlen, 16);
offset += 16;
writer->puts(buf ? buf : "-");
} while (rlen);
if (buf)
free(buf);
}
int OvmsVehicleRenaultZoePh2CAN::CanRequest(uint16_t txid, uint16_t rxid, string request, string& response, int timeout_ms /*=3000*/) int OvmsVehicleRenaultZoePh2CAN::CanRequest(uint16_t txid, uint16_t rxid, string request, string& response, int timeout_ms /*=3000*/)
{ {
OvmsMutexLock lock(&zoe_can1_request); OvmsMutexLock lock(&zoe_can1_request);
@ -163,6 +101,70 @@ int OvmsVehicleRenaultZoePh2CAN::CanRequest(uint16_t txid, uint16_t rxid, string
return (rxok == pdFALSE) ? -1 : (int)zoe_can1_rxerr; return (rxok == pdFALSE) ? -1 : (int)zoe_can1_rxerr;
} }
void OvmsVehicleRenaultZoePh2CAN::shell_can_request(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
OvmsVehicleRenaultZoePh2CAN* zoe_ph2_can = GetInstance(writer);
uint16_t txid = 0, rxid = 0;
string request;
string response;
// parse args:
string device = cmd->GetName();
if (device == "device") {
if (argc < 3) {
writer->puts("ERROR: too few args, need: txid rxid request");
return;
}
txid = strtol(argv[0], NULL, 16);
rxid = strtol(argv[1], NULL, 16);
request = hexdecode(argv[2]);
} else {
if (argc < 1) {
writer->puts("ERROR: too few args, need: request");
return;
}
request = hexdecode(argv[0]);
// "broadcast"
txid = 0x7df;
rxid = 0;
}
// validate request:
if (request.size() == 0) {
writer->puts("ERROR: no request");
return;
} else {
uint8_t type = request.at(0);
if ((POLL_TYPE_HAS_16BIT_PID(type) && request.size() < 3) ||
(POLL_TYPE_HAS_8BIT_PID(type) && request.size() < 2)) {
writer->printf("ERROR: request too short for type %02X\n", type);
return;
}
}
// execute request:
int err = zoe_ph2_can->CanRequest(txid, rxid, request, response, 3000);
if (err == -1) {
writer->puts("ERROR: timeout waiting for response");
return;
} else if (err) {
writer->printf("ERROR: request failed with response error code %02X\n", err);
return;
}
// output response as hex dump:
writer->puts("Response:");
char *buf = NULL;
size_t rlen = response.size(), offset = 0;
do {
rlen = FormatHexDump(&buf, response.data() + offset, rlen, 16);
offset += 16;
writer->puts(buf ? buf : "-");
} while (rlen);
if (buf)
free(buf);
}
OvmsVehicle::vehicle_command_t OvmsVehicleRenaultZoePh2CAN::CommandPreHeat(bool climatecontrolon) { OvmsVehicle::vehicle_command_t OvmsVehicleRenaultZoePh2CAN::CommandPreHeat(bool climatecontrolon) {
//ToDo: Sniff TCU packets for preheat/cool, OVMS is connected on TCU port //ToDo: Sniff TCU packets for preheat/cool, OVMS is connected on TCU port
return NotImplemented; return NotImplemented;

View file

@ -167,7 +167,7 @@ void OvmsVehicleRenaultZoePh2CAN::IncomingFrameCan2(CAN_frame_t* p_frame) {
* Handles incoming poll results * Handles incoming poll results
*/ */
void OvmsVehicleRenaultZoePh2CAN::IncomingPollReply(canbus* bus, uint16_t type, uint16_t pid, uint8_t* data, uint8_t length, uint16_t remain) { void OvmsVehicleRenaultZoePh2CAN::IncomingPollReply(canbus* bus, uint16_t type, uint16_t pid, uint8_t* data, uint8_t length, uint16_t remain) {
string& rxbuf = zoe_obd_rxbuf; string& rxbuf = zoe_can1_rxbuf;
//ESP_LOGV(TAG, "pid: %04x length: %d m_poll_ml_remain: %d m_poll_ml_frame: %d", pid, length, m_poll_ml_remain, m_poll_ml_frame); //ESP_LOGV(TAG, "pid: %04x length: %d m_poll_ml_remain: %d m_poll_ml_frame: %d", pid, length, m_poll_ml_remain, m_poll_ml_frame);
@ -326,7 +326,8 @@ void OvmsVehicleRenaultZoePh2CAN::Ticker1(uint32_t ticker) {
if (!StandardMetrics.ms_v_env_locked->AsBool()) { if (!StandardMetrics.ms_v_env_locked->AsBool()) {
MyNotify.NotifyString("alert", "vehicle.lock", "Vehicle is not locked"); MyNotify.NotifyString("alert", "vehicle.lock", "Vehicle is not locked");
} }
}
}
if (StandardMetrics.ms_v_env_on->AsBool() && !CarIsDriving) { if (StandardMetrics.ms_v_env_on->AsBool() && !CarIsDriving) {
CarIsDriving = true; CarIsDriving = true;
@ -384,7 +385,7 @@ class OvmsVehicleRenaultZoePh2CANInit {
} MyOvmsVehicleRenaultZoePh2CANInit __attribute__ ((init_priority (9000))); } MyOvmsVehicleRenaultZoePh2CANInit __attribute__ ((init_priority (9000)));
OvmsVehicleRenaultZoePh2CANInit::OvmsVehicleRenaultZoePh2CANInit() OvmsVehicleRenaultZoePh2CANInit::OvmsVehicleRenaultZoePh2CANInit()
{ {
ESP_LOGI(TAG, "Registering Vehicle: Renault Zoe Ph2 (CAN) (9000)"); ESP_LOGI(TAG, "Registering Vehicle: Renault Zoe Ph2 (CAN) (9000)");
MyVehicleFactory.RegisterVehicle<OvmsVehicleRenaultZoePh2CAN>("RZ2","Renault Zoe Ph2 (CAN)"); MyVehicleFactory.RegisterVehicle<OvmsVehicleRenaultZoePh2CAN>("RZ2","Renault Zoe Ph2 (CAN)");

View file

@ -67,8 +67,9 @@ class OvmsVehicleRenaultZoePh2CAN : public OvmsVehicle {
static void WebCfgCommon(PageEntry_t& p, PageContext_t& c); static void WebCfgCommon(PageEntry_t& p, PageContext_t& c);
void ConfigChanged(OvmsConfigParam* param); void ConfigChanged(OvmsConfigParam* param);
void ZoeWakeUp(); void ZoeWakeUp();
void IncomingFrameCan1(CAN_frame_t* p_frame); void IncomingFrameCan1(CAN_frame_t* p_frame);
void IncomingPollReply(canbus* bus, uint16_t type, uint16_t pid, uint8_t* data, uint8_t length, uint16_t remain); void IncomingFrameCan2(CAN_frame_t* p_frame);
void IncomingPollReply(canbus* bus, uint16_t type, uint16_t pid, uint8_t* data, uint8_t length, uint16_t remain);
void WebInit(); void WebInit();
void WebDeInit(); void WebDeInit();
bool CarIsCharging = false; bool CarIsCharging = false;
@ -79,16 +80,16 @@ class OvmsVehicleRenaultZoePh2CAN : public OvmsVehicle {
float ACInputPowerFactor = 0.0; float ACInputPowerFactor = 0.0;
float Bat_cell_capacity = 0.0; float Bat_cell_capacity = 0.0;
protected: protected:
int m_range_ideal; int m_range_ideal;
int m_battery_capacity; int m_battery_capacity;
bool m_UseCarTrip = false; bool m_UseCarTrip = false;
bool m_UseBMScalculation = false; bool m_UseBMScalculation = false;
char zoe_vin[18] = ""; char zoe_vin[18] = "";
void IncomingINV(uint16_t type, uint16_t pid, const char* data, uint16_t len); void IncomingINV(uint16_t type, uint16_t pid, const char* data, uint16_t len);
void IncomingEVC(uint16_t type, uint16_t pid, const char* data, uint16_t len); void IncomingEVC(uint16_t type, uint16_t pid, const char* data, uint16_t len);
void IncomingBCM(uint16_t type, uint16_t pid, const char* data, uint16_t len); void IncomingBCM(uint16_t type, uint16_t pid, const char* data, uint16_t len);
void IncomingLBC(uint16_t type, uint16_t pid, const char* data, uint16_t len); void IncomingLBC(uint16_t type, uint16_t pid, const char* data, uint16_t len);
void IncomingHVAC(uint16_t type, uint16_t pid, const char* data, uint16_t len); void IncomingHVAC(uint16_t type, uint16_t pid, const char* data, uint16_t len);
void IncomingUCM(uint16_t type, uint16_t pid, const char* data, uint16_t len); void IncomingUCM(uint16_t type, uint16_t pid, const char* data, uint16_t len);
//void IncomingCLUSTER(uint16_t type, uint16_t pid, const char* data, uint16_t len); //void IncomingCLUSTER(uint16_t type, uint16_t pid, const char* data, uint16_t len);
@ -99,16 +100,8 @@ class OvmsVehicleRenaultZoePh2CAN : public OvmsVehicle {
virtual void Ticker10(uint32_t ticker);//Handle charge, energy statistics virtual void Ticker10(uint32_t ticker);//Handle charge, energy statistics
virtual void Ticker1(uint32_t ticker); //Handle trip counter virtual void Ticker1(uint32_t ticker); //Handle trip counter
protected:
OvmsSemaphore zoe_can1_rxwait;
uint16_t zoe_can1_rxerr;
string zoe_can1_rxbuf;
OvmsMutex zoe_can1_request;
private: private:
unsigned int m_can1_activity_timer; unsigned int m_can1_activity_timer;
OvmsCommand *cmd_zoe_ph2;
// Renault ZOE specific metrics // Renault ZOE specific metrics
OvmsMetricBool *mt_bus_awake; //CAN bus awake status OvmsMetricBool *mt_bus_awake; //CAN bus awake status
@ -135,14 +128,24 @@ class OvmsVehicleRenaultZoePh2CAN : public OvmsVehicle {
OvmsMetricString *mt_hvac_compressor_mode; //Compressor mode OvmsMetricString *mt_hvac_compressor_mode; //Compressor mode
OvmsMetricFloat *mt_v_env_pressure; //Ambient air pressure OvmsMetricFloat *mt_v_env_pressure; //Ambient air pressure
//CAN Commands section
public: public:
static OvmsVehicleRenaultZoePh2CAN* GetInstance(OvmsWriter* writer=NULL);
void CanInit();
int CanRequest(uint16_t txid, uint16_t rxid, string request, string& response, int timeout_ms);
static void shell_can_request(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv);
virtual vehicle_command_t CommandPreHeat(bool climatecontrolon);
virtual vehicle_command_t CommandWakeup(); virtual vehicle_command_t CommandWakeup();
virtual vehicle_command_t CommandPreHeat(bool enable);
virtual vehicle_command_t CommandLock(const char* pin); virtual vehicle_command_t CommandLock(const char* pin);
virtual vehicle_command_t CommandUnlock(const char* pin); virtual vehicle_command_t CommandUnlock(const char* pin);
protected: protected:
string zoe_obd_rxbuf; OvmsCommand *cmd_zoe_ph2;
string zoe_can1_rxbuf;
OvmsSemaphore zoe_can1_rxwait;
uint16_t zoe_can1_rxerr;
OvmsMutex zoe_can1_request;
}; };
#endif //#ifndef __VEHICLE_RENAULTZOE_PH2_OBD_H__ #endif //#ifndef __VEHICLE_RENAULTZOE_PH2_OBD_H__

View file

@ -353,20 +353,20 @@ config OVMS_VEHICLE_CAN_RX_QUEUE_SIZE
help help
The size of the CAN bus RX queue (at the vehicle component). The size of the CAN bus RX queue (at the vehicle component).
config OVMS_VEHICLE_RENAULTZOE_PH2_OBD config OVMS_VEHICLE_RENAULTZOE_PH2_OBD
bool "Include support for Renault Zoe PH2 vehicles via OBD port (read-only)" bool "Include support for Renault Zoe PH2 vehicles via OBD port (read-only)"
default y default y
depends on OVMS depends on OVMS
help help
Enable to include support for Renault Zoe Ph2 vehicles via OBD port (read-only). Enable to include support for Renault Zoe Ph2 vehicles via OBD port (read-only).
config OVMS_VEHICLE_RENAULTZOE_PH2_CAN config OVMS_VEHICLE_RENAULTZOE_PH2_CAN
bool "Include support for Renault Zoe PH2 vehicles via direct CAN access after Core Can Gateway" bool "Include support for Renault Zoe PH2 vehicles via direct CAN access after Core Can Gateway"
default y default y
depends on OVMS depends on OVMS
help help
Enable to include support for Renault Zoe Ph2 vehicles via direct CAN access after Core Can Gateway. Enable to include support for Renault Zoe Ph2 vehicles via direct CAN access after Core Can Gateway.
endmenu # Vehicle Support endmenu # Vehicle Support

View file

@ -201,7 +201,7 @@ MetricsStandard::MetricsStandard()
// //
// Motor metrics // Motor metrics
// //
ms_v_mot_rpm = new OvmsMetricInt(MS_V_MOT_RPM, SM_STALE_MID, rpm); ms_v_mot_rpm = new OvmsMetricInt(MS_V_MOT_RPM, SM_STALE_MID);
ms_v_mot_temp = new OvmsMetricFloat(MS_V_MOT_TEMP, SM_STALE_MID, Celcius, true); ms_v_mot_temp = new OvmsMetricFloat(MS_V_MOT_TEMP, SM_STALE_MID, Celcius, true);
// //

View file

@ -34,6 +34,7 @@ static const char *TAG = "boot";
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/xtensa_api.h" #include "freertos/xtensa_api.h"
#include "rom/rtc.h" #include "rom/rtc.h"
#include "rom/uart.h"
#include "soc/rtc_cntl_reg.h" #include "soc/rtc_cntl_reg.h"
#include "esp_system.h" #include "esp_system.h"
#include "esp_panic.h" #include "esp_panic.h"
@ -165,10 +166,18 @@ void boot_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc,
writer->printf("\nLast crash: "); writer->printf("\nLast crash: ");
if (boot_data.crash_data.is_abort) 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); 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 else
{ {
// Hardware exception:
int exccause = boot_data.crash_data.reg[19]; int exccause = boot_data.crash_data.reg[19];
writer->printf("%s exception on core %d\n", writer->printf("%s exception on core %d\n",
(exccause < NUM_EDESCS) ? edesc[exccause] : "Unknown", boot_data.crash_data.core_id); (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++) 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"); 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 "); writer->printf(" Backtrace:\n ");
for (int i=0; i<OVMS_BT_LEVELS && boot_data.crash_data.bt[i].pc; i++) 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); writer->printf(" 0x%08lx", boot_data.crash_data.bt[i].pc);
if (boot_data.curr_event_name[0]) 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, writer->printf("\n Event: %s@%s %u secs", boot_data.curr_event_name, boot_data.curr_event_handler,
boot_data.curr_event_runtime); boot_data.curr_event_runtime);
} }
if (MyBoot.GetResetReason() == ESP_RST_TASK_WDT) if (MyBoot.GetResetReason() == ESP_RST_TASK_WDT)
{ {
writer->printf("\n WDT tasks: %s", boot_data.wdt_tasknames); 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 Version: %s\n", StdMetrics.ms_m_version->AsString("").c_str());
writer->printf("\n Hardware: %s\n", StdMetrics.ms_m_hardware->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++; boot_data.boot_count++;
ESP_LOGI(TAG, "Boot #%d reasons for CPU0=%d and CPU1=%d",boot_data.boot_count,cpu0,cpu1); 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) if (boot_data.soft_reset)
{ {
boot_data.crash_count_total = 0; boot_data.crash_count_total = 0;
boot_data.crash_count_early = 0; boot_data.crash_count_early = 0;
m_bootreason = BR_SoftReset; m_bootreason = BR_SoftReset;
m_resetreason = ESP_RST_SW;
ESP_LOGI(TAG, "Soft reset by user"); ESP_LOGI(TAG, "Soft reset by user");
} }
else if (boot_data.firmware_update) else if (boot_data.firmware_update)
@ -281,6 +299,7 @@ Boot::Boot()
boot_data.crash_count_early = 0; boot_data.crash_count_early = 0;
m_bootreason = BR_FirmwareUpdate; m_bootreason = BR_FirmwareUpdate;
ESP_LOGI(TAG, "Firmware update reset"); ESP_LOGI(TAG, "Firmware update reset");
m_resetreason = ESP_RST_SW;
} }
else if (!boot_data.stable_reached) else if (!boot_data.stable_reached)
{ {
@ -288,24 +307,33 @@ Boot::Boot()
boot_data.crash_count_early++; boot_data.crash_count_early++;
m_bootreason = BR_EarlyCrash; m_bootreason = BR_EarlyCrash;
ESP_LOGE(TAG, "Early crash #%d detected", boot_data.crash_count_early); 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 else
{ {
boot_data.crash_count_total++; boot_data.crash_count_total++;
m_bootreason = BR_Crash; m_bootreason = BR_Crash;
ESP_LOGE(TAG, "Crash #%d detected", boot_data.crash_count_total); 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_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_cpu0 = cpu0;
boot_data.bootreason_cpu1 = cpu1; boot_data.bootreason_cpu1 = cpu1;
boot_data.reset_hint = ESP_RST_UNKNOWN;
// reset flags: // reset flags:
boot_data.soft_reset = false; boot_data.soft_reset = false;
boot_data.firmware_update = false; boot_data.firmware_update = false;
boot_data.stable_reached = false; boot_data.stable_reached = false;
boot_data.stack_overflow = false;
boot_data.crc = boot_data.calc_crc(); boot_data.crc = boot_data.calc_crc();
@ -351,6 +379,8 @@ const char* Boot::GetBootReasonName()
const char* Boot::GetResetReasonName() const char* Boot::GetResetReasonName()
{ {
if (m_stack_overflow)
return "Stack overflow";
return (m_resetreason >= 0 && m_resetreason < NUM_RESETREASONS) return (m_resetreason >= 0 && m_resetreason < NUM_RESETREASONS)
? resetreason_name[m_resetreason] ? resetreason_name[m_resetreason]
: "Unknown reset reason"; : "Unknown reset reason";
@ -448,6 +478,52 @@ bool Boot::IsShuttingDown()
return m_shutting_down; 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) void Boot::ErrorCallback(XtExcFrame *frame, int core_id, bool is_abort)
{ {
boot_data.reset_hint = ovms_reset_reason_get_hint(); 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; boot_data.curr_event_runtime = 0;
} }
// Save TWDT task info: // Save current tasks:
esp_task_wdt_get_trigger_tasknames(boot_data.wdt_tasknames, sizeof(boot_data.wdt_tasknames)); 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(); boot_data.crc = boot_data.calc_crc();
} }
@ -514,6 +610,8 @@ void Boot::NotifyDebugCrash()
// ,<curr_event_name>,<curr_event_handler>,<curr_event_runtime> // ,<curr_event_name>,<curr_event_handler>,<curr_event_runtime>
// ,<wdt_tasknames> // ,<wdt_tasknames>
// ,<hardware_info> // ,<hardware_info>
// ,<stack_overflow_task>
// ,<core0_task>,<core0_stackfree>,<core1_task>,<core1_stackfree>
StringWriter buf; StringWriter buf;
buf.reserve(2048); buf.reserve(2048);
@ -557,6 +655,20 @@ void Boot::NotifyDebugCrash()
buf.append(","); buf.append(",");
buf.append(mp_encode(StdMetrics.ms_m_hardware->AsString(""))); 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()); MyNotify.NotifyString("data", "debug.crash", buf.c_str());
} }
} }

View file

@ -62,6 +62,12 @@ typedef struct
} bt[OVMS_BT_LEVELS]; } bt[OVMS_BT_LEVELS];
} crash_data_t; } crash_data_t;
typedef struct
{
char name[16];
uint32_t stackfree;
} task_info_t;
typedef struct typedef struct
{ {
// data consistency: // data consistency:
@ -83,6 +89,9 @@ typedef struct
char curr_event_handler[16]; // … MyEvents.m_current_callback->m_caller char curr_event_handler[16]; // … MyEvents.m_current_callback->m_caller
uint16_t curr_event_runtime; // … monotonictime-MyEvents.m_current_started uint16_t curr_event_runtime; // … monotonictime-MyEvents.m_current_started
char wdt_tasknames[32]; // Pipe (|) separated list of the tasks that triggered the TWDT 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; } boot_data_t;
extern boot_data_t boot_data; extern boot_data_t boot_data;
@ -128,6 +137,7 @@ class Boot
bootreason_t m_bootreason; bootreason_t m_bootreason;
esp_reset_reason_t m_resetreason; esp_reset_reason_t m_resetreason;
unsigned int m_crash_count_early; unsigned int m_crash_count_early;
bool m_stack_overflow;
}; };
extern Boot MyBoot; extern Boot MyBoot;

View file

@ -718,12 +718,25 @@ OvmsConfigParam* OvmsConfig::CachedParam(std::string param)
return *p; 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) bool OvmsConfig::ProtectedPath(std::string path)
{ {
#ifdef CONFIG_OVMS_DEV_CONFIGVFS #ifdef CONFIG_OVMS_DEV_CONFIGVFS
return false; return false;
#else #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 #endif // #ifdef CONFIG_OVMS_DEV_CONFIGVFS
} }

View file

@ -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_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_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__ #endif //#ifndef __OVMS_LOG_H__

View file

@ -58,6 +58,159 @@ std::map<std::size_t, const char*> pmetrics_keymap // hash key → me
OvmsMetrics MyMetrics OvmsMetrics MyMetrics
__attribute__ ((init_priority (1800))); __attribute__ ((init_priority (1800)));
typedef enum : uint8_t
{
GrpNone,
GrpOther,
GrpDistance,
GrpTemp,
GrpPressure,
GrpPower,
GrpEnergy,
GrpTime,
GrpDirection,
GrpSpeed,
GrpAccel,
GrpSignal,
GrpConsumption,
GrpTorque,
// These are where a dimension group is split and allows
// easily folding the 'short distances' back onto their equivalents.
GrpDistanceShort = GrpDistance + 0x80,
GrpAccelShort = GrpAccel + 0x80
} metric_group_t;
struct OvmsUnitInfo {
const char *UnitCode; //< The UnitCode identifying the unit
const char *Label; //< The suffix to print against the value
metric_unit_t MetricUnit; //< The Metric equivalent if there is one.
metric_unit_t ImperialUnit; //< The Imperial equivalent if there is one.
metric_group_t Group; //< The conversion group it belongs to.
};
#define UNIT_GAP {NULL,NULL, UnitNotFound, UnitNotFound, GrpNone}
// Mapping for information on metric info
static const OvmsUnitInfo unit_info[int(MetricUnitLast)+1] =
{
// Unit Code Label Metric Unt Imperial Unt Unit Group
{"native", "", Native, Native, GrpNone }, // 0
{"metric", "", Native, Native, GrpNone }, // 1
{"imperial", "", Native, Native, GrpNone }, // 2
UNIT_GAP, // 3
UNIT_GAP, // 4
UNIT_GAP, // 5
UNIT_GAP, // 6
UNIT_GAP, // 7
UNIT_GAP, // 8
UNIT_GAP, // 9
{"km", "km", Native, Miles, GrpDistance }, // 10
{"miles", "M", Kilometers, Native, GrpDistance }, // 11
{"meters", "m", Native, Feet, GrpDistanceShort }, // 12
{"feet", "ft", Meters, Native, GrpDistanceShort }, // 13
UNIT_GAP, // 14
UNIT_GAP, // 15
UNIT_GAP, // 16
UNIT_GAP, // 17
UNIT_GAP, // 18
UNIT_GAP, // 19
{"celcius", "°C", Native, Fahrenheit, GrpTemp }, // 20
{"fahrenheit","°F", Celcius, Native, GrpTemp }, // 21
UNIT_GAP, // 22
UNIT_GAP, // 23
UNIT_GAP, // 24
UNIT_GAP, // 25
UNIT_GAP, // 26
UNIT_GAP, // 27
UNIT_GAP, // 28
UNIT_GAP, // 29
{"kpa", "kPa", Native, PSI, GrpPressure}, // 30
{"pa", "Pa", Native, PSI, GrpPressure}, // 31
{"psi", "psi", kPa, Native, GrpPressure}, // 32
UNIT_GAP, // 33
UNIT_GAP, // 34
UNIT_GAP, // 35
UNIT_GAP, // 36
UNIT_GAP, // 37
UNIT_GAP, // 38
UNIT_GAP, // 39
{"volts", "V", Native, Native, GrpOther }, // 40
{"amps", "A", Native, Native, GrpOther }, // 41
{"amphours", "Ah", Native, Native, GrpOther }, // 42
{"kw", "kW", Native, Native, GrpPower }, // 43
{"kwh", "kWh", Native, Native, GrpEnergy}, // 44
{"watts", "W", Native, Native, GrpPower }, // 45
{"watthours","Wh", Native, Native, GrpEnergy}, // 46
UNIT_GAP, // 47
UNIT_GAP, // 48
UNIT_GAP, // 49
{"seconds", "Sec", Native, Native, GrpTime}, // 50
{"minutes", "Min", Native, Native, GrpTime}, // 51
{"hours", "Hour", Native, Native, GrpTime}, // 52
{"utc", "UTC", Native, Native, GrpTime}, // 53
{"localtz", "local", Native, Native, GrpTime}, // 54,
UNIT_GAP,// 55
UNIT_GAP,// 56
UNIT_GAP,// 57
UNIT_GAP,// 58
UNIT_GAP,// 59
{"degrees", "°", Native, Native, GrpDirection}, // 60
{"kmph", "km/h", Native, Mph, GrpSpeed}, // 61
{"miph", "Mph", Kph, Native, GrpSpeed}, // 62
UNIT_GAP,// 63
UNIT_GAP,// 64
UNIT_GAP,// 65
UNIT_GAP,// 66
UNIT_GAP,// 67
UNIT_GAP,// 68
UNIT_GAP,// 69
UNIT_GAP,// 70
// Acceleration:
{"kmphps", "km/h/s", Native, MphPS, GrpAccel}, // 71
{"miphps", "Mph/s", KphPS, Native, GrpAccel}, // 72
{"mpss", "m/s²", Native, FeetPSS, GrpAccelShort}, // 73
{"ftpss", "ft/s²", MetersPSS, Native, GrpAccelShort}, // 74
UNIT_GAP,// 75
UNIT_GAP,// 76
UNIT_GAP,// 77
UNIT_GAP,// 78
UNIT_GAP,// 79
{"dbm", "dBm", Native, sq, GrpSignal}, // 80
{"sq", "sq", dbm, Native, GrpSignal}, // 81
UNIT_GAP,// 82
UNIT_GAP,// 83
UNIT_GAP,// 84
UNIT_GAP,// 85
UNIT_GAP,// 86
UNIT_GAP,// 87
UNIT_GAP,// 88
UNIT_GAP,// 89
{"percent", "%", Native, Native, GrpOther}, // 90
UNIT_GAP,// 91
UNIT_GAP,// 92
UNIT_GAP,// 93
UNIT_GAP,// 94
UNIT_GAP,// 95
UNIT_GAP,// 96
UNIT_GAP,// 97
UNIT_GAP,// 98
UNIT_GAP,// 99
// Energy consumption:
{"whpkm", "Wh/km", Native, WattHoursPM, GrpConsumption}, // 100
{"whpmi", "Wh/mi", WattHoursPK,Native, GrpConsumption}, // 101
{"kwhp100km","kWh/100km",Native, WattHoursPM, GrpConsumption}, // 102
{"kmpkwh", "km/kWh", Native, MPkWh, GrpConsumption}, // 103
{"mipkwh", "mi/kWh", KPkWh, Native, GrpConsumption}, // 104
UNIT_GAP,// 105
UNIT_GAP,// 106
UNIT_GAP,// 107
UNIT_GAP,// 108
UNIT_GAP,// 109
// Torque:
{"nm", "Nm", Native, Native, GrpTorque} // 110
};
#undef UNIT_GAP
static inline int mi_to_km(int mi) static inline int mi_to_km(int mi)
{ {
return mi * 4023 / 2500; // mi * 1.6092 return mi * 4023 / 2500; // mi * 1.6092
@ -98,6 +251,70 @@ T pkm_to_pmi(T pkm)
return mi_to_km(pkm); return mi_to_km(pkm);
} }
/*
* Returns the group of the metric.
* simplify - Means those separated for (eventual) user config
* are folded to one metric.
*/
static metric_group_t GetMetricGroup(metric_unit_t unit)
{
uint8_t unit_i = static_cast<uint8_t>(unit);
if (unit_i <= uint8_t(MetricUnitLast))
return unit_info[unit_i].Group;
return GrpNone;
}
static inline metric_group_t GetMetricGroupSimplify(metric_unit_t unit)
{
// Removes High-bit to fold the 'Short' metrics back onto their equivalents.
return static_cast<metric_group_t>(static_cast<uint8_t>(GetMetricGroup(unit)) & 0x7f);
}
/*
* Modifies the 'to' target to match the real target (or Native for no change).
* Handles ToMetric/ToImperial conversion types.
*
* full_check takes into account whether a conversion CAN be done (used for
* printing correct labels)
*/
static void CheckTargetUnit(metric_unit_t from, metric_unit_t &to, bool full_check)
{
if (from == Other)
{
to = from;
return;
}
switch (to)
{
case Native: break;
case ToMetric:
{
uint8_t unit_i = static_cast<uint8_t>(from);
if (unit_i <= uint8_t(MetricUnitLast))
to = unit_info[unit_i].MetricUnit;
break;
}
case ToImperial:
{
uint8_t unit_i = static_cast<uint8_t>(from);
if (unit_i <= uint8_t(MetricUnitLast))
to = unit_info[unit_i].ImperialUnit;
break;
}
default:
if (to == from)
to = Native;
else if (full_check)
{
metric_group_t from_grp = GetMetricGroupSimplify(from);
if (from_grp == GrpNone || from_grp == GrpOther)
to = Native;
else if (from_grp != GetMetricGroupSimplify(to))
to = Native;
}
break;
}
}
void metrics_list(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) void metrics_list(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{ {
bool found = false; bool found = false;
@ -105,6 +322,7 @@ void metrics_list(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc,
bool show_set = false; bool show_set = false;
bool only_persist = false; bool only_persist = false;
bool display_strings = false; bool display_strings = false;
metric_unit_t def_unit = Native;
const char* show_only = NULL; const char* show_only = NULL;
int i; int i;
for (i=0;i<argc;i++) for (i=0;i<argc;i++)
@ -127,6 +345,12 @@ void metrics_list(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc,
case 'c': case 'c':
show_set = true; show_set = true;
break; break;
case 'i':
def_unit = ToImperial;
break;
case 'm':
def_unit = ToMetric;
break;
case 'p': case 'p':
only_persist = true; only_persist = true;
break; break;
@ -156,7 +380,19 @@ void metrics_list(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc,
writer->printf("metrics set %s %s\n", k, m->AsString().c_str()); writer->printf("metrics set %s %s\n", k, m->AsString().c_str());
continue; continue;
} }
std::string v = m->AsUnitString("", m->GetUnits() == TimeUTC ? TimeLocal : m->GetUnits());
metric_unit_t use_unit = def_unit;
metric_unit_t my_unit = m->GetUnits();
if (my_unit == TimeUTC)
use_unit = TimeLocal;
else
{
CheckTargetUnit(my_unit, use_unit, true);
if (use_unit == Native)
use_unit = my_unit;
}
std::string v = m->AsUnitString("", use_unit);
if (show_staleness) if (show_staleness)
{ {
int age = m->Age(); int age = m->Age();
@ -207,12 +443,24 @@ void metrics_persist(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int ar
void metrics_set(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) void metrics_set(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{ {
if (MyMetrics.Set(argv[0],argv[1])) const char *unit = NULL;
if (argc > 2)
unit = argv[2];
if (MyMetrics.Set(argv[0],argv[1], unit))
writer->puts("Metric set"); writer->puts("Metric set");
else else
writer->puts("Metric could not be set"); writer->puts("Metric could not be set");
} }
void metrics_get(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
const char *unit = NULL;
if (argc > 1)
unit = argv[1];
std::string str = MyMetrics.GetUnitStr(argv[0], unit);
writer->puts(str.c_str());
}
bool pmetrics_check() bool pmetrics_check()
{ {
bool ret = true; bool ret = true;
@ -334,20 +582,81 @@ void metrics_trace(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc
writer->printf("Metric tracing is now %s\n",cmd->GetName()); writer->printf("Metric tracing is now %s\n",cmd->GetName());
} }
void metrics_units(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
const char* show_only = NULL;
int i;
for (i=0;i<argc;i++)
{
const char *cp = argv[i];
if (*cp != '-')
{
if (show_only != NULL)
{
cmd->PutUsage(writer);
return;
}
show_only = cp;
continue;
}
}
bool found = false;
for (metric_unit_t unit = MetricUnitFirst; unit <= MetricUnitLast; unit = metric_unit_t(1+(uint8_t)unit))
{
const char *metric_name = OvmsMetricUnitName(unit);
if (metric_name == NULL)
continue;
if (show_only != NULL && strstr(metric_name, show_only) == NULL)
continue;
const char *metric_label = OvmsMetricUnitLabel(unit);
writer->printf("%12s : %s\n", metric_name, metric_label);
found = true;
}
if (show_only && !found)
writer->puts("Unrecognised unit name");
}
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
static duk_ret_t DukOvmsMetricHasValue(duk_context *ctx)
{
DukContext dc(ctx);
const char *mn = duk_to_string(ctx,0);
OvmsMetric *m = MyMetrics.Find(mn);
if (!m)
return 0;
dc.Push(m->IsDefined());
return 1;
}
static duk_ret_t DukOvmsMetricValue(duk_context *ctx) static duk_ret_t DukOvmsMetricValue(duk_context *ctx)
{ {
DukContext dc(ctx); DukContext dc(ctx);
bool decode = duk_opt_boolean(ctx, 1, true);
const char *mn = duk_to_string(ctx,0); const char *mn = duk_to_string(ctx,0);
OvmsMetric *m = MyMetrics.Find(mn); OvmsMetric *m = MyMetrics.Find(mn);
if (m) if (!m)
return 0;
bool decode = true;
const char *un = NULL;
bool has_unit = false;
if (duk_check_type_mask(ctx, 1, DUK_TYPE_MASK_BOOLEAN))
decode = duk_opt_boolean(ctx, 1, true);
else
{
un = duk_opt_string(ctx, 1, NULL);
decode = duk_opt_boolean(ctx, 2, true);
has_unit = un != NULL;
}
metric_unit_t unit = OvmsMetricUnitFromName(un);
if (m && unit != UnitNotFound)
{ {
if (decode) if (decode)
m->DukPush(dc); m->DukPush(dc, unit);
else if (has_unit)
dc.Push(m->AsUnitString("", unit));
else else
dc.Push(m->AsString()); dc.Push(m->AsString(""));
return 1; /* one return value */ return 1; /* one return value */
} }
else else
@ -371,9 +680,11 @@ static duk_ret_t DukOvmsMetricFloat(duk_context *ctx)
{ {
const char *mn = duk_to_string(ctx,0); const char *mn = duk_to_string(ctx,0);
OvmsMetric *m = MyMetrics.Find(mn); OvmsMetric *m = MyMetrics.Find(mn);
if (m) const char *un = duk_opt_string(ctx,1,NULL);
metric_unit_t unit = OvmsMetricUnitFromName(un);
if (m && unit != UnitNotFound)
{ {
duk_push_number(ctx, float2double(m->AsFloat())); duk_push_number(ctx, float2double(m->AsFloat(0, unit)));
return 1; /* one return value */ return 1; /* one return value */
} }
else else
@ -384,14 +695,29 @@ static duk_ret_t DukOvmsMetricGetValues(duk_context *ctx)
{ {
OvmsMetric *m; OvmsMetric *m;
DukContext dc(ctx); DukContext dc(ctx);
bool decode = duk_opt_boolean(ctx, 1, true);
bool has_unit = false;
bool decode = true;
const char *un = NULL;
if (duk_check_type_mask(ctx, 1, DUK_TYPE_MASK_BOOLEAN))
decode = duk_opt_boolean(ctx, 1, true);
else
{
un = duk_opt_string(ctx, 1, NULL);
has_unit = un != NULL;
decode = duk_opt_boolean(ctx, 2, true);
}
metric_unit_t unit = OvmsMetricUnitFromName(un);
duk_idx_t obj_idx = dc.PushObject(); duk_idx_t obj_idx = dc.PushObject();
// helper: set object property from metric // helper: set object property from metric
auto set_metric = [&dc, obj_idx, decode](OvmsMetric *m) auto set_metric = [&dc, obj_idx, decode, unit, has_unit](OvmsMetric *m)
{ {
if (decode) if (decode)
m->DukPush(dc); m->DukPush(dc, unit);
else if (has_unit)
dc.Push(m->AsUnitString("", unit));
else else
dc.Push(m->AsString()); dc.Push(m->AsString());
dc.PutProp(obj_idx, m->m_name); dc.PutProp(obj_idx, m->m_name);
@ -437,7 +763,7 @@ static duk_ret_t DukOvmsMetricGetValues(duk_context *ctx)
#endif //#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE #endif //#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
MetricCallbackEntry::MetricCallbackEntry(const char* caller, MetricCallback callback) MetricCallbackEntry::MetricCallbackEntry(std::string caller, MetricCallback callback)
{ {
m_caller = caller; m_caller = caller;
m_callback = callback; m_callback = callback;
@ -458,15 +784,21 @@ OvmsMetrics::OvmsMetrics()
// Register our commands // Register our commands
OvmsCommand* cmd_metric = MyCommandApp.RegisterCommand("metrics","METRICS framework"); OvmsCommand* cmd_metric = MyCommandApp.RegisterCommand("metrics","METRICS framework");
cmd_metric->RegisterCommand("list","Show all metrics", metrics_list, cmd_metric->RegisterCommand("list","Show all metrics", metrics_list,
"[-cpst] [<metric>]\n" "[-cimpst] [<metric>]\n"
"Display a metric, show all by default\n" "Display a metric, show all by default\n"
"-c = display persistent metrics set commands\n" "-c = display persistent metrics set commands\n"
"-i = display imperial units where possible\n"
"-m = display metric units where possible\n"
"-p = display only persistent metrics\n" "-p = display only persistent metrics\n"
"-s = show metric staleness\n" "-s = show metric staleness\n"
"-t = display non-printing characters and tabs in string metrics", 0, 2); "-t = display non-printing characters and tabs in string metrics", 0, 2);
cmd_metric->RegisterCommand("persist","Show persistent metrics info", metrics_persist, "[-r]\n" cmd_metric->RegisterCommand("persist","Show persistent metrics info", metrics_persist, "[-r]\n"
"-r = reset persistent metrics", 0, 1); "-r = reset persistent metrics", 0, 1);
cmd_metric->RegisterCommand("set","Set the value of a metric",metrics_set, "<metric> <value>", 2, 2); cmd_metric->RegisterCommand("set","Set the value of a metric",metrics_set, "<metric> <value> [<unit>]", 2, 3);
cmd_metric->RegisterCommand("get","Get the value of a metric",metrics_get, "<metric> [<unit>]", 1, 2);
cmd_metric->RegisterCommand("units","List available units",metrics_units, "[<name>]",0,1);
OvmsCommand* cmd_metrictrace = cmd_metric->RegisterCommand("trace","METRIC trace framework"); OvmsCommand* cmd_metrictrace = cmd_metric->RegisterCommand("trace","METRIC trace framework");
cmd_metrictrace->RegisterCommand("on","Turn metric tracing ON",metrics_trace); cmd_metrictrace->RegisterCommand("on","Turn metric tracing ON",metrics_trace);
cmd_metrictrace->RegisterCommand("off","Turn metric tracing OFF",metrics_trace); cmd_metrictrace->RegisterCommand("off","Turn metric tracing OFF",metrics_trace);
@ -474,10 +806,11 @@ OvmsMetrics::OvmsMetrics()
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
ESP_LOGI(TAG, "Expanding DUKTAPE javascript engine"); ESP_LOGI(TAG, "Expanding DUKTAPE javascript engine");
DuktapeObjectRegistration* dto = new DuktapeObjectRegistration("OvmsMetrics"); DuktapeObjectRegistration* dto = new DuktapeObjectRegistration("OvmsMetrics");
dto->RegisterDuktapeFunction(DukOvmsMetricValue, 1, "Value"); dto->RegisterDuktapeFunction(DukOvmsMetricHasValue, 1, "HasValue");
dto->RegisterDuktapeFunction(DukOvmsMetricValue, 3, "Value");
dto->RegisterDuktapeFunction(DukOvmsMetricJSON, 1, "AsJSON"); dto->RegisterDuktapeFunction(DukOvmsMetricJSON, 1, "AsJSON");
dto->RegisterDuktapeFunction(DukOvmsMetricFloat, 1, "AsFloat"); dto->RegisterDuktapeFunction(DukOvmsMetricFloat, 2, "AsFloat");
dto->RegisterDuktapeFunction(DukOvmsMetricGetValues, 2, "GetValues"); dto->RegisterDuktapeFunction(DukOvmsMetricGetValues, 3, "GetValues");
MyDuktape.RegisterDuktapeObject(dto); MyDuktape.RegisterDuktapeObject(dto);
#endif //#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE #endif //#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
@ -559,12 +892,35 @@ void OvmsMetrics::DeregisterMetric(OvmsMetric* metric)
} }
} }
bool OvmsMetrics::Set(const char* metric, const char* value) std::string OvmsMetrics::GetUnitStr(const char* metric, const char *unit)
{
OvmsMetric* m = Find(metric);
if (m == NULL) return "(not found)";
metric_unit_t metric_unit = Native;
if (unit != NULL)
{
metric_unit_t found_unit = OvmsMetricUnitFromName(unit);
if (found_unit == UnitNotFound)
return "(invalid unit)";
metric_unit = found_unit;
}
return m->AsUnitString("(not set)", metric_unit);
}
bool OvmsMetrics::Set(const char* metric, const char* value, const char *unit)
{ {
OvmsMetric* m = Find(metric); OvmsMetric* m = Find(metric);
if (m == NULL) return false; if (m == NULL) return false;
metric_unit_t metric_unit = Native;
if (unit != NULL)
{
metric_unit_t found_unit = OvmsMetricUnitFromName(unit);
if (found_unit == UnitNotFound)
return false;
metric_unit = found_unit;
}
m->SetValue(std::string(value)); m->SetValue(std::string(value), metric_unit);
return true; return true;
} }
@ -646,7 +1002,7 @@ OvmsMetricString* OvmsMetrics::InitString(const char* metric, uint16_t autostale
return m; return m;
} }
void OvmsMetrics::RegisterListener(const char* caller, const char* name, MetricCallback callback) void OvmsMetrics::RegisterListener(std::string caller, std::string name, MetricCallback callback)
{ {
auto k = m_listeners.find(name); auto k = m_listeners.find(name);
if (k == m_listeners.end()) if (k == m_listeners.end())
@ -656,7 +1012,7 @@ void OvmsMetrics::RegisterListener(const char* caller, const char* name, MetricC
} }
if (k == m_listeners.end()) if (k == m_listeners.end())
{ {
ESP_LOGE(TAG, "Problem registering metric %s for caller %s",name,caller); ESP_LOGE(TAG, "Problem registering metric %s for caller %s",name.c_str(),caller.c_str());
return; return;
} }
@ -664,7 +1020,7 @@ void OvmsMetrics::RegisterListener(const char* caller, const char* name, MetricC
ml->push_back(new MetricCallbackEntry(caller,callback)); ml->push_back(new MetricCallbackEntry(caller,callback));
} }
void OvmsMetrics::DeregisterListener(const char* caller) void OvmsMetrics::DeregisterListener(std::string caller)
{ {
MetricCallbackMap::iterator itm=m_listeners.begin(); MetricCallbackMap::iterator itm=m_listeners.begin();
while (itm!=m_listeners.end()) while (itm!=m_listeners.end())
@ -765,7 +1121,11 @@ std::string OvmsMetric::AsUnitString(const char* defvalue, metric_unit_t units,
{ {
if (!IsDefined()) if (!IsDefined())
return std::string(defvalue); return std::string(defvalue);
return AsString(defvalue, units, precision) + OvmsMetricUnitLabel(units==Native ? GetUnits() : units);
// Need the converted unit for putting the label.
auto currentUnits = GetUnits();
CheckTargetUnit(currentUnits, units, true);
return AsString(defvalue, units, precision) + OvmsMetricUnitLabel(units==Native ? currentUnits : units);
} }
std::string OvmsMetric::AsJSON(const char* defvalue, metric_unit_t units, int precision) std::string OvmsMetric::AsJSON(const char* defvalue, metric_unit_t units, int precision)
@ -782,13 +1142,13 @@ float OvmsMetric::AsFloat(const float defvalue, metric_unit_t units)
} }
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
void OvmsMetric::DukPush(DukContext &dc) void OvmsMetric::DukPush(DukContext &dc, metric_unit_t units)
{ {
dc.Push(AsString()); dc.Push(AsString("", units));
} }
#endif #endif
bool OvmsMetric::SetValue(std::string value) bool OvmsMetric::SetValue(std::string value, metric_unit_t units)
{ {
return false; return false;
} }
@ -990,7 +1350,7 @@ std::string OvmsMetricInt::AsString(const char* defvalue, metric_unit_t units, i
{ {
char buffer[33]; char buffer[33];
int value = m_value; int value = m_value;
if ((units != Other)&&(units != m_units)) if ((units != Native)&&(units != m_units))
value = UnitConvert(m_units,units,m_value); value = UnitConvert(m_units,units,m_value);
if (units == TimeUTC || units == TimeLocal) if (units == TimeUTC || units == TimeLocal)
{ {
@ -1028,7 +1388,7 @@ int OvmsMetricInt::AsInt(const int defvalue, metric_unit_t units)
{ {
if (IsDefined()) if (IsDefined())
{ {
if ((units != Other)&&(units != m_units)) if ((units != Native)&&(units != m_units))
return UnitConvert(m_units,units,m_value); return UnitConvert(m_units,units,m_value);
else else
return m_value; return m_value;
@ -1038,16 +1398,17 @@ int OvmsMetricInt::AsInt(const int defvalue, metric_unit_t units)
} }
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
void OvmsMetricInt::DukPush(DukContext &dc) void OvmsMetricInt::DukPush(DukContext &dc, metric_unit_t units)
{ {
dc.Push(m_value); dc.Push(AsInt(0, units));
} }
#endif #endif
bool OvmsMetricInt::SetValue(int value, metric_unit_t units) bool OvmsMetricInt::SetValue(int value, metric_unit_t units)
{ {
int nvalue = value; int nvalue = value;
if ((units != Other)&&(units != m_units)) nvalue=UnitConvert(units,m_units,value); if ((units != Other)&&(units != m_units))
nvalue=UnitConvert(units,m_units,value);
if (m_value != nvalue) if (m_value != nvalue)
{ {
@ -1064,22 +1425,10 @@ bool OvmsMetricInt::SetValue(int value, metric_unit_t units)
} }
} }
bool OvmsMetricInt::SetValue(std::string value) bool OvmsMetricInt::SetValue(std::string value, metric_unit_t units)
{ {
int nvalue = atoi(value.c_str()); int nvalue = atoi(value.c_str());
if (m_value != nvalue) return SetValue(nvalue, units);
{
m_value = nvalue;
if (m_valuep)
*m_valuep = m_value;
SetModified(true);
return true;
}
else
{
SetModified(false);
return false;
}
} }
bool OvmsMetricInt::SetValue(dbcNumber& value) bool OvmsMetricInt::SetValue(dbcNumber& value)
@ -1200,7 +1549,7 @@ int OvmsMetricBool::AsBool(const bool defvalue)
} }
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
void OvmsMetricBool::DukPush(DukContext &dc) void OvmsMetricBool::DukPush(DukContext &dc, metric_unit_t units)
{ {
dc.Push(m_value); dc.Push(m_value);
} }
@ -1223,7 +1572,7 @@ bool OvmsMetricBool::SetValue(bool value)
} }
} }
bool OvmsMetricBool::SetValue(std::string value) bool OvmsMetricBool::SetValue(std::string value, metric_unit_t units)
{ {
bool nvalue = strtobool(value); bool nvalue = strtobool(value);
if (m_value != nvalue) if (m_value != nvalue)
@ -1362,9 +1711,9 @@ int OvmsMetricFloat::AsInt(const int defvalue, metric_unit_t units)
} }
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
void OvmsMetricFloat::DukPush(DukContext &dc) void OvmsMetricFloat::DukPush(DukContext &dc, metric_unit_t units)
{ {
dc.Push(m_value); dc.Push(AsFloat(0, units));
} }
#endif #endif
@ -1387,22 +1736,10 @@ bool OvmsMetricFloat::SetValue(float value, metric_unit_t units)
} }
} }
bool OvmsMetricFloat::SetValue(std::string value) bool OvmsMetricFloat::SetValue(std::string value, metric_unit_t units)
{ {
float nvalue = atof(value.c_str()); float nvalue = atof(value.c_str());
if (m_value != nvalue) return SetValue(nvalue, units);
{
m_value = nvalue;
if (m_valuep)
*m_valuep = m_value;
SetModified(true);
return true;
}
else
{
SetModified(false);
return false;
}
} }
bool OvmsMetricFloat::SetValue(dbcNumber& value) bool OvmsMetricFloat::SetValue(dbcNumber& value)
@ -1439,14 +1776,14 @@ std::string OvmsMetricString::AsString(const char* defvalue, metric_unit_t units
} }
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
void OvmsMetricString::DukPush(DukContext &dc) void OvmsMetricString::DukPush(DukContext &dc, metric_unit_t units)
{ {
OvmsMutexLock lock(&m_mutex); OvmsMutexLock lock(&m_mutex);
dc.Push(m_value); dc.Push(m_value);
} }
#endif #endif
bool OvmsMetricString::SetValue(std::string value) bool OvmsMetricString::SetValue(std::string value, metric_unit_t units)
{ {
if (m_mutex.Lock()) if (m_mutex.Lock())
{ {
@ -1471,49 +1808,44 @@ void OvmsMetricString::Clear()
const char* OvmsMetricUnitLabel(metric_unit_t units) const char* OvmsMetricUnitLabel(metric_unit_t units)
{ {
switch (units) uint8_t unit_i = static_cast<uint8_t>(units);
if (unit_i > uint8_t(MetricUnitLast))
return "";
const char *res = unit_info[unit_i].Label;
if (res == NULL)
return "";
return res;
}
const char* OvmsMetricUnitName(metric_unit_t units)
{
uint8_t unit_i = static_cast<uint8_t>(units);
if (unit_i > uint8_t(MetricUnitLast))
return NULL;
return unit_info[unit_i].UnitCode;
}
metric_unit_t OvmsMetricUnitFromName(const char* unit)
{
if (unit == NULL || unit[0] == '\0')
return Native;
for (metric_unit_t metric = MetricUnitFirst; metric <= MetricUnitLast; metric = metric_unit_t(1+(uint8_t)metric))
{ {
case Kilometers: return "km"; const char * name = unit_info[(uint8_t)metric].UnitCode;
case Miles: return "M";
case Meters: return "m"; if (name != NULL && strcasecmp(name,unit) == 0)
case Feet: return "ft"; return metric;
case Celcius: return "°C";
case Fahrenheit: return "°F";
case kPa: return "kPa";
case Pa: return "Pa";
case PSI: return "psi";
case Volts: return "V";
case Amps: return "A";
case AmpHours: return "Ah";
case kW: return "kW";
case kWh: return "kWh";
case Watts: return "W";
case WattHours: return "Wh";
case Seconds: return "Sec";
case Minutes: return "Min";
case Hours: return "Hour";
case TimeUTC: return "UTC";
case Degrees: return "°";
case Kph: return "km/h";
case Mph: return "Mph";
case KphPS: return "km/h/s";
case MphPS: return "Mph/s";
case MetersPSS: return "m/s²";
case dbm: return "dBm";
case sq: return "sq";
case Percentage: return "%";
case WattHoursPK: return "Wh/km";
case WattHoursPM: return "Wh/mi";
case kWhP100K: return "kWh/100km";
case KPkWh: return "km/kWh";
case MPkWh: return "mi/kWh";
case Nm: return "Nm";
default: return "";
} }
return UnitNotFound;
} }
int UnitConvert(metric_unit_t from, metric_unit_t to, int value) int UnitConvert(metric_unit_t from, metric_unit_t to, int value)
{ {
CheckTargetUnit(from, to, false);
if (to == Native)
return value;
switch (from) switch (from)
{ {
case Kilometers: case Kilometers:
@ -1549,17 +1881,37 @@ int UnitConvert(metric_unit_t from, metric_unit_t to, int value)
default: break; default: break;
} }
case KphPS: case KphPS:
if (to == MphPS) return km_to_mi(value); switch (to)
else if (to == MetersPSS) return (value*1000)/3600; {
break; case MphPS: return km_to_mi(value);
case MetersPSS: return value * 10 /36;
case FeetPSS: return km_to_mi(value*feet_per_mile)/3600;
default: break;
}
case MphPS: case MphPS:
if (to == KphPS) return mi_to_km(value); switch (to)
else if (to == MetersPSS) return mi_to_km(value*1000) / 3600; // (value*8000)/(5*3600); {
break; case KphPS: return mi_to_km(value);
case MetersPSS: return mi_to_km(value*10)/36;
case FeetPSS: return value*feet_per_mile/3600;
default: break;
}
case MetersPSS: case MetersPSS:
if (to == KphPS) return (value*3600 / 1000); switch (to)
else if (to == MphPS) return km_to_mi(value*3600) / 1000; {
break; case KphPS: return (value*36) / 10;
case MphPS: return km_to_mi(value * 36) / 10;
case FeetPSS: return km_to_mi(value*feet_per_mile);
default: break;
}
case FeetPSS:
switch (to)
{
case KphPS: return (mi_to_km(value * 36 )/(feet_per_mile*10));
case MphPS: return value *3600/feet_per_mile;
case MetersPSS: return mi_to_km(value/feet_per_mile)*1000;
default: break;
}
case kW: case kW:
if (to == Watts) return (value*1000); if (to == Watts) return (value*1000);
break; break;
@ -1681,6 +2033,10 @@ int UnitConvert(metric_unit_t from, metric_unit_t to, int value)
float UnitConvert(metric_unit_t from, metric_unit_t to, float value) float UnitConvert(metric_unit_t from, metric_unit_t to, float value)
{ {
CheckTargetUnit(from, to, false);
if (to == Native)
return value;
switch (from) switch (from)
{ {
case Kilometers: case Kilometers:
@ -1716,17 +2072,37 @@ float UnitConvert(metric_unit_t from, metric_unit_t to, float value)
default: break; default: break;
} }
case KphPS: case KphPS:
if (to == MphPS) return km_to_mi(value); switch (to)
else if (to == MetersPSS) return value/3.6; {
break; case MphPS: return km_to_mi(value);
case MetersPSS: return value/3.6;
case FeetPSS: return km_to_mi(value)*feet_per_mile/3600;
default: break;
}
case MphPS: case MphPS:
if (to == KphPS) return mi_to_km(value); switch (to)
else if (to == MetersPSS) return (mi_to_km(value)/3.6); {
break; case KphPS: return mi_to_km(value);
case MetersPSS: return mi_to_km(value)/3.6;
case FeetPSS: return value*feet_per_mile/3600;
default: break;
}
case MetersPSS: case MetersPSS:
if (to == KphPS) return (value*3.6); switch (to)
else if (to == MphPS) return (km_to_mi(value)*3.6); {
break; case KphPS: return (value*3.6);
case MphPS: return km_to_mi(value)*3.6;
case FeetPSS: return km_to_mi(value)*feet_per_mile;
default: break;
}
case FeetPSS:
switch (to)
{
case KphPS: return (mi_to_km(value/feet_per_mile)*3.6);
case MphPS: return value *3600/feet_per_mile;
case MetersPSS: return mi_to_km(value/feet_per_mile)*1000;
default: break;
}
case kW: case kW:
if (to == Watts) return (value*1000); if (to == Watts) return (value*1000);
break; break;

File diff suppressed because it is too large Load diff

View file

@ -38,6 +38,7 @@
#include "ovms_events.h" #include "ovms_events.h"
#include "metrics_standard.h" #include "metrics_standard.h"
#include "ovms_version.h" #include "ovms_version.h"
#include "esp_idf_version.h"
/** /**
* chargestate_code: convert legacy chargestate key to code * 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); strret.assign<const char *>(begin, end);
return true; 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

View file

@ -351,7 +351,7 @@ INT sign_extend( UINT uvalue, uint8_t signbit)
UINT signmask = UINT(1U) << signbit; UINT signmask = UINT(1U) << signbit;
if ( newuvalue & signmask) if ( newuvalue & signmask)
newuvalue |= ~ (static_cast<uint_t>(signmask) - 1); 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; uint_t newuvalue = uvalue;
if ( newuvalue & ( UINT(1U) << SIGNBIT) ) if ( newuvalue & ( UINT(1U) << SIGNBIT) )
newuvalue |= ~((uint_t(1U) << SIGNBIT) - 1); newuvalue |= ~((uint_t(1U) << SIGNBIT) - 1);
return reinterpret_cast<INT &>(uvalue); return reinterpret_cast<INT &>(newuvalue);
} }
/** /**

View file

@ -51,64 +51,145 @@ static const char *TAG = "vfs";
#include "vfsedit.h" #include "vfsedit.h"
#endif // #ifdef CONFIG_OVMS_COMP_EDITOR #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; DIR *dir;
struct dirent *dp; struct dirent *dp;
char size[64], mod[64], path[PATH_MAX];
struct stat st;
if (argc == 0) if ((dir = opendir(startpath.c_str())) != NULL) {
{ while ((dp = readdir(dir)) != NULL) {
if ((dir = opendir (".")) == NULL) Direntry dirent(startpath, dp);
{ if (dirent.is_skip) {
writer->puts("Error: VFS cannot open directory listing"); continue;
return; }
entries.insert(dirent);
if (recurse && dirent.is_dir && !dirent.is_protected) {
read_entries(entries, dirent.path());
} }
} }
else closedir(dir);
{
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);
} }
}
/**
* 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) 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"); 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("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("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("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); cmd_vfs->RegisterCommand("stat","VFS Status of a file",vfs_stat, "<file>", 1, 1);

View file

@ -169,6 +169,17 @@ void test_watchdog(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc
writer->puts("Error: We should never get here"); 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 test_realloc(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{ {
void* buf; void* buf;
@ -385,6 +396,7 @@ TestFrameworkInit::TestFrameworkInit()
cmd_test->RegisterCommand("chargen","Character generator",test_chargen,"[<#lines>] [<delay_ms>]",0,2); cmd_test->RegisterCommand("chargen","Character generator",test_chargen,"[<#lines>] [<delay_ms>]",0,2);
cmd_test->RegisterCommand("echo", "Test getchar", test_echo); cmd_test->RegisterCommand("echo", "Test getchar", test_echo);
cmd_test->RegisterCommand("watchdog", "Test task spinning (and watchdog firing)", test_watchdog); 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("realloc", "Test memory re-allocations", test_realloc);
cmd_test->RegisterCommand("spiram", "Test SPI RAM memory usage", test_spiram); cmd_test->RegisterCommand("spiram", "Test SPI RAM memory usage", test_spiram);
cmd_test->RegisterCommand("strverscmp", "Test strverscmp function", test_strverscmp, "", 2, 2); cmd_test->RegisterCommand("strverscmp", "Test strverscmp function", test_strverscmp, "", 2, 2);