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

391 lines
17 KiB
C++

/*
; Project: Open Vehicle Monitor System
; Date: 19th Nov 2022
;
; (C) 2022 Carsten Schmiemann
;
; 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"
#include <stdio.h>
#include <string>
#include <iomanip>
#include "pcp.h"
#include "ovms_metrics.h"
#include "ovms_events.h"
#include "ovms_config.h"
#include "ovms_command.h"
#include "metrics_standard.h"
#include "ovms_notify.h"
#include "ovms_peripherals.h"
#include "ovms_netmanager.h"
#include "vehicle_renaultzoe_ph2_can.h"
#include "ph2_poller.h"
const char *OvmsVehicleRenaultZoePh2CAN::s_tag = "v-zoe-ph2-can";
OvmsVehicleRenaultZoePh2CAN::OvmsVehicleRenaultZoePh2CAN() {
ESP_LOGI(TAG, "Start Renault Zoe Ph2 (CAN) vehicle module");
//Init variables supressing push on boot up
StandardMetrics.ms_v_type->SetValue("RZ2");
StandardMetrics.ms_v_charge_inprogress->SetValue(false);
StandardMetrics.ms_v_charge_pilot->SetValue(false);
StandardMetrics.ms_v_charge_state->SetValue("stopped");
StandardMetrics.ms_v_charge_substate->SetValue("stopped");
StandardMetrics.ms_v_env_on->SetValue(false);
MyConfig.RegisterParam("xrz2c", "Renault Zoe Ph2 (CAN) configuration", true, true);
ConfigChanged(NULL);
// CAN1: Init Zoe Ph2 M-CAN Interface
RegisterCanBus(1, CAN_MODE_ACTIVE, CAN_SPEED_500KBPS);
// CAN1: Init Zoe Ph2 V1-CAN Interface
RegisterCanBus(2, CAN_MODE_LISTEN, CAN_SPEED_500KBPS);
POLLSTATE_OFF;
ESP_LOGI(TAG, "Pollstate switched to OFF");
PollSetPidList(m_can1, renault_zoe_polls);
PollSetThrottling(10);
PollSetResponseSeparationTime(20);
// Renault ZOE specific metrics
mt_bus_awake = MyMetrics.InitBool("zph2.v.bus.awake", SM_STALE_NONE, false);
mt_pos_odometer_start = MyMetrics.InitFloat("zph2.v.pos.odometer.start", SM_STALE_MID, 0, Kilometers, true);
mt_bat_used_start = MyMetrics.InitFloat("zph2.b.used.start", SM_STALE_MID, 0, kWh, true);
mt_bat_recd_start = MyMetrics.InitFloat("zph2.b.recd.start", SM_STALE_MID, 0, kWh, true);
mt_bat_chg_start = MyMetrics.InitFloat("zph2.b.chg.start", SM_STALE_MID, 0, kWh, true);
mt_bat_available_energy = MyMetrics.InitFloat("zph2.b.avail.energy", SM_STALE_NONE, 0, kWh, true);
mt_bat_aux_power_consumer = MyMetrics.InitFloat("zph2.b.aux.power.consumer", SM_STALE_MID, 0, Watts);
mt_bat_aux_power_ptc = MyMetrics.InitFloat("zph2.b.aux.power.ptc", SM_STALE_MID, 0, Watts);
mt_bat_max_charge_power = MyMetrics.InitFloat("zph2.b.max.charge.power", SM_STALE_MID, 0, kW, true);
mt_bat_cycles = MyMetrics.InitFloat("zph2.b.cycles", SM_STALE_MID, 0, Other, true);
mt_main_power_available = MyMetrics.InitFloat("zph2.c.main.power.available", SM_STALE_MIN, 0, kW);
mt_main_phases = MyMetrics.InitString("zph2.c.main.phases", SM_STALE_MIN, 0);
mt_main_phases_num = MyMetrics.InitFloat("zph2.c.main.phases.num", SM_STALE_MIN, 0);
mt_inv_status = MyMetrics.InitString("zph2.m.inverter.status", SM_STALE_NONE, 0);
mt_inv_hv_voltage = MyMetrics.InitFloat("zph2.i.voltage", SM_STALE_MID, 0, Volts, true);
mt_inv_hv_current = MyMetrics.InitFloat("zph2.i.current", SM_STALE_MID, 0, Amps);
mt_mot_temp_stator1 = MyMetrics.InitFloat("zph2.m.temp.stator1", SM_STALE_MID, 0, Celcius);
mt_mot_temp_stator2 = MyMetrics.InitFloat("zph2.m.temp.stator2", SM_STALE_MID, 0, Celcius);
mt_hvac_compressor_speed = MyMetrics.InitFloat("zph2.h.compressor.speed", SM_STALE_MID, 0);
mt_hvac_compressor_pressure = MyMetrics.InitFloat("zph2.h.compressor.pressure", SM_STALE_MID, 0);
mt_hvac_compressor_power = MyMetrics.InitFloat("zph2.h.compressor.power", SM_STALE_MID, 0, Watts);
mt_hvac_compressor_mode = MyMetrics.InitString("zph2.h.compressor.mode", SM_STALE_MID, 0, Other);
// BMS configuration:
BmsSetCellArrangementVoltage(96, 1);
BmsSetCellArrangementTemperature(12, 1);
BmsSetCellLimitsVoltage(2.0, 5.0);
BmsSetCellLimitsTemperature(-39, 200);
BmsSetCellDefaultThresholdsVoltage(0.030, 0.050);
BmsSetCellDefaultThresholdsTemperature(4.0, 5.0);
#ifdef CONFIG_OVMS_COMP_WEBSERVER
WebInit();
#endif
}
OvmsVehicleRenaultZoePh2CAN::~OvmsVehicleRenaultZoePh2CAN() {
ESP_LOGI(TAG, "Stop Renault Zoe Ph2 (CAN) vehicle module");
}
void OvmsVehicleRenaultZoePh2CAN::ConfigChanged(OvmsConfigParam* param) {
if (param && param->GetName() != "xrz2c")
return;
// get values from config store
m_range_ideal = MyConfig.GetParamValueInt("xrz2c", "rangeideal", 350);
m_battery_capacity = MyConfig.GetParamValueInt("xrz2c", "battcapacity", 52000);
m_UseBMScalculation = MyConfig.GetParamValueBool("xrz2c", "UseBMScalculation", false);
StandardMetrics.ms_v_bat_range_ideal->SetValue(m_range_ideal, Kilometers);
if (m_battery_capacity == 52000) {
Bat_cell_capacity = 78.0 * 2 * (StandardMetrics.ms_v_bat_soh->AsFloat() / 100.0);
}
if (m_battery_capacity == 41000) {
Bat_cell_capacity = 65.6 * 2 * (StandardMetrics.ms_v_bat_soh->AsFloat() / 100.0);
}
if (m_battery_capacity == 22000) {
Bat_cell_capacity = 36.0 * 2 * (StandardMetrics.ms_v_bat_soh->AsFloat() / 100.0);
}
ESP_LOGI(TAG, "Renault Zoe Ph2 (CAN) reload configuration: Range ideal: %d, Battery capacity: %d, Use BMS as energy counter: %s", m_range_ideal, m_battery_capacity, m_UseBMScalculation ? "yes" : "no");
}
void OvmsVehicleRenaultZoePh2CAN::ZoeWakeUp() {
ESP_LOGI(TAG,"Zoe woke up (CAN Bus activity detected)");
mt_bus_awake->SetValue(true);;
StandardMetrics.ms_v_env_charging12v->SetValue(true);
POLLSTATE_ON;
ESP_LOGI(TAG, "Pollstate switched to ON");
}
/**
* Handles incoming CAN-frames on bus 1, connected to M-CAN
*/
void OvmsVehicleRenaultZoePh2CAN::IncomingFrameCan1(CAN_frame_t* p_frame) {
uint8_t *data = p_frame->data.u8;
ESP_LOGI(TAG, "M-CAN: PID:%x DATA: %02x %02x %02x %02x %02x %02x %02x %02x", p_frame->MsgID, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
//If a CAN message comes in, start polling
if (!mt_bus_awake->AsBool()) {
ZoeWakeUp();
}
//Reset CAN1 inactivity timer, inactivity after 5 sec detected to stop polling
m_can1_activity_timer = 5;
}
/**
* Handles incoming CAN-frames on bus 2, connected to V1-CAN
*/
void OvmsVehicleRenaultZoePh2CAN::IncomingFrameCan2(CAN_frame_t* p_frame) {
uint8_t *data = p_frame->data.u8;
ESP_LOGI(TAG, "V1-CAN: PID:%x DATA: %02x %02x %02x %02x %02x %02x %02x %02x", p_frame->MsgID, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
//ToDo: Analyse PIDs and copy data to useful metrics
}
/**
* Handles incoming poll results
*/
void OvmsVehicleRenaultZoePh2CAN::IncomingPollReply(canbus* bus, uint16_t type, uint16_t pid, uint8_t* data, uint8_t length, uint16_t remain) {
string& rxbuf = zoe_obd_rxbuf;
//ESP_LOGV(TAG, "pid: %04x length: %d m_poll_ml_remain: %d m_poll_ml_frame: %d", pid, length, m_poll_ml_remain, m_poll_ml_frame);
// init / fill rx buffer:
if (m_poll_ml_frame == 0) {
rxbuf.clear();
rxbuf.reserve(length + remain);
}
rxbuf.append((char*)data, length);
if (remain)
return;
switch (m_poll_moduleid_low) {
// ****** INV *****
case 0x18daf1df:
IncomingINV(type, pid, rxbuf.data(), rxbuf.size());
break;
// ****** EVC *****
case 0x18daf1da:
IncomingEVC(type, pid, rxbuf.data(), rxbuf.size());
break;
// ****** BCM *****
case 0x765:
IncomingBCM(type, pid, rxbuf.data(), rxbuf.size());
break;
// ****** LBC *****
case 0x18daf1db:
IncomingLBC(type, pid, rxbuf.data(), rxbuf.size());
break;
// ****** HVAC *****
case 0x764:
IncomingHVAC(type, pid, rxbuf.data(), rxbuf.size());
break;
// ****** UCM *****
case 0x76D:
IncomingUCM(type, pid, rxbuf.data(), rxbuf.size());
break;
// ****** CLUSTER *****
//case 0x763:
// IncomingCLUSTER(type, pid, rxbuf.data(), rxbuf.size());
// break;
}
}
int OvmsVehicleRenaultZoePh2CAN::calcMinutesRemaining(float charge_voltage, float charge_current) {
float bat_soc = StandardMetrics.ms_v_bat_soc->AsFloat(100);
float remaining_wh = m_battery_capacity - (m_battery_capacity * bat_soc / 100.0);
float remaining_hours = remaining_wh / (charge_current * charge_voltage);
float remaining_mins = remaining_hours * 60.0;
//ESP_LOGD(TAG, "SOC: %f, BattCap:%d, Current: %f, Voltage: %f, RemainWH: %f, RemainHour: %f, RemainMin: %f", bat_soc, m_battery_capacity, charge_current, charge_voltage, remaining_wh, remaining_hours, remaining_mins);
return MIN( 1440, (int)remaining_mins );
}
//Handle Charging values
void OvmsVehicleRenaultZoePh2CAN::ChargeStatistics() {
float charge_current = fabs(StandardMetrics.ms_v_bat_current->AsFloat(0, Amps));
float charge_voltage = StandardMetrics.ms_v_bat_voltage->AsFloat(0, Volts);
float battery_power = fabs(StandardMetrics.ms_v_bat_power->AsFloat(0, kW));
float charger_power = StandardMetrics.ms_v_charge_power->AsFloat(0, kW);
float ac_current = StandardMetrics.ms_v_charge_current->AsFloat(0, Amps);
string ac_phases = mt_main_phases->AsString();
if (CarIsCharging != CarLastCharging) {
if (CarIsCharging) {
StandardMetrics.ms_v_charge_kwh->SetValue(0);
StandardMetrics.ms_v_charge_inprogress->SetValue(CarIsCharging);
if (m_UseBMScalculation) {
mt_bat_chg_start->SetValue(StandardMetrics.ms_v_charge_kwh_grid_total->AsFloat());
}
} else {
StandardMetrics.ms_v_charge_inprogress->SetValue(CarIsCharging);
}
}
CarLastCharging = CarIsCharging;
if (!StandardMetrics.ms_v_charge_pilot->AsBool() || !StandardMetrics.ms_v_charge_inprogress->AsBool() || (charge_current <= 0.0) ) {
return;
}
if (charge_voltage > 0 && charge_current > 0) {
float power = charge_voltage * charge_current / 1000.0;
float energy = power / 3600.0 * 10.0;
float c_efficiency;
if (m_UseBMScalculation) {
StandardMetrics.ms_v_charge_kwh->SetValue(StandardMetrics.ms_v_charge_kwh_grid_total->AsFloat() - mt_bat_chg_start->AsFloat());
} else {
StandardMetrics.ms_v_charge_kwh->SetValue( StandardMetrics.ms_v_charge_kwh->AsFloat() + energy);
}
int minsremaining = calcMinutesRemaining(charge_voltage, charge_current);
StandardMetrics.ms_v_charge_duration_full->SetValue(minsremaining, Minutes);
if (StandardMetrics.ms_v_charge_type->AsString() == "type2") {
c_efficiency = (battery_power / charger_power) * 100.0;
if (c_efficiency < 100.0) {
StandardMetrics.ms_v_charge_efficiency->SetValue(c_efficiency);
}
ESP_LOGI(TAG, "Charge time remaining: %d mins, AC Charge at %.2f kW with %.1f amps, %s at %.1f efficiency, %.2f powerfactor", minsremaining, charger_power, ac_current, ac_phases.c_str(), StandardMetrics.ms_v_charge_efficiency->AsFloat(100), ACInputPowerFactor);
} else if (StandardMetrics.ms_v_charge_type->AsString() == "ccs" || StandardMetrics.ms_v_charge_type->AsString() == "chademo") {
StandardMetrics.ms_v_charge_power->SetValue(battery_power);
ESP_LOGI(TAG, "Charge time remaining: %d mins, DC Charge at %.2f kW", minsremaining, battery_power);
}
}
}
void OvmsVehicleRenaultZoePh2CAN::EnergyStatisticsOVMS() {
float voltage = StandardMetrics.ms_v_bat_voltage->AsFloat(0, Volts);
float current = StandardMetrics.ms_v_bat_current->AsFloat(0, Amps);
float power = voltage * current / 1000.0;
if (power != 0.0 && StandardMetrics.ms_v_env_on->AsBool()) {
float energy = power / 3600.0 * 10.0;
if (energy > 0.0f)
StandardMetrics.ms_v_bat_energy_used->SetValue( StandardMetrics.ms_v_bat_energy_used->AsFloat() + energy);
else
StandardMetrics.ms_v_bat_energy_recd->SetValue( StandardMetrics.ms_v_bat_energy_recd->AsFloat() - energy);
}
}
void OvmsVehicleRenaultZoePh2CAN::EnergyStatisticsBMS() {
StandardMetrics.ms_v_bat_energy_used->SetValue(StandardMetrics.ms_v_bat_energy_used_total->AsFloat() - mt_bat_used_start->AsFloat());
StandardMetrics.ms_v_bat_energy_recd->SetValue(StandardMetrics.ms_v_bat_energy_recd_total->AsFloat() - mt_bat_recd_start->AsFloat());
}
void OvmsVehicleRenaultZoePh2CAN::Ticker10(uint32_t ticker) {
if (StandardMetrics.ms_v_charge_pilot->AsBool() && !StandardMetrics.ms_v_env_on->AsBool()) {
ChargeStatistics();
} else {
if (m_UseBMScalculation) {
EnergyStatisticsBMS();
} else {
EnergyStatisticsOVMS();
}
}
}
void OvmsVehicleRenaultZoePh2CAN::Ticker1(uint32_t ticker) {
//Stop polling after 5 seconds of inactivity on CAN1 (M-CAN)
if (m_can1_activity_timer > 0) {
if (--m_can1_activity_timer == 0) {
ESP_LOGI(TAG,"Zoe has gone to sleep (No messages on M-CAN for 10 seconds)");
mt_bus_awake->SetValue(false);
StandardMetrics.ms_v_env_awake->SetValue(false);
StandardMetrics.ms_v_env_charging12v->SetValue(false);
StandardMetrics.ms_v_pos_speed->SetValue( 0 );
StandardMetrics.ms_v_bat_12v_current->SetValue( 0 );
StandardMetrics.ms_v_charge_12v_current->SetValue( 0 );
StandardMetrics.ms_v_bat_current->SetValue( 0 );
POLLSTATE_OFF;
ESP_LOGI(TAG, "Pollstate switched to OFF");
//Check if car is locked, if not send notify
if (!StandardMetrics.ms_v_env_locked->AsBool()) {
MyNotify.NotifyString("alert", "vehicle.lock", "Vehicle is not locked");
}
if (StandardMetrics.ms_v_env_on->AsBool() && !CarIsDriving) {
CarIsDriving = true;
//Start trip after power on
StandardMetrics.ms_v_pos_trip->SetValue(0);
mt_pos_odometer_start->SetValue(StandardMetrics.ms_v_pos_odometer->AsFloat(0));
StandardMetrics.ms_v_bat_energy_used->SetValue(0);
StandardMetrics.ms_v_bat_energy_recd->SetValue(0);
if (m_UseBMScalculation) {
mt_bat_used_start->SetValue(StandardMetrics.ms_v_bat_energy_used_total->AsFloat());
mt_bat_recd_start->SetValue(StandardMetrics.ms_v_bat_energy_recd_total->AsFloat());
}
}
if (CarIsDriving && !CarIsDrivingInit) {
CarIsDrivingInit = true;
ESP_LOGI(TAG, "Pollstate switched to DRIVING");
POLLSTATE_DRIVING;
}
if (!StandardMetrics.ms_v_env_on->AsBool() && CarIsDriving) {
CarIsDriving = false;
CarIsDrivingInit = false;
ESP_LOGI(TAG, "Pollstate switched to ON");
POLLSTATE_ON;
}
if (StandardMetrics.ms_v_charge_pilot->AsBool() && StandardMetrics.ms_v_charge_inprogress->AsBool() && !CarIsCharging) {
CarIsCharging = true;
ESP_LOGI(TAG, "Pollstate switched to CHARGING");
POLLSTATE_CHARGING;
} else if (!StandardMetrics.ms_v_charge_pilot->AsBool() && CarIsCharging) {
CarIsCharging = false;
StandardMetrics.ms_v_charge_duration_full->SetValue(0);
ESP_LOGI(TAG, "Pollstate switched to ON");
POLLSTATE_ON;
} else if (StandardMetrics.ms_v_charge_pilot->AsBool() && CarIsCharging && StandardMetrics.ms_v_charge_substate->AsString() == "powerwait") {
CarIsCharging = false;
StandardMetrics.ms_v_charge_duration_full->SetValue(0);
ESP_LOGI(TAG, "Pollstate switched to OFF, Wait for power...");
POLLSTATE_OFF;
} else if (StandardMetrics.ms_v_charge_pilot->AsBool() && CarIsCharging && StandardMetrics.ms_v_charge_state->AsString() == "done") {
CarIsCharging = false;
StandardMetrics.ms_v_charge_duration_full->SetValue(0);
ESP_LOGI(TAG, "Pollstate switched to OFF, done charging...");
POLLSTATE_OFF;
}
if (StandardMetrics.ms_v_env_on->AsBool()) {
StandardMetrics.ms_v_pos_trip->SetValue(StandardMetrics.ms_v_pos_odometer->AsFloat(0) - mt_pos_odometer_start->AsFloat(0));
}
StandardMetrics.ms_v_bat_range_est->SetValue((m_range_ideal * (StandardMetrics.ms_v_bat_soc->AsFloat(1) * 0.01)), Kilometers);
}
class OvmsVehicleRenaultZoePh2CANInit {
public: OvmsVehicleRenaultZoePh2CANInit();
} MyOvmsVehicleRenaultZoePh2CANInit __attribute__ ((init_priority (9000)));
OvmsVehicleRenaultZoePh2CANInit::OvmsVehicleRenaultZoePh2CANInit()
{
ESP_LOGI(TAG, "Registering Vehicle: Renault Zoe Ph2 (CAN) (9000)");
MyVehicleFactory.RegisterVehicle<OvmsVehicleRenaultZoePh2CAN>("RZ2","Renault Zoe Ph2 (CAN)");
}