Compare commits
3 commits
9a451be72a
...
6c8ca654bc
Author | SHA1 | Date | |
---|---|---|---|
Carsten Schmiemann | 6c8ca654bc | ||
Carsten Schmiemann | 0db69f901c | ||
Carsten Schmiemann | 3d87aa9a79 |
|
@ -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;
|
||||||
|
|
|
@ -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)");
|
||||||
|
|
|
@ -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__
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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__
|
||||||
|
|
|
@ -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
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue