2293 lines
73 KiB
C++
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");
|
|
}
|