Sync upstream
This commit is contained in:
parent
9eb77d6aef
commit
1b7124c2c7
|
@ -213,7 +213,7 @@ void can_tx(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const
|
|||
uint32_t uv = strtoul(argv[0], &ep, 16);
|
||||
if (*ep != '\0' || uv > idmax)
|
||||
{
|
||||
writer->printf("Error: Invalid CAN ID \"%s\" (0x%lx max)\n", argv[0], idmax);
|
||||
writer->printf("Error: Invalid CAN ID \"%s\" (0x%x max)\n", argv[0], idmax);
|
||||
return;
|
||||
}
|
||||
frame.MsgID = uv;
|
||||
|
@ -298,7 +298,7 @@ void can_testtx(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, c
|
|||
uint32_t uv = strtoul(argv[0], &ep, 16);
|
||||
if (*ep != '\0' || uv > idmax)
|
||||
{
|
||||
writer->printf("Error: Invalid CAN ID \"%s\" (0x%lx max)\n", argv[0], idmax);
|
||||
writer->printf("Error: Invalid CAN ID \"%s\" (0x%x max)\n", argv[0], idmax);
|
||||
return;
|
||||
}
|
||||
frame.MsgID = uv;
|
||||
|
@ -570,7 +570,8 @@ static const char* const CAN_log_type_names[] = {
|
|||
"Status",
|
||||
"Comment",
|
||||
"Info",
|
||||
"Event"
|
||||
"Event",
|
||||
"Metric"
|
||||
};
|
||||
|
||||
const char* GetCanLogTypeName(CAN_log_type_t type)
|
||||
|
|
|
@ -251,6 +251,7 @@ typedef enum
|
|||
CAN_LogInfo_Comment, // general comment
|
||||
CAN_LogInfo_Config, // logger setup info (type, file, filters, vehicle)
|
||||
CAN_LogInfo_Event, // system event (i.e. vehicle started)
|
||||
CAN_LogInfo_Metric, // system or plugin metric (i.e. v.p.altitude)
|
||||
} CAN_log_type_t;
|
||||
|
||||
// Log message:
|
||||
|
|
|
@ -123,10 +123,11 @@ std::string canformat_crtd::get(CAN_log_message_t* message)
|
|||
case CAN_LogInfo_Comment:
|
||||
case CAN_LogInfo_Config:
|
||||
case CAN_LogInfo_Event:
|
||||
case CAN_LogInfo_Metric:
|
||||
snprintf(buf,sizeof(buf),"%ld.%06ld %c%s %s %s",
|
||||
message->timestamp.tv_sec, message->timestamp.tv_usec,
|
||||
busnumber,
|
||||
(message->type == CAN_LogInfo_Event) ? "CEV" : "CXX",
|
||||
(message->type == CAN_LogInfo_Event) ? "CEV" : (message->type == CAN_LogInfo_Metric) ? "CMT" : "CXX",
|
||||
GetCanLogTypeName(message->type),
|
||||
message->text);
|
||||
break;
|
||||
|
@ -151,7 +152,7 @@ std::string canformat_crtd::getheader(struct timeval *time)
|
|||
time = &t;
|
||||
}
|
||||
|
||||
snprintf(buf,sizeof(buf),"%ld.%06ld CXX OVMS CRTD\n%ld.%06ld CVR 3.0\n",
|
||||
snprintf(buf,sizeof(buf),"%ld.%06ld CXX OVMS CRTD\n%ld.%06ld CVR 3.1\n",
|
||||
time->tv_sec, time->tv_usec,
|
||||
time->tv_sec, time->tv_usec);
|
||||
|
||||
|
|
|
@ -42,6 +42,8 @@ static const char *TAG = "canlog";
|
|||
#include "ovms_peripherals.h"
|
||||
#include "metrics_standard.h"
|
||||
|
||||
static const char *CAN_PARAM = "can";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Command Processing
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
@ -352,8 +354,12 @@ canlog::canlog(const char* type, std::string format, canformat::canformat_serve_
|
|||
using std::placeholders::_1;
|
||||
using std::placeholders::_2;
|
||||
MyEvents.RegisterEvent(IDTAG, "*", std::bind(&canlog::EventListener, this, _1, _2));
|
||||
MyEvents.RegisterEvent(IDTAG,"config.mounted", std::bind(&canlog::UpdatedConfig, this, _1, _2));
|
||||
MyEvents.RegisterEvent(IDTAG,"config.changed", std::bind(&canlog::UpdatedConfig, this, _1, _2));
|
||||
MyMetrics.RegisterListener(IDTAG, "*", std::bind(&canlog::MetricListener, this, _1));
|
||||
|
||||
int queuesize = MyConfig.GetParamValueInt("can", "log.queuesize",100);
|
||||
int queuesize = MyConfig.GetParamValueInt(CAN_PARAM, "log.queuesize",100);
|
||||
LoadConfig();
|
||||
m_queue = xQueueCreate(queuesize, sizeof(CAN_log_message_t));
|
||||
xTaskCreatePinnedToCore(RxTask, "OVMS CanLog", 4096, (void*)this, 10, &m_task, CORE(1));
|
||||
}
|
||||
|
@ -361,6 +367,7 @@ canlog::canlog(const char* type, std::string format, canformat::canformat_serve_
|
|||
canlog::~canlog()
|
||||
{
|
||||
MyEvents.DeregisterEvent(IDTAG);
|
||||
MyMetrics.DeregisterListener(IDTAG);
|
||||
|
||||
if (m_task)
|
||||
{
|
||||
|
@ -383,6 +390,7 @@ canlog::~canlog()
|
|||
case CAN_LogInfo_Comment:
|
||||
case CAN_LogInfo_Config:
|
||||
case CAN_LogInfo_Event:
|
||||
case CAN_LogInfo_Metric:
|
||||
free(msg.text);
|
||||
break;
|
||||
default:
|
||||
|
@ -418,6 +426,7 @@ void canlog::RxTask(void *context)
|
|||
case CAN_LogInfo_Comment:
|
||||
case CAN_LogInfo_Config:
|
||||
case CAN_LogInfo_Event:
|
||||
case CAN_LogInfo_Metric:
|
||||
me->OutputMsg(msg);
|
||||
free(msg.text);
|
||||
break;
|
||||
|
@ -429,13 +438,179 @@ void canlog::RxTask(void *context)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a comma-separated list of filters, and assign them to a member of the class.
|
||||
* We have 3 kind of comparisons, and an unlimited list of filters.
|
||||
*
|
||||
* A filter can be:
|
||||
* - A "startsWith" comparison - when ending with '*',
|
||||
* - An "endsWith" comparison - when starting with '*',
|
||||
* - Invalid (and skipped) if empty, or with a '*' in any other position than beginning or end,
|
||||
* - A "string equal" comparison for all other cases
|
||||
*/
|
||||
static void LoadFilters(conn_filters_arr_t &member, const std::string &value)
|
||||
{
|
||||
// Empty all previously defined filters, for all operators
|
||||
for (int i=0; i<COUNT_OF_OPERATORS; i++)
|
||||
{
|
||||
member[i].clear();
|
||||
}
|
||||
|
||||
if (!value.empty())
|
||||
{
|
||||
std::stringstream stream (value);
|
||||
std::string item;
|
||||
unsigned char comparison_operator;
|
||||
|
||||
// Comma-separated list
|
||||
while (getline (stream, item, ','))
|
||||
{
|
||||
trim(item); // Removing leading and trailing spaces
|
||||
|
||||
if (item.empty())
|
||||
{
|
||||
ESP_LOGW(TAG, "LoadFilters: skipping empty value in the filter list");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if there is a wildcard ('*') in any other place than first or last position
|
||||
size_t wildcard_position = item.find('*', 1);
|
||||
if ((wildcard_position != std::string::npos) && (wildcard_position != item.size()-1))
|
||||
{
|
||||
ESP_LOGW(TAG, "LoadFilters: skipping incorrect value (%s) in the filter list (wildcard in wrong position)", item.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Depending on the presence and position of the wildcard, push the filter
|
||||
// in the proper vector (without the wildcard)
|
||||
if (item.front() == '*')
|
||||
{
|
||||
comparison_operator = OPERATOR_ENDSWITH;
|
||||
item.erase(0, 1);
|
||||
}
|
||||
else if (item.back() == '*')
|
||||
{
|
||||
comparison_operator = OPERATOR_STARTSWITH;
|
||||
item.pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
comparison_operator = OPERATOR_EQUALS;
|
||||
}
|
||||
member[comparison_operator].push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
// for (int i=0; i<COUNT_OF_OPERATORS; i++)
|
||||
// {
|
||||
// ESP_LOGI(TAG, "LoadFilters: filters for operator %d:", i);
|
||||
// for (std::vector<std::string>::iterator it=member[i].begin(); it!=member[i].end(); ++it)
|
||||
// {
|
||||
// ESP_LOGI(TAG, "LoadFilters: filter value '%s'", it->c_str());
|
||||
// }
|
||||
// if (member[i].begin() == member[i].end())
|
||||
// {
|
||||
// ESP_LOGI(TAG, "LoadFilters: (empty filter list)");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Load, or reload, the configuration of events and metrics filters.
|
||||
*
|
||||
* The configuration item is a string containing a comma-separated list of filters.
|
||||
*/
|
||||
void canlog::LoadConfig()
|
||||
{
|
||||
std::size_t str_hash;
|
||||
|
||||
std::string list_of_events_filters = MyConfig.GetParamValue(CAN_PARAM, "log.events_filters", "x*,vehicle*");
|
||||
str_hash = std::hash<std::string>{}(list_of_events_filters);
|
||||
if (str_hash != m_events_filters_hash)
|
||||
{
|
||||
m_events_filters_hash = str_hash;
|
||||
LoadFilters(m_events_filters, list_of_events_filters);
|
||||
MyCan.LogInfo(NULL, CAN_LogInfo_Config, ("Events filters: " + list_of_events_filters).c_str());
|
||||
}
|
||||
std::string list_of_metrics_filters = MyConfig.GetParamValue(CAN_PARAM, "log.metrics_filters");
|
||||
str_hash = std::hash<std::string>{}(list_of_metrics_filters);
|
||||
if (str_hash != m_metrics_filters_hash)
|
||||
{
|
||||
m_metrics_filters_hash = str_hash;
|
||||
LoadFilters(m_metrics_filters, list_of_metrics_filters);
|
||||
MyCan.LogInfo(NULL, CAN_LogInfo_Config, ("Metrics filters: " + list_of_metrics_filters).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load, or reload, the configuration if a config event occurred.
|
||||
*/
|
||||
void canlog::UpdatedConfig(std::string event, void* data)
|
||||
{
|
||||
if (event == "config.changed")
|
||||
{
|
||||
// Only reload if our parameter has changed
|
||||
OvmsConfigParam*p = (OvmsConfigParam*)data;
|
||||
if (p->GetName() != CAN_PARAM)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
LoadConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value matches in a list of filters.
|
||||
*
|
||||
* Match can be a:
|
||||
* - startsWith match,
|
||||
* - endsWith match,
|
||||
* - equality match.
|
||||
*/
|
||||
static bool CheckFilter(conn_filters_arr_t &member, const std::string &value)
|
||||
{
|
||||
for (int i=0; i<COUNT_OF_OPERATORS; i++)
|
||||
{
|
||||
for (std::vector<std::string>::iterator it=member[i].begin(); it!=member[i].end(); ++it)
|
||||
{
|
||||
if ((i == OPERATOR_STARTSWITH) && (startsWith(value, *it)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if ((i == OPERATOR_ENDSWITH) && (endsWith(value, *it)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if ((i == OPERATOR_EQUALS) && (value == *it))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void canlog::EventListener(std::string event, void* data)
|
||||
{
|
||||
// Log vehicle custom (x…) & framework events:
|
||||
if (startsWith(event, 'x') || startsWith(event, "vehicle"))
|
||||
if (CheckFilter(m_events_filters, event))
|
||||
LogInfo(NULL, CAN_LogInfo_Event, event.c_str());
|
||||
}
|
||||
|
||||
void canlog::MetricListener(OvmsMetric* metric)
|
||||
{
|
||||
std::string name = metric->m_name;
|
||||
// Log metrics (in JSON for later parsing):
|
||||
if (CheckFilter(m_metrics_filters, name))
|
||||
{
|
||||
std::string metric_text = "{ ";
|
||||
metric_text += "\"name\": \"" + json_encode(name) + "\", ";
|
||||
metric_text += "\"value\": " + metric->AsJSON() + ", ";
|
||||
metric_text += "\"unit\": \"" + json_encode(std::string(OvmsMetricUnitLabel(metric->GetUnits()))) + "\" }";
|
||||
LogInfo(NULL, CAN_LogInfo_Metric, metric_text.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
const char* canlog::GetType()
|
||||
{
|
||||
return m_type;
|
||||
|
|
|
@ -95,6 +95,11 @@ class canlogconnection: public InternalRamAllocated
|
|||
uint32_t m_filtercount;
|
||||
};
|
||||
|
||||
#define OPERATOR_STARTSWITH 0
|
||||
#define OPERATOR_ENDSWITH 1
|
||||
#define OPERATOR_EQUALS 2
|
||||
#define COUNT_OF_OPERATORS 3
|
||||
typedef std::array<std::vector<std::string>, COUNT_OF_OPERATORS> conn_filters_arr_t;
|
||||
|
||||
class canlog : public InternalRamAllocated
|
||||
{
|
||||
|
@ -105,6 +110,7 @@ class canlog : public InternalRamAllocated
|
|||
public:
|
||||
static void RxTask(void* context);
|
||||
void EventListener(std::string event, void* data);
|
||||
void MetricListener(OvmsMetric* metric);
|
||||
|
||||
public:
|
||||
const char* GetType();
|
||||
|
@ -152,6 +158,16 @@ class canlog : public InternalRamAllocated
|
|||
uint32_t m_msgcount;
|
||||
uint32_t m_dropcount;
|
||||
uint32_t m_filtercount;
|
||||
|
||||
protected:
|
||||
virtual void UpdatedConfig(std::string event, void* data);
|
||||
virtual void LoadConfig();
|
||||
|
||||
protected:
|
||||
conn_filters_arr_t m_events_filters;
|
||||
size_t m_events_filters_hash = 0;
|
||||
conn_filters_arr_t m_metrics_filters;
|
||||
size_t m_metrics_filters_hash = 0;
|
||||
};
|
||||
|
||||
#endif // __CANLOG_H__
|
||||
|
|
|
@ -109,6 +109,7 @@ void canlog_monitor_conn::OutputMsg(CAN_log_message_t& msg, std::string &result)
|
|||
case CAN_LogInfo_Comment:
|
||||
case CAN_LogInfo_Config:
|
||||
case CAN_LogInfo_Event:
|
||||
case CAN_LogInfo_Metric:
|
||||
ESP_LOGD(TAG,"%s",result.c_str());
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -89,7 +89,7 @@ OvmsCanLogVFSInit::OvmsCanLogVFSInit()
|
|||
|
||||
|
||||
canlog_vfs_conn::canlog_vfs_conn(canlog* logger, std::string format, canformat::canformat_serve_mode_t mode)
|
||||
: canlogconnection(logger, format, mode)
|
||||
: canlogconnection(logger, format, mode), m_file_size(0)
|
||||
{
|
||||
m_file = NULL;
|
||||
}
|
||||
|
@ -114,7 +114,10 @@ void canlog_vfs_conn::OutputMsg(CAN_log_message_t& msg, std::string &result)
|
|||
}
|
||||
|
||||
if (result.length()>0)
|
||||
{
|
||||
fwrite(result.c_str(),result.length(),1,m_file);
|
||||
m_file_size += result.length();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -181,7 +184,10 @@ bool canlog_vfs::Open()
|
|||
|
||||
std::string header = m_formatter->getheader();
|
||||
if (header.length()>0)
|
||||
{
|
||||
fwrite(header.c_str(),header.length(),1,clc->m_file);
|
||||
clc->m_file_size += header.length();
|
||||
}
|
||||
|
||||
m_connmap[NULL] = clc;
|
||||
m_isopen = true;
|
||||
|
@ -207,6 +213,47 @@ void canlog_vfs::Close()
|
|||
}
|
||||
}
|
||||
|
||||
size_t canlog_vfs::GetFileSize()
|
||||
{
|
||||
size_t result = 0;
|
||||
if (m_isopen)
|
||||
{
|
||||
for (conn_map_t::iterator it=m_connmap.begin(); it!=m_connmap.end(); ++it)
|
||||
{
|
||||
canlog_vfs_conn * clc = static_cast<canlog_vfs_conn *>(it->second);
|
||||
result += clc->m_file_size;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string canlog_vfs_conn::GetStats()
|
||||
{
|
||||
char bufsize[15];
|
||||
format_file_size(bufsize, sizeof(bufsize), m_file_size);
|
||||
|
||||
std::string result = "Size:";
|
||||
result.append(bufsize);
|
||||
result.append(" ");
|
||||
result.append(canlogconnection::GetStats());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string canlog_vfs::GetStats()
|
||||
{
|
||||
char bufsize[15];
|
||||
size_t size = GetFileSize();
|
||||
format_file_size(bufsize, sizeof(bufsize), size);
|
||||
|
||||
std::string result = "Size:";
|
||||
result.append(bufsize);
|
||||
result.append(" ");
|
||||
result.append(canlog::GetStats());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string canlog_vfs::GetInfo()
|
||||
{
|
||||
std::string result = canlog::GetInfo();
|
||||
|
|
|
@ -39,9 +39,11 @@ class canlog_vfs_conn: public canlogconnection
|
|||
|
||||
public:
|
||||
virtual void OutputMsg(CAN_log_message_t& msg, std::string &result);
|
||||
virtual std::string GetStats();
|
||||
|
||||
public:
|
||||
FILE* m_file;
|
||||
size_t m_file_size;
|
||||
};
|
||||
|
||||
|
||||
|
@ -55,9 +57,11 @@ class canlog_vfs : public canlog
|
|||
virtual bool Open();
|
||||
virtual void Close();
|
||||
virtual std::string GetInfo();
|
||||
virtual size_t GetFileSize();
|
||||
|
||||
public:
|
||||
virtual void MountListener(std::string event, void* data);
|
||||
virtual std::string GetStats();
|
||||
|
||||
public:
|
||||
std::string m_path;
|
||||
|
|
|
@ -75,7 +75,7 @@ class ConsoleSSH : public OvmsConsole
|
|||
void Sent();
|
||||
void Exit();
|
||||
int puts(const char* s);
|
||||
int printf(const char* fmt, ...);
|
||||
int printf(const char* fmt, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
ssize_t write(const void *buf, size_t nbyte);
|
||||
int RecvCallback(char* buf, uint32_t size);
|
||||
bool IsDraining() { return m_drain > 0; }
|
||||
|
|
|
@ -64,7 +64,7 @@ class ConsoleTelnet : public OvmsConsole
|
|||
void Receive();
|
||||
void Exit();
|
||||
int puts(const char* s);
|
||||
int printf(const char* fmt, ...);
|
||||
int printf(const char* fmt, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
ssize_t write(const void *buf, size_t nbyte);
|
||||
|
||||
protected:
|
||||
|
|
|
@ -119,7 +119,7 @@ void modem::Task()
|
|||
.use_ref_tick = 0,
|
||||
};
|
||||
uart_param_config(m_uartnum, &uart_config);
|
||||
uart_set_pin(m_uartnum, m_txpin, m_rxpin, 0, 0);
|
||||
uart_set_pin(m_uartnum, m_txpin, m_rxpin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
uart_driver_install(m_uartnum,
|
||||
CONFIG_OVMS_HW_CELLULAR_MODEM_UART_SIZE,
|
||||
CONFIG_OVMS_HW_CELLULAR_MODEM_UART_SIZE,
|
||||
|
@ -245,11 +245,20 @@ void modem::Task()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_state1 == PoweredOff && MyBoot.IsShuttingDown())
|
||||
{
|
||||
m_task = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown:
|
||||
uart_driver_delete(m_uartnum);
|
||||
ESP_LOGD(TAG, "UART shutdown");
|
||||
m_queue = 0;
|
||||
uart_wait_tx_done(m_uartnum, portMAX_DELAY);
|
||||
uart_flush(m_uartnum);
|
||||
uart_driver_delete(m_uartnum);
|
||||
if (MyBoot.IsShuttingDown()) MyBoot.ShutdownReady(TAG);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
|
@ -662,7 +671,6 @@ void modem::State1Enter(modem_state1_t newstate)
|
|||
case PoweredOff:
|
||||
ClearNetMetrics();
|
||||
MyEvents.SignalEvent("system.modem.poweredoff", NULL);
|
||||
if (MyBoot.IsShuttingDown()) MyBoot.ShutdownReady(TAG);
|
||||
StopMux();
|
||||
if (m_driver)
|
||||
{
|
||||
|
@ -1018,13 +1026,16 @@ void modem::StandardLineHandler(int channel, OvmsBuffer* buf, std::string line)
|
|||
line = m_line_buffer;
|
||||
}
|
||||
|
||||
const char *cp = line.c_str();
|
||||
if ((line.length()>2)&&(cp[0]!='$')&&(cp[1])!='G')
|
||||
if (line.compare(0, 2, "$G") == 0)
|
||||
{
|
||||
// Log incoming data other than GPS NMEA
|
||||
ESP_LOGD(TAG, "mux-rx-line #%d: %s", channel, line.c_str());
|
||||
// GPS NMEA URC:
|
||||
if (m_nmea) m_nmea->IncomingLine(line);
|
||||
return;
|
||||
}
|
||||
|
||||
// Log incoming data other than GPS NMEA
|
||||
ESP_LOGD(TAG, "mux-rx-line #%d: %s", channel, line.c_str());
|
||||
|
||||
if ((line.compare(0, 8, "CONNECT ") == 0)&&(m_state1 == NetStart)&&(m_state1_userdata == 1))
|
||||
{
|
||||
ESP_LOGI(TAG, "PPP Connection is ready to start");
|
||||
|
@ -1175,9 +1186,10 @@ void modem::StandardLineHandler(int channel, OvmsBuffer* buf, std::string line)
|
|||
}
|
||||
else if (line.compare(0, 30, "+CME ERROR: incorrect password") == 0)
|
||||
{
|
||||
std::string pincode = MyConfig.GetParamValue("modem", "pincode");
|
||||
ESP_LOGE(TAG,"Wrong PIN code entered!");
|
||||
MyEvents.SignalEvent("system.modem.wrongpingcode", NULL);
|
||||
MyNotify.NotifyStringf("alert", "modem.wrongpincode", "Wrong pin code (%s) entered!", MyConfig.GetParamValue("modem", "pincode"));
|
||||
MyNotify.NotifyStringf("alert", "modem.wrongpincode", "Wrong pin code (%s) entered!", pincode.c_str());
|
||||
MyConfig.SetParamValueBool("modem","wrongpincode",true);
|
||||
}
|
||||
else if (line.compare(0, 28, "+CME ERROR: SIM not inserted") == 0)
|
||||
|
@ -1188,8 +1200,8 @@ void modem::StandardLineHandler(int channel, OvmsBuffer* buf, std::string line)
|
|||
}
|
||||
|
||||
// MMI/USSD response (URC):
|
||||
// sent on all free channels, so we only process m_mux_channel_CMD
|
||||
else if (channel == m_mux_channel_CMD && line.compare(0, 7, "+CUSD: ") == 0)
|
||||
// sent on all free channels or only on POLL, so we only process m_mux_channel_POLL
|
||||
else if (channel == m_mux_channel_POLL && line.compare(0, 7, "+CUSD: ") == 0)
|
||||
{
|
||||
// Format: +CUSD: 0,"…msg…",15
|
||||
// The message string may contain CR/LF so can come on multiple lines, with unknown length
|
||||
|
@ -1333,7 +1345,7 @@ void modem::StartTask()
|
|||
if (!m_task)
|
||||
{
|
||||
ESP_LOGV(TAG, "Starting modem task");
|
||||
xTaskCreatePinnedToCore(MODEM_task, "OVMS Cellular", CONFIG_OVMS_HW_CELLULAR_MODEM_STACK_SIZE, (void*)this, 20, &m_task, CORE(0));
|
||||
xTaskCreatePinnedToCore(MODEM_task, "OVMS Cellular", CONFIG_OVMS_HW_CELLULAR_MODEM_STACK_SIZE, (void*)this, 20, (void**)&m_task, CORE(0));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1351,27 +1363,42 @@ void modem::StopTask()
|
|||
}
|
||||
}
|
||||
|
||||
void modem::StartNMEA()
|
||||
bool modem::StartNMEA(bool force /*=false*/)
|
||||
{
|
||||
if ( (m_nmea == NULL) &&
|
||||
(MyConfig.GetParamValueBool("modem", "enable.gps", false)) )
|
||||
(force || MyConfig.GetParamValueBool("modem", "enable.gps", false)) )
|
||||
{
|
||||
ESP_LOGV(TAG, "Starting NMEA");
|
||||
m_nmea = new GsmNMEA(m_mux, m_mux_channel_NMEA, m_mux_channel_CMD);
|
||||
m_nmea->Startup();
|
||||
m_driver->StartupNMEA();
|
||||
if (!m_mux || !m_driver)
|
||||
{
|
||||
ESP_LOGE(TAG, "StartNMEA failed: MUX or driver not available");
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGV(TAG, "Starting NMEA");
|
||||
m_nmea = new GsmNMEA(m_mux, m_mux_channel_NMEA, m_mux_channel_CMD);
|
||||
m_nmea->Startup();
|
||||
m_driver->StartupNMEA();
|
||||
}
|
||||
}
|
||||
return (m_nmea != NULL);
|
||||
}
|
||||
|
||||
void modem::StopNMEA()
|
||||
{
|
||||
if (m_nmea != NULL)
|
||||
{
|
||||
ESP_LOGV(TAG, "Stopping NMEA");
|
||||
m_driver->ShutdownNMEA();
|
||||
m_nmea->Shutdown();
|
||||
delete m_nmea;
|
||||
m_nmea = NULL;
|
||||
if (!m_mux || !m_driver)
|
||||
{
|
||||
ESP_LOGE(TAG, "StopNMEA failed: MUX or driver not available");
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGV(TAG, "Stopping NMEA");
|
||||
m_driver->ShutdownNMEA();
|
||||
m_nmea->Shutdown();
|
||||
delete m_nmea;
|
||||
m_nmea = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1446,7 +1473,8 @@ void modem::Ticker(std::string event, void* data)
|
|||
|
||||
ev.event.type = TICKER1;
|
||||
|
||||
xQueueSend(m_queue,&ev,0);
|
||||
QueueHandle_t queue = m_queue;
|
||||
if (queue) xQueueSend(queue,&ev,0);
|
||||
}
|
||||
|
||||
void modem::EventListener(std::string event, void* data)
|
||||
|
@ -1474,17 +1502,7 @@ void modem::IncomingMuxData(GsmMuxChannel* channel)
|
|||
}
|
||||
else if (channel->m_channel == m_mux_channel_NMEA)
|
||||
{
|
||||
if (m_nmea != NULL)
|
||||
{
|
||||
while (channel->m_buffer.HasLine() >= 0)
|
||||
{
|
||||
m_nmea->IncomingLine(channel->m_buffer.ReadLine());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
channel->m_buffer.EmptyAll();
|
||||
}
|
||||
StandardIncomingHandler(channel->m_channel, &channel->m_buffer);
|
||||
}
|
||||
else if (channel->m_channel == m_mux_channel_DATA)
|
||||
{
|
||||
|
@ -1523,7 +1541,8 @@ void modem::SendSetState1(modem_state1_t newstate)
|
|||
ev.event.type = SETSTATE;
|
||||
ev.event.data.newstate = newstate;
|
||||
|
||||
xQueueSend(m_queue,&ev,0);
|
||||
QueueHandle_t queue = m_queue;
|
||||
if (queue) xQueueSend(queue,&ev,0);
|
||||
}
|
||||
|
||||
bool modem::IsStarted()
|
||||
|
@ -1722,6 +1741,53 @@ void modem_setstate(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int arg
|
|||
writer->printf("Error: Unrecognised state %s\n",statename);
|
||||
}
|
||||
|
||||
void modem_gps_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
||||
{
|
||||
writer->printf("GPS status: autostart %s, currently %s.\n",
|
||||
MyConfig.GetParamValueBool("modem", "enable.gps", false) ? "enabled" : "disabled",
|
||||
(MyModem && MyModem->m_nmea) ? "running" : "not running");
|
||||
}
|
||||
|
||||
void modem_gps_start(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
||||
{
|
||||
if (!MyModem || !MyModem->m_mux || !MyModem->m_mux->IsMuxUp())
|
||||
{
|
||||
writer->puts("ERROR: Modem not ready");
|
||||
return;
|
||||
}
|
||||
if (MyModem->m_nmea)
|
||||
{
|
||||
writer->puts("GPS already running.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (MyModem->StartNMEA(true))
|
||||
{
|
||||
writer->puts("GPS started (may take a minute to find satellites).");
|
||||
}
|
||||
else
|
||||
{
|
||||
writer->puts("ERROR: GPS startup failed.");
|
||||
}
|
||||
}
|
||||
|
||||
void modem_gps_stop(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
||||
{
|
||||
if (!MyModem || !MyModem->m_mux || !MyModem->m_mux->IsMuxUp())
|
||||
{
|
||||
writer->puts("ERROR: Modem not ready");
|
||||
return;
|
||||
}
|
||||
if (!MyModem->m_nmea)
|
||||
{
|
||||
writer->puts("GPS already stopped.");
|
||||
return;
|
||||
}
|
||||
|
||||
MyModem->StopNMEA();
|
||||
writer->puts("GPS stopped.");
|
||||
}
|
||||
|
||||
void cellular_drivers(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
||||
{
|
||||
writer->puts("Type Name");
|
||||
|
@ -1777,6 +1843,11 @@ CellularModemInit::CellularModemInit()
|
|||
cmd_setstate->RegisterCommand(ModemState1Name((modem::modem_state1_t)x),"Force CELLULAR MODEM state change",modem_setstate);
|
||||
}
|
||||
|
||||
OvmsCommand* cmd_gps = cmd_cellular->RegisterCommand("gps", "GPS/GNSS state control", modem_gps_status);
|
||||
cmd_gps->RegisterCommand("status", "GPS/GNSS status", modem_gps_status);
|
||||
cmd_gps->RegisterCommand("start", "Start GPS/GNSS", modem_gps_start);
|
||||
cmd_gps->RegisterCommand("stop", "Stop GPS/GNSS", modem_gps_stop);
|
||||
|
||||
MyConfig.RegisterParam("modem", "Modem Configuration", true, true);
|
||||
// Our instances:
|
||||
// 'driver': Driver to use (default: auto)
|
||||
|
|
|
@ -58,7 +58,7 @@ class modem : public pcp, public InternalRamAllocated
|
|||
~modem();
|
||||
|
||||
protected:
|
||||
TaskHandle_t m_task;
|
||||
volatile TaskHandle_t m_task;
|
||||
volatile QueueHandle_t m_queue;
|
||||
int m_baud;
|
||||
int m_rxpin;
|
||||
|
@ -201,7 +201,7 @@ class modem : public pcp, public InternalRamAllocated
|
|||
// High level API functions
|
||||
void StartTask();
|
||||
void StopTask();
|
||||
void StartNMEA();
|
||||
bool StartNMEA(bool force=false);
|
||||
void StopNMEA();
|
||||
void StartMux();
|
||||
void StopMux();
|
||||
|
|
|
@ -82,9 +82,9 @@ void modemdriver::Restart()
|
|||
{
|
||||
ESP_LOGI(TAG, "Restart");
|
||||
if (MyConfig.GetParamValueBool("auto", "modem", false))
|
||||
m_modem->SetState1((m_modem->GetState1() != modem::PoweredOff) ? modem::PowerOffOn : modem::PoweringOn);
|
||||
m_modem->SendSetState1((m_modem->GetState1() != modem::PoweredOff) ? modem::PowerOffOn : modem::PoweringOn);
|
||||
else
|
||||
m_modem->SetState1(modem::PoweringOff);
|
||||
m_modem->SendSetState1(modem::PoweringOff);
|
||||
}
|
||||
|
||||
void modemdriver::PowerOff()
|
||||
|
@ -101,6 +101,7 @@ void modemdriver::PowerCycle()
|
|||
m_powercyclefactor = m_powercyclefactor % 3;
|
||||
ESP_LOGI(TAG, "Power Cycle %dms", psd);
|
||||
|
||||
uart_wait_tx_done(m_modem->m_uartnum, portMAX_DELAY);
|
||||
uart_flush(m_modem->m_uartnum); // Flush the ring buffer, to try to address MUX start issues
|
||||
#ifdef CONFIG_OVMS_COMP_MAX7317
|
||||
MyPeripherals->m_max7317->Output(MODEM_EGPIO_PWR, 0); // Modem EN/PWR line low
|
||||
|
@ -142,7 +143,12 @@ void modemdriver::ShutdownNMEA()
|
|||
{
|
||||
// Switch off GPS:
|
||||
if (m_modem->m_mux != NULL)
|
||||
{ m_modem->muxtx(GetMuxChannelCMD(), "AT+CGPS=0\r\n"); }
|
||||
{
|
||||
// send single commands, as each can fail:
|
||||
m_modem->muxtx(GetMuxChannelCMD(), "AT+CGPSNMEA=0\r\n");
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
m_modem->muxtx(GetMuxChannelCMD(), "AT+CGPS=0\r\n");
|
||||
}
|
||||
else
|
||||
{ ESP_LOGE(TAG, "Attempt to transmit on non running mux"); }
|
||||
}
|
||||
|
|
|
@ -136,7 +136,7 @@ bool OvmsHttpClient::Request(std::string url, const char* method)
|
|||
{
|
||||
// ESP_LOGI(TAG, "Got response %s",m_buf->ReadLine().c_str());
|
||||
std::string header = m_buf->ReadLine();
|
||||
if (header.compare(0,15,"Content-Length:") == 0)
|
||||
if (strncasecmp(header.c_str(), "Content-Length:", 15) == 0)
|
||||
{
|
||||
m_bodysize = atoi(header.substr(15).c_str());
|
||||
}
|
||||
|
|
|
@ -290,9 +290,10 @@ void OvmsLocation::Store(std::string& buf)
|
|||
|
||||
void OvmsLocation::Render(std::string& buf)
|
||||
{
|
||||
char val[32];
|
||||
snprintf(val, sizeof(val), "%0.6f,%0.6f (%dm)", m_latitude, m_longitude, m_radius);
|
||||
buf = val;
|
||||
metric_unit_t user_length = OvmsMetricGetUserUnit(GrpDistanceShort, Meters);
|
||||
|
||||
buf = string_format("%0.6f,%0.6f (%d%s)",
|
||||
m_latitude, m_longitude, UnitConvert(Meters, user_length, m_radius), OvmsMetricUnitLabel(user_length));
|
||||
bool first = true;
|
||||
for (ActionList::iterator it = m_actions.begin(); it != m_actions.end(); ++it)
|
||||
{
|
||||
|
@ -369,11 +370,22 @@ void location_list(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc
|
|||
writer->puts("NOTE: ACC actions are not implemented yet!"); // XXX IMPLEMENT AND REMOVE THIS!
|
||||
}
|
||||
|
||||
int location_set_validate(OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv, bool complete)
|
||||
{
|
||||
if (argc == 5)
|
||||
{
|
||||
return OvmsMetricUnit_Validate(writer, argc, argv[4], complete, GrpDistanceShort);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void location_set(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
||||
{
|
||||
const char *name = argv[0];
|
||||
float latitude, longitude;
|
||||
int radius = LOCATION_DEFRADIUS;
|
||||
int base_value = radius;
|
||||
metric_unit_t user_length = Meters;
|
||||
|
||||
if (strcmp(name, "?") == 0)
|
||||
{
|
||||
|
@ -391,12 +403,37 @@ void location_set(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc,
|
|||
longitude = MyLocations.m_longitude;
|
||||
}
|
||||
|
||||
if (argc > 3) radius = atoi(argv[3]);
|
||||
if (argc > 3)
|
||||
{
|
||||
radius = atoi(argv[3]);
|
||||
base_value = radius;
|
||||
if (argc < 5)
|
||||
user_length = OvmsMetricGetUserUnit(GrpDistanceShort, Meters);
|
||||
else
|
||||
{
|
||||
user_length = OvmsMetricUnitFromName(argv[4]);
|
||||
if (user_length == UnitNotFound)
|
||||
{
|
||||
writer->printf("Error: Invalid Metric %s\n", argv[4]);
|
||||
return;
|
||||
}
|
||||
user_length = OvmsMetricCheckUnit(Meters, user_length);
|
||||
if (user_length == UnitNotFound)
|
||||
{
|
||||
writer->printf("Error: Metric %s is not a length unit\n", argv[4]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
char val[32];
|
||||
snprintf(val,sizeof(val),"%0.6f,%0.6f,%d",latitude,longitude,radius);
|
||||
MyConfig.SetParamValue(LOCATIONS_PARAM,name,val);
|
||||
writer->puts("Location defined");
|
||||
radius = UnitConvert(user_length, Meters, radius);
|
||||
}
|
||||
|
||||
std::string val = string_format("%0.6f,%0.6f,%d",latitude,longitude,radius);
|
||||
MyConfig.SetParamValue(LOCATIONS_PARAM,name,val.c_str());
|
||||
if (user_length == Meters)
|
||||
writer->printf("Location defined with radius of %dm\n", base_value);
|
||||
else
|
||||
writer->printf("Location defined with radius of %d%s = %dm\n", base_value, OvmsMetricUnitLabel(user_length), radius);
|
||||
}
|
||||
|
||||
void location_radius(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
||||
|
@ -409,12 +446,35 @@ void location_radius(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int ar
|
|||
writer->printf("Error: No location %s defined\n",name);
|
||||
return;
|
||||
}
|
||||
metric_unit_t user_length;
|
||||
if (argc < 3)
|
||||
user_length = OvmsMetricGetUserUnit(GrpDistanceShort, Meters);
|
||||
else
|
||||
{
|
||||
user_length = OvmsMetricUnitFromName(argv[2]);
|
||||
if (user_length == UnitNotFound)
|
||||
{
|
||||
writer->printf("Error: Invalid Metric %s\n", argv[2]);
|
||||
return;
|
||||
}
|
||||
user_length = OvmsMetricCheckUnit(Meters, user_length);
|
||||
if (user_length == UnitNotFound)
|
||||
{
|
||||
writer->printf("Error: Metric %s is not a length unit\n", argv[2]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::string buf;
|
||||
OvmsLocation* loc = *locp;
|
||||
loc->m_radius = atoi(argv[1]);
|
||||
int base_value = atoi(argv[1]);
|
||||
int radius_m = UnitConvert(user_length, Meters, base_value);
|
||||
loc->m_radius = radius_m;
|
||||
loc->Store(buf);
|
||||
writer->puts("Location radius set");
|
||||
if (user_length == Meters)
|
||||
writer->printf("Location radius set to %dm\n", base_value);
|
||||
else
|
||||
writer->printf("Location radius set to %d%s = %dm\n", base_value, OvmsMetricUnitLabel(user_length), radius_m);
|
||||
}
|
||||
|
||||
void location_rm(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
||||
|
@ -477,6 +537,16 @@ int location_validate(OvmsWriter* writer, OvmsCommand* cmd, int argc, const char
|
|||
return -1;
|
||||
}
|
||||
|
||||
int location_radius_validate(OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv, bool complete)
|
||||
{
|
||||
switch (argc)
|
||||
{
|
||||
case 1: return MyLocations.m_locations.Validate(writer, argc, argv[0], complete);
|
||||
case 3: return OvmsMetricUnit_Validate(writer, argc, argv[2], complete, GrpDistanceShort);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void location_action(int verbosity, OvmsWriter* writer, enum LocationAction act, std::string& params)
|
||||
{
|
||||
const char* const* rargv = writer->GetArgv();
|
||||
|
@ -634,8 +704,8 @@ OvmsLocations::OvmsLocations()
|
|||
// Register our commands
|
||||
OvmsCommand* cmd_location = MyCommandApp.RegisterCommand("location","LOCATION framework", location_status, "", 0, 0, false);
|
||||
cmd_location->RegisterCommand("list","Show all locations",location_list);
|
||||
cmd_location->RegisterCommand("set","Set the position of a location",location_set, "<name> [<latitude> <longitude> [<radius>]]", 1, 4);
|
||||
cmd_location->RegisterCommand("radius","Set the radius of a location",location_radius, "<name> <radius>", 2, 2, true, location_validate);
|
||||
cmd_location->RegisterCommand("set","Set the position of a location",location_set, "<name> [<latitude> <longitude> [<radius> [<unit>]] ]", 1, 5, true, location_set_validate);
|
||||
cmd_location->RegisterCommand("radius","Set the radius of a location (defaults to user 'height' units)",location_radius, "<name> <radius> [<unit>]", 2, 3, true, location_radius_validate);
|
||||
cmd_location->RegisterCommand("rm","Remove a defined location",location_rm, "<name>", 1, 1, true, location_validate);
|
||||
cmd_location->RegisterCommand("status","Show location status",location_status);
|
||||
OvmsCommand* cmd_action = cmd_location->RegisterCommand("action","Set an action for a location");
|
||||
|
|
|
@ -198,7 +198,7 @@ size_t OvmsNetHttpAsyncClient::IncomingData(void *data, size_t length)
|
|||
{
|
||||
// Process the header
|
||||
ESP_LOGD(TAG, "OvmsNetHttpAsyncClient Headers got %s", header.c_str());
|
||||
if (header.compare(0,15,"Content-Length:") == 0)
|
||||
if (strncasecmp(header.c_str(), "Content-Length:", 15) == 0)
|
||||
{
|
||||
m_bodysize = atoi(header.substr(15).c_str());
|
||||
ESP_LOGD(TAG, "OvmsNetHttpAsyncClient content-length is %d", m_bodysize);
|
||||
|
|
|
@ -38,7 +38,9 @@ static const char *TAG = "ota";
|
|||
#include <string.h>
|
||||
#include <esp_system.h>
|
||||
#include <esp_ota_ops.h>
|
||||
#if ESP_IDF_VERSION_MAJOR < 4
|
||||
#include "strverscmp.h"
|
||||
#endif
|
||||
#include "ovms_ota.h"
|
||||
#include "ovms_command.h"
|
||||
#include "ovms_boot.h"
|
||||
|
@ -180,7 +182,7 @@ void ota_flash_vfs(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc
|
|||
writer->printf("Error: Cannot find file %s\n",argv[0]);
|
||||
return;
|
||||
}
|
||||
writer->printf("Source image is %d bytes in size\n",ds.st_size);
|
||||
writer->printf("Source image is %ld bytes in size\n",ds.st_size);
|
||||
|
||||
FILE* f = fopen(argv[0], "r");
|
||||
if (f == NULL)
|
||||
|
@ -242,7 +244,7 @@ void ota_flash_vfs(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc
|
|||
return;
|
||||
}
|
||||
|
||||
writer->printf("OTA flash was successful\n Flashed %d bytes from %s\n Next boot will be from '%s'\n",
|
||||
writer->printf("OTA flash was successful\n Flashed %ld bytes from %s\n Next boot will be from '%s'\n",
|
||||
ds.st_size,argv[0],target->label);
|
||||
MyConfig.SetParamValue("ota", "vfs.mru", argv[0]);
|
||||
}
|
||||
|
@ -288,7 +290,7 @@ void ota_flash_http(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int arg
|
|||
|
||||
url = MyConfig.GetParamValue("ota","server");
|
||||
if (url.empty())
|
||||
url = "ovms-ota.bit-cloud.de";
|
||||
url = "api.openvehicles.com/firmware/ota";
|
||||
|
||||
url.append("/");
|
||||
url.append(GetOVMSProduct());
|
||||
|
@ -830,7 +832,7 @@ void OvmsOTA::GetStatus(ota_info& info, bool check_update /*=true*/)
|
|||
std::string tag = MyConfig.GetParamValue("ota","tag");
|
||||
std::string url = MyConfig.GetParamValue("ota","server");
|
||||
if (url.empty())
|
||||
url = "ovms-ota.bit-cloud.de";
|
||||
url = "api.openvehicles.com/firmware/ota";
|
||||
url.append("/");
|
||||
url.append(GetOVMSProduct());
|
||||
url.append("/");
|
||||
|
@ -931,7 +933,11 @@ static void OTAFlashTask(void *pvParameters)
|
|||
|
||||
if (fromsd)
|
||||
{
|
||||
#ifdef CONFIG_OVMS_COMP_SDCARD
|
||||
success = MyOTA.AutoFlashSD();
|
||||
#else
|
||||
success = false;
|
||||
#endif //CONFIG_OVMS_COMP_SDCARD
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1029,7 +1035,7 @@ bool OvmsOTA::AutoFlash(bool force)
|
|||
std::string tag = MyConfig.GetParamValue("ota","tag");
|
||||
std::string url = MyConfig.GetParamValue("ota","server");
|
||||
if (url.empty())
|
||||
url = "ovms-ota.bit-cloud.de";
|
||||
url = "api.openvehicles.com/firmware/ota";
|
||||
|
||||
url.append("/");
|
||||
url.append(GetOVMSProduct());
|
||||
|
|
|
@ -36,7 +36,10 @@ static const char *TAG = "pluginstore";
|
|||
#include <sys/stat.h>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include "esp_idf_version.h"
|
||||
#if ESP_IDF_VERSION_MAJOR < 4
|
||||
#include "strverscmp.h"
|
||||
#endif
|
||||
#include "ovms_plugins.h"
|
||||
#include "ovms_command.h"
|
||||
#include "ovms_config.h"
|
||||
|
|
|
@ -180,9 +180,9 @@ DuktapeHTTPRequest::DuktapeHTTPRequest(duk_context *ctx, int obj_idx)
|
|||
m_headers.append(": ");
|
||||
m_headers.append(val);
|
||||
m_headers.append("\r\n");
|
||||
if (key == "User-Agent")
|
||||
if (strcasecmp(key.c_str(), "User-Agent") == 0)
|
||||
have_useragent = true;
|
||||
else if (key == "Content-Type")
|
||||
else if (strcasecmp(key.c_str(), "Content-Type") == 0)
|
||||
have_contenttype = true;
|
||||
}
|
||||
duk_pop(ctx); // enum
|
||||
|
@ -325,7 +325,7 @@ void DuktapeHTTPRequest::MongooseCallback(struct mg_connection *nc, int ev, void
|
|||
key.assign(hm->header_names[i].p, hm->header_names[i].len);
|
||||
val.assign(hm->header_values[i].p, hm->header_values[i].len);
|
||||
m_response_headers.push_back(std::make_pair(key, val));
|
||||
if (key == "Location") location = val;
|
||||
if (strcasecmp(key.c_str(), "Location") == 0) location = val;
|
||||
}
|
||||
|
||||
// follow redirect?
|
||||
|
|
|
@ -140,8 +140,10 @@ void tpms_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc,
|
|||
|
||||
if (StandardMetrics.ms_v_tpms_pressure->IsDefined())
|
||||
{
|
||||
writer->printf("Pressure...[kPa]: ");
|
||||
for (auto val : StandardMetrics.ms_v_tpms_pressure->AsVector())
|
||||
|
||||
metric_unit_t user_pressure = OvmsMetricGetUserUnit(GrpPressure, kPa);
|
||||
writer->printf("Pressure...[%s]: ", OvmsMetricUnitLabel(user_pressure) );
|
||||
for (auto val : StandardMetrics.ms_v_tpms_pressure->AsVector(user_pressure))
|
||||
writer->printf(" %8.1f", val);
|
||||
writer->puts(StandardMetrics.ms_v_tpms_pressure->IsStale() ? " [stale]" : "");
|
||||
data_shown = true;
|
||||
|
@ -149,8 +151,9 @@ void tpms_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc,
|
|||
|
||||
if (StandardMetrics.ms_v_tpms_temp->IsDefined())
|
||||
{
|
||||
writer->printf("Temperature.[°C]: ");
|
||||
for (auto val : StandardMetrics.ms_v_tpms_temp->AsVector())
|
||||
metric_unit_t user_temp = OvmsMetricGetUserUnit(GrpTemp, Celcius);
|
||||
writer->printf("Temperature.[%s]: ", OvmsMetricUnitLabel(user_temp));
|
||||
for (auto val : StandardMetrics.ms_v_tpms_temp->AsVector(user_temp))
|
||||
writer->printf(" %8.1f", val);
|
||||
writer->puts(StandardMetrics.ms_v_tpms_temp->IsStale() ? " [stale]" : "");
|
||||
data_shown = true;
|
||||
|
|
|
@ -402,6 +402,180 @@ $.fn.loadcmd = function(command, filter, timeout) {
|
|||
var monitorTimer, last_monotonic = 0;
|
||||
var ws, ws_inhibit = 0;
|
||||
var metrics = {};
|
||||
var units = { metrics: {}, prefs: {} };
|
||||
|
||||
mi_to_km = function(mi) { return mi * 1.609347; }
|
||||
km_to_mi = function(km) { return km * 0.6213700; }
|
||||
pkm_to_pmi = function(pkm) { return pkm * 1.609347; }
|
||||
pmi_to_pkm = function(pmi) { return pmi * 0.6213700; }
|
||||
no_conversion = function (value) { return value;}
|
||||
x_to_kx = function (value) { return value/1000; }
|
||||
kx_to_x = function (value) { return value*1000; }
|
||||
|
||||
const feet_per_mile = 5280;
|
||||
|
||||
var unit_conversions = {
|
||||
"native": no_conversion,
|
||||
"km>miles": km_to_mi,
|
||||
"km>meters": kx_to_x,
|
||||
"km>feet": function (value) { return km_to_mi(value) * feet_per_mile; },
|
||||
"miles>km": mi_to_km,
|
||||
"miles>meters": function (value) { return (mi_to_km(value)*1000); },
|
||||
"miles>feet": function (value) { return value * feet_per_mile; },
|
||||
"meters>miles": function (value) { return km_to_mi(value/1000); },
|
||||
"meters>km": x_to_kx,
|
||||
"meters>feet": function (value) { return km_to_mi(value/1000) * feet_per_mile; },
|
||||
"feet>km": function (value) { return mi_to_km(value/feet_per_mile); },
|
||||
"feet>meters": function (value) { return (mi_to_km(value/feet_per_mile)*1000); },
|
||||
"feet>miles": function (value) { return value / feet_per_mile; },
|
||||
"kmphps>miphps": km_to_mi,
|
||||
"kmphps>mpss": function (value) { return value/3.6; },
|
||||
"kmphps>ftpss": function (value) { return km_to_mi(value)*feet_per_mile/3600; },
|
||||
"miphps>kmphps": mi_to_km,
|
||||
"miphps>mpss": function (value) { return mi_to_km(value)/3.6; },
|
||||
"miphps>ftpss": function (value) { return value*feet_per_mile/3600; },
|
||||
"mpss>kmphps": function (value) { return (value*3.6); },
|
||||
"mpss>miphps": function (value) { return km_to_mi(value)*3.6; },
|
||||
"mpss>ftpss": function (value) { return km_to_mi(value)*feet_per_mile; },
|
||||
"ftpss>kmphps": function (value) { return (mi_to_km(value/feet_per_mile)*3.6); },
|
||||
"ftpss>miphps": function (value) { return value *3600/feet_per_mile; },
|
||||
"ftpss>mpss": function (value) { return mi_to_km(value/feet_per_mile)*1000; },
|
||||
"kw>watts": kx_to_x,
|
||||
"watts>kw": x_to_kx,
|
||||
"kwh>watthours": kx_to_x,
|
||||
"watthours>kwh": x_to_kx,
|
||||
"whpkm>whpmi": pkm_to_pmi,
|
||||
"whpkm>kwhp100km": function (value) { return value / 10; },
|
||||
"whpkm>kmpkwh": function (value) { return value ? 1000.0 / value : 0; },
|
||||
"whpkm>mipkwh": function (value) { return value ? (km_to_mi(1000.0 / value)) : 0; },
|
||||
"whpmi>whpkm": pmi_to_pkm,
|
||||
"whpmi>kwhp100km": function (value) { return pmi_to_pkm(value) / 10; },
|
||||
"whpmi>kmpkwh": function (value) { return value ? (mi_to_km(1000.0 / value)) : 0; },
|
||||
"whpmi>mipkwh": function (value) { return value ? (1000.0 / value) : 0; },
|
||||
"kwhp100km>whpmi": function (value) { return pkm_to_pmi(value * 10); },
|
||||
"kwhp100km>whpkm": function (value) { return value * 10; },
|
||||
"kwhp100km>kmpkwh": function (value) { return value ? (100.0 / value) : 0; },
|
||||
"kwhp100km>mipkwh": function (value) { return value ? km_to_mi(100.0 / value) : 0; },
|
||||
"kmpkwh>whpmi": function (value) { return value ? (1000.0 / km_to_mi(value)) : 0;},
|
||||
"kmpkwh>whpkm": function (value) { return value ? (1/(1000.0 * value)) : 0;},
|
||||
"kmpkwh>kwhp100km": function (value) { return value ? (100.0/value) : 0;},
|
||||
"kmpkwh>mipkwh": km_to_mi,
|
||||
"mipkwh>whpmi": function (value) { return value ? 1000/value : 0;},
|
||||
"mipkwh>whpkm": function (value) { return value ? (1000 / mi_to_km(value)) : 0;},
|
||||
"mipkwh>kwhp100km": function (value) { return value ? (100.0/mi_to_km(value)) : 0;},
|
||||
"mipkwh>kmpkwh": mi_to_km,
|
||||
"celcius>fahrenheit": function (value) { return ((value*9)/5) + 32; },
|
||||
"fahrenheit>celcius": function (value) { return ((value-32)*5)/9; },
|
||||
"kpa>pa": kx_to_x,
|
||||
"kpa>bar": function (value) { return value/100; },
|
||||
"kpa>psi": function (value) { return value * 0.14503773773020923; },
|
||||
"pa>kpa": x_to_kx,
|
||||
"pa>bar": function (value) { return value/100000; },
|
||||
"pa>psi": function (value) { return value * 0.00014503773773020923; },
|
||||
"psi>kpa": function (value) { return value * 6.894757293168361; },
|
||||
"psi>pa": function (value) { return value * 6894.757293168361; },
|
||||
"psi>bar": function (value) { return value * 0.06894757293168361; },
|
||||
"bar>pa": function (value) { return value*100000; },
|
||||
"bar>kpa": function (value) { return value*100; },
|
||||
"bar>psi": function (value) { return value * 14.503773773020923; },
|
||||
"seconds>minutes": function (value) { return value/60; },
|
||||
"seconds>hours": function (value) { return value/3600; },
|
||||
"minutes>seconds": function (value) { return value*60; },
|
||||
"minutes>hours": function (value) { return value/60; },
|
||||
"hours>seconds": function (value) { return value*3600; },
|
||||
"hours>minutes": function (value) { return value*60; },
|
||||
"kmph>miph": km_to_mi,
|
||||
"miph>kmph": mi_to_km,
|
||||
"dbm>sq": function (value) { return Math.round((value <= -51) ? ((value + 113)/2) : 0); },
|
||||
"sq>dbm": function (value) { return Math.round((value <= 31) ? (-113 + (value*2)) : 0); },
|
||||
"percent>permille": function (value) { return value*10.0; },
|
||||
"permille>percent": function (value) { return value*0.10; }
|
||||
}
|
||||
convertUnitFunction = function (from, to) {
|
||||
return unit_conversions[from + ">" + to] || no_conversion;
|
||||
}
|
||||
convertUnits = function (from, to, value) {
|
||||
return convertUnitFunction(from, to)(value);
|
||||
}
|
||||
|
||||
units.convertMetricToUserUnits = function (value, name) {
|
||||
if (value == undefined)
|
||||
return value
|
||||
var unit_entry = this.metrics[name];
|
||||
if (unit_entry == undefined)
|
||||
return value
|
||||
var cnvfn = unit_entry.user_fn;
|
||||
if (cnvfn == undefined) {
|
||||
cnvfn = convertUnitFunction(unit_entry.native, unit_entry.code);
|
||||
this.metrics[name].user_fn = cnvfn;
|
||||
}
|
||||
return cnvfn(value);
|
||||
}
|
||||
units.userUnitLabelFromMetric = function (name) {
|
||||
var unit_entry = this.metrics[name];
|
||||
if (unit_entry == undefined)
|
||||
return "";
|
||||
return unit_entry.label;
|
||||
}
|
||||
units.unitLabelToUser = function (unitType, defaultLabel) {
|
||||
var res = this.prefs[unitType];
|
||||
return (res && res.label) ? res.label : defaultLabel
|
||||
}
|
||||
units.unitValueToUser = function (unitType, value) {
|
||||
var entry = this.prefs[unitType];
|
||||
if (!entry)
|
||||
return value;
|
||||
return convertUnits(value, unitType, entry.unit);
|
||||
}
|
||||
|
||||
// Works for units and metrics collection.
|
||||
metricsProxyHas = function(target, name) {
|
||||
return target[name] != undefined
|
||||
}
|
||||
|
||||
var metrics_all = new Proxy(metrics, {
|
||||
get: function(target, name) {
|
||||
if (name == Symbol.toStringTag)
|
||||
return 'metrics_all[]';
|
||||
if (!(typeof name === "string" || name instanceof String))
|
||||
return undefined;
|
||||
var names = name.split('#',2);
|
||||
var name = names[0];
|
||||
var value_type = names[1]
|
||||
if (value_type === "unit")
|
||||
return units.userUnitLabelFromMetric(name);
|
||||
var value = target[name];
|
||||
if (value_type === "label")
|
||||
value = units.convertMetricToUserUnits(value, name)
|
||||
return value;
|
||||
},
|
||||
has: metricsProxyHas
|
||||
});
|
||||
|
||||
var metrics_user = new Proxy(metrics, {
|
||||
get:
|
||||
function(target, name) {
|
||||
if (name == Symbol.toStringTag)
|
||||
return 'metrics_user[]';
|
||||
return units.convertMetricToUserUnits(target[name], name)
|
||||
},
|
||||
has: metricsProxyHas
|
||||
});
|
||||
|
||||
var metrics_label = new Proxy(units.metrics, {
|
||||
get:
|
||||
function(target, name) {
|
||||
if (name == Symbol.toStringTag)
|
||||
return 'metrics_label[]';
|
||||
var unit_entry = target[name];
|
||||
if (unit_entry == undefined) {
|
||||
return "";
|
||||
}
|
||||
return unit_entry.label;
|
||||
},
|
||||
has: metricsProxyHas
|
||||
});
|
||||
|
||||
var shellhist = [""], shellhpos = 0;
|
||||
var loghist = [];
|
||||
const loghist_maxsize = 100;
|
||||
|
@ -415,6 +589,7 @@ function initSocketConnection(){
|
|||
ws.onopen = function(ev) {
|
||||
console.log("WebSocket OPENED", ev);
|
||||
$(".receiver").subscribe();
|
||||
subscribeToTopic("units/#");
|
||||
};
|
||||
ws.onerror = function(ev) { console.log("WebSocket ERROR", ev); };
|
||||
ws.onclose = function(ev) { console.log("WebSocket CLOSED", ev); };
|
||||
|
@ -443,6 +618,21 @@ function initSocketConnection(){
|
|||
$.extend(metrics, msg.metrics);
|
||||
$(".receiver").trigger("msg:metrics", msg.metrics);
|
||||
}
|
||||
else if (msgtype == "units") {
|
||||
for (var subtype in msg.units) {
|
||||
if (subtype == "metrics") {
|
||||
$.extend(units.metrics, msg.units.metrics);
|
||||
$(".receiver").trigger("msg:units:metrics", msg.units.metrics);
|
||||
var msgmetrics = {};
|
||||
for (metricname in msg.units.metrics)
|
||||
msgmetrics[metricname] = metrics[metricname];
|
||||
$(".receiver").trigger("msg:metrics", msgmetrics);
|
||||
} else if (subtype == "prefs") {
|
||||
$.extend(units.prefs, msg.units.prefs);
|
||||
$(".receiver").trigger("msg:units:prefs", msg.units.prefs);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (msgtype == "notify") {
|
||||
processNotification(msg.notify);
|
||||
$(".receiver").trigger("msg:notify", msg.notify);
|
||||
|
@ -513,7 +703,14 @@ function processNotification(msg) {
|
|||
confirmdialog(opts.title, opts.body, ["OK"], opts.timeout);
|
||||
}
|
||||
|
||||
|
||||
subscribeToTopic = function (topic) {
|
||||
try {
|
||||
console.debug("subscribe " + topic);
|
||||
if (ws) ws.send("subscribe " + topic);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
$.fn.subscribe = function(topics) {
|
||||
return this.each(function() {
|
||||
var subscriptions = $(this).data("subscriptions");
|
||||
|
@ -526,13 +723,7 @@ $.fn.subscribe = function(topics) {
|
|||
var tops = topics ? topics.split(' ') : [];
|
||||
for (var i = 0; i < tops.length; i++) {
|
||||
if (tops[i] && !subs.includes(tops[i])) {
|
||||
try {
|
||||
console.log("subscribe " + tops[i]);
|
||||
if (ws) ws.send("subscribe " + tops[i]);
|
||||
subs.push(tops[i]);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
subscribeToTopic(tops[i]);
|
||||
}
|
||||
}
|
||||
$(this).data("subscriptions", subs.join(' '));
|
||||
|
@ -1721,20 +1912,43 @@ $(function(){
|
|||
// Metrics displays:
|
||||
$("body").on('msg:metrics', '.receiver', function(e, update) {
|
||||
$(this).find(".metric").each(function() {
|
||||
var $el = $(this), metric = $el.data("metric"), prec = $el.data("prec"), scale = $el.data("scale");
|
||||
var $el = $(this), metric = $el.data("metric"), prec = $el.data("prec"), scale = $el.data("scale"), useUser = $el.data("user");
|
||||
if (!metric) return;
|
||||
// filter:
|
||||
var keys = metric.split(","), val;
|
||||
var metricName = "";
|
||||
for (var i=0; i<keys.length; i++) {
|
||||
if ((val = update[keys[i]]) != null) break;
|
||||
metricName = keys[i];
|
||||
if ((val = update[metricName]) != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (val == null) return;
|
||||
|
||||
// process:
|
||||
if ($el.hasClass("text")) {
|
||||
$el.children(".value").text(val);
|
||||
var elt = $el.children(".value");
|
||||
if (elt) elt.text(val);
|
||||
elt = $el.children(".unit");
|
||||
if (elt) elt.text(val);
|
||||
|
||||
} else if ($el.hasClass("number")) {
|
||||
var vf = val;
|
||||
if (scale != null) vf = Number(vf) * scale;
|
||||
if (scale != null)
|
||||
vf = Number(vf) * scale;
|
||||
else {
|
||||
var mun = units.userUnitLabelFromMetric(metricName);
|
||||
if (mun != "") {
|
||||
// If there's a .unit.. then convert it.
|
||||
item = $el.children(".unit");
|
||||
if (item) {
|
||||
item.text(mun);
|
||||
useUser = true;
|
||||
}
|
||||
}
|
||||
if (useUser)
|
||||
vf = units.convertMetricToUserUnits(vf, metricName);
|
||||
}
|
||||
if (prec != null) vf = Number(vf).toFixed(prec);
|
||||
$el.children(".value").text(vf);
|
||||
} else if ($el.hasClass("progress")) {
|
||||
|
|
|
@ -829,6 +829,180 @@ $.fn.loadcmd = function(command, filter, timeout) {
|
|||
var monitorTimer, last_monotonic = 0;
|
||||
var ws, ws_inhibit = 0;
|
||||
var metrics = {};
|
||||
var units = { metrics: {}, prefs: {} };
|
||||
|
||||
mi_to_km = function(mi) { return mi * 1.609347; }
|
||||
km_to_mi = function(km) { return km * 0.6213700; }
|
||||
pkm_to_pmi = function(pkm) { return pkm * 1.609347; }
|
||||
pmi_to_pkm = function(pmi) { return pmi * 0.6213700; }
|
||||
no_conversion = function (value) { return value;}
|
||||
x_to_kx = function (value) { return value/1000; }
|
||||
kx_to_x = function (value) { return value*1000; }
|
||||
|
||||
const feet_per_mile = 5280;
|
||||
|
||||
var unit_conversions = {
|
||||
"native": no_conversion,
|
||||
"km>miles": km_to_mi,
|
||||
"km>meters": kx_to_x,
|
||||
"km>feet": function (value) { return km_to_mi(value) * feet_per_mile; },
|
||||
"miles>km": mi_to_km,
|
||||
"miles>meters": function (value) { return (mi_to_km(value)*1000); },
|
||||
"miles>feet": function (value) { return value * feet_per_mile; },
|
||||
"meters>miles": function (value) { return km_to_mi(value/1000); },
|
||||
"meters>km": x_to_kx,
|
||||
"meters>feet": function (value) { return km_to_mi(value/1000) * feet_per_mile; },
|
||||
"feet>km": function (value) { return mi_to_km(value/feet_per_mile); },
|
||||
"feet>meters": function (value) { return (mi_to_km(value/feet_per_mile)*1000); },
|
||||
"feet>miles": function (value) { return value / feet_per_mile; },
|
||||
"kmphps>miphps": km_to_mi,
|
||||
"kmphps>mpss": function (value) { return value/3.6; },
|
||||
"kmphps>ftpss": function (value) { return km_to_mi(value)*feet_per_mile/3600; },
|
||||
"miphps>kmphps": mi_to_km,
|
||||
"miphps>mpss": function (value) { return mi_to_km(value)/3.6; },
|
||||
"miphps>ftpss": function (value) { return value*feet_per_mile/3600; },
|
||||
"mpss>kmphps": function (value) { return (value*3.6); },
|
||||
"mpss>miphps": function (value) { return km_to_mi(value)*3.6; },
|
||||
"mpss>ftpss": function (value) { return km_to_mi(value)*feet_per_mile; },
|
||||
"ftpss>kmphps": function (value) { return (mi_to_km(value/feet_per_mile)*3.6); },
|
||||
"ftpss>miphps": function (value) { return value *3600/feet_per_mile; },
|
||||
"ftpss>mpss": function (value) { return mi_to_km(value/feet_per_mile)*1000; },
|
||||
"kw>watts": kx_to_x,
|
||||
"watts>kw": x_to_kx,
|
||||
"kwh>watthours": kx_to_x,
|
||||
"watthours>kwh": x_to_kx,
|
||||
"whpkm>whpmi": pkm_to_pmi,
|
||||
"whpkm>kwhp100km": function (value) { return value / 10; },
|
||||
"whpkm>kmpkwh": function (value) { return value ? 1000.0 / value : 0; },
|
||||
"whpkm>mipkwh": function (value) { return value ? (km_to_mi(1000.0 / value)) : 0; },
|
||||
"whpmi>whpkm": pmi_to_pkm,
|
||||
"whpmi>kwhp100km": function (value) { return pmi_to_pkm(value) / 10; },
|
||||
"whpmi>kmpkwh": function (value) { return value ? (mi_to_km(1000.0 / value)) : 0; },
|
||||
"whpmi>mipkwh": function (value) { return value ? (1000.0 / value) : 0; },
|
||||
"kwhp100km>whpmi": function (value) { return pkm_to_pmi(value * 10); },
|
||||
"kwhp100km>whpkm": function (value) { return value * 10; },
|
||||
"kwhp100km>kmpkwh": function (value) { return value ? (100.0 / value) : 0; },
|
||||
"kwhp100km>mipkwh": function (value) { return value ? km_to_mi(100.0 / value) : 0; },
|
||||
"kmpkwh>whpmi": function (value) { return value ? (1000.0 / km_to_mi(value)) : 0;},
|
||||
"kmpkwh>whpkm": function (value) { return value ? (1/(1000.0 * value)) : 0;},
|
||||
"kmpkwh>kwhp100km": function (value) { return value ? (100.0/value) : 0;},
|
||||
"kmpkwh>mipkwh": km_to_mi,
|
||||
"mipkwh>whpmi": function (value) { return value ? 1000/value : 0;},
|
||||
"mipkwh>whpkm": function (value) { return value ? (1000 / mi_to_km(value)) : 0;},
|
||||
"mipkwh>kwhp100km": function (value) { return value ? (100.0/mi_to_km(value)) : 0;},
|
||||
"mipkwh>kmpkwh": mi_to_km,
|
||||
"celcius>fahrenheit": function (value) { return ((value*9)/5) + 32; },
|
||||
"fahrenheit>celcius": function (value) { return ((value-32)*5)/9; },
|
||||
"kpa>pa": kx_to_x,
|
||||
"kpa>bar": function (value) { return value/100; },
|
||||
"kpa>psi": function (value) { return value * 0.14503773773020923; },
|
||||
"pa>kpa": x_to_kx,
|
||||
"pa>bar": function (value) { return value/100000; },
|
||||
"pa>psi": function (value) { return value * 0.00014503773773020923; },
|
||||
"psi>kpa": function (value) { return value * 6.894757293168361; },
|
||||
"psi>pa": function (value) { return value * 6894.757293168361; },
|
||||
"psi>bar": function (value) { return value * 0.06894757293168361; },
|
||||
"bar>pa": function (value) { return value*100000; },
|
||||
"bar>kpa": function (value) { return value*100; },
|
||||
"bar>psi": function (value) { return value * 14.503773773020923; },
|
||||
"seconds>minutes": function (value) { return value/60; },
|
||||
"seconds>hours": function (value) { return value/3600; },
|
||||
"minutes>seconds": function (value) { return value*60; },
|
||||
"minutes>hours": function (value) { return value/60; },
|
||||
"hours>seconds": function (value) { return value*3600; },
|
||||
"hours>minutes": function (value) { return value*60; },
|
||||
"kmph>miph": km_to_mi,
|
||||
"miph>kmph": mi_to_km,
|
||||
"dbm>sq": function (value) { return Math.round((value <= -51) ? ((value + 113)/2) : 0); },
|
||||
"sq>dbm": function (value) { return Math.round((value <= 31) ? (-113 + (value*2)) : 0); },
|
||||
"percent>permille": function (value) { return value*10.0; },
|
||||
"permille>percent": function (value) { return value*0.10; }
|
||||
}
|
||||
convertUnitFunction = function (from, to) {
|
||||
return unit_conversions[from + ">" + to] || no_conversion;
|
||||
}
|
||||
convertUnits = function (from, to, value) {
|
||||
return convertUnitFunction(from, to)(value);
|
||||
}
|
||||
|
||||
units.convertMetricToUserUnits = function (value, name) {
|
||||
if (value == undefined)
|
||||
return value
|
||||
var unit_entry = this.metrics[name];
|
||||
if (unit_entry == undefined)
|
||||
return value
|
||||
var cnvfn = unit_entry.user_fn;
|
||||
if (cnvfn == undefined) {
|
||||
cnvfn = convertUnitFunction(unit_entry.native, unit_entry.code);
|
||||
this.metrics[name].user_fn = cnvfn;
|
||||
}
|
||||
return cnvfn(value);
|
||||
}
|
||||
units.userUnitLabelFromMetric = function (name) {
|
||||
var unit_entry = this.metrics[name];
|
||||
if (unit_entry == undefined)
|
||||
return "";
|
||||
return unit_entry.label;
|
||||
}
|
||||
units.unitLabelToUser = function (unitType, defaultLabel) {
|
||||
var res = this.prefs[unitType];
|
||||
return (res && res.label) ? res.label : defaultLabel
|
||||
}
|
||||
units.unitValueToUser = function (unitType, value) {
|
||||
var entry = this.prefs[unitType];
|
||||
if (!entry)
|
||||
return value;
|
||||
return convertUnits(value, unitType, entry.unit);
|
||||
}
|
||||
|
||||
// Works for units and metrics collection.
|
||||
metricsProxyHas = function(target, name) {
|
||||
return target[name] != undefined
|
||||
}
|
||||
|
||||
var metrics_all = new Proxy(metrics, {
|
||||
get: function(target, name) {
|
||||
if (name == Symbol.toStringTag)
|
||||
return 'metrics_all[]';
|
||||
if (!(typeof name === "string" || name instanceof String))
|
||||
return undefined;
|
||||
var names = name.split('#',2);
|
||||
var name = names[0];
|
||||
var value_type = names[1]
|
||||
if (value_type === "unit")
|
||||
return units.userUnitLabelFromMetric(name);
|
||||
var value = target[name];
|
||||
if (value_type === "label")
|
||||
value = units.convertMetricToUserUnits(value, name)
|
||||
return value;
|
||||
},
|
||||
has: metricsProxyHas
|
||||
});
|
||||
|
||||
var metrics_user = new Proxy(metrics, {
|
||||
get:
|
||||
function(target, name) {
|
||||
if (name == Symbol.toStringTag)
|
||||
return 'metrics_user[]';
|
||||
return units.convertMetricToUserUnits(target[name], name)
|
||||
},
|
||||
has: metricsProxyHas
|
||||
});
|
||||
|
||||
var metrics_label = new Proxy(units.metrics, {
|
||||
get:
|
||||
function(target, name) {
|
||||
if (name == Symbol.toStringTag)
|
||||
return 'metrics_label[]';
|
||||
var unit_entry = target[name];
|
||||
if (unit_entry == undefined) {
|
||||
return "";
|
||||
}
|
||||
return unit_entry.label;
|
||||
},
|
||||
has: metricsProxyHas
|
||||
});
|
||||
|
||||
var shellhist = [""], shellhpos = 0;
|
||||
var loghist = [];
|
||||
const loghist_maxsize = 100;
|
||||
|
@ -842,6 +1016,7 @@ function initSocketConnection(){
|
|||
ws.onopen = function(ev) {
|
||||
console.log("WebSocket OPENED", ev);
|
||||
$(".receiver").subscribe();
|
||||
subscribeToTopic("units/#");
|
||||
};
|
||||
ws.onerror = function(ev) { console.log("WebSocket ERROR", ev); };
|
||||
ws.onclose = function(ev) { console.log("WebSocket CLOSED", ev); };
|
||||
|
@ -870,6 +1045,21 @@ function initSocketConnection(){
|
|||
$.extend(metrics, msg.metrics);
|
||||
$(".receiver").trigger("msg:metrics", msg.metrics);
|
||||
}
|
||||
else if (msgtype == "units") {
|
||||
for (var subtype in msg.units) {
|
||||
if (subtype == "metrics") {
|
||||
$.extend(units.metrics, msg.units.metrics);
|
||||
$(".receiver").trigger("msg:units:metrics", msg.units.metrics);
|
||||
var msgmetrics = {};
|
||||
for (metricname in msg.units.metrics)
|
||||
msgmetrics[metricname] = metrics[metricname];
|
||||
$(".receiver").trigger("msg:metrics", msgmetrics);
|
||||
} else if (subtype == "prefs") {
|
||||
$.extend(units.prefs, msg.units.prefs);
|
||||
$(".receiver").trigger("msg:units:prefs", msg.units.prefs);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (msgtype == "notify") {
|
||||
processNotification(msg.notify);
|
||||
$(".receiver").trigger("msg:notify", msg.notify);
|
||||
|
@ -940,7 +1130,14 @@ function processNotification(msg) {
|
|||
confirmdialog(opts.title, opts.body, ["OK"], opts.timeout);
|
||||
}
|
||||
|
||||
|
||||
subscribeToTopic = function (topic) {
|
||||
try {
|
||||
console.debug("subscribe " + topic);
|
||||
if (ws) ws.send("subscribe " + topic);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
$.fn.subscribe = function(topics) {
|
||||
return this.each(function() {
|
||||
var subscriptions = $(this).data("subscriptions");
|
||||
|
@ -953,13 +1150,7 @@ $.fn.subscribe = function(topics) {
|
|||
var tops = topics ? topics.split(' ') : [];
|
||||
for (var i = 0; i < tops.length; i++) {
|
||||
if (tops[i] && !subs.includes(tops[i])) {
|
||||
try {
|
||||
console.log("subscribe " + tops[i]);
|
||||
if (ws) ws.send("subscribe " + tops[i]);
|
||||
subs.push(tops[i]);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
subscribeToTopic(tops[i]);
|
||||
}
|
||||
}
|
||||
$(this).data("subscriptions", subs.join(' '));
|
||||
|
@ -2148,20 +2339,43 @@ $(function(){
|
|||
// Metrics displays:
|
||||
$("body").on('msg:metrics', '.receiver', function(e, update) {
|
||||
$(this).find(".metric").each(function() {
|
||||
var $el = $(this), metric = $el.data("metric"), prec = $el.data("prec"), scale = $el.data("scale");
|
||||
var $el = $(this), metric = $el.data("metric"), prec = $el.data("prec"), scale = $el.data("scale"), useUser = $el.data("user");
|
||||
if (!metric) return;
|
||||
// filter:
|
||||
var keys = metric.split(","), val;
|
||||
var metricName = "";
|
||||
for (var i=0; i<keys.length; i++) {
|
||||
if ((val = update[keys[i]]) != null) break;
|
||||
metricName = keys[i];
|
||||
if ((val = update[metricName]) != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (val == null) return;
|
||||
|
||||
// process:
|
||||
if ($el.hasClass("text")) {
|
||||
$el.children(".value").text(val);
|
||||
var elt = $el.children(".value");
|
||||
if (elt) elt.text(val);
|
||||
elt = $el.children(".unit");
|
||||
if (elt) elt.text(val);
|
||||
|
||||
} else if ($el.hasClass("number")) {
|
||||
var vf = val;
|
||||
if (scale != null) vf = Number(vf) * scale;
|
||||
if (scale != null)
|
||||
vf = Number(vf) * scale;
|
||||
else {
|
||||
var mun = units.userUnitLabelFromMetric(metricName);
|
||||
if (mun != "") {
|
||||
// If there's a .unit.. then convert it.
|
||||
item = $el.children(".unit");
|
||||
if (item) {
|
||||
item.text(mun);
|
||||
useUser = true;
|
||||
}
|
||||
}
|
||||
if (useUser)
|
||||
vf = units.convertMetricToUserUnits(vf, metricName);
|
||||
}
|
||||
if (prec != null) vf = Number(vf).toFixed(prec);
|
||||
$el.children(".value").text(vf);
|
||||
} else if ($el.hasClass("progress")) {
|
||||
|
|
|
@ -6,9 +6,28 @@ OVMS V3 is based on metrics. Metrics can be single numerical or textual values o
|
|||
like sets and arrays. The web framework keeps all metrics in a global object, which can be read
|
||||
simply by e.g. ``metrics["v.b.soc"]``.
|
||||
|
||||
In addition to the raw metric values, there are 2 main proxy arrays that give access to the
|
||||
user-configured versions of the raw metric values. The ``metrics_user`` array
|
||||
converts the 'metrics' value to user-configured value and the ``metrics_label`` array
|
||||
provides the corresponding label for that metric.
|
||||
So for example the user could configure distance values to be in miles, and in this case
|
||||
``metrics["v.p.odometer"]`` would still contain the value in km (the default) but
|
||||
``metrics_user["v.p.odometer"]`` would give the value converted to miles and
|
||||
``metrics_label["v.p.odometer"]`` would return "M".
|
||||
|
||||
The user conversion information is contained in another object ``units``. ``units.metrics``
|
||||
has the user configuration for each metric and ``units.prefs`` has the user configuration
|
||||
for each group of metrics (distance, temperature, consumption, pressure etc). There also some methods
|
||||
for general conversions allowing user preferences.
|
||||
- The method ``units.unitLabelToUser(unitType,name)`` will return the user
|
||||
defined label for that 'unitType', defaulting to ``name``.
|
||||
- The method ``units.unitValueToUser(unitType,value)`` will convert ``value``
|
||||
to the user defined unit (if set) for the group.
|
||||
|
||||
Metrics updates (as well as other updates) are sent to all DOM elements having the
|
||||
``receiver`` class. To hook into these updates, simply add an event listener for
|
||||
``msg:metrics``.
|
||||
``msg:metrics:``. The event ``msg:units:metrics`` is called when ``units.metrics`` is change
|
||||
and ``msg:units:prefs`` when ``units.prefs`` are changed.
|
||||
|
||||
Listening to the event is not necessary though if all you need is some metrics
|
||||
display. This is covered by the ``metric`` widget class family as shown here.
|
||||
|
@ -30,6 +49,11 @@ The following example covers…
|
|||
- Gauges
|
||||
- Charts
|
||||
|
||||
Where a number element of class 'metric' contains both elements of class
|
||||
'value' and 'unit', these will be automatically displayed in the units selected
|
||||
in the user preferences. Having a 'data-user' attribute will also cause the
|
||||
'value' element to be displayed in user units (unless 'data-scale' attribute is present).
|
||||
|
||||
Gauges & charts use the HighCharts library, which is included in the web server. The other widgets
|
||||
are simple standard Bootstrap widgets extended by an automatic metrics value update mechanism.
|
||||
|
||||
|
|
|
@ -1086,3 +1086,105 @@ void OvmsWebServer::HandleLogout(PageEntry_t& p, PageContext_t& c)
|
|||
"<script>loggedin = false; $(\"#menu\").load(\"/menu\"); loaduri(\"#main\", \"get\", \"/home\", {})</script>");
|
||||
c.done();
|
||||
}
|
||||
|
||||
// Dash Gauge implementations.
|
||||
//
|
||||
|
||||
dash_gauge_t::dash_gauge_t(const char *titlePrefix, metric_unit_t defUnit, metric_group_t group)
|
||||
{
|
||||
has_tick = false;
|
||||
title_prefix = titlePrefix ? titlePrefix : "";
|
||||
if (group == GrpNone)
|
||||
group = GetMetricGroup(defUnit);
|
||||
if (group == GrpOther)
|
||||
user_unit = defUnit;
|
||||
else
|
||||
user_unit = MyUnitConfig.GetUserUnit(group, defUnit);
|
||||
base_unit = defUnit;
|
||||
}
|
||||
float dash_gauge_t::UntConvert( float inValue ) const
|
||||
{
|
||||
return UnitConvert(base_unit, user_unit, inValue);
|
||||
}
|
||||
float dash_gauge_t::UntConvert( float inValue, float roundValue ) const
|
||||
{
|
||||
if (base_unit == user_unit)
|
||||
return inValue;
|
||||
return truncf(UnitConvert(base_unit, user_unit, inValue) / roundValue) * roundValue;
|
||||
}
|
||||
|
||||
void dash_gauge_t::SetMinMax( float minValue, float maxValue)
|
||||
{
|
||||
min_value = UntConvert(minValue);
|
||||
float temp_max = UntConvert(maxValue);
|
||||
if (min_value < temp_max)
|
||||
max_value = temp_max;
|
||||
else {
|
||||
max_value = min_value;
|
||||
min_value = temp_max;
|
||||
}
|
||||
}
|
||||
|
||||
void dash_gauge_t::SetMinMax( float minValue, float maxValue, float roundValue)
|
||||
{
|
||||
min_value = UntConvert(minValue, roundValue);
|
||||
float temp_max = UntConvert(maxValue, roundValue);
|
||||
if (min_value < temp_max)
|
||||
max_value = temp_max;
|
||||
else {
|
||||
max_value = min_value;
|
||||
min_value = temp_max;
|
||||
}
|
||||
}
|
||||
void dash_gauge_t::SetTick( float tickValue)
|
||||
{
|
||||
tick_value = UntConvert(tickValue);
|
||||
has_tick =true;
|
||||
}
|
||||
void dash_gauge_t::SetTick( float tickValue, float roundValue)
|
||||
{
|
||||
tick_value = UntConvert(tickValue);
|
||||
has_tick = true;
|
||||
}
|
||||
void dash_gauge_t::DoAddBand( const std::string &colour, float minValue, float maxValue, bool round, float roundValue)
|
||||
{
|
||||
dash_plot_band_t new_band;
|
||||
new_band.colour = colour;
|
||||
new_band.min_value = round ? UntConvert(minValue,roundValue) : UntConvert(minValue);
|
||||
float temp_max = round ? UntConvert(maxValue, roundValue) : UntConvert(maxValue);
|
||||
if (new_band.min_value <= temp_max) {
|
||||
new_band.max_value = temp_max;
|
||||
bands.insert(bands.end(), new_band);
|
||||
} else
|
||||
{
|
||||
new_band.max_value = new_band.min_value;
|
||||
new_band.min_value = temp_max;
|
||||
bands.insert(bands.begin(), new_band);
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream &dash_gauge_t::Output(std::ostream &ostream) const
|
||||
{
|
||||
ostream <<
|
||||
"{"
|
||||
"title: { text: '"<< title_prefix << PageContext::encode_html(OvmsMetricUnitLabel(user_unit)) << "' },";
|
||||
if (min_value < max_value) {
|
||||
ostream <<
|
||||
"min: " << min_value << ", max: " << max_value << ",";
|
||||
}
|
||||
if (has_tick)
|
||||
ostream << "tickInterval: " << tick_value << ",";
|
||||
|
||||
ostream << "plotBands: [";
|
||||
bool first = true;
|
||||
for (auto iter = bands.begin() ; iter != bands.end(); ++iter) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
ostream << ",";
|
||||
ostream <<
|
||||
"{ from: " << iter->min_value << ", to: " << iter->max_value << ", className: '" << iter->colour <<"-band' }";
|
||||
}
|
||||
ostream << "]}";
|
||||
return ostream;
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ struct PageContext : public ExternalRamAllocated
|
|||
void print(const std::string text);
|
||||
void print(const extram::string text);
|
||||
void print(const char* text);
|
||||
void printf(const char *fmt, ...);
|
||||
void printf(const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
void done();
|
||||
void panel_start(const char* type, const char* title);
|
||||
void panel_end(const char* footer="");
|
||||
|
@ -367,6 +367,8 @@ enum WebSocketTxJobType
|
|||
WSTX_Config, // payload: config (todo)
|
||||
WSTX_Notify, // payload: notification
|
||||
WSTX_LogBuffers, // payload: logbuffers
|
||||
WSTX_UnitMetricUpdate, // payload: -
|
||||
WSTX_UnitPrefsUpdate, // payload: -
|
||||
};
|
||||
|
||||
struct WebSocketTxJob
|
||||
|
@ -412,6 +414,10 @@ class WebSocketHandler : public MgHandler, public OvmsWriter
|
|||
void Unsubscribe(std::string topic);
|
||||
bool IsSubscribedTo(std::string topic);
|
||||
|
||||
void SubscriptionChanged();
|
||||
void UnitsCheckSubscribe();
|
||||
void UnitsCheckVehicleSubscribe();
|
||||
|
||||
// OvmsWriter:
|
||||
public:
|
||||
void Log(LogBuffers* message);
|
||||
|
@ -428,7 +434,10 @@ class WebSocketHandler : public MgHandler, public OvmsWriter
|
|||
WebSocketTxJob m_job = {};
|
||||
int m_sent = 0;
|
||||
int m_ack = 0;
|
||||
int m_last = 0; // last entry sent up
|
||||
std::set<std::string> m_subscriptions;
|
||||
bool m_units_subscribed;
|
||||
bool m_units_prefs_subscribed;
|
||||
};
|
||||
|
||||
struct WebSocketSlot
|
||||
|
@ -469,7 +478,7 @@ class HttpCommandStream : public OvmsShell, public MgHandler
|
|||
void Initialize(bool print);
|
||||
virtual bool IsInteractive() { return false; }
|
||||
int puts(const char* s);
|
||||
int printf(const char* fmt, ...);
|
||||
int printf(const char* fmt, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
ssize_t write(const void *buf, size_t nbyte);
|
||||
void Log(LogBuffers* message);
|
||||
};
|
||||
|
@ -536,7 +545,7 @@ class OvmsWebServer : public ExternalRamAllocated
|
|||
static void HandleLogin(PageEntry_t& p, PageContext_t& c);
|
||||
static void HandleLogout(PageEntry_t& p, PageContext_t& c);
|
||||
static void OutputReboot(PageEntry_t& p, PageContext_t& c);
|
||||
static void OutputReconnect(PageEntry_t& p, PageContext_t& c, const char* info=NULL);
|
||||
static void OutputReconnect(PageEntry_t& p, PageContext_t& c, const char* info=NULL, const char* cmd=NULL);
|
||||
|
||||
public:
|
||||
static void HandleStatus(PageEntry_t& p, PageContext_t& c);
|
||||
|
@ -610,6 +619,80 @@ class OvmsWebServer : public ExternalRamAllocated
|
|||
|
||||
extern OvmsWebServer MyWebServer;
|
||||
|
||||
/** Dashboard Gauge generator.
|
||||
* Handles unit conversions.
|
||||
*/
|
||||
struct dash_gauge_t {
|
||||
protected:
|
||||
struct dash_plot_band_t {
|
||||
std::string colour;
|
||||
float min_value, max_value;
|
||||
};
|
||||
std::string title_prefix;
|
||||
metric_unit_t user_unit, base_unit;
|
||||
float min_value, max_value, tick_value;
|
||||
bool has_tick;
|
||||
std::vector<dash_plot_band_t> bands;
|
||||
void DoAddBand( const std::string &colour, float minValue, float maxValue, bool round, float roundValue);
|
||||
public:
|
||||
|
||||
/**
|
||||
* @param titlePrefix The title/caption prefix (to the unit)
|
||||
* @param defUnit The unit used as a default for these metrics.
|
||||
* @param group (Optional) The group of units it belong to.
|
||||
*/
|
||||
dash_gauge_t(const char *titlePrefix, metric_unit_t defUnit, metric_group_t group = GrpNone);
|
||||
/**
|
||||
* Convert a unit to the user unit.
|
||||
*/
|
||||
float UntConvert( float inValue ) const;
|
||||
/**
|
||||
* Convert a unit to the user unit with rounding to the nearest value.
|
||||
*/
|
||||
float UntConvert( float inValue, float roundValue ) const;
|
||||
/**
|
||||
* Set the minimum and maximum values for the gauge (in original units).
|
||||
*/
|
||||
void SetMinMax( float minValue, float maxValue);
|
||||
/**
|
||||
* Set the minimum and maximum values for the gauge (in original units) including
|
||||
* rounding to the nearest value.
|
||||
*/
|
||||
void SetMinMax( float minValue, float maxValue, float roundValue);
|
||||
/**
|
||||
* Set the tickmark intervals (in original units).
|
||||
*/
|
||||
void SetTick( float tickValue);
|
||||
/**
|
||||
* Set the tickmark intervals (in original units) with rounding to nearest.
|
||||
*/
|
||||
void SetTick( float tickValue, float roundValue);
|
||||
/** Zero the minimum. Used when 0 cannot be originally used as a minimum.
|
||||
*/
|
||||
inline void ZeroMin() { if (max_value > 0) min_value = 0; }
|
||||
|
||||
/** Add a colour band to the gauge.
|
||||
*/
|
||||
inline void AddBand( const std::string &colour, float minValue, float maxValue)
|
||||
{
|
||||
DoAddBand(colour, minValue, maxValue, false, 0);
|
||||
}
|
||||
/** Add a colour band to the gauge with rounding to the nearest value.
|
||||
*/
|
||||
inline void AddBand( const std::string &colour, float minValue, float maxValue, float roundValue)
|
||||
{
|
||||
DoAddBand(colour, minValue, maxValue, true, roundValue);
|
||||
}
|
||||
|
||||
/** Output this element to a stream.
|
||||
*/
|
||||
std::ostream &Output(std::ostream &ostream) const;
|
||||
};
|
||||
inline std::ostream &operator<<(std::ostream &out, const dash_gauge_t& gauge)
|
||||
{
|
||||
return gauge.Output(out);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* DashboardConfig:
|
||||
|
|
|
@ -71,7 +71,12 @@ WebSocketHandler::WebSocketHandler(mg_connection* nc, size_t slot, size_t modifi
|
|||
m_jobqueue_overflow_dropcnt = 0;
|
||||
m_jobqueue_overflow_dropcntref = 0;
|
||||
m_job.type = WSTX_None;
|
||||
m_sent = m_ack = 0;
|
||||
m_sent = m_ack = m_last = 0;
|
||||
m_units_subscribed = false;
|
||||
m_units_prefs_subscribed = false;
|
||||
|
||||
MyMetrics.InitialiseSlot(m_slot);
|
||||
MyUnitConfig.InitialiseSlot(m_slot);
|
||||
|
||||
// Register as logging console:
|
||||
SetMonitoring(true);
|
||||
|
@ -116,39 +121,42 @@ void WebSocketHandler::ProcessTxJob()
|
|||
case WSTX_MetricsAll:
|
||||
case WSTX_MetricsUpdate:
|
||||
{
|
||||
// Note: this loops over the metrics by index, keeping the checked count
|
||||
// in m_sent. It will not detect new metrics added between polls if they are
|
||||
// inserted before m_sent, so new metrics may not be sent until first changed.
|
||||
// Note: this loops over the metrics by index, keeping the last checked position
|
||||
// in m_last. It will not detect new metrics added between polls if they are
|
||||
// inserted before m_last, so new metrics may not be sent until first changed.
|
||||
// The Metrics set normally is static, so this should be no problem.
|
||||
|
||||
// find start:
|
||||
int i;
|
||||
OvmsMetric* m;
|
||||
for (i=0, m=MyMetrics.m_first; i < m_sent && m != NULL; m=m->m_next, i++);
|
||||
for (i=0, m=MyMetrics.m_first; i < m_last && m != NULL; m=m->m_next, i++);
|
||||
|
||||
// build msg:
|
||||
std::string msg;
|
||||
msg.reserve(2*XFER_CHUNK_SIZE+128);
|
||||
msg = "{\"metrics\":{";
|
||||
for (i=0; m && msg.size() < XFER_CHUNK_SIZE; m=m->m_next) {
|
||||
if (m->IsModifiedAndClear(m_modifier) || m_job.type == WSTX_MetricsAll) {
|
||||
if (i) msg += ',';
|
||||
msg += '\"';
|
||||
msg += m->m_name;
|
||||
msg += "\":";
|
||||
msg += m->AsJSON();
|
||||
i++;
|
||||
if (m) {
|
||||
std::string msg;
|
||||
msg.reserve(2*XFER_CHUNK_SIZE+128);
|
||||
msg = "{\"metrics\":{";
|
||||
for (i=0; m && msg.size() < XFER_CHUNK_SIZE; m=m->m_next) {
|
||||
++m_last;
|
||||
if (m->IsModifiedAndClear(m_modifier) || m_job.type == WSTX_MetricsAll) {
|
||||
if (i) msg += ',';
|
||||
msg += '\"';
|
||||
msg += m->m_name;
|
||||
msg += "\":";
|
||||
msg += m->AsJSON();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// send msg:
|
||||
if (i) {
|
||||
msg += "}}";
|
||||
ESP_EARLY_LOGV(TAG, "WebSocket msg: %s", msg.c_str());
|
||||
mg_send_websocket_frame(m_nc, WEBSOCKET_OP_TEXT, msg.data(), msg.size());
|
||||
m_sent += i;
|
||||
}
|
||||
}
|
||||
|
||||
// send msg:
|
||||
if (i) {
|
||||
msg += "}}";
|
||||
ESP_EARLY_LOGV(TAG, "WebSocket msg: %s", msg.c_str());
|
||||
mg_send_websocket_frame(m_nc, WEBSOCKET_OP_TEXT, msg.data(), msg.size());
|
||||
m_sent += i;
|
||||
}
|
||||
|
||||
|
||||
// done?
|
||||
if (!m && m_ack == m_sent) {
|
||||
if (m_sent)
|
||||
|
@ -158,7 +166,135 @@ void WebSocketHandler::ProcessTxJob()
|
|||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case WSTX_UnitMetricUpdate:
|
||||
{
|
||||
// Note: this loops over the metrics by index, keeping the last checked position
|
||||
// in m_last. It will not detect new metrics added between polls if they are
|
||||
// inserted before m_last, so new metrics may not be sent until first changed.
|
||||
// The Metrics set normally is static, so this should be no problem.
|
||||
|
||||
ESP_EARLY_LOGD(TAG, "WebSocketHandler[%p/%d]: ProcessTxJob MetricsUnitUpdate, last=%d sent=%d ack=%d", m_nc, m_modifier, m_last, m_sent, m_ack);
|
||||
// find start:
|
||||
int i;
|
||||
OvmsMetric* m;
|
||||
for (i=0, m=MyMetrics.m_first; i < m_last && m != NULL; m=m->m_next, i++);
|
||||
ESP_EARLY_LOGD(TAG, "WebSocketHandler[%p/%d]: ProcessTxJob MetricsUnitUpdate, i=%d", m_nc, m_modifier, i);
|
||||
if (m) { // Bypass this if we are on the 'just sent' leg.
|
||||
// build msg:
|
||||
std::string msg;
|
||||
msg.reserve(2*XFER_CHUNK_SIZE+128);
|
||||
msg = "{\"units\":{\"metrics\":{";
|
||||
|
||||
// Cache the user mappings for each group.
|
||||
for (i=0; m && msg.size() < XFER_CHUNK_SIZE; m=m->m_next) {
|
||||
++m_last;
|
||||
bool send = m->IsUnitSendAndClear(m_modifier);
|
||||
if (send) {
|
||||
if (i)
|
||||
msg += ',';
|
||||
metric_unit_t units = m->m_units;
|
||||
metric_unit_t user_units = MyUnitConfig.GetUserUnit(units);
|
||||
if (user_units == UnitNotFound)
|
||||
user_units = Native;
|
||||
std::string unitlabel = OvmsMetricUnitLabel((user_units == Native) ? units : user_units);
|
||||
|
||||
const char *metricname = (units == Native) ? "Other" : OvmsMetricUnitName(units);
|
||||
if (metricname == NULL)
|
||||
metricname = "";
|
||||
const char *user_metricname = (user_units == Native) ? metricname : OvmsMetricUnitName(user_units);
|
||||
if (user_metricname == NULL)
|
||||
user_metricname = metricname;
|
||||
|
||||
std::string entry = string_format("\"%s\":{\"native\":\"%s\",\"code\":\"%s\",\"label\":\"%s\"}",
|
||||
m->m_name, metricname, user_metricname, json_encode(unitlabel).c_str()
|
||||
);
|
||||
msg += entry;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// send msg:
|
||||
if (i) {
|
||||
msg += "}}}";
|
||||
ESP_EARLY_LOGD(TAG, "WebSocket msg: %s", msg.c_str());
|
||||
mg_send_websocket_frame(m_nc, WEBSOCKET_OP_TEXT, msg.data(), msg.size());
|
||||
m_sent += i;
|
||||
}
|
||||
}
|
||||
|
||||
// done?
|
||||
if (!m && m_ack == m_sent) {
|
||||
if (m_sent)
|
||||
ESP_EARLY_LOGD(TAG, "WebSocketHandler[%p/%d]: ProcessTxJob MetricsUnitsUpdate done, sent=%d metrics", m_nc, m_modifier, m_sent);
|
||||
ClearTxJob(m_job);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WSTX_UnitPrefsUpdate:
|
||||
{
|
||||
// Note: this loops over the metrics by index, keeping the last checked position
|
||||
// in m_last. It will not detect new metrics added between polls if they are
|
||||
// inserted before m_last, so new metrics may not be sent until first changed.
|
||||
// The Metrics set normally is static, so this should be no problem.
|
||||
|
||||
ESP_EARLY_LOGD(TAG, "WebSocketHandler[%p/%d]: ProcessTxJob MetricsVehicleUpdate, last=%d sent=%d ack=%d", m_nc, m_modifier, m_last, m_sent, m_ack);
|
||||
if (m_last < MyUnitConfig.config_groups.size()) {
|
||||
// Bypass this if we are on the 'just sent' leg.
|
||||
// build msg:
|
||||
std::string msg;
|
||||
msg.reserve(2*XFER_CHUNK_SIZE+128);
|
||||
msg = "{\"units\":{\"prefs\":{";
|
||||
|
||||
// Cache the user mappings for each group.
|
||||
int i = 0;
|
||||
for (int groupindex = m_last;
|
||||
groupindex < MyUnitConfig.config_groups.size() && msg.size() < XFER_CHUNK_SIZE;
|
||||
++groupindex) {
|
||||
++m_last;
|
||||
metric_group_t group = MyUnitConfig.config_groups[groupindex];
|
||||
|
||||
bool send = MyUnitConfig.IsModifiedAndClear(group, m_modifier);
|
||||
if (send) {
|
||||
metric_unit_t user_units = MyUnitConfig.GetUserUnit(group);
|
||||
std::string unitLabel;
|
||||
if (user_units == UnitNotFound)
|
||||
unitLabel = "null";
|
||||
else {
|
||||
|
||||
unitLabel = '"';
|
||||
unitLabel += json_encode(std::string(OvmsMetricUnitLabel(user_units)));
|
||||
unitLabel += '"';
|
||||
}
|
||||
const char *groupName = OvmsMetricGroupName(group);
|
||||
const char *unitName = (user_units == Native) ? "Native" : OvmsMetricUnitName(user_units);
|
||||
std::string entry = string_format("%s\"%s\":{\"unit\":\"%s\",\"label\":%s}",
|
||||
i ? "," : "",
|
||||
groupName, unitName, unitLabel.c_str()
|
||||
);
|
||||
msg += entry;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// send msg:
|
||||
if (i) {
|
||||
msg += "}}}";
|
||||
ESP_EARLY_LOGD(TAG, "WebSocket msg: %s", msg.c_str());
|
||||
mg_send_websocket_frame(m_nc, WEBSOCKET_OP_TEXT, msg.data(), msg.size());
|
||||
m_sent += i;
|
||||
}
|
||||
}
|
||||
|
||||
// done?
|
||||
if (m_last >= MyUnitConfig.config_groups.size() && m_ack == m_sent) {
|
||||
if (m_sent)
|
||||
ESP_EARLY_LOGD(TAG, "WebSocketHandler[%p/%d]: ProcessTxJob MetricsUnitsUpdate done, sent=%d metrics", m_nc, m_modifier, m_sent);
|
||||
ClearTxJob(m_job);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case WSTX_Notify:
|
||||
{
|
||||
if (m_sent && m_ack == m_job.notification->GetValueSize()+1) {
|
||||
|
@ -300,7 +436,7 @@ bool WebSocketHandler::GetNextTxJob()
|
|||
if (!m_jobqueue) return false;
|
||||
if (xQueueReceive(m_jobqueue, &m_job, 0) == pdTRUE) {
|
||||
// init new job state:
|
||||
m_sent = m_ack = 0;
|
||||
m_sent = m_ack = m_last = 0;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -610,17 +746,31 @@ void OvmsWebServer::UpdateTicker(TimerHandle_t timer)
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// trigger metrics update:
|
||||
|
||||
// trigger metrics update if required.
|
||||
unsigned long mask_all = MyMetrics.GetUnitSendAll();
|
||||
for (auto slot: MyWebServer.m_client_slots) {
|
||||
if (slot.handler)
|
||||
if (slot.handler) {
|
||||
slot.handler->AddTxJob({ WSTX_MetricsUpdate, NULL });
|
||||
if (slot.handler->m_units_subscribed) {
|
||||
unsigned long bit = 1ul << slot.handler->m_modifier;
|
||||
bool addJob = (bit & mask_all) != 0;
|
||||
if (addJob) {
|
||||
// Trigger Units update:
|
||||
slot.handler->AddTxJob({ WSTX_UnitMetricUpdate, NULL });
|
||||
}
|
||||
}
|
||||
if (slot.handler->m_units_prefs_subscribed) {
|
||||
// Triger unit group config update.
|
||||
if (MyUnitConfig.HasModified(slot.handler->m_modifier))
|
||||
slot.handler->AddTxJob({ WSTX_UnitPrefsUpdate, NULL });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
xSemaphoreGive(MyWebServer.m_client_mutex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notifications:
|
||||
*/
|
||||
|
@ -642,18 +792,57 @@ void WebSocketHandler::Subscribe(std::string topic)
|
|||
}
|
||||
m_subscriptions.insert(topic);
|
||||
ESP_LOGD(TAG, "WebSocketHandler[%p]: subscription '%s' added", m_nc, topic.c_str());
|
||||
SubscriptionChanged();
|
||||
}
|
||||
|
||||
void WebSocketHandler::Unsubscribe(std::string topic)
|
||||
{
|
||||
bool changed = false;
|
||||
for (auto it = m_subscriptions.begin(); it != m_subscriptions.end();) {
|
||||
if (mg_mqtt_match_topic_expression(mg_mk_str(topic.c_str()), mg_mk_str((*it).c_str()))) {
|
||||
ESP_LOGD(TAG, "WebSocketHandler[%p]: subscription '%s' removed", m_nc, (*it).c_str());
|
||||
it = m_subscriptions.erase(it);
|
||||
changed = true;
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
if (changed)
|
||||
SubscriptionChanged();
|
||||
}
|
||||
|
||||
void WebSocketHandler::SubscriptionChanged()
|
||||
{
|
||||
UnitsCheckSubscribe();
|
||||
UnitsCheckVehicleSubscribe();
|
||||
}
|
||||
|
||||
void WebSocketHandler::UnitsCheckSubscribe()
|
||||
{
|
||||
bool newSubscribe = IsSubscribedTo("units/metrics");
|
||||
if (newSubscribe != m_units_subscribed) {
|
||||
m_units_subscribed = newSubscribe;
|
||||
if (newSubscribe) {
|
||||
ESP_LOGD(TAG, "WebSocketHandler[%p/%d]: Subscribed to units/metrics", m_nc, m_modifier);
|
||||
MyMetrics.SetAllUnitSend(m_modifier);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "WebSocketHandler[%p/%d]: Unsubscribed from units/metrics", m_nc, m_modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketHandler::UnitsCheckVehicleSubscribe()
|
||||
{
|
||||
bool newSubscribe = IsSubscribedTo("units/prefs");
|
||||
if (newSubscribe != m_units_prefs_subscribed) {
|
||||
m_units_prefs_subscribed = newSubscribe;
|
||||
if (newSubscribe) {
|
||||
ESP_LOGD(TAG, "WebSocketHandler[%p/%d]: Subscribed to units/prefs", m_nc, m_modifier);
|
||||
MyUnitConfig.InitialiseSlot(m_modifier);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "WebSocketHandler[%p/%d]: Unsubscribed from units/prefs", m_nc, m_modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WebSocketHandler::IsSubscribedTo(std::string topic)
|
||||
|
|
|
@ -74,6 +74,12 @@ void OvmsWebServer::HandleStatus(PageEntry_t& p, PageContext_t& c)
|
|||
c.done();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// "network restart", "wifi reconnect"
|
||||
OutputReconnect(p, c, NULL, cmd.c_str());
|
||||
c.done();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PAGE_HOOK("body.pre");
|
||||
|
@ -209,7 +215,10 @@ void OvmsWebServer::HandleStatus(PageEntry_t& p, PageContext_t& c)
|
|||
c.panel_start("primary", "Network");
|
||||
output = ExecuteCommand("network status");
|
||||
c.printf("<samp class=\"monitor\" data-updcmd=\"network status\" data-events=\"^network\">%s</samp>", _html(output));
|
||||
c.panel_end();
|
||||
c.panel_end(
|
||||
"<ul class=\"list-inline\">"
|
||||
"<li><button type=\"button\" class=\"btn btn-default btn-sm\" name=\"action\" value=\"network restart\">Restart network</button></li>"
|
||||
"</ul>");
|
||||
|
||||
c.print(
|
||||
"</div>"
|
||||
|
@ -218,7 +227,10 @@ void OvmsWebServer::HandleStatus(PageEntry_t& p, PageContext_t& c)
|
|||
c.panel_start("primary", "Wifi");
|
||||
output = ExecuteCommand("wifi status");
|
||||
c.printf("<samp class=\"monitor\" data-updcmd=\"wifi status\" data-events=\"\\.wifi\\.\">%s</samp>", _html(output));
|
||||
c.panel_end();
|
||||
c.panel_end(
|
||||
"<ul class=\"list-inline\">"
|
||||
"<li><button type=\"button\" class=\"btn btn-default btn-sm\" name=\"action\" value=\"wifi reconnect\">Reconnect Wifi</button></li>"
|
||||
"</ul>");
|
||||
|
||||
c.print(
|
||||
"</div>"
|
||||
|
@ -229,8 +241,10 @@ void OvmsWebServer::HandleStatus(PageEntry_t& p, PageContext_t& c)
|
|||
c.printf("<samp class=\"monitor\" data-updcmd=\"cellular status\" data-events=\"\\.modem\\.\">%s</samp>", _html(output));
|
||||
c.panel_end(
|
||||
"<ul class=\"list-inline\">"
|
||||
"<li><button type=\"button\" class=\"btn btn-default btn-sm\" data-target=\"#modem-cmdres\" data-cmd=\"power cellular on\">Start cellular modem</button></li>"
|
||||
"<li><button type=\"button\" class=\"btn btn-default btn-sm\" data-target=\"#modem-cmdres\" data-cmd=\"power cellular off\">Stop cellular modem</button></li>"
|
||||
"<li><button type=\"button\" class=\"btn btn-default btn-sm\" data-target=\"#modem-cmdres\" data-cmd=\"power cellular on\">Start modem</button></li>"
|
||||
"<li><button type=\"button\" class=\"btn btn-default btn-sm\" data-target=\"#modem-cmdres\" data-cmd=\"power cellular off\">Stop modem</button></li>"
|
||||
"<li><button type=\"button\" class=\"btn btn-default btn-sm\" data-target=\"#modem-cmdres\" data-cmd=\"cellular gps start\">Start GPS</button></li>"
|
||||
"<li><button type=\"button\" class=\"btn btn-default btn-sm\" data-target=\"#modem-cmdres\" data-cmd=\"cellular gps stop\">Stop GPS</button></li>"
|
||||
"<li><samp id=\"modem-cmdres\" class=\"samp-inline\"></samp></li>"
|
||||
"</ul>");
|
||||
|
||||
|
@ -591,9 +605,13 @@ void OvmsWebServer::HandleCfgPassword(PageEntry_t& p, PageContext_t& c)
|
|||
void OvmsWebServer::HandleCfgVehicle(PageEntry_t& p, PageContext_t& c)
|
||||
{
|
||||
std::string error, info;
|
||||
std::string vehicleid, vehicletype, vehiclename, timezone, timezone_region, units_distance, pin;
|
||||
std::string vehicleid, vehicletype, vehiclename, timezone, timezone_region, pin;
|
||||
std::string bat12v_factor, bat12v_ref, bat12v_alert;
|
||||
|
||||
std::map<metric_group_t,std::string> units_values;
|
||||
metric_group_list_t unit_groups;
|
||||
OvmsMetricGroupConfigList(unit_groups);
|
||||
|
||||
if (c.method == "POST") {
|
||||
// process form submission:
|
||||
vehicleid = c.getvar("vehicleid");
|
||||
|
@ -601,7 +619,13 @@ void OvmsWebServer::HandleCfgVehicle(PageEntry_t& p, PageContext_t& c)
|
|||
vehiclename = c.getvar("vehiclename");
|
||||
timezone = c.getvar("timezone");
|
||||
timezone_region = c.getvar("timezone_region");
|
||||
units_distance = c.getvar("units_distance");
|
||||
for ( auto grpiter = unit_groups.begin(); grpiter != unit_groups.end(); ++grpiter) {
|
||||
std::string name = OvmsMetricGroupName(*grpiter);
|
||||
std::string cfg = "units_";
|
||||
cfg += name;
|
||||
units_values[*grpiter] = c.getvar(cfg);
|
||||
}
|
||||
|
||||
bat12v_factor = c.getvar("bat12v_factor");
|
||||
bat12v_ref = c.getvar("bat12v_ref");
|
||||
bat12v_alert = c.getvar("bat12v_alert");
|
||||
|
@ -627,7 +651,12 @@ void OvmsWebServer::HandleCfgVehicle(PageEntry_t& p, PageContext_t& c)
|
|||
MyConfig.SetParamValue("vehicle", "name", vehiclename);
|
||||
MyConfig.SetParamValue("vehicle", "timezone", timezone);
|
||||
MyConfig.SetParamValue("vehicle", "timezone_region", timezone_region);
|
||||
MyConfig.SetParamValue("vehicle", "units.distance", units_distance);
|
||||
for ( auto grpiter = unit_groups.begin(); grpiter != unit_groups.end(); ++grpiter) {
|
||||
std::string name = OvmsMetricGroupName(*grpiter);
|
||||
std::string value = units_values[*grpiter];
|
||||
OvmsMetricSetUserConfig(*grpiter, value);
|
||||
}
|
||||
|
||||
MyConfig.SetParamValue("system.adc", "factor12v", bat12v_factor);
|
||||
MyConfig.SetParamValue("vehicle", "12v.ref", bat12v_ref);
|
||||
MyConfig.SetParamValue("vehicle", "12v.alert", bat12v_alert);
|
||||
|
@ -656,7 +685,8 @@ void OvmsWebServer::HandleCfgVehicle(PageEntry_t& p, PageContext_t& c)
|
|||
vehiclename = MyConfig.GetParamValue("vehicle", "name");
|
||||
timezone = MyConfig.GetParamValue("vehicle", "timezone");
|
||||
timezone_region = MyConfig.GetParamValue("vehicle", "timezone_region");
|
||||
units_distance = MyConfig.GetParamValue("vehicle", "units.distance");
|
||||
for ( auto grpiter = unit_groups.begin(); grpiter != unit_groups.end(); ++grpiter)
|
||||
units_values[*grpiter] = OvmsMetricGetUserConfig(*grpiter);
|
||||
bat12v_factor = MyConfig.GetParamValue("system.adc", "factor12v");
|
||||
bat12v_ref = MyConfig.GetParamValue("vehicle", "12v.ref");
|
||||
bat12v_alert = MyConfig.GetParamValue("vehicle", "12v.alert");
|
||||
|
@ -703,10 +733,40 @@ void OvmsWebServer::HandleCfgVehicle(PageEntry_t& p, PageContext_t& c)
|
|||
, _attr(timezone_region)
|
||||
, _attr(timezone));
|
||||
|
||||
c.input_radiobtn_start("Distance units", "units_distance");
|
||||
c.input_radiobtn_option("units_distance", "Kilometers", "K", units_distance == "K");
|
||||
c.input_radiobtn_option("units_distance", "Miles", "M", units_distance == "M");
|
||||
c.input_radiobtn_end();
|
||||
for ( auto grpiter = unit_groups.begin(); grpiter != unit_groups.end(); ++grpiter) {
|
||||
std::string name = OvmsMetricGroupName(*grpiter);
|
||||
metric_unit_set_t group_units;
|
||||
if (OvmsMetricGroupUnits(*grpiter,group_units)) {
|
||||
bool use_select = group_units.size() > 3;
|
||||
std::string cfg = "units_";
|
||||
cfg += name;
|
||||
std::string value = units_values[*grpiter];
|
||||
if (use_select)
|
||||
c.input_select_start(OvmsMetricGroupLabel(*grpiter), cfg.c_str() );
|
||||
else
|
||||
c.input_radiobtn_start(OvmsMetricGroupLabel(*grpiter), cfg.c_str() );
|
||||
|
||||
bool checked = value.empty();
|
||||
if (use_select)
|
||||
c.input_select_option( "Default", "", checked);
|
||||
else
|
||||
c.input_radiobtn_option(cfg.c_str(), "Default", "", checked);
|
||||
for (auto unititer = group_units.begin(); unititer != group_units.end(); ++unititer) {
|
||||
const char* unit_name = OvmsMetricUnitName(*unititer);
|
||||
const char* unit_label = OvmsMetricUnitLabel(*unititer);
|
||||
checked = value == unit_name;
|
||||
if (use_select)
|
||||
c.input_select_option( unit_label, unit_name, checked);
|
||||
else
|
||||
c.input_radiobtn_option(cfg.c_str(), unit_label, unit_name, checked);
|
||||
}
|
||||
if (use_select)
|
||||
c.input_select_end();
|
||||
else
|
||||
c.input_radiobtn_end();
|
||||
}
|
||||
}
|
||||
|
||||
c.input_password("PIN", "pin", "", "empty = no change",
|
||||
"<p>Vehicle PIN code used for unlocking etc.</p>", "autocomplete=\"section-vehiclepin new-password\"");
|
||||
|
||||
|
@ -963,8 +1023,7 @@ void OvmsWebServer::HandleCfgPushover(PageEntry_t& p, PageContext_t& c)
|
|||
}
|
||||
|
||||
if (error == "") {
|
||||
if (c.getvar("action") == "save")
|
||||
{
|
||||
if (c.getvar("action") == "save") {
|
||||
// save:
|
||||
param->m_map.clear();
|
||||
param->m_map = std::move(pmap);
|
||||
|
@ -975,9 +1034,8 @@ void OvmsWebServer::HandleCfgPushover(PageEntry_t& p, PageContext_t& c)
|
|||
OutputHome(p, c);
|
||||
c.done();
|
||||
return;
|
||||
}
|
||||
else if (c.getvar("action") == "test")
|
||||
{
|
||||
} else if (c.getvar("action") == "test")
|
||||
{
|
||||
std::string reply;
|
||||
std::string popup;
|
||||
c.head(200);
|
||||
|
@ -991,10 +1049,10 @@ void OvmsWebServer::HandleCfgPushover(PageEntry_t& p, PageContext_t& c)
|
|||
atoi(c.getvar("retry").c_str()),
|
||||
atoi(c.getvar("expire").c_str()),
|
||||
true /* receive server reply as reply/pushover-type notification */ ))
|
||||
{
|
||||
{
|
||||
c.alert("danger", "<p class=\"lead\">Could not send test message!</p>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// output error, return to form:
|
||||
|
@ -1163,7 +1221,7 @@ void OvmsWebServer::HandleCfgPushover(PageEntry_t& p, PageContext_t& c)
|
|||
"<td><button type=\"button\" class=\"btn btn-danger\" onclick=\"delRow(this)\"><strong>✖</strong></button></td>"
|
||||
"<td><input type=\"text\" class=\"form-control\" name=\"nfy_%d\" value=\"%s\" placeholder=\"Enter notification type/subtype\""
|
||||
" autocomplete=\"section-notification-type\"></td>"
|
||||
"<td width=\"20%\"><select class=\"form-control\" name=\"np_%d\" size=\"1\">"
|
||||
"<td width=\"20%%\"><select class=\"form-control\" name=\"np_%d\" size=\"1\">"
|
||||
, max, _attr(name)
|
||||
, max);
|
||||
gen_options_priority(kv.second);
|
||||
|
@ -3931,7 +3989,7 @@ void OvmsWebServer::HandleEditor(PageEntry_t& p, PageContext_t& c)
|
|||
"text-align: center !important;\n"
|
||||
"}\n"
|
||||
"}\n"
|
||||
".log { font-size: 87%; color: gray; }\n"
|
||||
".log { font-size: 87%%; color: gray; }\n"
|
||||
".log.log-I { color: green; }\n"
|
||||
".log.log-W { color: darkorange; }\n"
|
||||
".log.log-E { color: red; }\n"
|
||||
|
|
|
@ -885,7 +885,7 @@ std::string OvmsWebServer::CfgInit3(PageEntry_t& p, PageContext_t& c, std::strin
|
|||
std::string OvmsWebServer::CfgInit4(PageEntry_t& p, PageContext_t& c, std::string step)
|
||||
{
|
||||
std::string error, info;
|
||||
std::string vehicletype, units_distance;
|
||||
std::string vehicletype, units_distance, units_temp, units_pressure;
|
||||
std::string server, vehicleid, password;
|
||||
|
||||
if (c.method == "POST") {
|
||||
|
@ -905,6 +905,8 @@ std::string OvmsWebServer::CfgInit4(PageEntry_t& p, PageContext_t& c, std::strin
|
|||
// process form input:
|
||||
vehicletype = c.getvar("vehicletype");
|
||||
units_distance = c.getvar("units_distance");
|
||||
units_temp = c.getvar("units_temp");
|
||||
units_pressure = c.getvar("units_pressure");
|
||||
server = c.getvar("server");
|
||||
vehicleid = c.getvar("vehicleid");
|
||||
password = c.getvar("password");
|
||||
|
@ -917,7 +919,22 @@ std::string OvmsWebServer::CfgInit4(PageEntry_t& p, PageContext_t& c, std::strin
|
|||
error += "<li data-input=\"vehicleid\">Vehicle ID may only contain ASCII letters, digits and '-'</li>";
|
||||
|
||||
// configure vehicle:
|
||||
MyConfig.SetParamValue("vehicle", "units.distance", units_distance);
|
||||
OvmsMetricSetUserConfig(GrpDistance, units_distance);
|
||||
if (units_distance == "miles") {
|
||||
OvmsMetricSetUserConfig(GrpDistanceShort, "feet");
|
||||
OvmsMetricSetUserConfig(GrpSpeed, "miph");
|
||||
OvmsMetricSetUserConfig(GrpAccel, "miphps");
|
||||
OvmsMetricSetUserConfig(GrpAccelShort, "ftpss");
|
||||
OvmsMetricSetUserConfig(GrpConsumption, "mipkwh");
|
||||
} else {
|
||||
// Set to their defaults.
|
||||
OvmsMetricSetUserConfig(GrpDistanceShort, "");
|
||||
OvmsMetricSetUserConfig(GrpSpeed, "");
|
||||
OvmsMetricSetUserConfig(GrpAccel, "");
|
||||
OvmsMetricSetUserConfig(GrpAccelShort, "");
|
||||
OvmsMetricSetUserConfig(GrpConsumption, "");
|
||||
}
|
||||
|
||||
MyConfig.SetParamValue("auto", "vehicle.type", vehicletype);
|
||||
|
||||
// configure server:
|
||||
|
@ -945,14 +962,17 @@ std::string OvmsWebServer::CfgInit4(PageEntry_t& p, PageContext_t& c, std::strin
|
|||
// read configuration:
|
||||
vehicleid = MyConfig.GetParamValue("vehicle", "id");
|
||||
vehicletype = MyConfig.GetParamValue("auto", "vehicle.type");
|
||||
units_distance = MyConfig.GetParamValue("vehicle", "units.distance", "K");
|
||||
units_distance = OvmsMetricGetUserConfig(GrpDistance);
|
||||
units_temp = OvmsMetricGetUserConfig(GrpTemp);
|
||||
units_pressure = OvmsMetricGetUserConfig(GrpPressure);
|
||||
|
||||
server = MyConfig.GetParamValue("server.v2", "server");
|
||||
password = MyConfig.GetParamValue("password","server.v2");
|
||||
|
||||
// default data server = ota server:
|
||||
if (server.empty()) {
|
||||
server = MyConfig.GetParamValue("ota", "server");
|
||||
if (startsWith(server, "ovms-ota.bit-cloud.de"))
|
||||
if (startsWith(server, "ovms-ota.bit-cloud.de"))
|
||||
server = "ovms-server.bit-cloud.de";
|
||||
else
|
||||
server = "ovms.dexters-web.de";
|
||||
|
@ -1029,9 +1049,22 @@ std::string OvmsWebServer::CfgInit4(PageEntry_t& p, PageContext_t& c, std::strin
|
|||
c.input_select_option(k->second.name, k->first, (vehicletype == k->first));
|
||||
c.input_select_end();
|
||||
|
||||
c.input_radiobtn_start("Distance units", "units_distance");
|
||||
c.input_radiobtn_option("units_distance", "Kilometers", "K", units_distance == "K");
|
||||
c.input_radiobtn_option("units_distance", "Miles", "M", units_distance == "M");
|
||||
bool is_metric = units_distance != "miles";
|
||||
c.input_radiobtn_start("Distance related units", "units_distance");
|
||||
c.input_radiobtn_option("units_distance", "Metric (km & metres)", "", is_metric);
|
||||
c.input_radiobtn_option("units_distance", "Imperial (miles & feet)", "miles", !is_metric);
|
||||
c.input_radiobtn_end();
|
||||
|
||||
is_metric = units_temp != "fahrenheit";
|
||||
c.input_radiobtn_start("Temperature units", "units_temp");
|
||||
c.input_radiobtn_option("units_temp", "Metric (°C)", "", is_metric);
|
||||
c.input_radiobtn_option("units_temp", "Imperial (°F)", "fahrenheit", !is_metric);
|
||||
c.input_radiobtn_end();
|
||||
|
||||
is_metric = units_pressure != "psi";
|
||||
c.input_radiobtn_start("Pressure units", "units_pressure");
|
||||
c.input_radiobtn_option("units_pressure", "Metric (kPa)", "", is_metric);
|
||||
c.input_radiobtn_option("units_pressure", "Imperial (PSI)", "psi", !is_metric);
|
||||
c.input_radiobtn_end();
|
||||
|
||||
c.input_radio_start("OVMS data server", "server");
|
||||
|
|
|
@ -305,7 +305,7 @@ void OvmsWebServer::HandleDashboard(PageEntry_t& p, PageContext_t& c)
|
|||
"</div>"
|
||||
"<div class=\"underlay\">"
|
||||
"<div class=\"voltage-value\"><span class=\"value\">0</span></div>"
|
||||
"<div class=\"soc-value\"><span class=\"value\">0</span></div>"
|
||||
"<div class=\"soc-value\"><span class=\"value\">0</span><span class=\"unit\">%</span></div>"
|
||||
"<div class=\"consumption-value\"><span class=\"value\">0</span></div>"
|
||||
"<div class=\"power-value\"><span class=\"value\">0</span></div>"
|
||||
"</div>"
|
||||
|
@ -320,29 +320,29 @@ void OvmsWebServer::HandleDashboard(PageEntry_t& p, PageContext_t& c)
|
|||
"var gaugeset1;"
|
||||
""
|
||||
"function get_dashboard_data() {"
|
||||
"var rmin = metrics[\"v.b.range.est\"]||0, rmax = metrics[\"v.b.range.ideal\"]||0;\n"
|
||||
"var euse = metrics[\"v.b.energy.used\"]||0, erec = metrics[\"v.b.energy.recd\"]||0;\n"
|
||||
"var voltage = metrics[\"v.b.voltage\"]||0, soc = metrics[\"v.b.soc\"]||0;\n"
|
||||
"var consumption = metrics[\"v.b.consumption\"]||0, power = metrics[\"v.b.power\"]||0;\n"
|
||||
"var rmin = metrics_user[\"v.b.range.est\"]||0, rmax = metrics_user[\"v.b.range.ideal\"]||0;\n"
|
||||
"var euse = metrics_user[\"v.b.energy.used\"]||0, erec = metrics_user[\"v.b.energy.recd\"]||0;\n"
|
||||
"var voltage = metrics[\"v.b.voltage\"]||0, soc = metrics_user[\"v.b.soc\"]||0;\n"
|
||||
"var consumption = metrics_user[\"v.b.consumption\"]||0, power = metrics_user[\"v.b.power\"]||0;\n"
|
||||
"euse = Math.floor(euse*10)/10; erec = Math.floor(erec*10)/10;"
|
||||
"if (rmin > rmax) { var x = rmin; rmin = rmax; rmax = x; }"
|
||||
"var md = {"
|
||||
"range: { value: \"▼\" + rmin.toFixed(0) + \" ▲\" + rmax.toFixed(0) },"
|
||||
"energy: { value: \"▲\" + euse.toFixed(1) + \" ▼\" + erec.toFixed(1) },"
|
||||
"range: { value: \"▼\" + rmin.toFixed(0) + \" ▲\" + rmax.toFixed(0), unit: metrics_label[\"v.b.range.est\"] },"
|
||||
"energy: { value: \"▲\" + euse.toFixed(1) + \" ▼\" + erec.toFixed(1), unit: metrics_label[\"v.b.energy.used\"] },"
|
||||
"voltage: { value: voltage.toFixed(0) },"
|
||||
"soc: { value: soc.toFixed(0) },"
|
||||
"consumption: { value: consumption.toFixed(0) },"
|
||||
"power: { value: power.toFixed(0) },"
|
||||
"soc: { value: soc.toFixed(0), unit: metrics_label[\"v.b.soc\"] },"
|
||||
"consumption: { value: consumption.toFixed(0), unit: metrics_label[\"v.b.consumption\"] },"
|
||||
"power: { value: power.toFixed(0), unit: metrics_label[\"v.b.power\"] },"
|
||||
"series: ["
|
||||
"{ data: [metrics[\"v.p.speed\"]] },"
|
||||
"{ data: [metrics_user[\"v.p.speed\"]] },"
|
||||
"{ data: [metrics[\"v.b.voltage\"]] },"
|
||||
"{ data: [metrics[\"v.b.soc\"]] },"
|
||||
"{ data: [metrics[\"v.b.consumption\"]] },"
|
||||
"{ data: [metrics[\"v.b.power\"]] },"
|
||||
"{ data: [metrics[\"v.c.temp\"]] },"
|
||||
"{ data: [metrics[\"v.b.temp\"]] },"
|
||||
"{ data: [metrics[\"v.i.temp\"]] },"
|
||||
"{ data: [metrics[\"v.m.temp\"]] }],"
|
||||
"{ data: [metrics_user[\"v.b.soc\"]] },"
|
||||
"{ data: [metrics_user[\"v.b.consumption\"]] },"
|
||||
"{ data: [metrics_user[\"v.b.power\"]] },"
|
||||
"{ data: [metrics_user[\"v.c.temp\"]] },"
|
||||
"{ data: [metrics_user[\"v.b.temp\"]] },"
|
||||
"{ data: [metrics_user[\"v.i.temp\"]] },"
|
||||
"{ data: [metrics_user[\"v.m.temp\"]] }],"
|
||||
"};"
|
||||
"return md;"
|
||||
"}"
|
||||
|
@ -350,9 +350,12 @@ void OvmsWebServer::HandleDashboard(PageEntry_t& p, PageContext_t& c)
|
|||
"function update_dashboard() {"
|
||||
"var md = get_dashboard_data();"
|
||||
"$('.range-value .value').text(md.range.value);"
|
||||
"$('.range-value .unit').text(md.range.unit);"
|
||||
"$('.energy-value .value').text(md.energy.value);"
|
||||
"$('.energy-value .unit').text(md.energy.unit);"
|
||||
"$('.voltage-value .value').text(md.voltage.value);"
|
||||
"$('.soc-value .value').text(md.soc.value);"
|
||||
"$('.soc-value .unit').text(md.soc.unit);"
|
||||
"$('.consumption-value .value').text(md.consumption.value);"
|
||||
"$('.power-value .value').text(md.power.value);"
|
||||
"gaugeset1.update({ series: md.series });"
|
||||
|
@ -534,7 +537,7 @@ void OvmsWebServer::HandleDashboard(PageEntry_t& p, PageContext_t& c)
|
|||
""
|
||||
"/* Inject vehicle config: */"
|
||||
"for (var i = 0; i < chart_config.yAxis.length; i++) {"
|
||||
"$.extend(chart_config.yAxis[i], vehicle_config.yAxis[i]);"
|
||||
"$.extend(true, chart_config.yAxis[i], vehicle_config.yAxis[i]);"
|
||||
"}"
|
||||
""
|
||||
"gaugeset1 = Highcharts.chart('gaugeset1', chart_config,"
|
||||
|
@ -1088,20 +1091,20 @@ void OvmsWebServer::HandleBmsCellMonitor(PageEntry_t& p, PageContext_t& c)
|
|||
"// get_temp_data: build boxplot dataset from metrics\n"
|
||||
"function get_temp_data() {\n"
|
||||
"var data = { cells: [], temps: [], devmax: [], tempmean: 0, sdlo: 0, sdhi: 0, sdmaxlo: 0, sdmaxhi: 0 };\n"
|
||||
"var cnt = metrics[\"v.b.c.temp\"] ? metrics[\"v.b.c.temp\"].length : 0;\n"
|
||||
"var cnt = metrics_user[\"v.b.c.temp\"] ? metrics_user[\"v.b.c.temp\"].length : 0;\n"
|
||||
"if (cnt == 0)\n"
|
||||
"return data;\n"
|
||||
"var i, act, min, max, devmax, dalert, dlow, dhigh;\n"
|
||||
"data.tempmean = metrics[\"v.b.p.temp.avg\"] || 0;\n"
|
||||
"data.sdlo = data.tempmean - (metrics[\"v.b.p.temp.stddev\"] || 0);\n"
|
||||
"data.sdhi = data.tempmean + (metrics[\"v.b.p.temp.stddev\"] || 0);\n"
|
||||
"data.sdmaxlo = data.tempmean - (metrics[\"v.b.p.temp.stddev.max\"] || 0);\n"
|
||||
"data.sdmaxhi = data.tempmean + (metrics[\"v.b.p.temp.stddev.max\"] || 0);\n"
|
||||
"data.tempmean = metrics_user[\"v.b.p.temp.avg\"] || 0;\n"
|
||||
"data.sdlo = data.tempmean - (metrics_user[\"v.b.p.temp.stddev\"] || 0);\n"
|
||||
"data.sdhi = data.tempmean + (metrics_user[\"v.b.p.temp.stddev\"] || 0);\n"
|
||||
"data.sdmaxlo = data.tempmean - (metrics_user[\"v.b.p.temp.stddev.max\"] || 0);\n"
|
||||
"data.sdmaxhi = data.tempmean + (metrics_user[\"v.b.p.temp.stddev.max\"] || 0);\n"
|
||||
"for (i=0; i<cnt; i++) {\n"
|
||||
"act = metrics[\"v.b.c.temp\"][i];\n"
|
||||
"min = metrics[\"v.b.c.temp.min\"][i] || act;\n"
|
||||
"max = metrics[\"v.b.c.temp.max\"][i] || act;\n"
|
||||
"devmax = metrics[\"v.b.c.temp.dev.max\"][i] || 0;\n"
|
||||
"act = metrics_user[\"v.b.c.temp\"][i];\n"
|
||||
"min = metrics_user[\"v.b.c.temp.min\"][i] || act;\n"
|
||||
"max = metrics_user[\"v.b.c.temp.max\"][i] || act;\n"
|
||||
"devmax = metrics_user[\"v.b.c.temp.dev.max\"][i] || 0;\n"
|
||||
"dalert = metrics[\"v.b.c.temp.alert\"][i] || 0;\n"
|
||||
"if (devmax > 0) {\n"
|
||||
"dlow = data.tempmean;\n"
|
||||
|
|
|
@ -49,22 +49,32 @@
|
|||
* & → &
|
||||
*/
|
||||
std::string PageContext::encode_html(const char* text) {
|
||||
std::string buf;
|
||||
for (int i=0; i<strlen(text); i++) {
|
||||
if (text[i] == '\"')
|
||||
buf += """;
|
||||
else if (text[i] == '\'')
|
||||
buf += "'";
|
||||
else if(text[i] == '<')
|
||||
buf += "<";
|
||||
else if(text[i] == '>')
|
||||
buf += ">";
|
||||
else if(text[i] == '&')
|
||||
buf += "&";
|
||||
else
|
||||
buf += text[i];
|
||||
int len = strlen(text);
|
||||
std::string buf;
|
||||
buf.reserve(len);
|
||||
for (int i=0; i < len; i++) {
|
||||
char ch = text[i];
|
||||
switch(ch) {
|
||||
case '\"':
|
||||
buf.append(""");
|
||||
break;
|
||||
case '\'':
|
||||
buf.append("'");
|
||||
break;
|
||||
case '<':
|
||||
buf.append("<");
|
||||
break;
|
||||
case '>':
|
||||
buf.append(">");
|
||||
break;
|
||||
case '&':
|
||||
buf.append("&");
|
||||
break;
|
||||
default:
|
||||
buf.append(&ch,1);
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string PageContext::encode_html(std::string text) {
|
||||
|
@ -72,23 +82,31 @@ std::string PageContext::encode_html(std::string text) {
|
|||
}
|
||||
|
||||
extram::string PageContext::encode_html(const extram::string& text) {
|
||||
extram::string buf;
|
||||
extram::string buf;
|
||||
buf.reserve(text.length() + 500);
|
||||
for (int i=0; i<text.length(); i++) {
|
||||
if (text[i] == '\"')
|
||||
buf += """;
|
||||
else if (text[i] == '\'')
|
||||
buf += "'";
|
||||
else if(text[i] == '<')
|
||||
buf += "<";
|
||||
else if(text[i] == '>')
|
||||
buf += ">";
|
||||
else if(text[i] == '&')
|
||||
buf += "&";
|
||||
else
|
||||
buf += text[i];
|
||||
for (int i=0; i<text.length(); i++) {
|
||||
char ch = text[i];
|
||||
switch (ch) {
|
||||
case '\"':
|
||||
buf.append(""");
|
||||
break;
|
||||
case '\'':
|
||||
buf.append("'");
|
||||
break;
|
||||
case '<':
|
||||
buf.append("<");
|
||||
break;
|
||||
case '>':
|
||||
buf.append(">");
|
||||
break;
|
||||
case '&':
|
||||
buf.append("&");
|
||||
break;
|
||||
default:
|
||||
buf.append(&ch,1);
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
return buf;
|
||||
}
|
||||
|
||||
#define _attr(text) (encode_html(text).c_str())
|
||||
|
@ -102,11 +120,12 @@ extram::string PageContext::encode_html(const extram::string& text) {
|
|||
*
|
||||
*/
|
||||
std::string PageContext::make_id(const char* text) {
|
||||
std::string buf;
|
||||
std::string buf;
|
||||
char lc = 0;
|
||||
for (int i=0; i<strlen(text); i++) {
|
||||
if (isalnum(text[i]))
|
||||
buf += (lc = tolower(text[i]));
|
||||
int len = strlen(text);
|
||||
for (int i=0; i<len; i++) {
|
||||
if (isalnum(text[i]))
|
||||
buf += (lc = tolower(text[i]));
|
||||
else if (lc && lc != '-')
|
||||
buf += (lc = '-');
|
||||
}
|
||||
|
@ -114,7 +133,7 @@ std::string PageContext::make_id(const char* text) {
|
|||
lc = buf.back();
|
||||
buf.pop_back();
|
||||
}
|
||||
return buf;
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string PageContext::make_id(std::string text) {
|
||||
|
@ -361,7 +380,7 @@ void PageContext::input_slider(const char* label, const char* name, int size, co
|
|||
"<label class=\"control-label col-sm-3\" for=\"input-%s\">%s:</label>"
|
||||
"<div class=\"col-sm-9\">"
|
||||
"<div class=\"form-control slider\" data-default=\"%g\" data-reset=\"false\""
|
||||
" data-value=\"%g\" data-min=\"%g\" data-max=\"%g\" data-step=\"%g\">"
|
||||
" data-value=\"%g\" data-min=\"%g\" data-max=\"%g\" data-step=\"%g\" data-checked=\"%s\">"
|
||||
"<div class=\"slider-control form-inline\">"
|
||||
"<input class=\"slider-enable\" type=\"%s\" %s> "
|
||||
"<input class=\"form-control slider-value\" %s type=\"number\" style=\"width:%dpx;\""
|
||||
|
@ -380,6 +399,7 @@ void PageContext::input_slider(const char* label, const char* name, int size, co
|
|||
, _attr(name)
|
||||
, label
|
||||
, defval, value, min, max, step
|
||||
, (enabled != 0) ? "true" : "false"
|
||||
, (enabled < 0) ? "hidden" : "checkbox" // -1 => no checkbox
|
||||
, (enabled > 0) ? "checked" : ""
|
||||
, (enabled == 0) ? "disabled" : ""
|
||||
|
@ -714,7 +734,8 @@ void OvmsWebServer::OutputReboot(PageEntry_t& p, PageContext_t& c)
|
|||
/**
|
||||
* OutputReconnect: output reconnect script
|
||||
*/
|
||||
void OvmsWebServer::OutputReconnect(PageEntry_t& p, PageContext_t& c, const char* info /*=NULL*/)
|
||||
void OvmsWebServer::OutputReconnect(PageEntry_t& p, PageContext_t& c, const char* info /*=NULL*/,
|
||||
const char* cmd /*=NULL*/)
|
||||
{
|
||||
c.printf(
|
||||
"<div class=\"alert alert-warning\">"
|
||||
|
@ -733,8 +754,16 @@ void OvmsWebServer::OutputReconnect(PageEntry_t& p, PageContext_t& c, const char
|
|||
"$(\"#dots\").append(\"•\");"
|
||||
"}"
|
||||
"}, 1000);"
|
||||
"</script>"
|
||||
, info ? info : "Reconnecting…");
|
||||
|
||||
if (cmd) {
|
||||
c.printf(
|
||||
"loadcmd(\"%s\");"
|
||||
, __attr(cmd));
|
||||
}
|
||||
|
||||
c.print(
|
||||
"</script>");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ void pushover_send_message(int verbosity, OvmsWriter* writer, OvmsCommand* cmd,
|
|||
sound = "pushover";
|
||||
}
|
||||
|
||||
writer->printf("Sending PUSHOVER message \"%s\" with priority %d and sound %s\n",argv[0],priority,sound);
|
||||
writer->printf("Sending PUSHOVER message \"%s\" with priority %d and sound %s\n",argv[0],priority,sound.c_str());
|
||||
MyPushoverClient.SendMessage(argv[0], priority, sound);
|
||||
}
|
||||
|
||||
|
|
|
@ -199,7 +199,7 @@ void scanStart(int, OvmsWriter* writer, OvmsCommand*, int argc, const char* cons
|
|||
if (start > end)
|
||||
{
|
||||
writer->printf(
|
||||
"Error: Invalid Start PID %04x is after End PID %04x\n", start, end
|
||||
"Error: Invalid Start PID %04lx is after End PID %04lx\n", start, end
|
||||
);
|
||||
valid = false;
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ void scanStart(int, OvmsWriter* writer, OvmsCommand*, int argc, const char* cons
|
|||
}
|
||||
if (POLL_TYPE_HAS_8BIT_PID(polltype) && end > 0xff)
|
||||
{
|
||||
writer->printf("Error: Poll type %x PID range is 00..ff\n");
|
||||
writer->printf("Error: Poll type %lx PID range is 00..ff\n", polltype);
|
||||
valid = false;
|
||||
}
|
||||
if (!valid)
|
||||
|
@ -229,7 +229,7 @@ void scanStart(int, OvmsWriter* writer, OvmsCommand*, int argc, const char* cons
|
|||
if (valid)
|
||||
{
|
||||
s_scanner = new OvmsReToolsPidScanner(can, ecu, rxid_low, rxid_high, polltype, start, end, step, timeout);
|
||||
writer->printf("Scan started: bus %d, ecu %x, rxid %x-%x, polltype %x, PID %x-%x (step %x), timeout %d seconds\n",
|
||||
writer->printf("Scan started: bus %ld, ecu %lx, rxid %lx-%lx, polltype %lx, PID %lx-%lx (step %lx), timeout %d seconds\n",
|
||||
bus, ecu, rxid_low, rxid_high, polltype, start, end, step, timeout);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,13 +97,13 @@ static void IRAM_ATTR sdcard_isr_handler(void* arg)
|
|||
sdcard::sdcard(const char* name, bool mode1bit, bool autoformat, int cdpin)
|
||||
: pcp(name)
|
||||
{
|
||||
m_host = SDMMC_HOST_DEFAULT();
|
||||
m_host = sdmmc_host_t SDMMC_HOST_DEFAULT();
|
||||
if (mode1bit)
|
||||
{
|
||||
m_host.flags = SDMMC_HOST_FLAG_1BIT;
|
||||
}
|
||||
|
||||
m_slot = SDMMC_SLOT_CONFIG_DEFAULT();
|
||||
m_slot = sdmmc_slot_config_t SDMMC_SLOT_CONFIG_DEFAULT();
|
||||
// Disable driver-level CD pin, as we do this ourselves
|
||||
// if (cdpin)
|
||||
// {
|
||||
|
|
|
@ -151,7 +151,9 @@ modem::modem_state1_t simcom5360::State1Ticker1(modem::modem_state1_t curstate)
|
|||
m_modem->tx("AT+CMUXSRVPORT=0,5\r\n");
|
||||
break;
|
||||
case 20:
|
||||
m_modem->tx("AT+CMUX=0\r\n");
|
||||
// start MUX mode, route URCs to MUX channel 3 (POLL)
|
||||
// Note: NMEA URCs will still be sent only on channel 1 (NMEA) by the SIMCOM 5360
|
||||
m_modem->tx("AT+CMUX=0;+CATR=6\r\n");
|
||||
break;
|
||||
}
|
||||
return modem::None;
|
||||
|
|
|
@ -73,6 +73,7 @@ void simcom7000::PowerCycle()
|
|||
m_powercyclefactor = m_powercyclefactor % 3;
|
||||
ESP_LOGI(TAG, "Power Cycle (SIM7000) %dms",psd);
|
||||
|
||||
uart_wait_tx_done(m_modem->m_uartnum, portMAX_DELAY);
|
||||
uart_flush(m_modem->m_uartnum); // Flush the ring buffer, to try to address MUX start issues
|
||||
#ifdef CONFIG_OVMS_COMP_MAX7317
|
||||
MyPeripherals->m_max7317->Output(MODEM_EGPIO_PWR, 0); // Modem EN/PWR line low
|
||||
|
@ -125,7 +126,8 @@ modem::modem_state1_t simcom7000::State1Ticker1(modem::modem_state1_t curstate)
|
|||
m_modem->tx("AT+CGMR;+ICCID\r\n");
|
||||
break;
|
||||
case 20:
|
||||
m_modem->tx("AT+CMUX=0\r\n");
|
||||
// start MUX mode, route URCs to MUX channel 3 (POLL)
|
||||
m_modem->tx("AT+CMUX=0;+CATR=6\r\n");
|
||||
break;
|
||||
}
|
||||
return modem::None;
|
||||
|
|
|
@ -80,6 +80,7 @@ void simcom7600::StartupNMEA()
|
|||
{
|
||||
m_modem->muxtx(GetMuxChannelCMD(), "AT+CGPS=0\r\n");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
// send single commands, as each can fail:
|
||||
m_modem->muxtx(GetMuxChannelCMD(), "AT+CGPSNMEA=258\r\n");
|
||||
m_modem->muxtx(GetMuxChannelCMD(), "AT+CGPSINFOCFG=5,258\r\n");
|
||||
m_modem->muxtx(GetMuxChannelCMD(), "AT+CGPS=1,1\r\n");
|
||||
|
@ -88,6 +89,21 @@ void simcom7600::StartupNMEA()
|
|||
{ ESP_LOGE(TAG, "Attempt to transmit on non running mux"); }
|
||||
}
|
||||
|
||||
void simcom7600::ShutdownNMEA()
|
||||
{
|
||||
// Switch off GPS:
|
||||
if (m_modem->m_mux != NULL)
|
||||
{
|
||||
// send single commands, as each can fail:
|
||||
m_modem->muxtx(GetMuxChannelCMD(), "AT+CGPSNMEA=0\r\n");
|
||||
m_modem->muxtx(GetMuxChannelCMD(), "AT+CGPSINFOCFG=0\r\n");
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
m_modem->muxtx(GetMuxChannelCMD(), "AT+CGPS=0\r\n");
|
||||
}
|
||||
else
|
||||
{ ESP_LOGE(TAG, "Attempt to transmit on non running mux"); }
|
||||
}
|
||||
|
||||
void simcom7600::StatusPoller()
|
||||
{
|
||||
if (m_modem->m_mux != NULL)
|
||||
|
@ -126,6 +142,7 @@ void simcom7600::PowerCycle()
|
|||
m_powercyclefactor = m_powercyclefactor % 3;
|
||||
ESP_LOGI(TAG, "Power Cycle (SIM7600) %dms",psd);
|
||||
|
||||
uart_wait_tx_done(m_modem->m_uartnum, portMAX_DELAY);
|
||||
uart_flush(m_modem->m_uartnum); // Flush the ring buffer, to try to address MUX start issues
|
||||
#ifdef CONFIG_OVMS_COMP_MAX7317
|
||||
MyPeripherals->m_max7317->Output(MODEM_EGPIO_PWR, 0); // Modem EN/PWR line low
|
||||
|
@ -176,7 +193,11 @@ modem::modem_state1_t simcom7600::State1Ticker1(modem::modem_state1_t curstate)
|
|||
m_modem->tx("AT+CGMR;+ICCID\r\n");
|
||||
break;
|
||||
case 20:
|
||||
m_modem->tx("AT+CMUX=0\r\n");
|
||||
// start MUX mode, route URCs to MUX channel 3 (POLL)
|
||||
// Note: NMEA URCs will now also be sent on channel 3 by the SIMCOM 7600;
|
||||
// without +CATR, NMEA URCs are sent identically on all channels;
|
||||
// there is no option to route these separately to channel 1 (NMEA)
|
||||
m_modem->tx("AT+CMUX=0;+CATR=6\r\n");
|
||||
break;
|
||||
}
|
||||
return modem::None;
|
||||
|
|
|
@ -50,6 +50,7 @@ class simcom7600 : public modemdriver
|
|||
int GetMuxChannelPOLL() { return 3; }
|
||||
int GetMuxChannelCMD() { return 4; }
|
||||
void StartupNMEA();
|
||||
void ShutdownNMEA();
|
||||
void StatusPoller();
|
||||
|
||||
void PowerCycle();
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
# please read the ESP-IDF documents if you need to do this.
|
||||
#
|
||||
|
||||
ifeq ($(shell expr $(IDF_VERSION_MAJOR) \< 4), 1)
|
||||
COMPONENT_ADD_INCLUDEDIRS:=src
|
||||
COMPONENT_SRCDIRS:=src
|
||||
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
|
||||
endif
|
||||
|
|
|
@ -358,6 +358,8 @@ OvmsVehicle::OvmsVehicle()
|
|||
m_brakelight_ignftbrk = false;
|
||||
|
||||
m_tpms_lastcheck = 0;
|
||||
m_inv_energyused = 0;
|
||||
m_inv_energyrecd = 0;
|
||||
|
||||
m_rxqueue = xQueueCreate(CONFIG_OVMS_VEHICLE_CAN_RX_QUEUE_SIZE,sizeof(CAN_frame_t));
|
||||
xTaskCreatePinnedToCore(OvmsVehicleRxTask, "OVMS Vehicle",
|
||||
|
@ -979,7 +981,6 @@ bool OvmsVehicle::TPMSWrite(std::vector<uint32_t> &tpms)
|
|||
*/
|
||||
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandStat(int verbosity, OvmsWriter* writer)
|
||||
{
|
||||
metric_unit_t rangeUnit = (MyConfig.GetParamValue("vehicle", "units.distance") == "M") ? Miles : Kilometers;
|
||||
|
||||
bool chargeport_open = StdMetrics.ms_v_door_chargeport->AsBool();
|
||||
std::string charge_state = StdMetrics.ms_v_charge_state->AsString();
|
||||
|
@ -1030,10 +1031,9 @@ OvmsVehicle::vehicle_command_t OvmsVehicle::CommandStat(int verbosity, OvmsWrite
|
|||
}
|
||||
|
||||
// Charge speed:
|
||||
if (StdMetrics.ms_v_bat_range_speed->AsFloat() != 0)
|
||||
if (StdMetrics.ms_v_bat_range_speed->IsDefined() && StdMetrics.ms_v_bat_range_speed->AsFloat() != 0)
|
||||
{
|
||||
metric_unit_t speedUnit = (rangeUnit == Miles) ? Mph : Kph;
|
||||
writer->printf("%s\n", StdMetrics.ms_v_bat_range_speed->AsUnitString("-", speedUnit, 1).c_str());
|
||||
writer->printf("%s\n", StdMetrics.ms_v_bat_range_speed->AsUnitString("-", ToUser, 1).c_str());
|
||||
}
|
||||
else if (show_vc)
|
||||
{
|
||||
|
@ -1048,13 +1048,13 @@ OvmsVehicle::vehicle_command_t OvmsVehicle::CommandStat(int verbosity, OvmsWrite
|
|||
int duration_soc = StdMetrics.ms_v_charge_duration_soc->AsInt();
|
||||
if (duration_soc > 0)
|
||||
writer->printf("%s: %d:%02dh\n",
|
||||
(char*) StdMetrics.ms_v_charge_limit_soc->AsUnitString("SOC", Native, 0).c_str(),
|
||||
(char*) StdMetrics.ms_v_charge_limit_soc->AsUnitString("SOC", ToUser, 0).c_str(),
|
||||
duration_soc / 60, duration_soc % 60);
|
||||
|
||||
int duration_range = StdMetrics.ms_v_charge_duration_range->AsInt();
|
||||
if (duration_range > 0)
|
||||
writer->printf("%s: %d:%02dh\n",
|
||||
(char*) StdMetrics.ms_v_charge_limit_range->AsUnitString("Range", rangeUnit, 0).c_str(),
|
||||
(char*) StdMetrics.ms_v_charge_limit_range->AsUnitString("Range", ToUser, 0).c_str(),
|
||||
duration_range / 60, duration_range % 60);
|
||||
}
|
||||
|
||||
|
@ -1062,12 +1062,12 @@ OvmsVehicle::vehicle_command_t OvmsVehicle::CommandStat(int verbosity, OvmsWrite
|
|||
if (StdMetrics.ms_v_charge_kwh_grid->IsDefined())
|
||||
{
|
||||
writer->printf("Drawn: %s\n",
|
||||
StdMetrics.ms_v_charge_kwh_grid->AsUnitString("-", Native, 1).c_str());
|
||||
StdMetrics.ms_v_charge_kwh_grid->AsUnitString("-", ToUser, 1).c_str());
|
||||
}
|
||||
if (StdMetrics.ms_v_charge_kwh->IsDefined())
|
||||
{
|
||||
writer->printf("Charged: %s\n",
|
||||
StdMetrics.ms_v_charge_kwh->AsUnitString("-", Native, 1).c_str());
|
||||
StdMetrics.ms_v_charge_kwh->AsUnitString("-", ToUser, 1).c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -1075,35 +1075,35 @@ OvmsVehicle::vehicle_command_t OvmsVehicle::CommandStat(int verbosity, OvmsWrite
|
|||
writer->puts("Not charging");
|
||||
}
|
||||
|
||||
writer->printf("SOC: %s\n", (char*) StdMetrics.ms_v_bat_soc->AsUnitString("-", Native, 1).c_str());
|
||||
writer->printf("SOC: %s\n", (char*) StdMetrics.ms_v_bat_soc->AsUnitString("-", ToUser, 1).c_str());
|
||||
|
||||
if (StdMetrics.ms_v_bat_range_ideal->IsDefined())
|
||||
{
|
||||
const std::string& range_ideal = StdMetrics.ms_v_bat_range_ideal->AsUnitString("-", rangeUnit, 0);
|
||||
const std::string& range_ideal = StdMetrics.ms_v_bat_range_ideal->AsUnitString("-", ToUser, 0);
|
||||
writer->printf("Ideal range: %s\n", range_ideal.c_str());
|
||||
}
|
||||
|
||||
if (StdMetrics.ms_v_bat_range_est->IsDefined())
|
||||
{
|
||||
const std::string& range_est = StdMetrics.ms_v_bat_range_est->AsUnitString("-", rangeUnit, 0);
|
||||
const std::string& range_est = StdMetrics.ms_v_bat_range_est->AsUnitString("-", ToUser, 0);
|
||||
writer->printf("Est. range: %s\n", range_est.c_str());
|
||||
}
|
||||
|
||||
if (StdMetrics.ms_v_pos_odometer->IsDefined())
|
||||
{
|
||||
const std::string& odometer = StdMetrics.ms_v_pos_odometer->AsUnitString("-", rangeUnit, 1);
|
||||
const std::string& odometer = StdMetrics.ms_v_pos_odometer->AsUnitString("-", ToUser, 1);
|
||||
writer->printf("ODO: %s\n", odometer.c_str());
|
||||
}
|
||||
|
||||
if (StdMetrics.ms_v_bat_cac->IsDefined())
|
||||
{
|
||||
const std::string& cac = StdMetrics.ms_v_bat_cac->AsUnitString("-", Native, 1);
|
||||
const std::string& cac = StdMetrics.ms_v_bat_cac->AsUnitString("-", ToUser, 1);
|
||||
writer->printf("CAC: %s\n", cac.c_str());
|
||||
}
|
||||
|
||||
if (StdMetrics.ms_v_bat_soh->IsDefined())
|
||||
{
|
||||
const std::string& soh = StdMetrics.ms_v_bat_soh->AsUnitString("-", Native, 0);
|
||||
const std::string& soh = StdMetrics.ms_v_bat_soh->AsUnitString("-", ToUser, 0);
|
||||
writer->printf("SOH: %s\n", soh.c_str());
|
||||
}
|
||||
|
||||
|
@ -1115,12 +1115,12 @@ OvmsVehicle::vehicle_command_t OvmsVehicle::CommandStat(int verbosity, OvmsWrite
|
|||
*/
|
||||
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandStatTrip(int verbosity, OvmsWriter* writer)
|
||||
{
|
||||
metric_unit_t rangeUnit = (MyConfig.GetParamValue("vehicle", "units.distance") == "M") ? Miles : Kilometers;
|
||||
metric_unit_t speedUnit = (rangeUnit == Miles) ? Mph : Kph;
|
||||
metric_unit_t accelUnit = (rangeUnit == Miles) ? MphPS : KphPS;
|
||||
metric_unit_t consumUnit = (rangeUnit == Miles) ? WattHoursPM : WattHoursPK;
|
||||
metric_unit_t rangeUnit = OvmsMetricGetUserUnit(GrpDistance, Kilometers);
|
||||
metric_unit_t speedUnit = OvmsMetricGetUserUnit(GrpSpeed, Kph);
|
||||
metric_unit_t accelUnit = OvmsMetricGetUserUnit(GrpAccel, KphPS);
|
||||
metric_unit_t consumUnit = OvmsMetricGetUserUnit(GrpConsumption, WattHoursPK);
|
||||
metric_unit_t energyUnit = kWh;
|
||||
metric_unit_t altitudeUnit = (rangeUnit == Miles) ? Feet : Meters;
|
||||
metric_unit_t altitudeUnit = OvmsMetricGetUserUnit(GrpDistanceShort, Meters);
|
||||
const char* rangeUnitLabel = OvmsMetricUnitLabel(rangeUnit);
|
||||
const char* speedUnitLabel = OvmsMetricUnitLabel(speedUnit);
|
||||
const char* accelUnitLabel = OvmsMetricUnitLabel(accelUnit);
|
||||
|
@ -1128,7 +1128,7 @@ OvmsVehicle::vehicle_command_t OvmsVehicle::CommandStatTrip(int verbosity, OvmsW
|
|||
const char* energyUnitLabel = OvmsMetricUnitLabel(energyUnit);
|
||||
const char* altitudeUnitLabel = OvmsMetricUnitLabel(altitudeUnit);
|
||||
|
||||
float trip_length = StdMetrics.ms_v_pos_trip->AsFloat(0, rangeUnit);
|
||||
float trip_length = StdMetrics.ms_v_pos_trip->AsFloat(0);
|
||||
|
||||
float speed_avg = (m_drive_speedcnt > 0)
|
||||
? UnitConvert(Kph, speedUnit, (float)(m_drive_speedsum / m_drive_speedcnt))
|
||||
|
@ -1144,7 +1144,7 @@ OvmsVehicle::vehicle_command_t OvmsVehicle::CommandStatTrip(int verbosity, OvmsW
|
|||
float energy_used = StdMetrics.ms_v_bat_energy_used->AsFloat();
|
||||
float energy_recd = StdMetrics.ms_v_bat_energy_recd->AsFloat();
|
||||
float energy_recd_perc = (energy_used > 0) ? energy_recd / energy_used * 100 : 0;
|
||||
float wh_per_rangeunit = (trip_length > 0) ? (energy_used - energy_recd) * 1000 / trip_length : 0;
|
||||
float wh_per_km = (trip_length > 0) ? (energy_used - energy_recd) * 1000 / trip_length : 0;
|
||||
|
||||
float soc = StdMetrics.ms_v_bat_soc->AsFloat();
|
||||
float soc_diff = soc - m_drive_startsoc;
|
||||
|
@ -1158,7 +1158,7 @@ OvmsVehicle::vehicle_command_t OvmsVehicle::CommandStatTrip(int verbosity, OvmsW
|
|||
<< "Trip "
|
||||
<< std::fixed
|
||||
<< std::setprecision(1)
|
||||
<< trip_length << rangeUnitLabel
|
||||
<< UnitConvert(Kilometers, rangeUnit, trip_length) << rangeUnitLabel
|
||||
<< " Avg "
|
||||
<< std::setprecision(0)
|
||||
<< speed_avg << speedUnitLabel
|
||||
|
@ -1166,11 +1166,11 @@ OvmsVehicle::vehicle_command_t OvmsVehicle::CommandStatTrip(int verbosity, OvmsW
|
|||
<< ((alt_diff >= 0) ? "+" : "")
|
||||
<< alt_diff << altitudeUnitLabel
|
||||
;
|
||||
if (wh_per_rangeunit != 0)
|
||||
if (wh_per_km != 0)
|
||||
{
|
||||
buf
|
||||
<< "\nEnergy "
|
||||
<< wh_per_rangeunit << consumUnitLabel
|
||||
<< UnitConvert(WattHoursPK, consumUnit, wh_per_km) << consumUnitLabel
|
||||
<< ", "
|
||||
<< energy_recd_perc << "% recd"
|
||||
;
|
||||
|
@ -2012,68 +2012,82 @@ OvmsVehicle::vehicle_command_t OvmsVehicle::ProcessMsgCommand(std::string &resul
|
|||
*/
|
||||
void OvmsVehicle::GetDashboardConfig(DashboardConfig& cfg)
|
||||
{
|
||||
cfg.gaugeset1 =
|
||||
"yAxis: [{"
|
||||
// Speed:
|
||||
"min: 0, max: 200,"
|
||||
"plotBands: ["
|
||||
"{ from: 0, to: 120, className: 'green-band' },"
|
||||
"{ from: 120, to: 160, className: 'yellow-band' },"
|
||||
"{ from: 160, to: 200, className: 'red-band' }]"
|
||||
"},{"
|
||||
// Voltage:
|
||||
"min: 310, max: 410,"
|
||||
"plotBands: ["
|
||||
"{ from: 310, to: 325, className: 'red-band' },"
|
||||
"{ from: 325, to: 340, className: 'yellow-band' },"
|
||||
"{ from: 340, to: 410, className: 'green-band' }]"
|
||||
"},{"
|
||||
// SOC:
|
||||
"min: 0, max: 100,"
|
||||
"plotBands: ["
|
||||
"{ from: 0, to: 12.5, className: 'red-band' },"
|
||||
"{ from: 12.5, to: 25, className: 'yellow-band' },"
|
||||
"{ from: 25, to: 100, className: 'green-band' }]"
|
||||
"},{"
|
||||
// Efficiency:
|
||||
"min: 0, max: 400,"
|
||||
"plotBands: ["
|
||||
"{ from: 0, to: 200, className: 'green-band' },"
|
||||
"{ from: 200, to: 300, className: 'yellow-band' },"
|
||||
"{ from: 300, to: 400, className: 'red-band' }]"
|
||||
"},{"
|
||||
// Power:
|
||||
"min: -50, max: 200,"
|
||||
"plotBands: ["
|
||||
"{ from: -50, to: 0, className: 'violet-band' },"
|
||||
"{ from: 0, to: 100, className: 'green-band' },"
|
||||
"{ from: 100, to: 150, className: 'yellow-band' },"
|
||||
"{ from: 150, to: 200, className: 'red-band' }]"
|
||||
"},{"
|
||||
// Charger temperature:
|
||||
"min: 20, max: 80, tickInterval: 20,"
|
||||
"plotBands: ["
|
||||
"{ from: 20, to: 65, className: 'normal-band border' },"
|
||||
"{ from: 65, to: 80, className: 'red-band border' }]"
|
||||
"},{"
|
||||
// Battery temperature:
|
||||
"min: -15, max: 65, tickInterval: 25,"
|
||||
"plotBands: ["
|
||||
"{ from: -15, to: 0, className: 'red-band border' },"
|
||||
"{ from: 0, to: 50, className: 'normal-band border' },"
|
||||
"{ from: 50, to: 65, className: 'red-band border' }]"
|
||||
"},{"
|
||||
// Inverter temperature:
|
||||
"min: 20, max: 80, tickInterval: 20,"
|
||||
"plotBands: ["
|
||||
"{ from: 20, to: 70, className: 'normal-band border' },"
|
||||
"{ from: 70, to: 80, className: 'red-band border' }]"
|
||||
"},{"
|
||||
// Motor temperature:
|
||||
"min: 50, max: 125, tickInterval: 25,"
|
||||
"plotBands: ["
|
||||
"{ from: 50, to: 110, className: 'normal-band border' },"
|
||||
"{ from: 110, to: 125, className: 'red-band border' }]"
|
||||
"}]";
|
||||
// Speed:
|
||||
dash_gauge_t speed_dash(NULL,Kph);
|
||||
speed_dash.SetMinMax(0, 200, 5);
|
||||
speed_dash.AddBand("green", 0, 120);
|
||||
speed_dash.AddBand("yellow", 120, 160);
|
||||
speed_dash.AddBand("red", 160, 200);
|
||||
|
||||
// Voltage:
|
||||
dash_gauge_t voltage_dash(NULL,Volts);
|
||||
voltage_dash.SetMinMax(310, 410);
|
||||
voltage_dash.AddBand("red", 310, 325);
|
||||
voltage_dash.AddBand("yellow", 325, 340);
|
||||
voltage_dash.AddBand("green", 340, 410);
|
||||
|
||||
// SOC:
|
||||
dash_gauge_t soc_dash("SOC ",Percentage);
|
||||
soc_dash.SetMinMax(0, 100);
|
||||
soc_dash.AddBand("red", 0, 12.5);
|
||||
soc_dash.AddBand("yellow", 12.5, 25);
|
||||
soc_dash.AddBand("green", 25, 100);
|
||||
|
||||
// Efficiency:
|
||||
dash_gauge_t eff_dash(NULL,WattHoursPK);
|
||||
eff_dash.SetMinMax(0, 400);
|
||||
eff_dash.AddBand("green", 0, 200);
|
||||
eff_dash.AddBand("yellow", 200, 300);
|
||||
eff_dash.AddBand("red", 300, 400);
|
||||
|
||||
// Power:
|
||||
dash_gauge_t power_dash(NULL,kW);
|
||||
power_dash.SetMinMax(-50, 200);
|
||||
power_dash.AddBand("violet", -50, 0);
|
||||
power_dash.AddBand("green", 0, 100);
|
||||
power_dash.AddBand("yellow", 100, 150);
|
||||
power_dash.AddBand("red", 150, 200);
|
||||
|
||||
// Charger temperature:
|
||||
dash_gauge_t charget_dash("CHG ",Celcius);
|
||||
charget_dash.SetMinMax(20, 80);
|
||||
charget_dash.SetTick(20);
|
||||
charget_dash.AddBand("normal", 20, 65);
|
||||
charget_dash.AddBand("red", 65, 80);
|
||||
|
||||
// Battery temperature:
|
||||
dash_gauge_t batteryt_dash("BAT ",Celcius);
|
||||
batteryt_dash.SetMinMax(-15, 65);
|
||||
batteryt_dash.SetTick(25);
|
||||
batteryt_dash.AddBand("red", -15, 0);
|
||||
batteryt_dash.AddBand("normal", 0, 50);
|
||||
batteryt_dash.AddBand("red", 50, 65);
|
||||
|
||||
// Inverter temperature:
|
||||
dash_gauge_t invertert_dash("PEM ",Celcius);
|
||||
invertert_dash.SetMinMax(20, 80);
|
||||
invertert_dash.SetTick(20);
|
||||
invertert_dash.AddBand("normal", 20, 70);
|
||||
invertert_dash.AddBand("red", 70, 80);
|
||||
|
||||
// Motor temperature:
|
||||
dash_gauge_t motort_dash("MOT ",Celcius);
|
||||
motort_dash.SetMinMax(50, 125);
|
||||
motort_dash.SetTick(25);
|
||||
motort_dash.AddBand("normal", 50, 110);
|
||||
motort_dash.AddBand("red", 110, 125);
|
||||
|
||||
std::ostringstream str;
|
||||
str
|
||||
<< batteryt_dash // Battery temperature
|
||||
<< voltage_dash // Voltage
|
||||
<< motort_dash // Motor temperature
|
||||
<< invertert_dash // Inverter temperature
|
||||
<< power_dash // Power
|
||||
<< soc_dash // SOC
|
||||
<< charget_dash // Charger temperature
|
||||
<< speed_dash // Speed
|
||||
<< eff_dash; // Efficiency
|
||||
cfg.gaugeset1 = str.str();
|
||||
}
|
||||
#endif // #ifdef CONFIG_OVMS_COMP_WEBSERVER
|
||||
|
|
|
@ -605,6 +605,14 @@ void OvmsVehicle::BmsStatus(int verbosity, OvmsWriter* writer, vehicle_bms_statu
|
|||
writer->printf("No BMS %s data available\n", datatype);
|
||||
return;
|
||||
}
|
||||
metric_unit_t user_temp = UnitNotFound;
|
||||
std::string temp_unit;
|
||||
if (show_temperature)
|
||||
{
|
||||
user_temp = OvmsMetricGetUserUnit(GrpTemp, Celcius);
|
||||
// (To Check: '°' is not SMS safe, so we only output 'C')
|
||||
temp_unit = OvmsMetricUnitLabel(user_temp);
|
||||
}
|
||||
|
||||
int vwarn=0, valert=0;
|
||||
int twarn=0, talert=0;
|
||||
|
@ -642,13 +650,13 @@ void OvmsVehicle::BmsStatus(int verbosity, OvmsWriter* writer, vehicle_bms_statu
|
|||
if (show_temperature)
|
||||
{
|
||||
writer->puts("Temperature:");
|
||||
writer->printf(" Average: %5.1fC [%5.1fC - %5.1fC]\n",
|
||||
StdMetrics.ms_v_bat_pack_tavg->AsFloat(),
|
||||
StdMetrics.ms_v_bat_pack_tmin->AsFloat(),
|
||||
StdMetrics.ms_v_bat_pack_tmax->AsFloat());
|
||||
writer->printf(" Deviation: SD %6.2fC [max %.2fC], %d warnings, %d alerts\n",
|
||||
StdMetrics.ms_v_bat_pack_tstddev->AsFloat(),
|
||||
StdMetrics.ms_v_bat_pack_tstddev_max->AsFloat(),
|
||||
writer->printf(" Average: %5.1f%s [%5.1f%s - %5.1f%s]\n",
|
||||
StdMetrics.ms_v_bat_pack_tavg->AsFloat(0, user_temp), temp_unit.c_str(),
|
||||
StdMetrics.ms_v_bat_pack_tmin->AsFloat(0, user_temp), temp_unit.c_str(),
|
||||
StdMetrics.ms_v_bat_pack_tmax->AsFloat(0, user_temp), temp_unit.c_str());
|
||||
writer->printf(" Deviation: SD %6.2f%s [max %.2f%s], %d warnings, %d alerts\n",
|
||||
StdMetrics.ms_v_bat_pack_tstddev->AsFloat(0, user_temp), temp_unit.c_str(),
|
||||
StdMetrics.ms_v_bat_pack_tstddev_max->AsFloat(0, user_temp), temp_unit.c_str(),
|
||||
twarn, talert);
|
||||
}
|
||||
|
||||
|
@ -728,7 +736,7 @@ void OvmsVehicle::BmsStatus(int verbosity, OvmsWriter* writer, vehicle_bms_statu
|
|||
{
|
||||
if (kt < m_bms_readings_t && (reading_left_t > 0))
|
||||
{
|
||||
writer->printf(" %5.1fC",m_bms_temperatures[kt]);
|
||||
writer->printf(" %5.1f%s",UnitConvert(Celcius, user_temp, m_bms_temperatures[kt]), temp_unit.c_str());
|
||||
--reading_left_t;
|
||||
++kt;
|
||||
}
|
||||
|
@ -788,8 +796,9 @@ bool OvmsVehicle::FormatBmsAlerts(int verbosity, OvmsWriter* writer, bool show_w
|
|||
writer->printf("%s\n", has_valerts ? "" : ", cells OK");
|
||||
|
||||
// Temperatures:
|
||||
// (Note: '°' is not SMS safe, so we only output 'C')
|
||||
writer->printf("Temperature: StdDev %.1fC", StdMetrics.ms_v_bat_pack_tstddev_max->AsFloat());
|
||||
metric_unit_t user_temp = OvmsMetricGetUserUnit(GrpTemp, Celcius);
|
||||
std::string temp_unit = OvmsMetricUnitLabel(user_temp);
|
||||
writer->printf("Temperature: StdDev %.1f%s", StdMetrics.ms_v_bat_pack_tstddev_max->AsFloat(0, user_temp), temp_unit.c_str());
|
||||
for (int i=0; i<m_bms_readings_v; i++)
|
||||
{
|
||||
OvmsStatus sts = OvmsStatus(StdMetrics.ms_v_bat_cell_talert->GetElemValue(i));
|
||||
|
@ -802,8 +811,8 @@ bool OvmsVehicle::FormatBmsAlerts(int verbosity, OvmsWriter* writer, bool show_w
|
|||
has_talerts++;
|
||||
if (verbose || has_talerts <= 5)
|
||||
{
|
||||
float dev = StdMetrics.ms_v_bat_cell_tdevmax->GetElemValue(i);
|
||||
writer->printf("\n %c #%02d: %+3.1fC", (sts==OvmsStatus::Warn) ? '?' : '!', i+1, dev);
|
||||
float dev = StdMetrics.ms_v_bat_cell_tdevmax->GetElemValue(i, user_temp);
|
||||
writer->printf("\n %c #%02d: %+3.1f%s", (sts==OvmsStatus::Warn) ? '?' : '!', i+1, dev, temp_unit.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue