OVMS3/OVMS.V3/components/vehicle/vehicle.cpp

2068 lines
65 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 = "vehicle";
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <ovms_command.h>
#include <ovms_script.h>
#include <ovms_metrics.h>
#include <ovms_notify.h>
#include <metrics_standard.h>
#ifdef CONFIG_OVMS_COMP_WEBSERVER
#include <ovms_webserver.h>
#endif // #ifdef CONFIG_OVMS_COMP_WEBSERVER
#include <ovms_peripherals.h>
#include <string_writer.h>
#include "vehicle.h"
OvmsVehicleFactory MyVehicleFactory __attribute__ ((init_priority (2000)));
OvmsVehicleFactory::OvmsVehicleFactory()
{
ESP_LOGI(TAG, "Initialising VEHICLE Factory (2000)");
m_currentvehicle = NULL;
m_currentvehicletype.clear();
OvmsCommand* cmd_vehicle = MyCommandApp.RegisterCommand("vehicle","Vehicle framework", vehicle_status, "", 0, 0, false);
cmd_vehicle->RegisterCommand("module","Set (or clear) vehicle module",vehicle_module,"<type>",0,1,true,vehicle_validate);
cmd_vehicle->RegisterCommand("list","Show list of available vehicle modules",vehicle_list);
cmd_vehicle->RegisterCommand("status","Show vehicle module status",vehicle_status);
MyCommandApp.RegisterCommand("wakeup","Wake up vehicle",vehicle_wakeup);
MyCommandApp.RegisterCommand("homelink","Activate specified homelink button",vehicle_homelink,"<homelink> [<duration=1000ms>]",1,2);
OvmsCommand* cmd_climate = MyCommandApp.RegisterCommand("climatecontrol","(De)Activate Climate Control");
cmd_climate->RegisterCommand("on","Activate Climate Control",vehicle_climatecontrol_on);
cmd_climate->RegisterCommand("off","Deactivate Climate Control",vehicle_climatecontrol_off);
MyCommandApp.RegisterCommand("lock","Lock vehicle",vehicle_lock,"<pin>",1,1);
MyCommandApp.RegisterCommand("unlock","Unlock vehicle",vehicle_unlock,"<pin>",1,1);
MyCommandApp.RegisterCommand("valet","Activate valet mode",vehicle_valet,"<pin>",1,1);
MyCommandApp.RegisterCommand("unvalet","Deactivate valet mode",vehicle_unvalet,"<pin>",1,1);
OvmsCommand* cmd_charge = MyCommandApp.RegisterCommand("charge","Charging framework");
OvmsCommand* cmd_chargemode = cmd_charge->RegisterCommand("mode","Set vehicle charge mode");
cmd_chargemode->RegisterCommand("standard","Set vehicle standard charge mode",vehicle_charge_mode);
cmd_chargemode->RegisterCommand("storage","Set vehicle standard charge mode",vehicle_charge_mode);
cmd_chargemode->RegisterCommand("range","Set vehicle standard charge mode",vehicle_charge_mode);
cmd_chargemode->RegisterCommand("performance","Set vehicle standard charge mode",vehicle_charge_mode);
cmd_charge->RegisterCommand("start","Start a vehicle charge",vehicle_charge_start);
cmd_charge->RegisterCommand("stop","Stop a vehicle charge",vehicle_charge_stop);
cmd_charge->RegisterCommand("current","Limit charge current",vehicle_charge_current,"<amps>",1,1);
cmd_charge->RegisterCommand("cooldown","Start a vehicle cooldown",vehicle_charge_cooldown);
OvmsCommand* cmd_stat = MyCommandApp.RegisterCommand("stat","Show vehicle status",vehicle_stat);
cmd_stat->RegisterCommand("trip","Show trip status",vehicle_stat_trip);
OvmsCommand* cmd_bms = MyCommandApp.RegisterCommand("bms","BMS framework", bms_status, "", 0, 0, false);
cmd_bms->RegisterCommand("status","Show BMS status",bms_status);
cmd_bms->RegisterCommand("temp","Show BMS temperature status",bms_status);
cmd_bms->RegisterCommand("volt","Show BMS voltage status",bms_status);
cmd_bms->RegisterCommand("reset","Reset BMS statistics",bms_reset);
cmd_bms->RegisterCommand("alerts","Show BMS alerts",bms_alerts);
OvmsCommand* cmd_obdii = MyCommandApp.RegisterCommand("obdii", "OBDII framework");
for (int k=1; k <= 4; k++)
{
static const char* name[4] = { "can1", "can2", "can3", "can4" };
OvmsCommand* cmd_canx = cmd_obdii->RegisterCommand(name[k-1], "select bus");
OvmsCommand* cmd_obdreq = cmd_canx->RegisterCommand(
"request", "Send OBD2/UDS request, output response");
cmd_obdreq->RegisterCommand(
"device", "Send OBD2/ISOTP request to a device", obdii_request,
"[-e|-E|-v] [-t<timeout_ms>] <txid> <rxid> <request>\n"
"Give <txid> and <rxid> as hexadecimal CAN IDs,"
" add -e to use ISO-TP extended addressing (19 bit IDs via standard frames)\n"
" or -E to use ISO-TP via extended frames (29 bit IDs)\n"
" or -v to use VW-TP 2.0 (VW/VAG specific transport protocol, txid=200, rxid=ECUID).\n"
"<request> is the hex string of the request type + arguments,"
" e.g. '223a4b' = read data from PID 0x3a4b.\n"
"Default timeout is 3000 ms.",
3, 5);
cmd_obdreq->RegisterCommand(
"broadcast", "Send OBD2/UDS request as broadcast", obdii_request,
"[-t<timeout_ms>] <request>\n"
"Sends the request to broadcast ID 7df, listens on IDs 7e8-7ef.\n"
"Note: only the first response will be shown, enable CAN log to check for more.\n"
"<request> is the hex string of the request type + arguments,"
" e.g. '223a4b' = read data from PID 0x3a4b.\n"
"Default timeout is 3000 ms.",
1, 2);
}
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
DuktapeObjectRegistration* dto = new DuktapeObjectRegistration("OvmsVehicle");
dto->RegisterDuktapeFunction(DukOvmsVehicleType, 0, "Type");
dto->RegisterDuktapeFunction(DukOvmsVehicleWakeup, 0, "Wakeup");
dto->RegisterDuktapeFunction(DukOvmsVehicleHomelink, 2, "Homelink");
dto->RegisterDuktapeFunction(DukOvmsVehicleClimateControl, 1, "ClimateControl");
dto->RegisterDuktapeFunction(DukOvmsVehicleLock, 1, "Lock");
dto->RegisterDuktapeFunction(DukOvmsVehicleUnlock, 1, "Unlock");
dto->RegisterDuktapeFunction(DukOvmsVehicleValet, 1, "Valet");
dto->RegisterDuktapeFunction(DukOvmsVehicleUnvalet, 1, "Unvalet");
dto->RegisterDuktapeFunction(DukOvmsVehicleSetChargeMode, 1, "SetChargeMode");
dto->RegisterDuktapeFunction(DukOvmsVehicleSetChargeCurrent, 1, "SetChargeCurrent");
dto->RegisterDuktapeFunction(DukOvmsVehicleSetChargeTimer, 2, "SetChargeTimer");
dto->RegisterDuktapeFunction(DukOvmsVehicleStopCharge, 0, "StopCharge");
dto->RegisterDuktapeFunction(DukOvmsVehicleStartCharge, 0, "StartCharge");
dto->RegisterDuktapeFunction(DukOvmsVehicleStartCooldown, 0, "StartCooldown");
dto->RegisterDuktapeFunction(DukOvmsVehicleStopCooldown, 0, "StopCooldown");
dto->RegisterDuktapeFunction(DukOvmsVehicleObdRequest, 1, "ObdRequest");
MyDuktape.RegisterDuktapeObject(dto);
#endif // #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
}
OvmsVehicleFactory::~OvmsVehicleFactory()
{
if (m_currentvehicle)
{
m_currentvehicle->m_ready = false;
delete m_currentvehicle;
m_currentvehicle = NULL;
m_currentvehicletype.clear();
}
}
OvmsVehicle* OvmsVehicleFactory::NewVehicle(const char* VehicleType)
{
OvmsVehicleFactory::map_vehicle_t::iterator iter = m_vmap.find(VehicleType);
if (iter != m_vmap.end())
{
return iter->second.construct();
}
return NULL;
}
void OvmsVehicleFactory::ClearVehicle()
{
if (m_currentvehicle)
{
m_currentvehicle->m_ready = false;
delete m_currentvehicle;
m_currentvehicle = NULL;
m_currentvehicletype.clear();
StandardMetrics.ms_v_type->SetValue("");
MyEvents.SignalEvent("vehicle.type.cleared", NULL);
}
}
void OvmsVehicleFactory::SetVehicle(const char* type)
{
if (m_currentvehicle)
{
m_currentvehicle->m_ready = false;
delete m_currentvehicle;
m_currentvehicle = NULL;
m_currentvehicletype.clear();
MyEvents.SignalEvent("vehicle.type.cleared", NULL);
}
m_currentvehicle = NewVehicle(type);
if (m_currentvehicle)
{
m_currentvehicle->m_ready = true;
}
m_currentvehicletype = std::string(type);
StandardMetrics.ms_v_type->SetValue(m_currentvehicle ? type : "");
MyEvents.SignalEvent("vehicle.type.set", (void*)type, strlen(type)+1);
}
void OvmsVehicleFactory::AutoInit()
{
std::string type = MyConfig.GetParamValue("auto", "vehicle.type");
if (!type.empty())
SetVehicle(type.c_str());
}
OvmsVehicle* OvmsVehicleFactory::ActiveVehicle()
{
return m_currentvehicle;
}
const char* OvmsVehicleFactory::ActiveVehicleType()
{
return m_currentvehicletype.c_str();
}
const char* OvmsVehicleFactory::ActiveVehicleName()
{
map_vehicle_t::iterator it = m_vmap.find(m_currentvehicletype.c_str());
if (it != m_vmap.end())
return it->second.name;
return "";
}
const char* OvmsVehicleFactory::ActiveVehicleShortName()
{
return m_currentvehicle ? m_currentvehicle->VehicleShortName() : "";
}
static void OvmsVehicleRxTask(void *pvParameters)
{
OvmsVehicle *me = (OvmsVehicle*)pvParameters;
me->RxTask();
}
OvmsVehicle::OvmsVehicle()
{
using std::placeholders::_1;
using std::placeholders::_2;
m_can1 = NULL;
m_can2 = NULL;
m_can3 = NULL;
m_can4 = NULL;
m_last_chargetime = 0;
m_last_drivetime = 0;
m_last_gentime = 0;
m_last_parktime = 0;
m_drive_startsoc = StdMetrics.ms_v_bat_soc->AsFloat();
m_drive_startrange = StdMetrics.ms_v_bat_range_est->AsFloat();
m_drive_startaltitude = StdMetrics.ms_v_pos_altitude->AsFloat();
m_drive_speedcnt = 0;
m_drive_speedsum = 0;
m_drive_accelcnt = 0;
m_drive_accelsum = 0;
m_drive_decelcnt = 0;
m_drive_decelsum = 0;
m_ticker = 0;
m_12v_ticker = 0;
m_chargestate_ticker = 0;
m_vehicleon_ticker = 0;
m_vehicleoff_ticker = 0;
m_idle_ticker = 0;
m_registeredlistener = false;
m_autonotifications = true;
m_ready = false;
m_poll_state = 0;
m_poll_bus = NULL;
m_poll_bus_default = NULL;
m_poll_txcallback = std::bind(&OvmsVehicle::PollerTxCallback, this, _1, _2);
m_poll_plist = NULL;
m_poll_plcur = NULL;
m_poll_entry = {};
m_poll_vwtp = {};
m_poll_ticker = 0;
m_poll_single_rxbuf = NULL;
m_poll_single_rxerr = 0;
m_poll_moduleid_sent = 0;
m_poll_moduleid_low = 0;
m_poll_moduleid_high = 0;
m_poll_type = 0;
m_poll_pid = 0;
m_poll_ml_remain = 0;
m_poll_ml_offset = 0;
m_poll_ml_frame = 0;
m_poll_wait = 0;
m_poll_sequence_max = 1;
m_poll_sequence_cnt = 0;
m_poll_fc_septime = 25; // response default timing: 25 milliseconds
m_poll_ch_keepalive = 60; // channel keepalive default: 60 seconds
m_bms_voltages = NULL;
m_bms_vmins = NULL;
m_bms_vmaxs = NULL;
m_bms_vdevmaxs = NULL;
m_bms_valerts = NULL;
m_bms_valerts_new = 0;
m_bms_vstddev_cnt = 0;
m_bms_vstddev_avg = 0;
m_bms_has_voltages = false;
m_bms_temperatures = NULL;
m_bms_tmins = NULL;
m_bms_tmaxs = NULL;
m_bms_tdevmaxs = NULL;
m_bms_talerts = NULL;
m_bms_talerts_new = 0;
m_bms_has_temperatures = false;
m_bms_bitset_v.clear();
m_bms_bitset_t.clear();
m_bms_bitset_cv = 0;
m_bms_bitset_ct = 0;
m_bms_readings_v = 0;
m_bms_readingspermodule_v = 0;
m_bms_readings_t = 0;
m_bms_readingspermodule_t = 0;
m_bms_limit_tmin = -1000;
m_bms_limit_tmax = 1000;
m_bms_limit_vmin = -1000;
m_bms_limit_vmax = 1000;
m_bms_defthr_vmaxgrad = BMS_DEFTHR_VMAXGRAD;
m_bms_defthr_vmaxsddev = BMS_DEFTHR_VMAXSDDEV;
m_bms_defthr_vwarn = BMS_DEFTHR_VWARN;
m_bms_defthr_valert = BMS_DEFTHR_VALERT;
m_bms_defthr_twarn = BMS_DEFTHR_TWARN;
m_bms_defthr_talert = BMS_DEFTHR_TALERT;
m_bms_vlog_last = 0;
m_bms_tlog_last = 0;
m_minsoc = 0;
m_minsoc_triggered = 0;
m_accel_refspeed = 0;
m_accel_reftime = 0;
m_accel_smoothing = 2.0;
m_batpwr_smoothing = 2.0;
m_batpwr_smoothed = 0;
m_brakelight_enable = false;
m_brakelight_on = 1.3;
m_brakelight_off = 0.7;
m_brakelight_port = 1;
m_brakelight_start = 0;
m_brakelight_basepwr = 0;
m_brakelight_ignftbrk = false;
m_tpms_lastcheck = 0;
m_rxqueue = xQueueCreate(CONFIG_OVMS_VEHICLE_CAN_RX_QUEUE_SIZE,sizeof(CAN_frame_t));
xTaskCreatePinnedToCore(OvmsVehicleRxTask, "OVMS Vehicle",
CONFIG_OVMS_VEHICLE_RXTASK_STACK, (void*)this, 10, &m_rxtask, CORE(1));
MyEvents.RegisterEvent(TAG, "ticker.1", std::bind(&OvmsVehicle::VehicleTicker1, this, _1, _2));
MyEvents.RegisterEvent(TAG, "config.changed", std::bind(&OvmsVehicle::VehicleConfigChanged, this, _1, _2));
MyEvents.RegisterEvent(TAG, "config.mounted", std::bind(&OvmsVehicle::VehicleConfigChanged, this, _1, _2));
VehicleConfigChanged("config.mounted", NULL);
MyMetrics.RegisterListener(TAG, "*", std::bind(&OvmsVehicle::MetricModified, this, _1));
}
OvmsVehicle::~OvmsVehicle()
{
if (m_can1) m_can1->SetPowerMode(Off);
if (m_can2) m_can2->SetPowerMode(Off);
if (m_can3) m_can3->SetPowerMode(Off);
if (m_can4) m_can4->SetPowerMode(Off);
if (m_bms_voltages != NULL)
{
delete [] m_bms_voltages;
m_bms_voltages = NULL;
}
if (m_bms_vmins != NULL)
{
delete [] m_bms_vmins;
m_bms_vmins = NULL;
}
if (m_bms_vmaxs != NULL)
{
delete [] m_bms_vmaxs;
m_bms_vmaxs = NULL;
}
if (m_bms_vdevmaxs != NULL)
{
delete [] m_bms_vdevmaxs;
m_bms_vdevmaxs = NULL;
}
if (m_bms_valerts != NULL)
{
delete [] m_bms_valerts;
m_bms_valerts = NULL;
}
if (m_bms_temperatures != NULL)
{
delete [] m_bms_temperatures;
m_bms_temperatures = NULL;
}
if (m_bms_tmins != NULL)
{
delete [] m_bms_tmins;
m_bms_tmins = NULL;
}
if (m_bms_tmaxs != NULL)
{
delete [] m_bms_tmaxs;
m_bms_tmaxs = NULL;
}
if (m_bms_tdevmaxs != NULL)
{
delete [] m_bms_tdevmaxs;
m_bms_tdevmaxs = NULL;
}
if (m_bms_talerts != NULL)
{
delete [] m_bms_talerts;
m_bms_talerts = NULL;
}
if (m_registeredlistener)
{
MyCan.DeregisterListener(m_rxqueue);
m_registeredlistener = false;
}
vQueueDelete(m_rxqueue);
vTaskDelete(m_rxtask);
MyEvents.DeregisterEvent(TAG);
MyMetrics.DeregisterListener(TAG);
}
const char* OvmsVehicle::VehicleShortName()
{
return MyVehicleFactory.ActiveVehicleName();
}
const char* OvmsVehicle::VehicleType()
{
return MyVehicleFactory.ActiveVehicleType();
}
void OvmsVehicle::RxTask()
{
CAN_frame_t frame;
while(1)
{
if (xQueueReceive(m_rxqueue, &frame, (portTickType)portMAX_DELAY)==pdTRUE)
{
if (!m_ready)
continue;
// Pass frame to poller protocol handlers:
if (frame.origin == m_poll_vwtp.bus && frame.MsgID == m_poll_vwtp.rxid)
{
PollerVWTPReceive(&frame, frame.MsgID);
}
else if (m_poll_wait && frame.origin == m_poll_bus && m_poll_plist)
{
uint32_t msgid;
if (m_poll_protocol == ISOTP_EXTADR)
msgid = frame.MsgID << 8 | frame.data.u8[0];
else
msgid = frame.MsgID;
if (msgid >= m_poll_moduleid_low && msgid <= m_poll_moduleid_high)
{
PollerISOTPReceive(&frame, msgid);
}
}
// Pass frame to standard handlers:
if (m_can1 == frame.origin) IncomingFrameCan1(&frame);
else if (m_can2 == frame.origin) IncomingFrameCan2(&frame);
else if (m_can3 == frame.origin) IncomingFrameCan3(&frame);
else if (m_can4 == frame.origin) IncomingFrameCan4(&frame);
}
}
}
void OvmsVehicle::IncomingFrameCan1(CAN_frame_t* p_frame)
{
}
void OvmsVehicle::IncomingFrameCan2(CAN_frame_t* p_frame)
{
}
void OvmsVehicle::IncomingFrameCan3(CAN_frame_t* p_frame)
{
}
void OvmsVehicle::IncomingFrameCan4(CAN_frame_t* p_frame)
{
}
void OvmsVehicle::Status(int verbosity, OvmsWriter* writer)
{
writer->printf("Vehicle module '%s' (code %s) loaded and running\n", VehicleShortName(), VehicleType());
}
void OvmsVehicle::RegisterCanBus(int bus, CAN_mode_t mode, CAN_speed_t speed, dbcfile* dbcfile)
{
switch (bus)
{
case 1:
m_can1 = (canbus*)MyPcpApp.FindDeviceByName("can1");
m_can1->SetPowerMode(On);
m_can1->Start(mode,speed,dbcfile);
break;
case 2:
m_can2 = (canbus*)MyPcpApp.FindDeviceByName("can2");
m_can2->SetPowerMode(On);
m_can2->Start(mode,speed,dbcfile);
break;
case 3:
m_can3 = (canbus*)MyPcpApp.FindDeviceByName("can3");
m_can3->SetPowerMode(On);
m_can3->Start(mode,speed,dbcfile);
break;
case 4:
m_can4 = (canbus*)MyPcpApp.FindDeviceByName("can4");
m_can4->SetPowerMode(On);
m_can4->Start(mode,speed,dbcfile);
break;
default:
break;
}
if (!m_registeredlistener)
{
m_registeredlistener = true;
MyCan.RegisterListener(m_rxqueue);
}
}
bool OvmsVehicle::PinCheck(char* pin)
{
if (!MyConfig.IsDefined("password","pin")) return false;
std::string vpin = MyConfig.GetParamValue("password","pin");
return (strcmp(vpin.c_str(),pin)==0);
}
void OvmsVehicle::VehicleTicker1(std::string event, void* data)
{
if (!m_ready)
return;
m_ticker++;
PollerStateTicker();
PollerSend(true);
Ticker1(m_ticker);
if ((m_ticker % 10) == 0) Ticker10(m_ticker);
if ((m_ticker % 60) == 0) Ticker60(m_ticker);
if ((m_ticker % 300) == 0) Ticker300(m_ticker);
if ((m_ticker % 600) == 0) Ticker600(m_ticker);
if ((m_ticker % 3600) == 0) Ticker3600(m_ticker);
if (StandardMetrics.ms_v_env_on->AsBool())
{
StandardMetrics.ms_v_env_parktime->SetValue(0);
m_last_drivetime = StandardMetrics.ms_v_env_drivetime->AsInt() + 1;
StandardMetrics.ms_v_env_drivetime->SetValue(m_last_drivetime);
}
else
{
StandardMetrics.ms_v_env_drivetime->SetValue(0);
m_last_parktime = StandardMetrics.ms_v_env_parktime->AsInt() + 1;
StandardMetrics.ms_v_env_parktime->SetValue(m_last_parktime);
}
if (StandardMetrics.ms_v_charge_inprogress->AsBool())
{
m_last_chargetime = StandardMetrics.ms_v_charge_time->AsInt() + 1;
StandardMetrics.ms_v_charge_time->SetValue(m_last_chargetime);
}
else
{
StandardMetrics.ms_v_charge_time->SetValue(0);
}
if (StandardMetrics.ms_v_gen_inprogress->AsBool())
{
m_last_gentime = StandardMetrics.ms_v_gen_time->AsInt() + 1;
StandardMetrics.ms_v_gen_time->SetValue(m_last_gentime);
}
else
{
StandardMetrics.ms_v_gen_time->SetValue(0);
}
if (m_chargestate_ticker > 0 && --m_chargestate_ticker == 0)
NotifyChargeState();
if (m_vehicleon_ticker > 0 && --m_vehicleon_ticker == 0)
NotifyVehicleOn();
if (m_vehicleoff_ticker > 0 && --m_vehicleoff_ticker == 0)
NotifyVehicleOff();
CalculateEfficiency();
// 12V battery monitor:
if (StandardMetrics.ms_v_env_charging12v->AsBool() == true)
{
// add two seconds calmdown per second charging, max 15 minutes:
if (m_12v_ticker < 15*60)
m_12v_ticker += 2;
}
else if (m_12v_ticker > 0)
{
--m_12v_ticker;
if (m_12v_ticker == 0)
{
// take 12V reference voltage:
StandardMetrics.ms_v_bat_12v_voltage_ref->SetValue(StandardMetrics.ms_v_bat_12v_voltage->AsFloat());
}
}
if ((m_ticker % 60) == 0)
{
// check 12V voltage:
float volt = StandardMetrics.ms_v_bat_12v_voltage->AsFloat();
// …against the maximum of default and measured reference voltage, so alerts will also
// be triggered if the measured ref follows a degrading battery:
float dref = MyConfig.GetParamValueFloat("vehicle", "12v.ref", 12.6);
float vref = MAX(StandardMetrics.ms_v_bat_12v_voltage_ref->AsFloat(), dref);
bool alert_on = StandardMetrics.ms_v_bat_12v_voltage_alert->AsBool();
float alert_threshold = MyConfig.GetParamValueFloat("vehicle", "12v.alert", 1.6);
if (!alert_on && volt > 0 && vref > 0 && vref-volt > alert_threshold)
{
StandardMetrics.ms_v_bat_12v_voltage_alert->SetValue(true);
MyEvents.SignalEvent("vehicle.alert.12v.on", NULL);
if (m_autonotifications) Notify12vCritical();
}
else if (alert_on && volt > 0 && vref > 0 && vref-volt < alert_threshold*0.6)
{
StandardMetrics.ms_v_bat_12v_voltage_alert->SetValue(false);
MyEvents.SignalEvent("vehicle.alert.12v.off", NULL);
if (m_autonotifications) Notify12vRecovered();
}
}
if ((m_ticker % 10)==0)
{
// Check MINSOC
int soc = (int) ceil(StandardMetrics.ms_v_bat_soc->AsFloat());
m_minsoc = MyConfig.GetParamValueInt("vehicle", "minsoc", 0);
if (m_minsoc <= 0)
{
m_minsoc_triggered = 0;
}
else if (soc >= m_minsoc+2)
{
m_minsoc_triggered = m_minsoc;
}
if ((m_minsoc_triggered > 0) && (soc <= m_minsoc_triggered))
{
// We have reached the minimum SOC level
if (m_autonotifications) NotifyMinSocCritical();
if (soc > 1)
m_minsoc_triggered = soc - 1;
else
m_minsoc_triggered = 0;
}
}
// BMS ticker:
BmsTicker();
// TPMS alerts:
if (StdMetrics.ms_v_tpms_alert->LastModified() > m_tpms_lastcheck)
{
m_tpms_lastcheck = StdMetrics.ms_v_tpms_alert->LastModified();
auto tpms_state = StdMetrics.ms_v_tpms_alert->AsVector();
m_tpms_laststate.resize(tpms_state.size());
bool notify = false;
for (int i = 0; i < tpms_state.size(); i++)
{
if (tpms_state[i] > m_tpms_laststate[i])
notify = true;
m_tpms_laststate[i] = tpms_state[i];
}
if (notify)
{
MyEvents.SignalEvent("vehicle.alert.tpms", NULL);
if (m_autonotifications && MyConfig.GetParamValueBool("vehicle", "tpms.alerts.enabled", true))
NotifyTpmsAlerts();
}
}
// Idle alert:
if (!StdMetrics.ms_v_env_awake->AsBool() || StdMetrics.ms_v_pos_speed->AsFloat() > 0)
{
m_idle_ticker = 15 * 60; // first alert after 15 minutes
}
else if (m_idle_ticker > 0 && --m_idle_ticker == 0)
{
NotifyVehicleIdling();
m_idle_ticker = 60 * 60; // successive alerts every 60 minutes
}
} // VehicleTicker1()
void OvmsVehicle::Ticker1(uint32_t ticker)
{
}
void OvmsVehicle::Ticker10(uint32_t ticker)
{
}
void OvmsVehicle::Ticker60(uint32_t ticker)
{
}
void OvmsVehicle::Ticker300(uint32_t ticker)
{
}
void OvmsVehicle::Ticker600(uint32_t ticker)
{
}
void OvmsVehicle::Ticker3600(uint32_t ticker)
{
}
void OvmsVehicle::NotifyChargeStart()
{
StringWriter buf(200);
CommandStat(COMMAND_RESULT_NORMAL, &buf);
MyNotify.NotifyString("info","charge.started",buf.c_str());
}
void OvmsVehicle::NotifyChargeTopOff()
{
StringWriter buf(200);
CommandStat(COMMAND_RESULT_NORMAL, &buf);
MyNotify.NotifyString("info","charge.toppingoff",buf.c_str());
}
void OvmsVehicle::NotifyHeatingStart()
{
StringWriter buf(200);
CommandStat(COMMAND_RESULT_NORMAL, &buf);
MyNotify.NotifyString("info","heating.started",buf.c_str());
}
void OvmsVehicle::NotifyChargeStopped()
{
StringWriter buf(200);
CommandStat(COMMAND_RESULT_NORMAL, &buf);
if (StdMetrics.ms_v_charge_substate->AsString() == "scheduledstop" ||
StdMetrics.ms_v_charge_substate->AsString() == "timerwait" ||
StdMetrics.ms_v_charge_substate->AsString() == "onrequest")
MyNotify.NotifyString("info","charge.stopped",buf.c_str());
else
MyNotify.NotifyString("alert","charge.stopped",buf.c_str());
}
void OvmsVehicle::NotifyChargeDone()
{
StringWriter buf(200);
CommandStat(COMMAND_RESULT_NORMAL, &buf);
MyNotify.NotifyString("info","charge.done",buf.c_str());
}
void OvmsVehicle::NotifyValetEnabled()
{
MyNotify.NotifyString("info", "valet.enabled", "Valet mode enabled");
}
void OvmsVehicle::NotifyValetDisabled()
{
MyNotify.NotifyString("info", "valet.disabled", "Valet mode disabled");
}
void OvmsVehicle::NotifyValetHood()
{
MyNotify.NotifyString("alert", "valet.hood", "Vehicle hood opened while in valet mode");
}
void OvmsVehicle::NotifyValetTrunk()
{
MyNotify.NotifyString("alert", "valet.trunk", "Vehicle trunk opened while in valet mode");
}
void OvmsVehicle::NotifyAlarmSounding()
{
MyNotify.NotifyString("alert", "alarm.sounding", "Vehicle alarm is sounding");
}
void OvmsVehicle::NotifyAlarmStopped()
{
MyNotify.NotifyString("alert", "alarm.stopped", "Vehicle alarm has stopped");
}
void OvmsVehicle::Notify12vCritical()
{
float volt = StandardMetrics.ms_v_bat_12v_voltage->AsFloat();
float dref = MyConfig.GetParamValueFloat("vehicle", "12v.ref", 12.6);
float vref = MAX(StandardMetrics.ms_v_bat_12v_voltage_ref->AsFloat(), dref);
MyNotify.NotifyStringf("alert", "batt.12v.alert", "12V Battery critical: %.1fV (ref=%.1fV)", volt, vref);
}
void OvmsVehicle::Notify12vRecovered()
{
float volt = StandardMetrics.ms_v_bat_12v_voltage->AsFloat();
float dref = MyConfig.GetParamValueFloat("vehicle", "12v.ref", 12.6);
float vref = MAX(StandardMetrics.ms_v_bat_12v_voltage_ref->AsFloat(), dref);
MyNotify.NotifyStringf("alert", "batt.12v.recovered", "12V Battery restored: %.1fV (ref=%.1fV)", volt, vref);
}
void OvmsVehicle::NotifyMinSocCritical()
{
float soc = StandardMetrics.ms_v_bat_soc->AsFloat();
MyNotify.NotifyStringf("alert", "batt.soc.alert", "Battery SOC critical: %.1f%% (alert<=%d%%)", soc, m_minsoc);
}
void OvmsVehicle::NotifyVehicleIdling()
{
MyNotify.NotifyString("alert", "vehicle.idle", "Vehicle is idling / stopped turned on");
}
std::vector<std::string> OvmsVehicle::GetTpmsLayout()
{
return { "FL", "FR", "RL", "RR" };
}
void OvmsVehicle::NotifyTpmsAlerts()
{
int maxlevel = 0;
for (int i = 0; i < m_tpms_laststate.size(); i++)
{
if (m_tpms_laststate[i] > maxlevel)
maxlevel = m_tpms_laststate[i];
}
if (maxlevel == 0)
return;
StringWriter buf(200);
std::vector<std::string> wheels = GetTpmsLayout();
const char* alertlevel[] = { "OK", "WARNING", "ALERT" };
buf.printf("TPMS %s:\n", maxlevel == 1 ? "INFO" : "ALERT");
for (int i = 0; i < m_tpms_laststate.size(); i++)
{
if (m_tpms_laststate[i])
{
buf.printf("%s wheel %s:", wheels[i].c_str(), alertlevel[m_tpms_laststate[i]]);
if (StdMetrics.ms_v_tpms_health->IsDefined())
buf.printf(" Health=%s", StdMetrics.ms_v_tpms_health->ElemAsUnitString(i, "", Native, 0).c_str());
if (StdMetrics.ms_v_tpms_pressure->IsDefined())
buf.printf(" Pressure=%s", StdMetrics.ms_v_tpms_pressure->ElemAsUnitString(i, "", Native, 1).c_str());
if (StdMetrics.ms_v_tpms_temp->IsDefined())
buf.printf(" Temp=%sC", StdMetrics.ms_v_tpms_temp->ElemAsString(i, "", Native, 1).c_str());
buf.append("\n");
}
}
if (maxlevel == 1)
MyNotify.NotifyString("info", "tpms.warning", buf.c_str());
else
MyNotify.NotifyString("alert", "tpms.alert", buf.c_str());
}
// Default efficiency calculation by speed & power per second, average smoothed over 5 seconds.
// Override if your vehicle provides more detail.
void OvmsVehicle::CalculateEfficiency()
{
float consumption = 0;
if (StdMetrics.ms_v_pos_speed->AsFloat() >= 5)
consumption = StdMetrics.ms_v_bat_power->AsFloat(0, Watts) / StdMetrics.ms_v_pos_speed->AsFloat();
StdMetrics.ms_v_bat_consumption->SetValue(
TRUNCPREC((StdMetrics.ms_v_bat_consumption->AsFloat() * 4 + consumption) / 5, 1));
}
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandSetChargeMode(vehicle_mode_t mode)
{
return NotImplemented;
}
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandSetChargeCurrent(uint16_t limit)
{
return NotImplemented;
}
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandStartCharge()
{
return NotImplemented;
}
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandStopCharge()
{
return NotImplemented;
}
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandSetChargeTimer(bool timeron, uint16_t timerstart)
{
return NotImplemented;
}
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandCooldown(bool cooldownon)
{
return NotImplemented;
}
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandClimateControl(bool climatecontrolon)
{
return NotImplemented;
}
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandWakeup()
{
return NotImplemented;
}
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandLock(const char* pin)
{
return NotImplemented;
}
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandUnlock(const char* pin)
{
return NotImplemented;
}
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandActivateValet(const char* pin)
{
return NotImplemented;
}
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandDeactivateValet(const char* pin)
{
return NotImplemented;
}
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandHomelink(int button, int durationms)
{
return NotImplemented;
}
#ifdef CONFIG_OVMS_COMP_TPMS
bool OvmsVehicle::TPMSRead(std::vector<uint32_t> *tpms)
{
ESP_LOGE(TAG, "TPMS tyre IDs not implemented in this vehicle");
return false;
}
bool OvmsVehicle::TPMSWrite(std::vector<uint32_t> &tpms)
{
ESP_LOGE(TAG, "TPMS tyre IDs not implemented in this vehicle");
return false;
}
#endif // #ifdef CONFIG_OVMS_COMP_TPMS
/**
* CommandStat: default implementation of vehicle status output
*/
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandStat(int verbosity, OvmsWriter* writer)
{
metric_unit_t rangeUnit = (MyConfig.GetParamValue("vehicle", "units.distance") == "M") ? Miles : Kilometers;
bool chargeport_open = StdMetrics.ms_v_door_chargeport->AsBool();
std::string charge_state = StdMetrics.ms_v_charge_state->AsString();
if (chargeport_open && charge_state != "")
{
std::string charge_mode = StdMetrics.ms_v_charge_mode->AsString();
bool show_details = !(charge_state == "done" || charge_state == "stopped");
// Translate mode codes:
if (charge_mode == "standard")
charge_mode = "Standard";
else if (charge_mode == "storage")
charge_mode = "Storage";
else if (charge_mode == "range")
charge_mode = "Range";
else if (charge_mode == "performance")
charge_mode = "Performance";
// Translate state codes:
if (charge_state == "charging")
charge_state = "Charging";
else if (charge_state == "topoff")
charge_state = "Topping off";
else if (charge_state == "done")
charge_state = "Charge Done";
else if (charge_state == "preparing")
charge_state = "Preparing";
else if (charge_state == "heating")
charge_state = "Charging, Heating";
else if (charge_state == "stopped")
charge_state = "Charge Stopped";
else if (charge_state == "timerwait")
charge_state = "Charge Stopped, Timer On";
if (charge_mode != "")
writer->printf("%s - ", charge_mode.c_str());
writer->printf("%s\n", charge_state.c_str());
if (show_details)
{
// Voltage & current:
bool show_vc = (StdMetrics.ms_v_charge_voltage->AsFloat() > 0 || StdMetrics.ms_v_charge_current->AsFloat() > 0);
if (show_vc)
{
writer->printf("%s/%s ",
(char*) StdMetrics.ms_v_charge_voltage->AsUnitString("-", Native, 1).c_str(),
(char*) StdMetrics.ms_v_charge_current->AsUnitString("-", Native, 1).c_str());
}
// Charge speed:
if (StdMetrics.ms_v_bat_range_speed->AsFloat() != 0)
{
metric_unit_t speedUnit = (rangeUnit == Miles) ? Mph : Kph;
writer->printf("%s\n", StdMetrics.ms_v_bat_range_speed->AsUnitString("-", speedUnit, 1).c_str());
}
else if (show_vc)
{
writer->puts("");
}
// Estimated time(s) remaining:
int duration_full = StdMetrics.ms_v_charge_duration_full->AsInt();
if (duration_full > 0)
writer->printf("Full: %d:%02dh\n", duration_full / 60, duration_full % 60);
int duration_soc = StdMetrics.ms_v_charge_duration_soc->AsInt();
if (duration_soc > 0)
writer->printf("%s: %d:%02dh\n",
(char*) StdMetrics.ms_v_charge_limit_soc->AsUnitString("SOC", Native, 0).c_str(),
duration_soc / 60, duration_soc % 60);
int duration_range = StdMetrics.ms_v_charge_duration_range->AsInt();
if (duration_range > 0)
writer->printf("%s: %d:%02dh\n",
(char*) StdMetrics.ms_v_charge_limit_range->AsUnitString("Range", rangeUnit, 0).c_str(),
duration_range / 60, duration_range % 60);
}
// Energy sums:
if (StdMetrics.ms_v_charge_kwh_grid->IsDefined())
{
writer->printf("Drawn: %s\n",
StdMetrics.ms_v_charge_kwh_grid->AsUnitString("-", Native, 1).c_str());
}
if (StdMetrics.ms_v_charge_kwh->IsDefined())
{
writer->printf("Charged: %s\n",
StdMetrics.ms_v_charge_kwh->AsUnitString("-", Native, 1).c_str());
}
}
else
{
writer->puts("Not charging");
}
writer->printf("SOC: %s\n", (char*) StdMetrics.ms_v_bat_soc->AsUnitString("-", Native, 1).c_str());
if (StdMetrics.ms_v_bat_range_ideal->IsDefined())
{
const std::string& range_ideal = StdMetrics.ms_v_bat_range_ideal->AsUnitString("-", rangeUnit, 0);
writer->printf("Ideal range: %s\n", range_ideal.c_str());
}
if (StdMetrics.ms_v_bat_range_est->IsDefined())
{
const std::string& range_est = StdMetrics.ms_v_bat_range_est->AsUnitString("-", rangeUnit, 0);
writer->printf("Est. range: %s\n", range_est.c_str());
}
if (StdMetrics.ms_v_pos_odometer->IsDefined())
{
const std::string& odometer = StdMetrics.ms_v_pos_odometer->AsUnitString("-", rangeUnit, 1);
writer->printf("ODO: %s\n", odometer.c_str());
}
if (StdMetrics.ms_v_bat_cac->IsDefined())
{
const std::string& cac = StdMetrics.ms_v_bat_cac->AsUnitString("-", Native, 1);
writer->printf("CAC: %s\n", cac.c_str());
}
if (StdMetrics.ms_v_bat_soh->IsDefined())
{
const std::string& soh = StdMetrics.ms_v_bat_soh->AsUnitString("-", Native, 0);
writer->printf("SOH: %s\n", soh.c_str());
}
return Success;
}
/**
* CommandStatTrip: default implementation of vehicle trip status report
*/
OvmsVehicle::vehicle_command_t OvmsVehicle::CommandStatTrip(int verbosity, OvmsWriter* writer)
{
metric_unit_t rangeUnit = (MyConfig.GetParamValue("vehicle", "units.distance") == "M") ? Miles : Kilometers;
metric_unit_t speedUnit = (rangeUnit == Miles) ? Mph : Kph;
metric_unit_t accelUnit = (rangeUnit == Miles) ? MphPS : KphPS;
metric_unit_t consumUnit = (rangeUnit == Miles) ? WattHoursPM : WattHoursPK;
metric_unit_t energyUnit = kWh;
metric_unit_t altitudeUnit = (rangeUnit == Miles) ? Feet : Meters;
const char* rangeUnitLabel = OvmsMetricUnitLabel(rangeUnit);
const char* speedUnitLabel = OvmsMetricUnitLabel(speedUnit);
const char* accelUnitLabel = OvmsMetricUnitLabel(accelUnit);
const char* consumUnitLabel = OvmsMetricUnitLabel(consumUnit);
const char* energyUnitLabel = OvmsMetricUnitLabel(energyUnit);
const char* altitudeUnitLabel = OvmsMetricUnitLabel(altitudeUnit);
float trip_length = StdMetrics.ms_v_pos_trip->AsFloat(0, rangeUnit);
float speed_avg = (m_drive_speedcnt > 0)
? UnitConvert(Kph, speedUnit, (float)(m_drive_speedsum / m_drive_speedcnt))
: 0;
float accel_avg = (m_drive_accelcnt > 0)
? UnitConvert(MetersPSS, accelUnit, (float)(m_drive_accelsum / m_drive_accelcnt))
: 0;
float decel_avg = (m_drive_decelcnt > 0)
? UnitConvert(MetersPSS, accelUnit, (float)(m_drive_decelsum / m_drive_decelcnt))
: 0;
float energy_recup_perc = (m_inv_energyused > 0) ? m_inv_energyrecd / m_inv_energyused * 100 : 0;
float energy_used = StdMetrics.ms_v_bat_energy_used->AsFloat();
float energy_recd = StdMetrics.ms_v_bat_energy_recd->AsFloat();
float energy_recd_perc = (energy_used > 0) ? energy_recd / energy_used * 100 : 0;
float wh_per_rangeunit = (trip_length > 0) ? (energy_used - energy_recd) * 1000 / trip_length : 0;
float soc = StdMetrics.ms_v_bat_soc->AsFloat();
float soc_diff = soc - m_drive_startsoc;
float range = StdMetrics.ms_v_bat_range_est->AsFloat();
float range_diff = range - m_drive_startrange;
float alt = StdMetrics.ms_v_pos_altitude->AsFloat();
float alt_diff = UnitConvert(Meters, altitudeUnit, alt - m_drive_startaltitude);
std::ostringstream buf;
buf
<< "Trip "
<< std::fixed
<< std::setprecision(1)
<< trip_length << rangeUnitLabel
<< " Avg "
<< std::setprecision(0)
<< speed_avg << speedUnitLabel
<< " Alt "
<< ((alt_diff >= 0) ? "+" : "")
<< alt_diff << altitudeUnitLabel
;
if (wh_per_rangeunit != 0)
{
buf
<< "\nEnergy "
<< wh_per_rangeunit << consumUnitLabel
<< ", "
<< energy_recd_perc << "% recd"
;
}
buf
<< std::setprecision(1)
<< "\nSOC "
<< ((soc_diff >= 0) ? "+" : "")
<< soc_diff << "%"
<< " = "
<< soc << "%"
<< "\nRange "
<< ((range_diff >= 0) ? "+" : "")
<< range_diff << rangeUnitLabel
<< " = "
<< range << rangeUnitLabel
;
if (accel_avg > 0)
{
buf
<< "\nAccel +"
<< accel_avg
<< " / "
<< decel_avg << accelUnitLabel
;
}
if (m_inv_energyused > 0)
{
buf
<< "\nMotor +"
<< std::setprecision(3)
<< m_inv_energyused
<< " / -"
<< m_inv_energyrecd << energyUnitLabel
<< std::setprecision(0)
<< " (" << energy_recup_perc << "% recd)"
;
}
writer->puts(buf.str().c_str());
return Success;
}
void OvmsVehicle::VehicleConfigChanged(std::string event, void* data)
{
OvmsConfigParam* param = (OvmsConfigParam*) data;
// read vehicle framework config:
if (!param || param->GetName() == "vehicle")
{
// acceleration calculation:
m_accel_smoothing = MyConfig.GetParamValueFloat("vehicle", "accel.smoothing", 2.0);
// brakelight battery power smoothing:
m_batpwr_smoothing = MyConfig.GetParamValueFloat("vehicle", "batpwr.smoothing", 2.0);
// brakelight control:
if (m_brakelight_enable)
{
SetBrakelight(0);
StdMetrics.ms_v_env_regenbrake->SetValue(false);
}
m_brakelight_enable = MyConfig.GetParamValueBool("vehicle", "brakelight.enable", false);
m_brakelight_on = MyConfig.GetParamValueFloat("vehicle", "brakelight.on", 1.3);
m_brakelight_off = MyConfig.GetParamValueFloat("vehicle", "brakelight.off", 0.7);
m_brakelight_port = MyConfig.GetParamValueInt("vehicle", "brakelight.port", 1);
m_brakelight_basepwr = MyConfig.GetParamValueFloat("vehicle", "brakelight.basepwr", 0);
m_brakelight_ignftbrk = MyConfig.GetParamValueBool("vehicle", "brakelight.ignftbrk", false);
m_brakelight_start = 0;
}
// read vehicle specific config:
ConfigChanged(param);
}
void OvmsVehicle::ConfigChanged(OvmsConfigParam* param)
{
}
void OvmsVehicle::MetricModified(OvmsMetric* metric)
{
if (metric == StandardMetrics.ms_v_env_on)
{
if (StandardMetrics.ms_v_env_on->AsBool())
{
m_drive_startsoc = StdMetrics.ms_v_bat_soc->AsFloat();
m_drive_startrange = StdMetrics.ms_v_bat_range_est->AsFloat();
m_drive_startaltitude = StdMetrics.ms_v_pos_altitude->AsFloat();
m_drive_speedcnt = 0;
m_drive_speedsum = 0;
m_drive_accelcnt = 0;
m_drive_accelsum = 0;
m_drive_decelcnt = 0;
m_drive_decelsum = 0;
m_inv_reftime = esp_log_timestamp();
m_inv_refpower = 0;
m_inv_energyused = 0;
m_inv_energyrecd = 0;
MyEvents.SignalEvent("vehicle.on",NULL);
if (m_autonotifications)
{
m_vehicleon_ticker = GetNotifyVehicleStateDelay("on");
if (m_vehicleon_ticker == 0)
NotifyVehicleOn();
}
}
else
{
if (m_brakelight_enable && m_brakelight_start)
{
SetBrakelight(0);
m_brakelight_start = 0;
StdMetrics.ms_v_env_regenbrake->SetValue(false);
}
MyEvents.SignalEvent("vehicle.off",NULL);
if (m_autonotifications)
{
m_vehicleoff_ticker = GetNotifyVehicleStateDelay("off");
if (m_vehicleoff_ticker == 0)
NotifyVehicleOff();
}
}
}
else if (metric == StandardMetrics.ms_v_env_awake)
{
if (StandardMetrics.ms_v_env_awake->AsBool())
{
MyEvents.SignalEvent("vehicle.awake",NULL);
NotifiedVehicleAwake();
}
else
{
MyEvents.SignalEvent("vehicle.asleep",NULL);
NotifiedVehicleAsleep();
}
}
else if (metric == StandardMetrics.ms_v_charge_inprogress)
{
if (StandardMetrics.ms_v_charge_inprogress->AsBool())
{
MyEvents.SignalEvent("vehicle.charge.start",NULL);
NotifiedVehicleChargeStart();
}
else
{
MyEvents.SignalEvent("vehicle.charge.stop",NULL);
NotifiedVehicleChargeStop();
}
}
else if (metric == StandardMetrics.ms_v_door_chargeport)
{
if (StandardMetrics.ms_v_door_chargeport->AsBool())
{
MyEvents.SignalEvent("vehicle.charge.prepare",NULL);
NotifiedVehicleChargePrepare();
}
else
{
MyEvents.SignalEvent("vehicle.charge.finish",NULL);
NotifiedVehicleChargeFinish();
}
}
else if (metric == StandardMetrics.ms_v_charge_pilot)
{
if (StandardMetrics.ms_v_charge_pilot->AsBool())
{
MyEvents.SignalEvent("vehicle.charge.pilot.on",NULL);
NotifiedVehicleChargePilotOn();
}
else
{
MyEvents.SignalEvent("vehicle.charge.pilot.off",NULL);
NotifiedVehicleChargePilotOff();
}
}
else if (metric == StandardMetrics.ms_v_charge_timermode)
{
if (StandardMetrics.ms_v_charge_timermode->AsBool())
{
MyEvents.SignalEvent("vehicle.charge.timermode.on",NULL);
NotifiedVehicleChargeTimermodeOn();
}
else
{
MyEvents.SignalEvent("vehicle.charge.timermode.off",NULL);
NotifiedVehicleChargeTimermodeOff();
}
}
else if (metric == StandardMetrics.ms_v_env_aux12v)
{
if (StandardMetrics.ms_v_env_aux12v->AsBool())
{
MyEvents.SignalEvent("vehicle.aux.12v.on", NULL);
NotifiedVehicleAux12vOn();
}
else
{
MyEvents.SignalEvent("vehicle.aux.12v.off", NULL);
NotifiedVehicleAux12vOff();
}
}
else if (metric == StandardMetrics.ms_v_env_charging12v)
{
if (StandardMetrics.ms_v_env_charging12v->AsBool())
{
if (m_12v_ticker < 30)
m_12v_ticker = 30; // min calmdown time
MyEvents.SignalEvent("vehicle.charge.12v.start",NULL);
NotifiedVehicleCharge12vStart();
}
else
{
MyEvents.SignalEvent("vehicle.charge.12v.stop",NULL);
NotifiedVehicleCharge12vStop();
}
}
else if (metric == StandardMetrics.ms_v_env_locked)
{
if (StandardMetrics.ms_v_env_locked->AsBool())
{
MyEvents.SignalEvent("vehicle.locked",NULL);
NotifiedVehicleLocked();
}
else
{
MyEvents.SignalEvent("vehicle.unlocked",NULL);
NotifiedVehicleUnlocked();
}
}
else if (metric == StandardMetrics.ms_v_env_valet)
{
if (StandardMetrics.ms_v_env_valet->AsBool())
{
MyEvents.SignalEvent("vehicle.valet.on",NULL);
if (m_autonotifications) NotifyValetEnabled();
NotifiedVehicleValetOn();
}
else
{
MyEvents.SignalEvent("vehicle.valet.off",NULL);
if (m_autonotifications) NotifyValetDisabled();
NotifiedVehicleValetOff();
}
}
else if (metric == StandardMetrics.ms_v_env_headlights)
{
if (StandardMetrics.ms_v_env_headlights->AsBool())
{
MyEvents.SignalEvent("vehicle.headlights.on",NULL);
NotifiedVehicleHeadlightsOn();
}
else
{
MyEvents.SignalEvent("vehicle.headlights.off",NULL);
NotifiedVehicleHeadlightsOff();
}
}
else if (metric == StandardMetrics.ms_v_door_hood)
{
if (StandardMetrics.ms_v_door_hood->AsBool() &&
StandardMetrics.ms_v_env_valet->AsBool())
{
if (m_autonotifications) NotifyValetHood();
}
}
else if (metric == StandardMetrics.ms_v_door_trunk)
{
if (StandardMetrics.ms_v_door_trunk->AsBool() &&
StandardMetrics.ms_v_env_valet->AsBool())
{
if (m_autonotifications) NotifyValetTrunk();
}
}
else if (metric == StandardMetrics.ms_v_env_alarm)
{
if (StandardMetrics.ms_v_env_alarm->AsBool())
{
MyEvents.SignalEvent("vehicle.alarm.on",NULL);
if (m_autonotifications) NotifyAlarmSounding();
NotifiedVehicleAlarmOn();
}
else
{
MyEvents.SignalEvent("vehicle.alarm.off",NULL);
if (m_autonotifications) NotifyAlarmStopped();
NotifiedVehicleAlarmOff();
}
}
else if (metric == StandardMetrics.ms_v_env_gear)
{
int gear = StandardMetrics.ms_v_env_gear->AsInt();
if (gear < 0)
MyEvents.SignalEvent("vehicle.gear.reverse", NULL);
else if (gear > 0)
MyEvents.SignalEvent("vehicle.gear.forward", NULL);
else
MyEvents.SignalEvent("vehicle.gear.neutral", NULL);
NotifiedVehicleGear(gear);
}
else if (metric == StandardMetrics.ms_v_env_drivemode)
{
std::string event = "vehicle.drivemode." + StandardMetrics.ms_v_env_drivemode->AsString();
MyEvents.SignalEvent(event, NULL);
NotifiedVehicleDrivemode(StandardMetrics.ms_v_env_drivemode->AsInt());
}
else if (metric == StandardMetrics.ms_v_charge_mode)
{
std::string m = metric->AsString();
const char* mc = m.c_str();
MyEvents.SignalEvent("vehicle.charge.mode",(void*)mc, strlen(mc)+1);
NotifiedVehicleChargeMode(mc);
}
else if (metric == StandardMetrics.ms_v_charge_state)
{
std::string m = metric->AsString();
const char* mc = m.c_str();
MyEvents.SignalEvent("vehicle.charge.state",(void*)mc, strlen(mc)+1);
if (m == "done")
{
StandardMetrics.ms_v_charge_duration_full->SetValue(0);
StandardMetrics.ms_v_charge_duration_range->SetValue(0);
StandardMetrics.ms_v_charge_duration_soc->SetValue(0);
}
if (m_autonotifications)
{
m_chargestate_ticker = GetNotifyChargeStateDelay(mc);
if (m_chargestate_ticker == 0)
NotifyChargeState();
}
}
else if (metric == StandardMetrics.ms_v_gen_state)
{
std::string state = metric->AsString();
MyEvents.SignalEvent("vehicle.gen.state", (void*)state.c_str(), state.size()+1);
if (m_autonotifications)
NotifyGenState();
}
else if (metric == StandardMetrics.ms_v_pos_speed)
{
// Collect data for trip speed average:
const float min_speed = 5.0; // slow speed exclusion [kph]
float speed = StandardMetrics.ms_v_pos_speed->AsFloat();
if (speed > min_speed)
{
m_drive_speedcnt++;
m_drive_speedsum += speed;
}
}
else if (metric == StdMetrics.ms_v_inv_power)
// collect data for trip pos. and neg. (recuperated) motor energies
{
uint32_t now = esp_log_timestamp();
if (now > m_inv_reftime)
{
float invpower = StdMetrics.ms_v_inv_power->AsFloat();
float invenergy = (m_inv_refpower + invpower) * (now - m_inv_reftime) / (2*1000*3600); // average of last 2 values in kWh
if ( invenergy < 0 )
{
m_inv_energyrecd -= invenergy; // sum should be positive
}
else
{
m_inv_energyused += invenergy;
}
m_inv_refpower = invpower;
m_inv_reftime = now;
}
}
else if (metric == StandardMetrics.ms_v_pos_acceleration)
{
if (m_brakelight_enable)
CheckBrakelight();
// Collect data for trip acceleration/deceleration average:
const float min_accel = 2.5 / 3.6; // cruising range exclusion [m/s²]
float accel = StandardMetrics.ms_v_pos_acceleration->AsFloat();
if (accel > min_accel)
{
m_drive_accelcnt++;
m_drive_accelsum += accel;
}
else if (accel < -min_accel)
{
m_drive_decelcnt++;
m_drive_decelsum += accel;
}
}
else if (metric == StandardMetrics.ms_v_bat_power)
{
if (m_batpwr_smoothing > 0)
m_batpwr_smoothed = (m_batpwr_smoothed + metric->AsFloat() * m_batpwr_smoothing) / (m_batpwr_smoothing + 1);
else
m_batpwr_smoothed = metric->AsFloat();
}
else if (metric == StdMetrics.ms_v_bat_current || metric == StdMetrics.ms_v_bat_cac ||
metric == StdMetrics.ms_v_bat_range_full)
{
CalculateRangeSpeed();
}
}
/**
* CalculateAcceleration: derive acceleration / deceleration level from speed change
* Note:
* IF you want to let the framework calculate acceleration, call this after your regular
* update to StdMetrics.ms_v_pos_speed. This is optional, you can set ms_v_pos_acceleration
* yourself if your vehicle provides this metric.
*/
void OvmsVehicle::CalculateAcceleration()
{
uint32_t now = esp_log_timestamp();
if (now > m_accel_reftime)
{
float speed = ABS(StdMetrics.ms_v_pos_speed->AsFloat(0, Kph)) * 1000 / 3600;
float accel = (speed - m_accel_refspeed) / (now - m_accel_reftime) * 1000;
// smooth out road bumps & gear box backlash:
if (m_accel_smoothing > 0)
accel = (accel + StdMetrics.ms_v_pos_acceleration->AsFloat() * m_accel_smoothing) / (m_accel_smoothing + 1);
StdMetrics.ms_v_pos_acceleration->SetValue(TRUNCPREC(accel, 3));
m_accel_refspeed = speed;
m_accel_reftime = now;
}
}
/**
* CheckBrakelight: check for regenerative braking, control brakelight accordingly
* Notes:
* a) This depends on a regular and frequent speed update with <= 100 ms period. If the vehicle
* delivers speed values at too large intervals, the trigger will still work but come
* too late (reducing/deactivating acceleration smoothing may help).
* If the vehicle raw speed is already smoothed, reducing acceleration smoothing will
* provide a faster trigger. Same applies for the battery power level.
* b) The battery power regen threshold is defined at -[brakelight.basepwr] for activation
* and +[brakelight.basepwr] for deactivation. The config default is 0 as that works
* on vehicles without the battery power metric.
* c) To reduce flicker the brake light has a minimum hold time of currently fixed 500 ms.
* d) Normal operation is "regen light XOR foot brake light", set [brakelight.ignftbrk]
* to true to disable this.
* Override to customize.
*/
void OvmsVehicle::CheckBrakelight()
{
uint32_t now = esp_log_timestamp();
float speed = ABS(StdMetrics.ms_v_pos_speed->AsFloat(0, Kph)) * 1000 / 3600;
float accel = StdMetrics.ms_v_pos_acceleration->AsFloat();
bool car_on = StdMetrics.ms_v_env_on->AsBool();
bool footbrake = StdMetrics.ms_v_env_footbrake->AsFloat() > 0;
const uint32_t holdtime = 500;
// activate brake light?
if (car_on && accel < -m_brakelight_on && speed >= 1 && m_batpwr_smoothed <= -m_brakelight_basepwr
&& (m_brakelight_ignftbrk || !footbrake))
{
if (!m_brakelight_start)
{
if (SetBrakelight(1))
{
ESP_LOGD(TAG, "brakelight on at speed=%.2f m/s, accel=%.2f m/s^2", speed, accel);
m_brakelight_start = now;
StdMetrics.ms_v_env_regenbrake->SetValue(true);
}
else
ESP_LOGW(TAG, "can't activate brakelight");
}
else
m_brakelight_start = now;
}
// deactivate brake light?
else if (!car_on || accel >= -m_brakelight_off || speed < 1 || m_batpwr_smoothed > m_brakelight_basepwr
|| (!m_brakelight_ignftbrk && footbrake))
{
if (m_brakelight_start && now >= m_brakelight_start + holdtime)
{
if (SetBrakelight(0))
{
ESP_LOGD(TAG, "brakelight off at speed=%.2f m/s, accel=%.2f m/s^2", speed, accel);
m_brakelight_start = 0;
StdMetrics.ms_v_env_regenbrake->SetValue(false);
}
else
ESP_LOGW(TAG, "can't deactivate brakelight");
}
}
}
/**
* SetBrakelight: hardware brake light control method
* Override for custom control, e.g. CAN.
*/
bool OvmsVehicle::SetBrakelight(int on)
{
#ifdef CONFIG_OVMS_COMP_MAX7317
// port 2 = SN65 for esp32can
if (m_brakelight_port == 1 || (m_brakelight_port >= 3 && m_brakelight_port <= 9))
{
MyPeripherals->m_max7317->Output(m_brakelight_port, on);
return true;
}
else
{
ESP_LOGE(TAG, "SetBrakelight: invalid port configuration (valid: 1, 3..9)");
return false;
}
#else // CONFIG_OVMS_COMP_MAX7317
ESP_LOGE(TAG, "SetBrakelight: OVMS_COMP_MAX7317 missing");
return false;
#endif // CONFIG_OVMS_COMP_MAX7317
}
/**
* CalculateRangeSpeed: derive momentary charge gain/loss speed (range charged/discharged per hour)
*/
void OvmsVehicle::CalculateRangeSpeed()
{
float
bat_current = StdMetrics.ms_v_bat_current->AsFloat(),
bat_capacity = StdMetrics.ms_v_bat_cac->AsFloat(),
range_full = StdMetrics.ms_v_bat_range_full->AsFloat();
if (bat_capacity > 0 && range_full > 0)
{
*StdMetrics.ms_v_bat_range_speed = TRUNCPREC(-bat_current / bat_capacity * range_full, 1);
}
}
void OvmsVehicle::NotifyChargeState()
{
std::string m = StandardMetrics.ms_v_charge_state->AsString();
if (m == "done")
NotifyChargeDone();
else if (m == "stopped" || m == "timerwait")
NotifyChargeStopped();
else if (m == "charging")
NotifyChargeStart();
else if (m == "topoff")
NotifyChargeTopOff();
else if (m == "heating")
NotifyHeatingStart();
if (m != "")
NotifyGridLog();
NotifiedVehicleChargeState(m.c_str());
}
void OvmsVehicle::NotifyGenState()
{
std::string m = StandardMetrics.ms_v_gen_state->AsString();
// Generator states TBD
if (m != "")
NotifyGridLog();
NotifiedVehicleGenState(m);
}
void OvmsVehicle::NotifyGridLog()
{
// Send grid (charge/generator) session log
// History type "*-LOG-Grid"
// Notification type "data", subtype "log.grid"
int storetime_days = MyConfig.GetParamValueInt("notify", "log.grid.storetime", 0);
if (storetime_days <= 0)
return;
std::ostringstream buf;
buf
<< "*-LOG-Grid,1," << storetime_days * 86400 // V1, increment on additions
<< std::noboolalpha
<< "," << (StdMetrics.ms_v_pos_gpslock->AsBool() ? 1 : 0)
<< std::fixed
<< std::setprecision(8)
<< "," << StdMetrics.ms_v_pos_latitude->AsFloat()
<< "," << StdMetrics.ms_v_pos_longitude->AsFloat()
<< std::setprecision(1)
<< "," << StdMetrics.ms_v_pos_altitude->AsFloat()
<< "," << mp_encode(StdMetrics.ms_v_pos_location->AsString())
<< "," << mp_encode(StdMetrics.ms_v_charge_type->AsString())
<< "," << mp_encode(StdMetrics.ms_v_charge_state->AsString())
<< "," << mp_encode(StdMetrics.ms_v_charge_substate->AsString())
<< "," << mp_encode(StdMetrics.ms_v_charge_mode->AsString())
<< "," << StdMetrics.ms_v_charge_climit->AsFloat()
<< "," << StdMetrics.ms_v_charge_limit_range->AsFloat()
<< "," << StdMetrics.ms_v_charge_limit_soc->AsFloat()
<< "," << mp_encode(StdMetrics.ms_v_gen_type->AsString())
<< "," << mp_encode(StdMetrics.ms_v_gen_state->AsString())
<< "," << mp_encode(StdMetrics.ms_v_gen_substate->AsString())
<< "," << mp_encode(StdMetrics.ms_v_gen_mode->AsString())
<< "," << StdMetrics.ms_v_gen_climit->AsFloat()
<< "," << StdMetrics.ms_v_gen_limit_range->AsFloat()
<< "," << StdMetrics.ms_v_gen_limit_soc->AsFloat()
<< std::setprecision(3)
<< "," << m_last_chargetime
<< "," << StdMetrics.ms_v_charge_kwh->AsFloat()
<< "," << StdMetrics.ms_v_charge_kwh_grid->AsFloat()
<< "," << StdMetrics.ms_v_charge_kwh_grid_total->AsFloat()
<< "," << m_last_gentime
<< "," << StdMetrics.ms_v_gen_kwh->AsFloat()
<< "," << StdMetrics.ms_v_gen_kwh_grid->AsFloat()
<< "," << StdMetrics.ms_v_gen_kwh_grid_total->AsFloat()
<< std::setprecision(1)
<< "," << StdMetrics.ms_v_bat_soc->AsFloat()
<< "," << StdMetrics.ms_v_bat_range_est->AsFloat()
<< "," << StdMetrics.ms_v_bat_range_ideal->AsFloat()
<< "," << StdMetrics.ms_v_bat_range_full->AsFloat()
<< "," << StdMetrics.ms_v_bat_voltage->AsFloat()
<< "," << StdMetrics.ms_v_bat_temp->AsFloat()
<< "," << StdMetrics.ms_v_charge_temp->AsFloat()
<< "," << StdMetrics.ms_v_charge_12v_temp->AsFloat()
<< "," << StdMetrics.ms_v_env_temp->AsFloat()
<< "," << StdMetrics.ms_v_env_cabintemp->AsFloat()
<< std::setprecision(3)
<< "," << StdMetrics.ms_v_bat_soh->AsFloat()
<< "," << mp_encode(StdMetrics.ms_v_bat_health->AsString())
<< "," << StdMetrics.ms_v_bat_cac->AsFloat()
<< "," << StdMetrics.ms_v_bat_energy_used_total->AsFloat()
<< "," << StdMetrics.ms_v_bat_energy_recd_total->AsFloat()
<< "," << StdMetrics.ms_v_bat_coulomb_used_total->AsFloat()
<< "," << StdMetrics.ms_v_bat_coulomb_recd_total->AsFloat()
;
MyNotify.NotifyString("data", "log.grid", buf.str().c_str());
}
void OvmsVehicle::NotifyVehicleOn()
{
NotifyTripLog();
NotifiedVehicleOn();
}
void OvmsVehicle::NotifyVehicleOff()
{
float trip = StdMetrics.ms_v_pos_trip->AsFloat();
if (trip >= MyConfig.GetParamValueFloat("notify", "log.trip.minlength", 0.2))
NotifyTripLog();
if (trip >= MyConfig.GetParamValueFloat("notify", "report.trip.minlength", 0.2))
NotifyTripReport();
NotifiedVehicleOff();
}
void OvmsVehicle::NotifyTripLog()
{
// Send trip log
// History type "*-LOG-Trip"
// Notification type "data", subtype "log.trip"
int storetime_days = MyConfig.GetParamValueInt("notify", "log.trip.storetime", 0);
if (storetime_days <= 0)
return;
// Get min/max TPMS values:
const auto t_temp = StdMetrics.ms_v_tpms_temp->AsVector();
const auto t_prss = StdMetrics.ms_v_tpms_pressure->AsVector();
const auto t_hlth = StdMetrics.ms_v_tpms_health->AsVector();
const auto t_temp_minmax = std::minmax_element(t_temp.begin(), t_temp.end());
const auto t_prss_minmax = std::minmax_element(t_prss.begin(), t_prss.end());
const auto t_hlth_minmax = std::minmax_element(t_hlth.begin(), t_hlth.end());
std::ostringstream buf;
buf
<< "*-LOG-Trip,1," << storetime_days * 86400 // V1, increment on additions
<< std::noboolalpha
<< "," << (StdMetrics.ms_v_pos_gpslock->AsBool() ? 1 : 0)
<< std::fixed
<< std::setprecision(8)
<< "," << StdMetrics.ms_v_pos_latitude->AsFloat()
<< "," << StdMetrics.ms_v_pos_longitude->AsFloat()
<< std::setprecision(1)
<< "," << StdMetrics.ms_v_pos_altitude->AsFloat()
<< "," << mp_encode(StdMetrics.ms_v_pos_location->AsString())
<< "," << StdMetrics.ms_v_pos_odometer->AsFloat()
<< "," << StdMetrics.ms_v_pos_trip->AsFloat()
<< "," << m_last_drivetime
<< "," << StdMetrics.ms_v_env_drivemode->AsInt()
<< "," << StdMetrics.ms_v_bat_soc->AsFloat()
<< "," << StdMetrics.ms_v_bat_range_est->AsFloat()
<< "," << StdMetrics.ms_v_bat_range_ideal->AsFloat()
<< "," << StdMetrics.ms_v_bat_range_full->AsFloat()
<< std::setprecision(3)
<< "," << StdMetrics.ms_v_bat_energy_used->AsFloat()
<< "," << StdMetrics.ms_v_bat_energy_recd->AsFloat()
<< "," << StdMetrics.ms_v_bat_coulomb_used->AsFloat()
<< "," << StdMetrics.ms_v_bat_coulomb_recd->AsFloat()
<< "," << StdMetrics.ms_v_bat_soh->AsFloat()
<< "," << mp_encode(StdMetrics.ms_v_bat_health->AsString())
<< "," << StdMetrics.ms_v_bat_cac->AsFloat()
<< "," << StdMetrics.ms_v_bat_energy_used_total->AsFloat()
<< "," << StdMetrics.ms_v_bat_energy_recd_total->AsFloat()
<< "," << StdMetrics.ms_v_bat_coulomb_used_total->AsFloat()
<< "," << StdMetrics.ms_v_bat_coulomb_recd_total->AsFloat()
<< std::setprecision(1)
<< "," << StdMetrics.ms_v_env_temp->AsFloat()
<< "," << StdMetrics.ms_v_env_cabintemp->AsFloat()
<< "," << StdMetrics.ms_v_bat_temp->AsFloat()
<< "," << StdMetrics.ms_v_inv_temp->AsFloat()
<< "," << StdMetrics.ms_v_mot_temp->AsFloat()
<< "," << StdMetrics.ms_v_charge_12v_temp->AsFloat()
;
if (t_temp.empty())
buf << ",,";
else
buf << "," << *std::get<0>(t_temp_minmax) << "," << *std::get<1>(t_temp_minmax);
if (t_prss.empty())
buf << ",,";
else
buf << "," << *std::get<0>(t_prss_minmax) << "," << *std::get<1>(t_prss_minmax);
if (t_hlth.empty())
buf << ",,";
else
buf << "," << *std::get<0>(t_hlth_minmax) << "," << *std::get<1>(t_hlth_minmax);
MyNotify.NotifyString("data", "log.trip", buf.str().c_str());
}
void OvmsVehicle::NotifyTripReport()
{
// Send trip report notification
// Notification type "info", subtype "drive.trip.report"
bool send_report = MyConfig.GetParamValueBool("notify", "report.trip.enable", false);
if (send_report)
{
StringWriter buf(200);
CommandStatTrip(COMMAND_RESULT_NORMAL, &buf);
MyNotify.NotifyString("info", "drive.trip.report", buf.c_str());
}
}
OvmsVehicle::vehicle_mode_t OvmsVehicle::VehicleModeKey(const std::string code)
{
vehicle_mode_t key;
if (code == "standard") key = Standard;
else if (code == "storage") key = Storage;
else if (code == "range") key = Range;
else if (code == "performance") key = Performance;
else key = Standard;
return key;
}
/**
* SetFeature: V2 compatibility config wrapper
* Note: V2 only supported integer values, V3 values may be text
*/
bool OvmsVehicle::SetFeature(int key, const char *value)
{
switch (key)
{
case 8:
MyConfig.SetParamValue("vehicle", "stream", value);
return true;
case 9:
MyConfig.SetParamValue("vehicle", "minsoc", value);
return true;
case 14:
MyConfig.SetParamValue("vehicle", "carbits", value);
return true;
case 15:
MyConfig.SetParamValue("vehicle", "canwrite", value);
return true;
default:
return false;
}
}
/**
* GetFeature: V2 compatibility config wrapper
* Note: V2 only supported integer values, V3 values may be text
*/
const std::string OvmsVehicle::GetFeature(int key)
{
switch (key)
{
case 8:
return MyConfig.GetParamValue("vehicle", "stream", "0");
case 9:
return MyConfig.GetParamValue("vehicle", "minsoc", "0");
case 14:
return MyConfig.GetParamValue("vehicle", "carbits", "0");
case 15:
return MyConfig.GetParamValue("vehicle", "canwrite", "0");
default:
return "0";
}
}
/**
* ProcessMsgCommand: V2 compatibility protocol message command processing
* result: optional payload or message to return to the caller with the command response
*/
OvmsVehicle::vehicle_command_t OvmsVehicle::ProcessMsgCommand(std::string &result, int command, const char* args)
{
return NotImplemented;
}
#ifdef CONFIG_OVMS_COMP_WEBSERVER
/**
* GetDashboardConfig: template / default configuration
* (override with vehicle specific configuration)
* see https://api.highcharts.com/highcharts/yAxis for details on options
*/
void OvmsVehicle::GetDashboardConfig(DashboardConfig& cfg)
{
cfg.gaugeset1 =
"yAxis: [{"
// Speed:
"min: 0, max: 200,"
"plotBands: ["
"{ from: 0, to: 120, className: 'green-band' },"
"{ from: 120, to: 160, className: 'yellow-band' },"
"{ from: 160, to: 200, className: 'red-band' }]"
"},{"
// Voltage:
"min: 310, max: 410,"
"plotBands: ["
"{ from: 310, to: 325, className: 'red-band' },"
"{ from: 325, to: 340, className: 'yellow-band' },"
"{ from: 340, to: 410, className: 'green-band' }]"
"},{"
// SOC:
"min: 0, max: 100,"
"plotBands: ["
"{ from: 0, to: 12.5, className: 'red-band' },"
"{ from: 12.5, to: 25, className: 'yellow-band' },"
"{ from: 25, to: 100, className: 'green-band' }]"
"},{"
// Efficiency:
"min: 0, max: 400,"
"plotBands: ["
"{ from: 0, to: 200, className: 'green-band' },"
"{ from: 200, to: 300, className: 'yellow-band' },"
"{ from: 300, to: 400, className: 'red-band' }]"
"},{"
// Power:
"min: -50, max: 200,"
"plotBands: ["
"{ from: -50, to: 0, className: 'violet-band' },"
"{ from: 0, to: 100, className: 'green-band' },"
"{ from: 100, to: 150, className: 'yellow-band' },"
"{ from: 150, to: 200, className: 'red-band' }]"
"},{"
// Charger temperature:
"min: 20, max: 80, tickInterval: 20,"
"plotBands: ["
"{ from: 20, to: 65, className: 'normal-band border' },"
"{ from: 65, to: 80, className: 'red-band border' }]"
"},{"
// Battery temperature:
"min: -15, max: 65, tickInterval: 25,"
"plotBands: ["
"{ from: -15, to: 0, className: 'red-band border' },"
"{ from: 0, to: 50, className: 'normal-band border' },"
"{ from: 50, to: 65, className: 'red-band border' }]"
"},{"
// Inverter temperature:
"min: 20, max: 80, tickInterval: 20,"
"plotBands: ["
"{ from: 20, to: 70, className: 'normal-band border' },"
"{ from: 70, to: 80, className: 'red-band border' }]"
"},{"
// Motor temperature:
"min: 50, max: 125, tickInterval: 25,"
"plotBands: ["
"{ from: 50, to: 110, className: 'normal-band border' },"
"{ from: 110, to: 125, className: 'red-band border' }]"
"}]";
}
#endif // #ifdef CONFIG_OVMS_COMP_WEBSERVER