OVMS3/OVMS.V3/components/ovms_server_v2/src/ovms_server_v2.cpp

2293 lines
73 KiB
C++

/*
; Project: Open Vehicle Monitor System
; Date: 14th March 2017
;
; Changes:
; 1.0 Initial release
;
; (C) 2011 Michael Stegen / Stegen Electronics
; (C) 2011-2017 Mark Webb-Johnson
; (C) 2011 Sonny Chen @ EPRO/DX
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in
; all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
; THE SOFTWARE.
*/
#include "ovms_log.h"
static const char *TAG = "ovms-server-v2";
#include "ovms.h"
#include "buffered_shell.h"
#include "ovms_peripherals.h"
#include "ovms_command.h"
#include "ovms_config.h"
#include "metrics_standard.h"
#include "crypt_base64.h"
#include "crypt_hmac.h"
#include "crypt_crc.h"
#include "ovms_server_v2.h"
#include "ovms_netmanager.h"
#include "vehicle.h"
#include "esp_system.h"
#include "ovms_utils.h"
#include "ovms_boot.h"
#include "ovms_tls.h"
// should this go in the .h or in the .cpp?
typedef union {
struct {
unsigned FrontLeftDoor:1; // 0x01
unsigned FrontRightDoor:1; // 0x02
unsigned ChargePort:1; // 0x04
unsigned PilotSignal:1; // 0x08
unsigned Charging:1; // 0x10
unsigned :1; // 0x20
unsigned HandBrake:1; // 0x40
unsigned CarON:1; // 0x80
} bits;
uint8_t flags;
} car_doors1_t;
typedef union {
struct {
unsigned :1; // 0x01
unsigned :1; // 0x02
unsigned :1; // 0x04
unsigned CarLocked:1; // 0x08
unsigned ValetMode:1; // 0x10
unsigned Headlights:1; // 0x20
unsigned Bonnet:1; // 0x40
unsigned Trunk:1; // 0x80
} bits;
uint8_t flags;
} car_doors2_t;
typedef union {
struct {
unsigned CarAwake:1; // 0x01
unsigned CoolingPump:1; // 0x02
unsigned :1; // 0x04
unsigned :1; // 0x08
unsigned :1; // 0x10
unsigned :1; // 0x20
unsigned CtrlLoggedIn:1; // 0x40 - logged into controller
unsigned CtrlCfgMode:1; // 0x80 - controller in configuration mode
} bits;
uint8_t flags;
} car_doors3_t;
typedef union {
struct {
unsigned :1; // 0x01
unsigned AlarmSounds:1; // 0x02
unsigned :1; // 0x04
unsigned :1; // 0x08
unsigned :1; // 0x10
unsigned :1; // 0x20
unsigned :1; // 0x40
unsigned :1; // 0x80
} bits;
uint8_t flags;
} car_doors4_t;
typedef union {
struct {
unsigned RearLeftDoor:1; // 0x01
unsigned RearRightDoor:1; // 0x02
unsigned Frunk:1; // 0x04
unsigned :1; // 0x08
unsigned Charging12V:1; // 0x10
unsigned Aux12V:1; // 0x20
unsigned :1; // 0x40
unsigned HVAC:1; // 0x80
} bits;
uint8_t flags;
} car_doors5_t;
#define PMAX_MAX 24
static struct
{
const char* param;
const char* instance;
} pmap[]
=
{
{ "vehicle", "registered.phone" }, // 0 PARAM_REGPHONE
{ "password", "module" }, // 1 PARAM_MODULEPASS
{ "vehicle", "units.distance" }, // 2 PARAM_MILESKM
{ "", "" }, // 3 PARAM_NOTIFIES
{ "server.v2", "server" }, // 4 PARAM_SERVERIP
{ "modem", "apn" }, // 5 PARAM_GPRSAPN
{ "modem", "apn.user" }, // 6 PARAM_GPRSUSER
{ "modem", "apn.password" }, // 7 PARAM_GPRSPASS
{ "vehicle", "id" }, // 8 PARAM_VEHICLEID
{ "password", "server.v2" }, // 9 PARAM_SERVERPASS
{ "", "" }, // 10 PARAM_PARANOID
{ "", "" }, // 11 PARAM_S_GROUP1
{ "", "" }, // 12 PARAM_S_GROUP2
{ "", "" }, // 13 PARAM_GSMLOCK
{ "auto", "vehicle.type" }, // 14 PARAM_VEHICLETYPE
{ "", "" }, // 15 PARAM_COOLDOWN
{ "", "" }, // 16 PARAM_ACC_1
{ "", "" }, // 17 PARAM_ACC_2
{ "", "" }, // 18 PARAM_ACC_3
{ "", "" }, // 19 PARAM_ACC_4
{ "", "" }, // 20
{ "", "" }, // 21
{ "network", "dns" }, // 22 PARAM_GPRSDNS
{ "vehicle", "timezone" } // 23 PARAM_TIMEZONE
};
OvmsServerV2 *MyOvmsServerV2 = NULL;
size_t MyOvmsServerV2Modifier = 0;
size_t MyOvmsServerV2Reader = 0;
bool OvmsServerV2ReaderCallback(OvmsNotifyType* type, OvmsNotifyEntry* entry)
{
if (MyOvmsServerV2)
return MyOvmsServerV2->IncomingNotification(type, entry);
else
return true; // No server v2 running, so just discard
}
bool OvmsServerV2ReaderFilterCallback(OvmsNotifyType* type, const char* subtype)
{
if (MyOvmsServerV2)
return MyOvmsServerV2->NotificationFilter(type, subtype);
else
return false;
}
static void OvmsServerV2MongooseCallback(struct mg_connection *nc, int ev, void *p)
{
switch (ev)
{
case MG_EV_CONNECT:
{
int *success = (int*)p;
ESP_LOGV(TAG, "OvmsServerV2MongooseCallback(MG_EV_CONNECT=%d)",*success);
if (*success == 0)
{
// Successful connection
ESP_LOGI(TAG, "Connection successful");
if (MyOvmsServerV2) MyOvmsServerV2->SendLogin(nc);
}
else
{
// Connection failed
ESP_LOGW(TAG, "Connection failed");
if (MyOvmsServerV2)
{
MyOvmsServerV2->SetStatus("Error: Connection failed", true, OvmsServerV2::WaitReconnect);
MyOvmsServerV2->m_connretry = 60;
}
}
}
break;
case MG_EV_CLOSE:
ESP_LOGV(TAG, "OvmsServerV2MongooseCallback(MG_EV_CLOSE)");
if (MyOvmsServerV2)
{
if (MyOvmsServerV2->m_state == OvmsServerV2::Authenticating)
{
MyOvmsServerV2->SetStatus("Authentication error (wrong ID/password)", true, OvmsServerV2::WaitReconnect);
MyOvmsServerV2->Reconnect(120);
}
else
{
MyOvmsServerV2->SetStatus("Disconnected", false, OvmsServerV2::WaitReconnect);
MyOvmsServerV2->Reconnect(60);
}
}
break;
case MG_EV_RECV:
ESP_LOGV(TAG, "OvmsServerV2MongooseCallback(MG_EV_RECV)");
if (MyOvmsServerV2)
mbuf_remove(&nc->recv_mbuf, MyOvmsServerV2->IncomingData((uint8_t*)nc->recv_mbuf.buf, nc->recv_mbuf.len));
break;
default:
break;
}
}
void OvmsServerV2::ProcessServerMsg()
{
m_lastrx_time = esp_log_timestamp();
std::string line = m_buffer->ReadLine();
if (line.compare(0,7,"MP-S 0 ") == 0)
{
ESP_LOGI(TAG, "Got server response: %s",line.c_str());
size_t sep = line.find(' ',7);
if (sep == std::string::npos)
{
SetStatus("Error: Server response invalid (no token/digest separator)", true, WaitReconnect);
Reconnect(60);
return;
}
std::string token = std::string(line,7,sep-7);
std::string digest = std::string(line,sep+1,line.length()-sep);
ESP_LOGI(TAG, "Server token is %s and digest is %s",token.c_str(),digest.c_str());
if (m_token == token)
{
SetStatus("Error: Detected token replay attack/collision", true, WaitReconnect);
Reconnect(60);
m_connretry = 60; // Try again in 60 seconds...
return;
}
uint8_t sdigest[OVMS_MD5_SIZE];
hmac_md5((uint8_t*) token.c_str(), token.length(), (uint8_t*)m_password.c_str(), m_password.length(), sdigest);
uint8_t sdb[OVMS_MD5_SIZE*2];
base64encode(sdigest, OVMS_MD5_SIZE, sdb);
if (digest.compare((char*)sdb) != 0)
{
SetStatus("Error: Server digest does not authenticate", true, WaitReconnect);
Reconnect(60);
return;
}
SetStatus("Server authentication ok. Now priming crypto.");
std::string key(token);
key.append(m_token);
ESP_LOGI(TAG, "Shared secret key is %s (%d bytes)",key.c_str(),key.length());
hmac_md5((uint8_t*)key.c_str(), key.length(), (uint8_t*)m_password.c_str(), m_password.length(), sdigest);
RC4_setup(&m_crypto_rx1, &m_crypto_rx2, sdigest, OVMS_MD5_SIZE);
for (int k=0;k<1024;k++)
{
uint8_t v = 0;
RC4_crypt(&m_crypto_rx1, &m_crypto_rx2, &v, 1);
}
RC4_setup(&m_crypto_tx1, &m_crypto_tx2, sdigest, OVMS_MD5_SIZE);
for (int k=0;k<1024;k++)
{
uint8_t v = 0;
RC4_crypt(&m_crypto_tx1, &m_crypto_tx2, &v, 1);
}
if (m_paranoid)
{
// Paranoid mode welcome
char token[OVMS_PROTOCOL_V2_TOKENSIZE+1];
// Generate a random paranoid mode token
for (int k=0;k<OVMS_PROTOCOL_V2_TOKENSIZE;k++)
{
token[k] = (char)cb64[esp_random()%64];
}
token[OVMS_PROTOCOL_V2_TOKENSIZE] = 0;
m_ptoken = std::string(token);
// Transmit the token (unencrypted) to the server
m_ptoken_ready = false;
std::string msg("MP-0 ET");
msg.append(m_ptoken);
Transmit(msg);
m_ptoken_ready = true;
// Generate, and store, the digest for future use
std::string modpass = MyConfig.GetParamValue("password","module");
hmac_md5((uint8_t*) token, OVMS_PROTOCOL_V2_TOKENSIZE, (uint8_t*)modpass.c_str(), modpass.length(), m_pdigest);
}
m_pending_notify_info = true;
m_pending_notify_error = true;
m_pending_notify_alert = true;
m_pending_notify_data = true;
m_pending_notify_data_last = 0;
m_pending_notify_data_retransmit = 0;
m_connretry = 0;
StandardMetrics.ms_s_v2_connected->SetValue(true);
if (m_paranoid)
{
SetStatus("OVMS V2 login successful, and crypto channel established (in paranoid mode)", false, Connected);
}
else
{
SetStatus("OVMS V2 login successful, and crypto channel established", false, Connected);
}
return;
}
uint8_t* b = new uint8_t[line.length()+1];
int len = base64decode(line.c_str(),b);
RC4_crypt(&m_crypto_rx1, &m_crypto_rx2, b, len);
b[len]=0;
line = std::string((char*)b);
delete [] b;
ESP_LOGI(TAG, "Incoming Msg: %s",line.c_str());
if (line.compare(0, 5, "MP-0 ") != 0)
{
SetStatus("Error: Invalid server message. Disconnecting.", true, WaitReconnect);
Reconnect(60);
return;
}
if ((line.at(5) == 'E')&&(line.at(6) == 'M'))
{
char code[2]; code[0] = line.at(7); code[1] = 0;
uint8_t *d = new uint8_t[line.length()-6];
len = base64decode(line.c_str()+7,d+1);
RC4_CTX1* pm_crypto1 = new RC4_CTX1;
RC4_CTX2* pm_crypto2 = new RC4_CTX2;
RC4_setup(pm_crypto1, pm_crypto2, m_pdigest, OVMS_MD5_SIZE);
for (int k=0;k<1024;k++)
{
uint8_t zero = 0;
RC4_crypt(pm_crypto1, pm_crypto2, &zero, 1);
}
RC4_crypt(pm_crypto1, pm_crypto2, d, len);
line.erase(5);
line = std::string("MP-0 ");
line.append(code);
line.append((char*)d);
len = line.length();
delete pm_crypto1;
delete pm_crypto2;
delete d;
ESP_LOGI(TAG, "Decoded Paranoid Msg: %s",line.c_str());
}
char code = line[5];
const char* payload = line.c_str()+6;
switch(code)
{
case 'A': // PING
{
Transmit(std::string("MP-0 a"));
break;
}
case 'Z': // Peer connections
{
int nc = atoi(payload);
StandardMetrics.ms_s_v2_peers->SetValue(nc);
if (nc > m_peers)
{
ESP_LOGI(TAG, "One or more peers have connected");
m_lasttx = 0; // A peer has connected, so force a transmission of status messages
}
if ((nc == 0)&&(m_peers != 0))
MyEvents.SignalEvent("app.disconnected",NULL);
else if (nc > 0)
MyEvents.SignalEvent("app.connected",NULL);
m_peers = nc;
break;
}
case 'h': // Historical data acknowledgement
{
HandleNotifyDataAck(atoi(payload));
break;
}
case 'C': // Command
{
ProcessCommand(payload);
break;
}
default:
break;
}
}
void OvmsServerV2::ProcessCommand(const char* payload)
{
int command = atoi(payload);
char *sep = index(payload,',');
int k = 0;
OvmsVehicle* vehicle = MyVehicleFactory.ActiveVehicle();
extram::ostringstream* buffer = new extram::ostringstream();
if (vehicle)
{
// Ask vehicle to process command first:
std::string rt;
OvmsVehicle::vehicle_command_t vc = vehicle->ProcessMsgCommand(rt, command, sep ? sep+1 : NULL);
if (vc != OvmsVehicle::NotImplemented)
{
*buffer << "MP-0 c" << command << "," << ((vc == OvmsVehicle::Success) ? 0 : 1) << "," << rt;
k = 1;
}
}
if (!k) switch (command)
{
case 1: // Request feature list
// Notes:
// - V2 only supported integer values, V3 values may be text
// - V2 only supported 16 features, V3 supports 32
{
for (k=0;k<32;k++)
{
*buffer << "MP-0 c1,0," << k << ",32," << (vehicle ? vehicle->GetFeature(k) : "0");
Transmit(buffer->str().c_str());
buffer->str("");
buffer->clear();
}
delete buffer;
return;
break;
}
case 2: // Set feature
{
int rc = 1;
const char* rt = "";
if (!vehicle)
rt = "No active vehicle";
else if (!sep)
rt = "Missing feature key";
else
{
k = atoi(sep+1);
sep = index(sep+1,',');
if (vehicle->SetFeature(k, sep ? sep+1 : ""))
rc = 0;
else
rt = "Feature not supported by vehicle";
}
*buffer << "MP-0 c2," << rc << "," << rt;
break;
}
case 3: // Request parameter list
{
for (k=0;k<32;k++)
{
*buffer << "MP-0 c3,0," << k << ",32,";
if ((k<PMAX_MAX)&&(pmap[k].param[0] != 0))
{
*buffer << MyConfig.GetParamValue(pmap[k].param, pmap[k].instance);
}
Transmit(buffer->str().c_str());
buffer->str("");
buffer->clear();
}
delete buffer;
return;
break;
}
case 4: // Set parameter
{
int rc = 1;
const char* rt = "";
if (!sep)
rt = "Missing parameter key";
else
{
k = atoi(sep+1);
if ((k<PMAX_MAX)&&(pmap[k].param[0] != 0))
{
sep = index(sep+1,',');
MyConfig.SetParamValue(pmap[k].param, pmap[k].instance, sep ? sep+1 : "");
rc = 0;
}
else
rt = "Parameter key not supported";
}
*buffer << "MP-0 c4," << rc << "," << rt;
break;
}
case 5: // Reboot
{
MyBoot.Restart();
break;
}
case 6: // Charge alert
MyNotify.NotifyCommand("info","charge.stopped","stat");
*buffer << "MP-0 c6,0";
break;
case 7: // Execute command
{
BufferedShell* bs = new BufferedShell(false, COMMAND_RESULT_NORMAL);
bs->SetSecure(true); // this is an authorized channel
bs->ProcessChars(sep+1, strlen(sep)-1);
bs->ProcessChar('\n');
extram::string val; bs->Dump(val);
delete bs;
*buffer << "MP-0 c7,0,";
*buffer << mp_encode(val);
break;
}
case 10: // Set Charge Mode
{
k = 1;
if (vehicle && sep)
{
if (vehicle->CommandSetChargeMode((OvmsVehicle::vehicle_mode_t)atoi(sep+1)) == OvmsVehicle::Success) k = 0;
}
*buffer << "MP-0 c10," << k;
break;
}
case 11: // Start Charge
{
k = 1;
if (vehicle)
{
if (vehicle->CommandStartCharge() == OvmsVehicle::Success) k = 0;
}
*buffer << "MP-0 c11," << k;
break;
}
case 12: // Stop Charge
{
k = 1;
if (vehicle)
{
if (vehicle->CommandStopCharge() == OvmsVehicle::Success) k = 0;
}
*buffer << "MP-0 c12," << k;
break;
}
case 15: // Set Charge Current
{
k = 1;
if (vehicle && sep)
{
if (vehicle->CommandSetChargeCurrent(atoi(sep+1)) == OvmsVehicle::Success) k = 0;
}
*buffer << "MP-0 c15," << k;
break;
}
case 16: // Set Charge Mode and Current
{
k = 1;
if (vehicle && sep)
{
OvmsVehicle::vehicle_mode_t mode = (OvmsVehicle::vehicle_mode_t)atoi(sep+1);
sep = index(sep+1,',');
if (sep)
{
if (vehicle->CommandSetChargeMode(mode) == OvmsVehicle::Success) k = 0;
if (k == 0)
{
vTaskDelay(50 / portTICK_PERIOD_MS);
if (vehicle->CommandSetChargeCurrent(atoi(sep+1)) != OvmsVehicle::Success)
k = 1;
}
}
}
*buffer << "MP-0 c16," << k;
break;
}
case 17: // Set Charge Timer Mode and Start Time
{
k = 1;
if (vehicle && sep)
{
bool timermode = atoi(sep+1);
sep = index(sep+1,',');
if (sep)
{
if (vehicle->CommandSetChargeTimer(timermode,atoi(sep+1)) == OvmsVehicle::Success) k = 0;
}
}
*buffer << "MP-0 c17," << k;
break;
}
case 18: // Wakeup Car
case 19: // Wakeup Temperature Subsystem
{
k = 1;
if (vehicle)
{
if (vehicle->CommandWakeup() == OvmsVehicle::Success) k = 0;
}
*buffer << "MP-0 c" << command << "," << k;
break;
}
case 20: // Lock Car
{
k = 1;
if (vehicle && sep)
{
if (vehicle->CommandLock(sep+1) == OvmsVehicle::Success) k = 0;
}
*buffer << "MP-0 c20," << k;
break;
}
case 21: // Activate Valet Mode
{
k = 1;
if (vehicle && sep)
{
if (vehicle->CommandActivateValet(sep+1) == OvmsVehicle::Success) k = 0;
}
*buffer << "MP-0 c21," << k;
break;
}
case 22: // Unlock Car
{
k = 1;
if (vehicle && sep)
{
if (vehicle->CommandUnlock(sep+1) == OvmsVehicle::Success) k = 0;
}
*buffer << "MP-0 c22," << k;
break;
}
case 23: // Deactivate Valet Mode
{
k = 1;
if (vehicle && sep)
{
if (vehicle->CommandDeactivateValet(sep+1) == OvmsVehicle::Success) k = 0;
}
*buffer << "MP-0 c23," << k;
break;
}
case 24: // Homelink
{
k = 1;
if (vehicle && sep)
{
if (vehicle->CommandHomelink(atoi(sep+1)) == OvmsVehicle::Success) k = 0;
}
else if (vehicle)
{
if (vehicle->CommandHomelink(-1) == OvmsVehicle::Success) k = 0;
}
*buffer << "MP-0 c24," << k;
break;
}
case 25: // Cooldown
{
k = 1;
if (vehicle)
{
if (vehicle->CommandCooldown(true) == OvmsVehicle::Success) k = 0;
}
*buffer << "MP-0 c25," << k;
break;
}
case 26: // Remote Climate Control
{
k = 1;
if (vehicle && sep)
{
if (vehicle->CommandClimateControl(atoi(sep+1)) == OvmsVehicle::Success) k = 0;
}
*buffer << "MP-0 c26," << k;
break;
}
case 40: // Send SMS
*buffer << "MP-0 c" << command << ",2";
break;
case 41: // Send MMI/USSD codes
if (!sep)
*buffer << "MP-0 c" << command << ",1,No command";
else
{
#ifdef CONFIG_OVMS_COMP_CELLULAR
*buffer << "AT+CUSD=1,\"" << sep+1 << "\",15\r\n";
extram::string msg = buffer->str();
buffer->str("");
if (MyPeripherals->m_cellular_modem->txcmd(msg.c_str(), msg.length()))
*buffer << "MP-0 c" << command << ",0";
else
*buffer << "MP-0 c" << command << ",1,Cannot send command";
#else // #ifdef CONFIG_OVMS_COMP_CELLULAR
*buffer << "MP-0 c" << command << ",1,No modem";
#endif // #ifdef CONFIG_OVMS_COMP_CELLULAR
}
break;
case 49: // Send raw AT command
*buffer << "MP-0 c" << command << ",2";
break;
default:
*buffer << "MP-0 c" << command << ",2";
break;
}
Transmit(buffer->str().c_str());
delete buffer;
}
bool OvmsServerV2::Transmit(const std::string& message)
{
OvmsMutexLock mg(&m_mgconn_mutex);
if (!m_mgconn)
return false;
int len = message.length();
char* s = new char[(len*2)+4];
memcpy(s,message.c_str(),len);
ESP_LOGI(TAG, "Send %s",message.c_str());
if ((m_ptoken_ready)&&
(s[5] != 'E')&&
(s[5] != 'A')&&
(s[5] != 'a')&&
(s[5] != 'g')&&
(s[5] != 'P'))
{
// We must convert the message to a paranoid one...
// The message is of the form MP-0 X...
// Where X is the code and ... is the (optional) data
char code[2]; code[0] = s[5]; code[1] = 0;
uint8_t* d = new uint8_t[len-6];
memcpy(d,s+6,len-6);
// Paranoid encrypt the message part of the transaction
RC4_CTX1* pm_crypto1 = new RC4_CTX1;
RC4_CTX2* pm_crypto2 = new RC4_CTX2;
RC4_setup(pm_crypto1, pm_crypto2, m_pdigest, OVMS_MD5_SIZE);
for (int k=0;k<1024;k++)
{
uint8_t zero = 0;
RC4_crypt(pm_crypto1, pm_crypto2, &zero, 1);
}
RC4_crypt(pm_crypto1, pm_crypto2, d, len-6);
strcpy(s,"MP-0 EM");
strcat(s,code);
base64encode(d, len-6, (uint8_t*)s+8);
// The messdage is now in paranoid mode...
delete d;
delete pm_crypto1;
delete pm_crypto2;
}
RC4_crypt(&m_crypto_tx1, &m_crypto_tx2, (uint8_t*)s, len);
char* buf = new char[(len*2)+4];
base64encode((uint8_t*)s, len, (uint8_t*)buf);
strcat(buf,"\r\n");
mg_send(m_mgconn, buf, strlen(buf));
delete [] buf;
delete [] s;
return true;
}
void OvmsServerV2::SetStatus(const char* status, bool fault, State newstate)
{
if (fault)
ESP_LOGE(TAG, "Status: %s", status);
else
ESP_LOGI(TAG, "Status: %s", status);
m_status = status;
if (newstate != Undefined)
{
m_state = newstate;
switch (m_state)
{
case OvmsServerV2::WaitNetwork:
MyEvents.SignalEvent("server.v2.waitnetwork", (void*)m_status.c_str(), m_status.length()+1);
break;
case OvmsServerV2::ConnectWait:
MyEvents.SignalEvent("server.v2.connectwait", (void*)m_status.c_str(), m_status.length()+1);
break;
case OvmsServerV2::Connecting:
MyEvents.SignalEvent("server.v2.connecting", (void*)m_status.c_str(), m_status.length()+1);
break;
case OvmsServerV2::Authenticating:
MyEvents.SignalEvent("server.v2.authenticating", (void*)m_status.c_str(), m_status.length()+1);
break;
case OvmsServerV2::Connected:
MyEvents.SignalEvent("server.v2.connected", (void*)m_status.c_str(), m_status.length()+1);
break;
case OvmsServerV2::Disconnected:
MyEvents.SignalEvent("server.v2.disconnected", (void*)m_status.c_str(), m_status.length()+1);
break;
case OvmsServerV2::WaitReconnect:
MyEvents.SignalEvent("server.v2.waitreconnect", (void*)m_status.c_str(), m_status.length()+1);
break;
default:
break;
}
}
}
void OvmsServerV2::Connect()
{
m_vehicleid = MyConfig.GetParamValue("vehicle", "id");
m_server = MyConfig.GetParamValue("server.v2", "server");
m_password = MyConfig.GetParamValue("password","server.v2");
m_port = MyConfig.GetParamValue("server.v2", "port");
m_tls = MyConfig.GetParamValueBool("server.v2", "tls",false);
m_paranoid = MyConfig.GetParamValueBool("server.v2", "paranoid",false);
if (m_port.empty()) m_port = (m_tls)?"6870":"6867";
std::string address(m_server);
address.append(":");
address.append(m_port);
ESP_LOGI(TAG, "Connection is %s:%s %s",
m_server.c_str(), m_port.c_str(),
m_vehicleid.c_str());
if (m_vehicleid.empty())
{
SetStatus("Error: Parameter vehicle/id must be defined", true, WaitReconnect);
m_connretry = 20; // Try again in 20 seconds...
return;
}
if (m_server.empty())
{
SetStatus("Error: Parameter server.v2/server must be defined", true, WaitReconnect);
m_connretry = 20; // Try again in 20 seconds...
return;
}
if (m_password.empty())
{
SetStatus("Error: Parameter server.v2/password must be defined", true, WaitReconnect);
m_connretry = 20; // Try again in 20 seconds...
return;
}
SetStatus("Connecting...", false, Connecting);
OvmsMutexLock mg(&m_mgconn_mutex);
struct mg_mgr* mgr = MyNetManager.GetMongooseMgr();
struct mg_connect_opts opts;
const char* err;
memset(&opts, 0, sizeof(opts));
opts.error_string = &err;
if (m_tls)
{
opts.ssl_ca_cert = MyOvmsTLS.GetTrustedList();
opts.ssl_server_name = m_server.c_str();
}
if ((m_mgconn = mg_connect_opt(mgr, address.c_str(), OvmsServerV2MongooseCallback, opts)) == NULL)
{
ESP_LOGE(TAG, "mg_connect(%s) failed: %s", address.c_str(), err);
SetStatus("Error: Connection failed", true, WaitReconnect);
m_connretry = 60; // Try again in 60 seconds...
return;
}
return;
}
void OvmsServerV2::Disconnect()
{
OvmsMutexLock mg(&m_mgconn_mutex);
if (m_mgconn)
{
m_mgconn->flags |= MG_F_CLOSE_IMMEDIATELY;
m_mgconn = NULL;
}
m_buffer->EmptyAll();
m_connretry = 0;
StandardMetrics.ms_s_v2_connected->SetValue(false);
StandardMetrics.ms_s_v2_peers->SetValue(0);
}
void OvmsServerV2::Reconnect(int connretry)
{
OvmsMutexLock mg(&m_mgconn_mutex);
if (m_mgconn)
{
m_mgconn->flags |= MG_F_CLOSE_IMMEDIATELY;
m_mgconn = NULL;
}
m_buffer->EmptyAll();
m_connretry = connretry;
StandardMetrics.ms_s_v2_connected->SetValue(false);
StandardMetrics.ms_s_v2_peers->SetValue(0);
}
size_t OvmsServerV2::IncomingData(uint8_t* data, size_t len)
{
// MyCommandApp.HexDump(TAG, "IncomingData", (const char*)data, len);
if (m_buffer->Push(data, len))
{
while ((m_buffer->HasLine()>=0)&&(m_mgconn))
{
// m_buffer->Diagnostics();
ProcessServerMsg();
}
return len;
}
return 0;
}
void OvmsServerV2::SendLogin(struct mg_connection *nc)
{
OvmsMutexLock mg(&m_mgconn_mutex);
SetStatus("Logging in...", false, Authenticating);
char token[OVMS_PROTOCOL_V2_TOKENSIZE+1];
for (int k=0;k<OVMS_PROTOCOL_V2_TOKENSIZE;k++)
{
token[k] = (char)cb64[esp_random()%64];
}
token[OVMS_PROTOCOL_V2_TOKENSIZE] = 0;
m_token = std::string(token);
m_ptoken.clear();
m_ptoken_ready = false;
uint8_t digest[OVMS_MD5_SIZE];
hmac_md5((uint8_t*) token, OVMS_PROTOCOL_V2_TOKENSIZE, (uint8_t*)m_password.c_str(), m_password.length(), digest);
char hello[256] = "";
strcat(hello,"MP-C 0 ");
strcat(hello,token);
strcat(hello," ");
base64encode(digest, OVMS_MD5_SIZE, (uint8_t*)(hello+strlen(hello)));
strcat(hello," ");
strcat(hello,m_vehicleid.c_str());
ESP_LOGI(TAG, "Sending server login: %s",hello);
strcat(hello,"\r\n");
mg_send(nc, hello, strlen(hello));
m_connretry = 60; // Give the server 60 seconds to respond
}
void OvmsServerV2::TransmitMsgStat(bool always)
{
m_now_stat = false;
bool modified =
StandardMetrics.ms_v_bat_soc->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_voltage->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_current->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_state->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_substate->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_mode->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_bat_range_ideal->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_bat_range_est->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_climit->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_kwh->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_timermode->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_timerstart->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_bat_cac->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_duration_full->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_duration_range->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_duration_soc->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_inprogress->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_limit_range->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_limit_soc->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_env_cooling->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_bat_range_full->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_bat_power->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_bat_voltage->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_bat_soh->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_power->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_efficiency->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_bat_current->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_bat_range_speed->IsModifiedAndClear(MyOvmsServerV2Modifier);
// Quick exit if nothing modified
if ((!always)&&(!modified)) return;
int mins_range = StandardMetrics.ms_v_charge_duration_range->AsInt();
int mins_soc = StandardMetrics.ms_v_charge_duration_soc->AsInt();
bool charging = StandardMetrics.ms_v_charge_inprogress->AsBool();
metric_unit_t units_speed = (m_units_distance == Miles) ? Mph : Kph;
extram::ostringstream buffer;
buffer
<< std::fixed
<< std::setprecision(2)
<< "MP-0 S"
<< StandardMetrics.ms_v_bat_soc->AsString("0", Other, 1)
<< ","
<< ((m_units_distance == Kilometers) ? "K" : "M")
<< ","
<< StandardMetrics.ms_v_charge_voltage->AsInt()
<< ","
<< StandardMetrics.ms_v_charge_current->AsFloat()
<< ","
<< StandardMetrics.ms_v_charge_state->AsString("stopped")
<< ","
<< StandardMetrics.ms_v_charge_mode->AsString("standard")
<< ","
<< StandardMetrics.ms_v_bat_range_ideal->AsInt(0, m_units_distance)
<< ","
<< StandardMetrics.ms_v_bat_range_est->AsInt(0, m_units_distance)
<< ","
<< StandardMetrics.ms_v_charge_climit->AsInt()
<< ","
<< StandardMetrics.ms_v_charge_time->AsInt(0,Seconds)
<< ","
<< "0" // car_charge_b4
<< ","
<< (int)(StandardMetrics.ms_v_charge_kwh->AsFloat() * 10)
<< ","
<< chargesubstate_key(StandardMetrics.ms_v_charge_substate->AsString(""))
<< ","
<< chargestate_key(StandardMetrics.ms_v_charge_state->AsString("stopped"))
<< ","
<< chargemode_key(StandardMetrics.ms_v_charge_mode->AsString("standard"))
<< ","
<< StandardMetrics.ms_v_charge_timermode->AsBool()
<< ","
<< StandardMetrics.ms_v_charge_timerstart->AsInt()
<< ","
<< "0" // car_stale_timer
<< ","
<< StandardMetrics.ms_v_bat_cac->AsFloat()
<< ","
<< StandardMetrics.ms_v_charge_duration_full->AsInt()
<< ","
<< (((mins_range >= 0) && (mins_range < mins_soc)) ? mins_range : mins_soc)
<< ","
<< (int) StandardMetrics.ms_v_charge_limit_range->AsFloat(0, m_units_distance)
<< ","
<< StandardMetrics.ms_v_charge_limit_soc->AsInt()
<< ","
<< (StandardMetrics.ms_v_env_cooling->AsBool() ? 0 : -1)
<< ","
<< "0" // car_cooldown_tbattery
<< ","
<< "0" // car_cooldown_timelimit
<< ","
<< "0" // car_chargeestimate
<< ","
<< mins_range
<< ","
<< mins_soc
<< ","
<< StandardMetrics.ms_v_bat_range_full->AsInt(0, m_units_distance)
<< ","
<< "0" // car_chargetype
<< ","
<< (charging ? -StandardMetrics.ms_v_bat_power->AsFloat() : 0)
<< ","
<< StandardMetrics.ms_v_bat_voltage->AsFloat()
<< ","
<< StandardMetrics.ms_v_bat_soh->AsInt()
<< ","
<< StandardMetrics.ms_v_charge_power->AsFloat()
<< ","
<< StandardMetrics.ms_v_charge_efficiency->AsFloat()
<< ","
<< StandardMetrics.ms_v_bat_current->AsFloat()
<< ","
<< std::setprecision(1)
<< StandardMetrics.ms_v_bat_range_speed->AsFloat(0, units_speed)
;
Transmit(buffer.str().c_str());
}
void OvmsServerV2::TransmitMsgGen(bool always)
{
m_now_gen = false;
bool modified =
StandardMetrics.ms_v_gen_inprogress->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_pilot->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_voltage->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_current->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_power->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_efficiency->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_type->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_state->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_substate->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_mode->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_climit->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_limit_range->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_limit_soc->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_kwh->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_limit_range->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_limit_soc->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_kwh_grid->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_kwh_grid_total->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_time->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_timermode->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_timerstart->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_duration_empty->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_duration_range->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_duration_soc->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_gen_temp->IsModifiedAndClear(MyOvmsServerV2Modifier);
// Quick exit if nothing modified
if ((!always)&&(!modified)) return;
extram::ostringstream buffer;
buffer
<< std::fixed
<< std::setprecision(1)
<< "MP-0 G"
<< StandardMetrics.ms_v_gen_inprogress->AsBool()
<< ";"
<< StandardMetrics.ms_v_gen_pilot->AsBool()
<< ","
<< StandardMetrics.ms_v_gen_voltage->AsInt()
<< ","
<< StandardMetrics.ms_v_gen_current->AsFloat()
<< ","
<< StandardMetrics.ms_v_gen_power->AsFloat()
<< ","
<< StandardMetrics.ms_v_gen_efficiency->AsFloat()
<< ","
<< StandardMetrics.ms_v_gen_type->AsString("")
<< ","
<< StandardMetrics.ms_v_gen_state->AsString("stopped")
<< ","
<< StandardMetrics.ms_v_gen_substate->AsString("")
<< ","
<< StandardMetrics.ms_v_gen_mode->AsString("standard")
<< ","
<< StandardMetrics.ms_v_gen_climit->AsFloat()
<< ","
<< StandardMetrics.ms_v_gen_limit_range->AsFloat(0, m_units_distance)
<< ","
<< StandardMetrics.ms_v_gen_limit_soc->AsInt()
<< ","
<< StandardMetrics.ms_v_gen_kwh->AsFloat()
<< ","
<< StandardMetrics.ms_v_gen_kwh_grid->AsFloat()
<< ","
<< StandardMetrics.ms_v_gen_kwh_grid_total->AsFloat()
<< ","
<< StandardMetrics.ms_v_gen_time->AsInt(0,Seconds)
<< ","
<< StandardMetrics.ms_v_gen_timermode->AsBool()
<< ","
<< StandardMetrics.ms_v_gen_timerstart->AsInt()
<< ","
<< StandardMetrics.ms_v_gen_duration_empty->AsInt()
<< ","
<< StandardMetrics.ms_v_gen_duration_range->AsInt()
<< ","
<< StandardMetrics.ms_v_gen_duration_soc->AsInt()
<< ";"
<< StandardMetrics.ms_v_gen_temp->AsFloat()
;
Transmit(buffer.str().c_str());
}
void OvmsServerV2::TransmitMsgGPS(bool always)
{
m_now_gps = false;
bool modified =
StandardMetrics.ms_v_pos_latitude->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_pos_longitude->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_pos_direction->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_pos_altitude->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_pos_gpslock->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_pos_gpssq->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_pos_gpsmode->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_pos_gpshdop->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_pos_satcount->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_pos_gpsspeed->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_pos_speed->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_env_drivemode->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_bat_power->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_bat_energy_used->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_bat_energy_recd->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_inv_power->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_inv_efficiency->IsModifiedAndClear(MyOvmsServerV2Modifier);
// Quick exit if nothing modified
if ((!always)&&(!modified)) return;
bool stale =
StandardMetrics.ms_v_pos_gpstime->IsStale();
char drivemode[10];
sprintf(drivemode, "%x", StandardMetrics.ms_v_env_drivemode->AsInt());
metric_unit_t units_speed = (m_units_distance == Kilometers) ? Kph : Mph;
extram::ostringstream buffer;
buffer
<< "MP-0 L"
<< StandardMetrics.ms_v_pos_latitude->AsString("0",Other,6)
<< ","
<< StandardMetrics.ms_v_pos_longitude->AsString("0",Other,6)
<< ","
<< StandardMetrics.ms_v_pos_direction->AsString("0")
<< ","
<< StandardMetrics.ms_v_pos_altitude->AsString("0")
<< ","
<< StandardMetrics.ms_v_pos_gpslock->AsBool(false)
<< ((stale)?",0,":",1,")
<< StandardMetrics.ms_v_pos_speed->AsString("0", units_speed, 1)
<< ","
<< int(StandardMetrics.ms_v_pos_trip->AsFloat(0, m_units_distance)*10)
<< ","
<< drivemode
<< ","
<< StandardMetrics.ms_v_bat_power->AsString("0",Other,3)
<< ","
<< StandardMetrics.ms_v_bat_energy_used->AsString("0",Other,3)
<< ","
<< StandardMetrics.ms_v_bat_energy_recd->AsString("0",Other,3)
<< ","
<< StandardMetrics.ms_v_inv_power->AsFloat()
<< ","
<< StandardMetrics.ms_v_inv_efficiency->AsFloat()
<< ","
<< StandardMetrics.ms_v_pos_gpsmode->AsString()
<< ","
<< StandardMetrics.ms_v_pos_satcount->AsInt()
<< ","
<< StandardMetrics.ms_v_pos_gpshdop->AsString("0", Native, 1)
<< ","
<< StandardMetrics.ms_v_pos_gpsspeed->AsString("0", units_speed, 1)
<< ","
<< StandardMetrics.ms_v_pos_gpssq->AsInt()
;
Transmit(buffer.str().c_str());
}
void OvmsServerV2::TransmitMsgTPMS(bool always)
{
m_now_tpms = false;
bool modified =
StandardMetrics.ms_v_tpms_pressure->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_tpms_temp->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_tpms_health->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_tpms_alert->IsModifiedAndClear(MyOvmsServerV2Modifier);
// Quick exit if nothing modified
if ((!always)&&(!modified)) return;
extram::ostringstream buffer;
// Transmit new "Y" message:
int defstale_pressure =
StandardMetrics.ms_v_tpms_pressure->IsDefined()
? (StandardMetrics.ms_v_tpms_pressure->IsStale() ? 0 : 1)
: -1;
int defstale_temp =
StandardMetrics.ms_v_tpms_temp->IsDefined()
? (StandardMetrics.ms_v_tpms_temp->IsStale() ? 0 : 1)
: -1;
int defstale_health =
StandardMetrics.ms_v_tpms_health->IsDefined()
? (StandardMetrics.ms_v_tpms_health->IsStale() ? 0 : 1)
: -1;
int defstale_alert =
StandardMetrics.ms_v_tpms_alert->IsDefined()
? (StandardMetrics.ms_v_tpms_alert->IsStale() ? 0 : 1)
: -1;
std::vector<std::string> wheels;
if (MyVehicleFactory.m_currentvehicle)
wheels = MyVehicleFactory.m_currentvehicle->GetTpmsLayout();
buffer
<< "MP-0 Y"
<< wheels.size();
for (auto wheel : wheels)
{
buffer << "," << wheel;
}
buffer
<< ","
<< StandardMetrics.ms_v_tpms_pressure->GetSize()
<< (StandardMetrics.ms_v_tpms_pressure->GetSize() ? "," : "")
<< StandardMetrics.ms_v_tpms_pressure->AsString("", kPa, 1)
<< "," << defstale_pressure
<< ","
<< StandardMetrics.ms_v_tpms_temp->GetSize()
<< (StandardMetrics.ms_v_tpms_temp->GetSize() ? "," : "")
<< StandardMetrics.ms_v_tpms_temp->AsString("", Celcius, 1)
<< "," << defstale_temp
<< ","
<< StandardMetrics.ms_v_tpms_health->GetSize()
<< (StandardMetrics.ms_v_tpms_health->GetSize() ? "," : "")
<< StandardMetrics.ms_v_tpms_health->AsString("", Percentage, 1)
<< "," << defstale_health
<< ","
<< StandardMetrics.ms_v_tpms_alert->GetSize()
<< (StandardMetrics.ms_v_tpms_alert->GetSize() ? "," : "")
<< StandardMetrics.ms_v_tpms_alert->AsString("")
<< "," << defstale_alert
;
Transmit(buffer.str().c_str());
// Transmit legacy "W" message (fixed four tyres, only pressures & temperatures):
bool stale =
StandardMetrics.ms_v_tpms_pressure->IsStale() ||
StandardMetrics.ms_v_tpms_temp->IsStale() ||
StandardMetrics.ms_v_tpms_health->IsStale() ||
StandardMetrics.ms_v_tpms_alert->IsStale();
bool defined =
StandardMetrics.ms_v_tpms_pressure->IsDefined() ||
StandardMetrics.ms_v_tpms_temp->IsDefined() ||
StandardMetrics.ms_v_tpms_health->IsDefined() ||
StandardMetrics.ms_v_tpms_alert->IsDefined();
int defstale;
if (!defined)
{ defstale = -1; }
else if (stale)
{ defstale = 0; }
else
{ defstale = 1; }
buffer.str("");
buffer.clear();
buffer
<< "MP-0 W"
<< StandardMetrics.ms_v_tpms_pressure->ElemAsString(MS_V_TPMS_IDX_FR, "0", PSI)
<< ","
<< StandardMetrics.ms_v_tpms_temp->ElemAsString(MS_V_TPMS_IDX_FR, "0")
<< ","
<< StandardMetrics.ms_v_tpms_pressure->ElemAsString(MS_V_TPMS_IDX_RR, "0", PSI)
<< ","
<< StandardMetrics.ms_v_tpms_temp->ElemAsString(MS_V_TPMS_IDX_RR, "0")
<< ","
<< StandardMetrics.ms_v_tpms_pressure->ElemAsString(MS_V_TPMS_IDX_FL, "0", PSI)
<< ","
<< StandardMetrics.ms_v_tpms_temp->ElemAsString(MS_V_TPMS_IDX_FL, "0")
<< ","
<< StandardMetrics.ms_v_tpms_pressure->ElemAsString(MS_V_TPMS_IDX_RL, "0", PSI)
<< ","
<< StandardMetrics.ms_v_tpms_temp->ElemAsString(MS_V_TPMS_IDX_RL, "0")
<< ","
<< defstale
;
Transmit(buffer.str().c_str());
}
void OvmsServerV2::TransmitMsgFirmware(bool always)
{
// As the signal quality is the only fast changing metric here, and will normally
// change continuously +/- 2 even while parking, we check this for an actual value
// difference > 2 to the last transmission, so we don't need to retransmit the
// -now- comparably huge static info contained here on every signal level change.
// TODO: move dynamic network status infos into another message (needs App updates)
static int last_m_net_sq = 0;
int curr_m_net_sq = StandardMetrics.ms_m_net_sq->AsInt(0, sq);
m_now_firmware = false;
bool modified =
StandardMetrics.ms_m_version->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_vin->IsModifiedAndClear(MyOvmsServerV2Modifier) |
(std::abs(curr_m_net_sq - last_m_net_sq) > 2) |
StandardMetrics.ms_v_type->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_m_net_provider->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_env_service_range->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_env_service_time->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_m_hardware->IsModifiedAndClear(MyOvmsServerV2Modifier);
// Quick exit if nothing modified
if ((!always)&&(!modified)) return;
last_m_net_sq = curr_m_net_sq;
extram::ostringstream buffer;
buffer
<< "MP-0 F"
<< mp_encode(StandardMetrics.ms_m_version->AsString(""))
<< ","
<< mp_encode(StandardMetrics.ms_v_vin->AsString(""))
<< ","
<< StandardMetrics.ms_m_net_sq->AsString("0",sq)
<< ","
<< MyConfig.GetParamValue("vehicle", "canwrite", "0")
<< ","
<< StandardMetrics.ms_v_type->AsString("")
<< ","
<< mp_encode(StandardMetrics.ms_m_net_provider->AsString(""))
<< ","
<< StandardMetrics.ms_v_env_service_range->AsString("-1", Kilometers, 0)
<< ","
<< StandardMetrics.ms_v_env_service_time->AsString("-1", Seconds, 0)
<< ","
<< mp_encode(StandardMetrics.ms_m_hardware->AsString(""))
;
Transmit(buffer.str().c_str());
}
uint8_t Doors1()
{
car_doors1_t car_doors1;
car_doors1.bits.FrontLeftDoor = StandardMetrics.ms_v_door_fl->AsBool();
car_doors1.bits.FrontRightDoor = StandardMetrics.ms_v_door_fr->AsBool();
car_doors1.bits.ChargePort = StandardMetrics.ms_v_door_chargeport->AsBool();
car_doors1.bits.PilotSignal = StandardMetrics.ms_v_charge_pilot->AsBool();
car_doors1.bits.Charging = StandardMetrics.ms_v_charge_inprogress->AsBool();
car_doors1.bits.HandBrake = StandardMetrics.ms_v_env_handbrake->AsBool();
car_doors1.bits.CarON = StandardMetrics.ms_v_env_on->AsBool();
return car_doors1.flags;
}
uint8_t Doors2()
{
car_doors2_t car_doors2;
car_doors2.bits.CarLocked = StandardMetrics.ms_v_env_locked->AsBool();
car_doors2.bits.ValetMode = StandardMetrics.ms_v_env_valet->AsBool();
car_doors2.bits.Headlights = StandardMetrics.ms_v_env_headlights->AsBool();
car_doors2.bits.Bonnet = StandardMetrics.ms_v_door_hood->AsBool();
car_doors2.bits.Trunk = StandardMetrics.ms_v_door_trunk->AsBool();
return car_doors2.flags;
}
uint8_t Doors3()
{
car_doors3_t car_doors3;
car_doors3.bits.CarAwake = StandardMetrics.ms_v_env_awake->AsBool();
car_doors3.bits.CoolingPump = StandardMetrics.ms_v_env_cooling->AsBool();
car_doors3.bits.CtrlLoggedIn = StandardMetrics.ms_v_env_ctrl_login->AsBool();
car_doors3.bits.CtrlCfgMode = StandardMetrics.ms_v_env_ctrl_config->AsBool();
return car_doors3.flags;
}
uint8_t Doors4()
{
car_doors4_t car_doors4;
car_doors4.bits.AlarmSounds = StandardMetrics.ms_v_env_alarm->AsBool();
return car_doors4.flags;
}
uint8_t Doors5()
{
car_doors5_t car_doors5;
car_doors5.bits.RearLeftDoor = StandardMetrics.ms_v_door_rl->AsBool();
car_doors5.bits.RearRightDoor = StandardMetrics.ms_v_door_rr->AsBool();
car_doors5.bits.Frunk = false; // should this be hood or something else?
car_doors5.bits.Charging12V = StandardMetrics.ms_v_env_charging12v->AsBool();
car_doors5.bits.Aux12V = StandardMetrics.ms_v_env_aux12v->AsBool();
car_doors5.bits.HVAC = StandardMetrics.ms_v_env_hvac->AsBool();
return car_doors5.flags;
}
void OvmsServerV2::TransmitMsgEnvironment(bool always)
{
m_now_environment = false;
bool modified =
// doors 1
StandardMetrics.ms_v_door_fl->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_door_fr->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_door_chargeport->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_pilot->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_inprogress->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_env_handbrake->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_env_on->IsModifiedAndClear(MyOvmsServerV2Modifier) |
// doors 2
StandardMetrics.ms_v_env_locked->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_env_valet->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_env_headlights->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_door_hood->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_door_trunk->IsModifiedAndClear(MyOvmsServerV2Modifier) |
// doors 3
StandardMetrics.ms_v_env_awake->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_env_cooling->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_env_ctrl_login->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_env_ctrl_config->IsModifiedAndClear(MyOvmsServerV2Modifier) |
// doors 4
StandardMetrics.ms_v_env_alarm->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_inv_temp->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_mot_temp->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_bat_temp->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_env_temp->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_bat_12v_voltage->IsModifiedAndClear(MyOvmsServerV2Modifier) |
// doors 5
StandardMetrics.ms_v_door_rl->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_door_rr->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_env_charging12v->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_env_aux12v->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_env_hvac->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_charge_temp->IsModifiedAndClear(MyOvmsServerV2Modifier) |
StandardMetrics.ms_v_env_cabintemp->IsModifiedAndClear(MyOvmsServerV2Modifier);
// Quick exit if nothing modified
if ((!always)&&(!modified)) return;
// v2 has one "stale" flag for all temperatures, we say they're stale only if
// all are stale, IE one valid temperature makes them all valid
bool stale_temps =
StandardMetrics.ms_v_inv_temp->IsStale() &&
StandardMetrics.ms_v_mot_temp->IsStale() &&
StandardMetrics.ms_v_bat_temp->IsStale() &&
StandardMetrics.ms_v_charge_temp->IsStale() &&
StandardMetrics.ms_v_env_temp->IsStale() &&
StandardMetrics.ms_v_env_cabintemp->IsStale();
extram::ostringstream buffer;
buffer
<< "MP-0 D"
<< (int)Doors1()
<< ","
<< (int)Doors2()
<< ","
<< (StandardMetrics.ms_v_env_locked->AsBool()?"4":"5")
<< ","
<< StandardMetrics.ms_v_inv_temp->AsString("0")
<< ","
<< StandardMetrics.ms_v_mot_temp->AsString("0")
<< ","
<< StandardMetrics.ms_v_bat_temp->AsString("0")
<< ","
<< int(StandardMetrics.ms_v_pos_trip->AsFloat(0, m_units_distance)*10)
<< ","
<< int(StandardMetrics.ms_v_pos_odometer->AsFloat(0, m_units_distance)*10)
<< ","
<< StandardMetrics.ms_v_pos_speed->AsString("0")
<< ","
<< StandardMetrics.ms_v_env_parktime->AsString("0")
<< ","
<< StandardMetrics.ms_v_env_temp->AsString("0")
<< ","
<< (int)Doors3()
<< ","
<< (stale_temps ? "0" : "1")
<< ","
<< (StandardMetrics.ms_v_env_temp->IsStale() ? "0" : "1")
<< ","
<< StandardMetrics.ms_v_bat_12v_voltage->AsString("0")
<< ","
<< (int)Doors4()
<< ","
<< StandardMetrics.ms_v_bat_12v_voltage_ref->AsString("0")
<< ","
<< (int)Doors5()
<< ","
<< StandardMetrics.ms_v_charge_temp->AsString("0")
<< ","
<< StandardMetrics.ms_v_bat_12v_current->AsString("0")
<< ","
<< StandardMetrics.ms_v_env_cabintemp->AsString("0")
;
Transmit(buffer.str().c_str());
}
void OvmsServerV2::TransmitMsgCapabilities(bool always)
{
m_now_capabilities = false;
}
void OvmsServerV2::TransmitMsgGroup(bool always)
{
m_now_group = false;
}
void OvmsServerV2::TransmitNotifyInfo()
{
m_pending_notify_info = false;
// Find the type object
OvmsNotifyType* info = MyNotify.GetType("info");
if (info == NULL) return;
while(1)
{
// Find the first entry
OvmsNotifyEntry* e = info->FirstUnreadEntry(MyOvmsServerV2Reader, 0);
if (e == NULL) return;
extram::ostringstream buffer;
buffer
<< "MP-0 PI"
<< mp_encode(e->GetValue());
if (Transmit(buffer.str().c_str()))
{
info->MarkRead(MyOvmsServerV2Reader, e);
}
else
{
m_pending_notify_info = true;
return;
}
}
}
void OvmsServerV2::TransmitNotifyError()
{
m_pending_notify_error = false;
// Find the type object
OvmsNotifyType* alert = MyNotify.GetType("error");
if (alert == NULL) return;
while(1)
{
// Find the first entry
OvmsNotifyEntry* e = alert->FirstUnreadEntry(MyOvmsServerV2Reader, 0);
if (e == NULL) return;
extram::ostringstream buffer;
buffer
<< "MP-0 PE"
<< e->GetValue(); // no mp_encode; payload structure "<vehicletype>,<errorcode>,<errordata>"
if (Transmit(buffer.str().c_str()))
{
alert->MarkRead(MyOvmsServerV2Reader, e);
}
else
{
m_pending_notify_error = true;
return;
}
}
}
void OvmsServerV2::TransmitNotifyAlert()
{
m_pending_notify_alert = false;
// Find the type object
OvmsNotifyType* alert = MyNotify.GetType("alert");
if (alert == NULL) return;
while(1)
{
// Find the first entry
OvmsNotifyEntry* e = alert->FirstUnreadEntry(MyOvmsServerV2Reader, 0);
if (e == NULL) return;
extram::ostringstream buffer;
buffer
<< "MP-0 PA"
<< mp_encode(e->GetValue());
if (Transmit(buffer.str().c_str()))
{
alert->MarkRead(MyOvmsServerV2Reader, e);
}
else
{
m_pending_notify_alert = true;
return;
}
}
}
void OvmsServerV2::TransmitNotifyData()
{
// Find the type object
OvmsNotifyType* data = MyNotify.GetType("data");
if (data == NULL) return;
uint32_t starttime = esp_log_timestamp();
int cnt = 0;
size_t size = 0;
while(1)
{
// Find the first entry
OvmsNotifyEntry* e = data->FirstUnreadEntry(MyOvmsServerV2Reader, m_pending_notify_data_last);
if (e == NULL)
{
m_pending_notify_data = false;
// if we have sent something, check for retransmissions in 10 seconds:
if (m_pending_notify_data_last)
m_pending_notify_data_retransmit = 10;
return;
}
extram::string msg = e->GetValue();
ESP_LOGD(TAG, "TransmitNotifyData: msg=%s", msg.c_str());
// terminate payload at first LF:
size_t eol = msg.find('\n');
if (eol != std::string::npos)
msg.resize(eol);
uint32_t now = esp_log_timestamp();
extram::ostringstream buffer;
buffer
<< "MP-0 h"
<< e->m_id
<< ","
<< -((int)(now - e->m_created) / 1000)
<< ","
<< msg;
if (!Transmit(buffer.str().c_str()))
{
m_pending_notify_data = true;
m_pending_notify_data_last = 0;
return;
}
m_pending_notify_data_last = e->m_id;
// be nice to other tasks, the network & the server:
// limits per second: 300 ms / 5 transmissions / 4000 bytes payload
cnt++;
size += buffer.str().size();
if (now - starttime >= 300 || cnt == 5 || size >= 4000)
{
ESP_LOGD(TAG, "TransmitNotifyData: used %d ms for %d records, %u bytes", now - starttime, cnt, size);
return;
}
}
}
void OvmsServerV2::HandleNotifyDataAck(uint32_t ack)
{
OvmsNotifyType* data = MyNotify.GetType("data");
if (data == NULL) return;
OvmsNotifyEntry* e = data->FindEntry(ack);
if (e)
{
data->MarkRead(MyOvmsServerV2Reader, e);
}
}
void OvmsServerV2::MetricModified(OvmsMetric* metric)
{
// A metric has been changed: if peers are connected,
// check for important changes that should be transmitted ASAP:
if (StandardMetrics.ms_s_v2_peers->AsInt() == 0)
return;
if ((metric == StandardMetrics.ms_v_vin)||
(metric == StandardMetrics.ms_v_type)||
(metric == StandardMetrics.ms_m_net_provider)||
(metric == StandardMetrics.ms_m_hardware))
{
m_now_firmware = true;
}
if ((metric == StandardMetrics.ms_v_charge_climit)||
(metric == StandardMetrics.ms_v_charge_limit_range)||
(metric == StandardMetrics.ms_v_charge_limit_soc)||
(metric == StandardMetrics.ms_v_charge_state)||
(metric == StandardMetrics.ms_v_charge_substate)||
(metric == StandardMetrics.ms_v_charge_mode)||
(metric == StandardMetrics.ms_v_charge_inprogress)||
(metric == StandardMetrics.ms_v_env_cooling)||
(metric == StandardMetrics.ms_v_bat_cac)||
(metric == StandardMetrics.ms_v_bat_soh))
{
m_now_stat = true;
}
if ((metric == StandardMetrics.ms_v_door_fl)||
(metric == StandardMetrics.ms_v_door_fr)||
(metric == StandardMetrics.ms_v_door_chargeport)||
(metric == StandardMetrics.ms_v_charge_pilot)||
(metric == StandardMetrics.ms_v_charge_inprogress)||
(metric == StandardMetrics.ms_v_env_handbrake)||
(metric == StandardMetrics.ms_v_env_on)||
(metric == StandardMetrics.ms_v_env_locked)||
(metric == StandardMetrics.ms_v_env_valet)||
(metric == StandardMetrics.ms_v_door_hood)||
(metric == StandardMetrics.ms_v_door_trunk)||
(metric == StandardMetrics.ms_v_env_awake)||
(metric == StandardMetrics.ms_v_env_cooling)||
(metric == StandardMetrics.ms_v_env_alarm)||
(metric == StandardMetrics.ms_v_door_rl)||
(metric == StandardMetrics.ms_v_door_rr)||
(metric == StandardMetrics.ms_v_env_charging12v)||
(metric == StandardMetrics.ms_v_env_aux12v)||
(metric == StandardMetrics.ms_v_env_hvac))
{
m_now_environment = true;
}
if ((metric == StandardMetrics.ms_v_env_drivemode)||
(metric == StandardMetrics.ms_v_pos_gpslock)||
(metric == StandardMetrics.ms_v_pos_gpsmode))
{
m_now_gps = true;
}
if ((metric == StandardMetrics.ms_v_tpms_alert))
{
m_now_tpms = true;
}
if ((metric == StandardMetrics.ms_v_gen_climit)||
(metric == StandardMetrics.ms_v_gen_limit_range)||
(metric == StandardMetrics.ms_v_gen_limit_soc)||
(metric == StandardMetrics.ms_v_gen_state)||
(metric == StandardMetrics.ms_v_gen_substate)||
(metric == StandardMetrics.ms_v_gen_mode)||
(metric == StandardMetrics.ms_v_gen_inprogress))
{
m_now_gen = true;
}
}
bool OvmsServerV2::NotificationFilter(OvmsNotifyType* type, const char* subtype)
{
if (strcmp(type->m_name, "info") == 0 ||
strcmp(type->m_name, "error") == 0 ||
strcmp(type->m_name, "alert") == 0 ||
strcmp(type->m_name, "data") == 0)
return true;
else
return false;
}
bool OvmsServerV2::IncomingNotification(OvmsNotifyType* type, OvmsNotifyEntry* entry)
{
if (strcmp(type->m_name,"info")==0)
{
// Info notifications
if (!StandardMetrics.ms_s_v2_connected->AsBool())
{
m_pending_notify_info = true;
return false; // No connection, so leave it queued for when we do
}
extram::ostringstream buffer;
buffer
<< "MP-0 PI"
<< mp_encode(entry->GetValue());
return Transmit(buffer.str().c_str()); // Mark it as read if we've managed to send it
}
else if (strcmp(type->m_name,"error")==0)
{
// Error notification
if (!StandardMetrics.ms_s_v2_connected->AsBool())
{
m_pending_notify_error = true;
return false; // No connection, so leave it queued for when we do
}
extram::ostringstream buffer;
buffer
<< "MP-0 PE"
<< entry->GetValue(); // no mp_encode; payload structure "<vehicletype>,<errorcode>,<errordata>"
return Transmit(buffer.str().c_str()); // Mark it as read if we've managed to send it
}
else if (strcmp(type->m_name,"alert")==0)
{
// Alert notifications
if (!StandardMetrics.ms_s_v2_connected->AsBool())
{
m_pending_notify_alert = true;
return false; // No connection, so leave it queued for when we do
}
extram::ostringstream buffer;
buffer
<< "MP-0 PA"
<< mp_encode(entry->GetValue());
return Transmit(buffer.str().c_str()); // Mark it as read if we've managed to send it
}
else if (strcmp(type->m_name,"data")==0)
{
// Data notifications
m_pending_notify_data = true;
return false; // We just flag it for later transmission
}
else
return true; // Mark it read, as no interest to us
}
/**
* EventListener:
*/
void OvmsServerV2::EventListener(std::string event, void* data)
{
if (event == "system.modem.received.ussd")
{
// forward USSD response to server:
std::string buf = "MP-0 c41,0,";
buf.append(mp_encode(std::string((char*) data)));
Transmit(buf);
}
else if (event == "config.changed" || event == "config.mounted")
{
ConfigChanged((OvmsConfigParam*) data);
}
else if (event == "location.alert.flatbed.moved")
{
m_now_gps = true;
}
}
/**
* ConfigChanged: read new configuration
* - param: NULL = read all configurations
*/
void OvmsServerV2::ConfigChanged(OvmsConfigParam* param)
{
m_streaming = MyConfig.GetParamValueInt("vehicle", "stream", 0);
m_updatetime_connected = MyConfig.GetParamValueInt("server.v2", "updatetime.connected", 60);
m_updatetime_idle = MyConfig.GetParamValueInt("server.v2", "updatetime.idle", 600);
}
void OvmsServerV2::NetUp(std::string event, void* data)
{
// workaround for wifi AP mode startup (manager up before interface)
if ( (m_mgconn == NULL) && MyNetManager.MongooseRunning() )
{
SetStatus("Network is up, so attempt network connection", false, ConnectWait);
m_connretry = 2;
}
}
void OvmsServerV2::NetDown(std::string event, void* data)
{
}
void OvmsServerV2::NetReconfigured(std::string event, void* data)
{
ESP_LOGI(TAG, "Network was reconfigured: disconnect, and reconnect in 10 seconds");
SetStatus("Network was reconfigured: disconnect, and reconnect in 10 seconds", false, ConnectWait);
Reconnect(10);
}
void OvmsServerV2::NetmanInit(std::string event, void* data)
{
if ((m_mgconn == NULL)&&(MyNetManager.m_connected_any))
{
SetStatus("Network is up, so attempt network connection", false, ConnectWait);
m_connretry = 2;
}
}
void OvmsServerV2::NetmanStop(std::string event, void* data)
{
if (m_mgconn)
{
SetStatus("Network is down, so disconnect network connection", false, WaitNetwork);
Disconnect();
}
}
void OvmsServerV2::Ticker1(std::string event, void* data)
{
if (m_connretry > 0)
{
if (MyNetManager.m_connected_any)
{
m_connretry--;
if (m_connretry == 0)
{
if (m_mgconn) Disconnect(); // Disconnect first (timeout)
Connect(); // Kick off the connection
}
}
}
if ((!MyNetManager.m_connected_any) && (m_state != WaitNetwork))
{
m_connretry = 0;
SetStatus("Server is ready to start", false, WaitNetwork);
}
if (StandardMetrics.ms_s_v2_connected->AsBool())
{
// check for issue #241 condition:
if (esp_log_timestamp() - m_lastrx_time > MyConfig.GetParamValueInt("server.v2", "timeout.rx", 960) * 1000)
{
ESP_LOGW(TAG, "Detected stale connection (issue #241), restarting network");
MyNetManager.RestartNetwork();
return;
}
// Periodic transmission of metrics
bool caron = StandardMetrics.ms_v_env_on->AsBool();
int now = StandardMetrics.ms_m_monotonic->AsInt();
int next = (m_peers==0) ? m_updatetime_idle : m_updatetime_connected;
if ((m_lasttx==0)||(now>(m_lasttx+next)))
{
TransmitMsgStat(true); // Send always, periodically
TransmitMsgEnvironment(true); // Send always, periodically
TransmitMsgGPS(m_lasttx==0);
TransmitMsgGroup(m_lasttx==0);
TransmitMsgTPMS(m_lasttx==0);
TransmitMsgFirmware(m_lasttx==0);
TransmitMsgCapabilities(m_lasttx==0);
if (StandardMetrics.ms_v_gen_current->AsFloat() > 0) TransmitMsgGen(true);
m_lasttx = m_lasttx_stream = now;
}
else if (m_streaming && caron && m_peers && now > m_lasttx_stream+m_streaming)
{
TransmitMsgGPS();
m_lasttx_stream = now;
}
if (m_now_stat) TransmitMsgStat();
if (m_now_gen) TransmitMsgGen();
if (m_now_environment) TransmitMsgEnvironment();
if (m_now_gps) TransmitMsgGPS();
if (m_now_group) TransmitMsgGroup();
if (m_now_tpms) TransmitMsgTPMS();
if (m_now_firmware) TransmitMsgFirmware();
if (m_now_capabilities) TransmitMsgCapabilities();
if (m_pending_notify_alert) TransmitNotifyAlert();
if (m_pending_notify_error) TransmitNotifyError();
if (m_pending_notify_info) TransmitNotifyInfo();
if (m_pending_notify_data)
{
TransmitNotifyData();
}
else if (m_pending_notify_data_retransmit > 0 && --m_pending_notify_data_retransmit == 0)
{
// check for retransmissions:
ESP_LOGD(TAG, "TransmitNotifyData: checking for retransmissions");
m_pending_notify_data_last = 0;
TransmitNotifyData();
}
}
}
void OvmsServerV2::RequestUpdate(bool txall)
{
if (txall)
{
m_lasttx = 0;
}
else
{
m_now_stat = true;
m_now_gen = true;
m_now_gps = true;
m_now_tpms = true;
m_now_firmware = true;
m_now_environment = true;
m_now_capabilities = true;
m_now_group = true;
}
}
OvmsServerV2::OvmsServerV2(const char* name)
: OvmsServer(name)
{
if (MyOvmsServerV2Modifier == 0)
{
MyOvmsServerV2Modifier = MyMetrics.RegisterModifier();
ESP_LOGI(TAG, "OVMS Server V2 registered metric modifier is #%d",MyOvmsServerV2Modifier);
}
m_buffer = new OvmsBuffer(1024);
SetStatus("Server has been started", false, WaitNetwork);
m_now_stat = false;
m_now_gen = false;
m_now_gps = false;
m_now_tpms = false;
m_now_firmware = false;
m_now_environment = false;
m_now_capabilities = false;
m_now_group = false;
m_streaming = 0;
m_updatetime_idle = 600;
m_updatetime_connected = 60;
m_lasttx = 0;
m_lasttx_stream = 0;
m_peers = 0;
m_connretry = 0;
m_mgconn = NULL;
m_pending_notify_info = false;
m_pending_notify_error = false;
m_pending_notify_alert = false;
m_pending_notify_data = false;
m_pending_notify_data_last = 0;
m_pending_notify_data_retransmit = 0;
if (MyConfig.GetParamValue("vehicle", "units.distance").compare("M") == 0)
m_units_distance = Miles;
else
m_units_distance = Kilometers;
ESP_LOGI(TAG, "OVMS Server v2 running");
#undef bind // Kludgy, but works
using std::placeholders::_1;
using std::placeholders::_2;
MyMetrics.RegisterListener(TAG, "*", std::bind(&OvmsServerV2::MetricModified, this, _1));
if (MyOvmsServerV2Reader == 0)
{
MyOvmsServerV2Reader = MyNotify.RegisterReader("ovmsv2", COMMAND_RESULT_NORMAL, std::bind(OvmsServerV2ReaderCallback, _1, _2),
true, std::bind(OvmsServerV2ReaderFilterCallback, _1, _2));
}
else
{
MyNotify.RegisterReader(MyOvmsServerV2Reader, "ovmsv2", COMMAND_RESULT_NORMAL, std::bind(OvmsServerV2ReaderCallback, _1, _2),
true, std::bind(OvmsServerV2ReaderFilterCallback, _1, _2));
}
// init event listener:
MyEvents.RegisterEvent(TAG,"network.up", std::bind(&OvmsServerV2::NetUp, this, _1, _2));
MyEvents.RegisterEvent(TAG,"network.down", std::bind(&OvmsServerV2::NetDown, this, _1, _2));
MyEvents.RegisterEvent(TAG,"network.reconfigured", std::bind(&OvmsServerV2::NetReconfigured, this, _1, _2));
MyEvents.RegisterEvent(TAG,"network.mgr.init", std::bind(&OvmsServerV2::NetmanInit, this, _1, _2));
MyEvents.RegisterEvent(TAG,"network.mgr.stop", std::bind(&OvmsServerV2::NetmanStop, this, _1, _2));
MyEvents.RegisterEvent(TAG,"ticker.1", std::bind(&OvmsServerV2::Ticker1, this, _1, _2));
MyEvents.RegisterEvent(TAG,"system.modem.received.ussd", std::bind(&OvmsServerV2::EventListener, this, _1, _2));
MyEvents.RegisterEvent(TAG,"config.changed", std::bind(&OvmsServerV2::EventListener, this, _1, _2));
MyEvents.RegisterEvent(TAG,"config.mounted", std::bind(&OvmsServerV2::EventListener, this, _1, _2));
MyEvents.RegisterEvent(TAG,"location.alert.flatbed.moved", std::bind(&OvmsServerV2::EventListener, this, _1, _2));
// read config:
ConfigChanged(NULL);
if (MyNetManager.m_connected_any)
{
Connect(); // Kick off the connection
}
}
OvmsServerV2::~OvmsServerV2()
{
MyMetrics.DeregisterListener(TAG);
MyEvents.DeregisterEvent(TAG);
MyNotify.ClearReader(MyOvmsServerV2Reader);
Disconnect();
if (m_buffer)
{
delete m_buffer;
m_buffer = NULL;
}
MyEvents.SignalEvent("server.v2.stopped", NULL);
}
void OvmsServerV2::SetPowerMode(PowerMode powermode)
{
m_powermode = powermode;
switch (powermode)
{
case On:
break;
case Sleep:
break;
case DeepSleep:
break;
case Off:
break;
default:
break;
}
}
void ovmsv2_start(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
if (MyOvmsServerV2 == NULL)
{
writer->puts("Launching OVMS Server V2 connection (oscv2)");
MyOvmsServerV2 = new OvmsServerV2("oscv2");
}
}
void ovmsv2_stop(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
if (MyOvmsServerV2 != NULL)
{
writer->puts("Stopping OVMS Server V2 connection (oscv2)");
OvmsServerV2 *instance = MyOvmsServerV2;
MyOvmsServerV2 = NULL;
delete instance;
}
else
{
writer->puts("OVMS v2 server has not been started");
}
}
void ovmsv2_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
if (MyOvmsServerV2 == NULL)
{
writer->puts("OVMS v2 server has not been started");
}
else
{
switch (MyOvmsServerV2->m_state)
{
case OvmsServerV2::WaitNetwork:
writer->puts("State: Waiting for network connectivity");
break;
case OvmsServerV2::ConnectWait:
writer->printf("State: Waiting to connect (%d second(s) remaining)\n",MyOvmsServerV2->m_connretry);
break;
case OvmsServerV2::Connecting:
writer->puts("State: Connecting...");
break;
case OvmsServerV2::Authenticating:
writer->puts("State: Authenticating...");
break;
case OvmsServerV2::Connected:
writer->puts("State: Connected");
break;
case OvmsServerV2::Disconnected:
writer->puts("State: Disconnected");
break;
case OvmsServerV2::WaitReconnect:
writer->printf("State: Waiting to reconnect (%d second(s) remaining)\n",MyOvmsServerV2->m_connretry);
break;
default:
writer->puts("State: undetermined");
break;
}
writer->printf(" %s\n",MyOvmsServerV2->m_status.c_str());
}
}
void ovmsv2_update(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
if (MyOvmsServerV2 == NULL)
{
writer->puts("ERROR: OVMS v2 server has not been started");
}
else
{
bool txall = (strcmp(cmd->GetName(), "all") == 0);
MyOvmsServerV2->RequestUpdate(txall);
writer->printf("Server V2 data update for %s metrics has been scheduled\n",
txall ? "all" : "modified");
}
}
OvmsServerV2Init MyOvmsServerV2Init __attribute__ ((init_priority (6100)));
OvmsServerV2Init::OvmsServerV2Init()
{
ESP_LOGI(TAG, "Initialising OVMS V2 Server (6100)");
OvmsCommand* cmd_server = MyCommandApp.FindCommand("server");
OvmsCommand* cmd_v2 = cmd_server->RegisterCommand("v2","OVMS Server V2 Protocol", ovmsv2_status, "", 0, 0, false);
cmd_v2->RegisterCommand("start","Start an OVMS V2 Server Connection",ovmsv2_start);
cmd_v2->RegisterCommand("stop","Stop an OVMS V2 Server Connection",ovmsv2_stop);
cmd_v2->RegisterCommand("status","Show OVMS V2 Server connection status",ovmsv2_status);
OvmsCommand* cmd_update = cmd_v2->RegisterCommand("update", "Request OVMS V2 Server data update", ovmsv2_update);
cmd_update->RegisterCommand("all", "Transmit all metrics covered by v2 protocol", ovmsv2_update);
cmd_update->RegisterCommand("modified", "Transmit modified metrics only", ovmsv2_update);
MyConfig.RegisterParam("server.v2", "V2 Server Configuration", true, true);
// Our instances:
// 'server': The server name/ip
// 'port': The port to connect to (default: 6867)
// 'updatetime.connected': Time between updates when one or more apps connected (default: 60)
// 'updatetime.idle': Time between updates when no apps connected (default: 600)
// Also note:
// Parameter "vehicle", instance "id", is the vehicle ID
// Server Password has been movied to password/server.v2
}
void OvmsServerV2Init::AutoInit()
{
if (MyConfig.GetParamValueBool("auto", "server.v2", false))
MyOvmsServerV2 = new OvmsServerV2("oscv2");
}