/* ; Project: Open Vehicle Monitor System ; Date: 11th Sep 2019 ; ; Changes: ; 1.0 Initial release ; ; (C) 2011 Michael Stegen / Stegen Electronics ; (C) 2011-2017 Mark Webb-Johnson ; (C) 2011 Sonny Chen @ EPRO/DX ; (C) 2018 Marcos Mezo ; (C) 2019 Thomas Heuer @Dimitrie78 ; ; 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 = "v-zoe-ph2"; #include #include #include #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.h" #include "ph2_poller.h" #include "INV_pids.h" #include "EVC_pids.h" #include "BCM_pids.h" #include "LBC_pids.h" #include "HVAC_pids.h" #include "UCM_pids.h" OvmsVehicleRenaultZoePh2* OvmsVehicleRenaultZoePh2::GetInstance(OvmsWriter* writer /*=NULL*/) { OvmsVehicleRenaultZoePh2* zoe = (OvmsVehicleRenaultZoePh2*) MyVehicleFactory.ActiveVehicle(); string type = StdMetrics.ms_v_type->AsString(); if (!zoe || type != "RZ2") { if (writer) writer->puts("Error: Renault Zoe Ph2 vehicle module not selected"); return NULL; } return zoe; } OvmsVehicleRenaultZoePh2::OvmsVehicleRenaultZoePh2() { ESP_LOGI(TAG, "Start Renault Zoe Ph2 vehicle module"); StandardMetrics.ms_v_type->SetValue("RZ2"); StandardMetrics.ms_v_charge_inprogress->SetValue(false); //MyConfig.RegisterParam("zph2", "Renault Zoe Ph2", true, true); //ConfigChanged(NULL); // Zoe CAN bus runs at 500 kbps RegisterCanBus(1, CAN_MODE_ACTIVE, CAN_SPEED_500KBPS); // Poll Specific PIDs POLLSTATE_ON; mt_bus_awake->SetValue(true); PollSetPidList(m_can1, renault_zoe_polls); PollSetThrottling(5); PollSetResponseSeparationTime(20); // init metrics: mt_pos_odometer_start = MyMetrics.InitFloat("zph2.v.pos.odometer.start", SM_STALE_MID, 0, Kilometers); mt_bus_awake = MyMetrics.InitBool("zph2.v.bus.awake", SM_STALE_MIN, false); mt_available_energy = MyMetrics.InitFloat("zph2.v.avail.energy", SM_STALE_MID, 0, kWh); mt_main_power_consumed = MyMetrics.InitFloat("zph2.c.main.power.consumed", SM_STALE_MID, 0, kWh); mt_inv_status = MyMetrics.InitString("zph2.m.inverter.status", SM_STALE_MID, 0); 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_aux_power_consumer = MyMetrics.InitFloat("zph2.c.aux.power.consumer", SM_STALE_MID, 0, Watts); mt_aux_power_ptc = MyMetrics.InitFloat("zph2.c.aux.power.ptc", SM_STALE_MID, 0, Watts); mt_inv_hv_voltage = MyMetrics.InitFloat("zph2.m.inverter.voltage", SM_STALE_MID, 0, Volts); mt_inv_hv_current = MyMetrics.InitFloat("zph2.m.inverter.current", SM_STALE_MID, 0, Amps); mt_bat_max_charge_power = MyMetrics.InitFloat("zph2.b.max.charge.power", SM_STALE_MID, 0, kW); mt_hvac_compressor_speed = MyMetrics.InitFloat("zph2.h.compressor.speed", SM_STALE_MID, 0); // init commands: cmd_zoe = MyCommandApp.RegisterCommand("zoe-ph2", "Renault Zoe Ph2"); // BMS configuration: BmsSetCellArrangementVoltage(96, 8); 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 } OvmsVehicleRenaultZoePh2::~OvmsVehicleRenaultZoePh2() { ESP_LOGI(TAG, "Stop Renault Zoe Ph2 vehicle module"); #ifdef CONFIG_OVMS_COMP_WEBSERVER WebDeInit(); #endif } /** * Handles incoming CAN-frames on bus 1 */ void OvmsVehicleRenaultZoePh2::IncomingFrameCan1(CAN_frame_t* p_frame) { uint8_t *data = p_frame->data.u8; ESP_LOGI(TAG, "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 (data[0] == 0x83 && data[1] == 0xc0 && mt_bus_awake) { ESP_LOGI(TAG,"Zoe has gone to sleep (CAN Gateway NAK response)"); mt_bus_awake->SetValue(false); StandardMetrics.ms_v_env_awake->SetValue(false); car_on(false); POLLSTATE_OFF; } } /** * Handles incoming poll results */ void OvmsVehicleRenaultZoePh2::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 0x1893: IncomingBCM(type, pid, rxbuf.data(), rxbuf.size()); break; // ****** LBC ***** case 0x18daf1db: IncomingLBC(type, pid, rxbuf.data(), rxbuf.size()); break; // ****** HVAC ***** case 0x1892: IncomingHVAC(type, pid, rxbuf.data(), rxbuf.size()); break; // ****** UCM ***** case 0x1901: IncomingUCM(type, pid, rxbuf.data(), rxbuf.size()); break; } } /** * Takes care of setting all the state appropriate when the car is on * or off. Centralized so we can more easily make on and off mirror * images. */ void OvmsVehicleRenaultZoePh2::car_on(bool isOn) { if (isOn && !StandardMetrics.ms_v_env_on->AsBool()) { // Car is beeing turned ON ESP_LOGI(TAG,"CAR IS ON"); StandardMetrics.ms_v_env_on->SetValue(isOn); StandardMetrics.ms_v_env_awake->SetValue(isOn); // Handle 12Vcharging StandardMetrics.ms_v_env_charging12v->SetValue(true); POLLSTATE_RUNNING; // Reset trip values if (!m_reset_trip) { StandardMetrics.ms_v_bat_energy_recd->SetValue(0); StandardMetrics.ms_v_bat_energy_used->SetValue(0); mt_pos_odometer_start->SetValue(StandardMetrics.ms_v_pos_odometer->AsFloat()); StandardMetrics.ms_v_pos_trip->SetValue(0); } } else if(!isOn && StandardMetrics.ms_v_env_on->AsBool()) { // Car is being turned OFF ESP_LOGI(TAG,"CAR IS OFF"); if (!StandardMetrics.ms_v_charge_inprogress->AsBool()) { StandardMetrics.ms_v_env_charging12v->SetValue(false); POLLSTATE_ON; } else { POLLSTATE_CHARGING; } StandardMetrics.ms_v_env_on->SetValue( isOn ); StandardMetrics.ms_v_env_awake->SetValue( isOn ); StandardMetrics.ms_v_pos_speed->SetValue( 0 ); if (StandardMetrics.ms_v_pos_trip->AsFloat(0) > 0.1) NotifyTrip(); } } void OvmsVehicleRenaultZoePh2::Ticker1(uint32_t ticker) { HandleEnergy(); // Handle Tripcounter if (mt_pos_odometer_start->AsFloat(0) == 0 && StandardMetrics.ms_v_pos_odometer->AsFloat(0) > 0.0) { mt_pos_odometer_start->SetValue(StandardMetrics.ms_v_pos_odometer->AsFloat()); } if (StandardMetrics.ms_v_env_on->AsBool() && StandardMetrics.ms_v_pos_odometer->AsFloat(0) > 0.0 && mt_pos_odometer_start->AsFloat(0) > 0.0) { StandardMetrics.ms_v_pos_trip->SetValue(StandardMetrics.ms_v_pos_odometer->AsFloat(0) - mt_pos_odometer_start->AsFloat(0)); } // Handle v2Server connection if (StandardMetrics.ms_s_v2_connected->AsBool()) { m_reboot_ticker = 5 * 60; // set reboot ticker } else if (m_reboot_ticker > 0 && --m_reboot_ticker == 0) { MyNetManager.RestartNetwork(); m_reboot_ticker = 5 * 60; //MyBoot.Restart(); // restart Module } } void OvmsVehicleRenaultZoePh2::Ticker10(uint32_t ticker) { HandleCharging(); } /** * Update derived energy metrics while driving * Called once per second */ void OvmsVehicleRenaultZoePh2::HandleEnergy() { float voltage = StandardMetrics.ms_v_bat_voltage->AsFloat(0, Volts); float current = StandardMetrics.ms_v_bat_current->AsFloat(0, Amps); // Power (in kw) resulting from voltage and current float power = voltage * current / 1000.0; StandardMetrics.ms_v_bat_power->SetValue(power * -1.0f); // Are we driving? if (power != 0.0 && StandardMetrics.ms_v_env_on->AsBool()) { // Update energy used and recovered float energy = power / 3600.0; // 1 second worth of energy in kwh's if (energy < 0.0f) StandardMetrics.ms_v_bat_energy_used->SetValue( StandardMetrics.ms_v_bat_energy_used->AsFloat() - energy); else // (energy > 0.0f) StandardMetrics.ms_v_bat_energy_recd->SetValue( StandardMetrics.ms_v_bat_energy_recd->AsFloat() + energy); } } /** * Calculates minutes remaining before target is reached. Based on current charge speed. */ int OvmsVehicleRenaultZoePh2::calcMinutesRemaining(float target_soc, float charge_voltage, float charge_current) { float bat_soc = StandardMetrics.ms_v_bat_soc->AsFloat(100); if (bat_soc == (float)100 ) { return 0; // Done! } float remaining_wh = m_battery_capacity * (target_soc - bat_soc) / 100.0; float remaining_hours = remaining_wh / (charge_current * charge_voltage); float remaining_mins = remaining_hours * 60.0; return MIN( 1440, (int)remaining_mins ); } /** * Update derived metrics when charging * Called once per 10 seconds from Ticker10 */ void OvmsVehicleRenaultZoePh2::HandleCharging() { float charge_current = StandardMetrics.ms_v_bat_current->AsFloat(0, Amps); float charge_voltage = StandardMetrics.ms_v_bat_voltage->AsFloat(0, Volts); // Are we charging? if (!StandardMetrics.ms_v_charge_pilot->AsBool() || !StandardMetrics.ms_v_charge_inprogress->AsBool() || (charge_current <= 0.0) ) { return; } // Check if we have what is needed to calculate energy and remaining minutes if (charge_voltage > 0 && charge_current > 0) { // Update energy taken // Value is reset to 0 when a new charging session starts... float power = charge_voltage * charge_current / 1000.0; // power in kw float energy = power / 3600.0 * 10.0; // 10 second worth of energy in kwh's StandardMetrics.ms_v_charge_kwh->SetValue( StandardMetrics.ms_v_charge_kwh->AsFloat() + energy); int minsremaining_soc = calcMinutesRemaining((float)100, charge_voltage, charge_current); StandardMetrics.ms_v_charge_duration_soc->SetValue(minsremaining_soc, Minutes); ESP_LOGV(TAG, "Time remaining: %d mins to %d soc", minsremaining_soc, 100); } } //----------------------------------------------------------------------------- // // RenaultZoeInit // class OvmsVehicleRenaultZoePh2Init { public: OvmsVehicleRenaultZoePh2Init(); } MyOvmsVehicleRenaultZoePh2Init __attribute__ ((init_priority (9000))); OvmsVehicleRenaultZoePh2Init::OvmsVehicleRenaultZoePh2Init() { ESP_LOGI(TAG, "Registering Vehicle: Renault Zoe Ph2 (9000)"); MyVehicleFactory.RegisterVehicle("RZ2","Renault Zoe Ph2"); }