/* ; Project: Open Vehicle Monitor System ; Date: 5th July 2018 ; ; Changes: ; 1.0 Initial release ; ; (C) 2011 Michael Stegen / Stegen Electronics ; (C) 2011-2018 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. */ /* ; Subproject: Integration of support for the VW e-UP via Comfort CAN ; Date: 28th January 2021 ; ; Changes: ; 0.1.0 Initial code : Code frame with correct TAG and vehicle/can registration ; ; 0.1.1 Started with some SOC capture demo code ; ; 0.1.2 Added VIN, speed, 12 volt battery detection ; ; 0.1.3 Added ODO (Dimitrie78), fixed speed ; ; 0.1.4 Added WLTP based ideal range, uncertain outdoor temperature ; ; 0.1.5 Finalized SOC calculation (sharkcow), added estimated range ; ; 0.1.6 Created a climate control first try, removed 12 volt battery status ; ; 0.1.7 Added status of doors ; ; 0.1.8 Added config page for the webfrontend. Features canwrite and modelyear. ; Differentiation between model years is now possible. ; ; 0.1.9 "Fixed" crash on climate control. Added A/C indicator. ; First shot on battery temperatur. ; ; 0.2.0 Added key detection and car_on / pollingstate routine ; ; 0.2.1 Removed battery temperature, corrected outdoor temperature ; ; 0.2.2 Collect VIN only once ; ; 0.2.3 Redesign climate control, removed bluetooth template ; ; 0.2.4 First implementation of the ringbus and ocu heartbeat ; ; 0.2.5 Fixed heartbeat ; ; 0.2.6 Refined climate control template ; ; 0.2.7 Implemented dev_mode, 5A7, 69E climate control messages ; 0.2.8 Refactoring for T26A <-> OBD source separation (by SokoFromNZ) ; ; 0.2.9 Fixed climate control ; ; 0.3.0 Added upgrade policy for pre vehicle id splitting versions ; ; 0.3.1 Removed alpha OBD source, corrected class name ; ; 0.3.2 First implementation of charging detection ; ; 0.3.3 Buffer 0x61C ghost messages ; ; 0.3.4 Climate Control is now beginning to work : ; 0.3.5 Stabilized Climate Control ; ; 0.3.6 Corrected log tag and namespaces ; ; 0.3.7 Add locked detection, add climate control via Homelink for iOS ; ; 0.3.8 Add lights, rear doors and trunk detection ; ; 0.3.9 Corrected estimated range ; ; 0.4.0 Implemnted ICCB charging detection ; ; 0.4.1 Corrected estimated range ; ; 0.4.2 Corrected locked status, cabin temperature : ; 0.4.3 Added "feature" parameters for model year and cabin temperature setting ; ; 0.4.4 Respond to vehicle turning climate control off ; ; 0.4.5 Moved to unified ODB/T26 code ; ; 0.4.6 Corrected cabin temperature selection ; ; 0.4.7 Beautified code ; ; 0.4.8 Started using ms_v_env_charging12v ; ; 0.4.9 Added T26 awake detection for OBD ; ; (C) 2021 Chris van der Meijden ; ; Big thanx to sharkcow, Dimitrie78, E-lmo, Dexter and 'der kleine Nik'. */ #include "ovms_log.h" static const char *TAG = "v-vweup"; #include #include "pcp.h" #include "vehicle_vweup.h" #include "vweup_t26.h" #include "metrics_standard.h" #include "ovms_webserver.h" #include "ovms_events.h" #include "ovms_metrics.h" void OvmsVehicleVWeUp::sendOcuHeartbeat(TimerHandle_t timer) { // Workaround for FreeRTOS duplicate timer callback bug // (see https://github.com/espressif/esp-idf/issues/8234) static TickType_t last_tick = 0; TickType_t tick = xTaskGetTickCount(); if (tick < last_tick + xTimerGetPeriod(timer) - 3) return; last_tick = tick; OvmsVehicleVWeUp *vweup = (OvmsVehicleVWeUp *)pvTimerGetTimerID(timer); vweup->SendOcuHeartbeat(); } void OvmsVehicleVWeUp::ccCountdown(TimerHandle_t timer) { // Workaround for FreeRTOS duplicate timer callback bug // (see https://github.com/espressif/esp-idf/issues/8234) static TickType_t last_tick = 0; TickType_t tick = xTaskGetTickCount(); if (tick < last_tick + xTimerGetPeriod(timer) - 3) return; last_tick = tick; OvmsVehicleVWeUp *vweup = (OvmsVehicleVWeUp *)pvTimerGetTimerID(timer); vweup->CCCountdown(); } void OvmsVehicleVWeUp::T26Init() { ESP_LOGI(TAG, "Starting connection: T26A (Comfort CAN)"); memset(m_vin, 0, sizeof(m_vin)); RegisterCanBus(3, CAN_MODE_ACTIVE, CAN_SPEED_100KBPS); MyConfig.RegisterParam("xvu", "VW e-Up", true, true); vin_part1 = false; vin_part2 = false; vin_part3 = false; vweup_remote_climate_ticker = 0; ocu_awake = false; ocu_working = false; ocu_what = false; ocu_wait = false; vweup_cc_on = false; vweup_cc_turning_on = false; vweup_cc_temp_int = 22; fas_counter_on = 0; fas_counter_off = 0; signal_ok = false; cc_count = 0; cd_count = 0; t26_12v_boost = false; t26_car_on = false; t26_ring_awake = false; t26_12v_boost_cnt = 0; t26_12v_wait_off = 0; dev_mode = false; // true disables writing on the comfort CAN. For code debugging only. StandardMetrics.ms_v_env_locked->SetValue(true); StandardMetrics.ms_v_env_headlights->SetValue(false); StandardMetrics.ms_v_env_charging12v->SetValue(false); StandardMetrics.ms_v_env_awake->SetValue(false); StandardMetrics.ms_v_env_aux12v->SetValue(false); StandardMetrics.ms_v_env_on->SetValue(false); if (HasNoOBD()) { StandardMetrics.ms_v_charge_mode->SetValue("standard"); } } void OvmsVehicleVWeUp::T26Ticker1(uint32_t ticker) { // Autodisable climate control ticker (30 min.) if (vweup_remote_climate_ticker != 0) { vweup_remote_climate_ticker--; if (vweup_remote_climate_ticker == 1) { SendCommand(AUTO_DISABLE_CLIMATE_CONTROL); } } // Car disabled climate control if (!StandardMetrics.ms_v_env_on->AsBool() && vweup_remote_climate_ticker < 1770 && vweup_remote_climate_ticker != 0 && !StandardMetrics.ms_v_env_hvac->AsBool()) { ESP_LOGI(TAG, "Car disabled Climate Control or cc did not turn on"); vweup_remote_climate_ticker = 0; vweup_cc_on = false; ocu_awake = true; } if (StdMetrics.ms_v_bat_12v_voltage->AsFloat() < 13 && !t26_ring_awake && StandardMetrics.ms_v_env_charging12v->AsBool()) { // Wait for 12v voltage to come up to 13.2v while getting a boost: t26_12v_boost_cnt++; if (t26_12v_boost_cnt > 20) { ESP_LOGI(TAG, "Car stopped itself charging the 12v battery"); StandardMetrics.ms_v_env_charging12v->SetValue(false); StandardMetrics.ms_v_env_aux12v->SetValue(false); t26_12v_boost = false; t26_12v_boost_cnt = 0; // Clear powers & currents that are not supported by T26: StdMetrics.ms_v_bat_current->SetValue(0); StdMetrics.ms_v_bat_power->SetValue(0); StdMetrics.ms_v_bat_12v_current->SetValue(0); StdMetrics.ms_v_charge_12v_current->SetValue(0); StdMetrics.ms_v_charge_12v_power->SetValue(0); t26_12v_wait_off = 120; // Wait for two minutes before allowing new polling PollSetState(VWEUP_OFF); } } if (StdMetrics.ms_v_bat_12v_voltage->AsFloat() >= 13 && t26_12v_boost_cnt == 0) { t26_12v_boost_cnt = 20; } if (t26_12v_boost_last_cnt == t26_12v_boost_cnt && t26_12v_boost_cnt != 0 && t26_12v_boost_cnt != 20) { // We are not waiting to charging 12v to come up anymore: t26_12v_boost_cnt = 0; } if (t26_12v_wait_off != 0) { t26_12v_wait_off--; } t26_12v_boost_last_cnt = t26_12v_boost_cnt; } // Takes care of setting all the state appropriate when the car is on // or off. // void OvmsVehicleVWeUp::vehicle_vweup_car_on(bool turnOn) { if (turnOn && !StandardMetrics.ms_v_env_on->AsBool()) { // Log once that car is being turned on ESP_LOGI(TAG, "CAR IS ON"); StandardMetrics.ms_v_env_on->SetValue(true); if (!StandardMetrics.ms_v_charge_inprogress->AsBool()) { t26_12v_boost_cnt = 0; t26_12v_wait_off = 0; SetUsePhase(UP_Driving); StdMetrics.ms_v_door_chargeport->SetValue(false); PollSetState(VWEUP_ON); } else { PollSetState(VWEUP_CHARGING); } ResetTripCounters(); // Turn off possibly running climate control timer if (ocu_awake) { xTimerStop(m_sendOcuHeartbeat, 0); xTimerDelete(m_sendOcuHeartbeat, 0); m_sendOcuHeartbeat = NULL; } if (cc_count != 0) { xTimerStop(m_ccCountdown, 0); xTimerDelete(m_ccCountdown, 0); m_ccCountdown = NULL; } ocu_awake = false; ocu_working = false; vweup_remote_climate_ticker = 0; fas_counter_on = 0; fas_counter_off = 0; t26_car_on = true; StandardMetrics.ms_v_env_charging12v->SetValue(true); StandardMetrics.ms_v_env_aux12v->SetValue(true); } else if (!turnOn && StandardMetrics.ms_v_env_on->AsBool()) { // Log once that car is being turned off ESP_LOGI(TAG, "CAR IS OFF"); t26_car_on = false; t26_12v_boost = false; StandardMetrics.ms_v_env_on->SetValue(false); // StandardMetrics.ms_v_charge_voltage->SetValue(0); // StandardMetrics.ms_v_charge_current->SetValue(0); if (StandardMetrics.ms_v_charge_inprogress->AsBool()) { PollSetState(VWEUP_CHARGING); } else { PollSetState(VWEUP_AWAKE); } } } void OvmsVehicleVWeUp::IncomingFrameCan3(CAN_frame_t *p_frame) { uint8_t *d = p_frame->data.u8; static bool isCharging = false; static bool lastCharging = false; // This will log all incoming frames // ESP_LOGD(TAG, "IFC %03x 8 %02x %02x %02x %02x %02x %02x %02x %02x", p_frame->MsgID, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7]); switch (p_frame->MsgID) { case 0x61A: // SOC. // If available, OBD is normally responsible for the SOC, but K-CAN occasionally // sends SOC updates while OBD is in state OFF: if (HasNoOBD() || IsOff()) { StandardMetrics.ms_v_bat_soc->SetValue(d[7] / 2.0); } if (HasNoOBD()) { if (vweup_modelyear >= 2020) { StandardMetrics.ms_v_bat_range_ideal->SetValue((260 * (d[7] / 2.0)) / 100.0); // This is dirty. Based on WLTP only. Should be based on SOH. } else { StandardMetrics.ms_v_bat_range_ideal->SetValue((160 * (d[7] / 2.0)) / 100.0); // This is dirty. Based on WLTP only. Should be based on SOH. } } break; case 0x52D: // KM range left (estimated). if (d[0] != 0xFE) { if (d[1] == 0x41) { StandardMetrics.ms_v_bat_range_est->SetValue(d[0] + 255); } else { StandardMetrics.ms_v_bat_range_est->SetValue(d[0]); } } break; case 0x65F: // VIN switch (d[0]) { case 0x00: // Part 1 m_vin[0] = d[5]; m_vin[1] = d[6]; m_vin[2] = d[7]; vin_part1 = true; break; case 0x01: // Part 2 if (vin_part1) { m_vin[3] = d[1]; m_vin[4] = d[2]; m_vin[5] = d[3]; m_vin[6] = d[4]; m_vin[7] = d[5]; m_vin[8] = d[6]; m_vin[9] = d[7]; vin_part2 = true; } break; case 0x02: // Part 3 - VIN complete if (vin_part2 && !vin_part3) { m_vin[10] = d[1]; m_vin[11] = d[2]; m_vin[12] = d[3]; m_vin[13] = d[4]; m_vin[14] = d[5]; m_vin[15] = d[6]; m_vin[16] = d[7]; m_vin[17] = 0; vin_part3 = true; StandardMetrics.ms_v_vin->SetValue((string)m_vin); } break; } break; case 0x65D: { // ODO float odo = (float) (((uint32_t)(d[3] & 0xf) << 12) | ((UINT)d[2] << 8) | d[1]); StandardMetrics.ms_v_pos_odometer->SetValue(odo); break; } case 0x320: // Speed StandardMetrics.ms_v_pos_speed->SetValue(((d[4] << 8) + d[3] - 1) / 190); UpdateTripOdo(); if (HasNoOBD()) CalculateAcceleration(); // only necessary until we find acceleration on T26 break; case 0x527: // Outdoor temperature StandardMetrics.ms_v_env_temp->SetValue((d[5] - 100) / 2); break; case 0x381: // Vehicle locked if (d[0] == 0x02) { StandardMetrics.ms_v_env_locked->SetValue(true); } else { StandardMetrics.ms_v_env_locked->SetValue(false); } break; case 0x3E3: // Cabin temperature if (d[2] != 0xFF) { StandardMetrics.ms_v_env_cabintemp->SetValue((d[2] - 100) / 2); // Set PEM inv temp to support older app version with cabin temp workaround display // StandardMetrics.ms_v_inv_temp->SetValue((d[2] - 100) / 2); } break; case 0x470: // Doors StandardMetrics.ms_v_door_fl->SetValue((d[1] & 0x01) > 0); StandardMetrics.ms_v_door_fr->SetValue((d[1] & 0x02) > 0); StandardMetrics.ms_v_door_rl->SetValue((d[1] & 0x04) > 0); StandardMetrics.ms_v_door_rr->SetValue((d[1] & 0x08) > 0); StandardMetrics.ms_v_door_trunk->SetValue((d[1] & 0x20) > 0); StandardMetrics.ms_v_door_hood->SetValue((d[1] & 0x10) > 0); break; case 0x531: // Head lights if (d[0] > 0) { StandardMetrics.ms_v_env_headlights->SetValue(true); } else { StandardMetrics.ms_v_env_headlights->SetValue(false); } break; // Check for running hvac. case 0x3E1: if (d[4] > 0) { StandardMetrics.ms_v_env_hvac->SetValue(true); } else { StandardMetrics.ms_v_env_hvac->SetValue(false); } break; case 0x571: // 12 Volt StandardMetrics.ms_v_charge_12v_voltage->SetValue(5 + (0.05 * d[0])); break; case 0x61C: // Charge detection cd_count++; if (d[2] < 0x07) { isCharging = true; } else { isCharging = false; } if (isCharging != lastCharging) { // count till 3 messages in a row to stop ghost triggering if (isCharging && cd_count == 3) { cd_count = 0; SetUsePhase(UP_Charging); ResetChargeCounters(); StandardMetrics.ms_v_door_chargeport->SetValue(true); StandardMetrics.ms_v_charge_pilot->SetValue(true); SetChargeState(true); StandardMetrics.ms_v_env_charging12v->SetValue(true); StandardMetrics.ms_v_env_aux12v->SetValue(true); ESP_LOGI(TAG, "Car charge session started"); t26_12v_wait_off = 0; t26_12v_boost_cnt = 0; PollSetState(VWEUP_CHARGING); } if (!isCharging && cd_count == 3) { cd_count = 0; StandardMetrics.ms_v_charge_pilot->SetValue(false); SetChargeState(false); if (StandardMetrics.ms_v_env_on->AsBool()) { SetUsePhase(UP_Driving); StdMetrics.ms_v_door_chargeport->SetValue(false); PollSetState(VWEUP_ON); } else { PollSetState(VWEUP_AWAKE); } ESP_LOGI(TAG, "Car charge session ended"); } } else { cd_count = 0; } if (cd_count == 0) { lastCharging = isCharging; } break; case 0x575: // Key position switch (d[0]) { case 0x00: // No key vehicle_vweup_car_on(false); StandardMetrics.ms_v_env_awake->SetValue(false); break; case 0x01: // Key in position 1, no ignition StandardMetrics.ms_v_env_awake->SetValue(true); vehicle_vweup_car_on(false); break; case 0x03: // Ignition is turned off break; case 0x05: // Ignition is turned on break; case 0x07: // Key in position 2, ignition on vehicle_vweup_car_on(true); break; case 0x0F: // Key in position 3, start the engine break; } break; case 0x400: // Welcome to the ring from ILM case 0x40C: // We know this one too. Climatronic. case 0x436: // Working in the ring. case 0x439: // Who are 436 and 439 and why do they differ on some cars? if (d[0] == 0x00 && !ocu_awake && !StandardMetrics.ms_v_charge_inprogress->AsBool() && !t26_12v_boost && !t26_car_on && d[1] != 0x31 && t26_12v_wait_off == 0) { // The car wakes up to charge the 12v battery StandardMetrics.ms_v_env_charging12v->SetValue(true); StandardMetrics.ms_v_env_aux12v->SetValue(true); t26_ring_awake = true; t26_12v_boost = true; t26_12v_boost_cnt = 0; PollSetState(VWEUP_AWAKE); ESP_LOGI(TAG, "Car woke up. Will try to charge 12v battery"); } if (d[1] == 0x31 && ocu_awake) { // We should go to sleep, no matter what ESP_LOGI(TAG, "Comfort CAN calls for sleep"); xTimerStop(m_sendOcuHeartbeat, 0); xTimerDelete(m_sendOcuHeartbeat, 0); m_sendOcuHeartbeat = NULL; if (cc_count != 0) { xTimerStop(m_ccCountdown, 0); xTimerDelete(m_ccCountdown, 0); m_ccCountdown = NULL; } ocu_awake = false; ocu_working = false; vweup_remote_climate_ticker = 0; fas_counter_on = 0; fas_counter_off = 0; t26_12v_boost = false; if (StandardMetrics.ms_v_charge_inprogress->AsBool()) { PollSetState(VWEUP_CHARGING); } else { t26_ring_awake = false; PollSetState(VWEUP_AWAKE); } break; } if (d[0] == 0x00 && d[1] != 0x31 && !t26_ring_awake) { t26_ring_awake = true; ESP_LOGI(TAG, "Ring awake"); if (t26_12v_wait_off != 0) { ESP_LOGI(TAG, "ODB AWAKE ist still blocked"); } } if (d[1] == 0x31 && t26_ring_awake) { t26_ring_awake = false; ESP_LOGI(TAG, "Ring asleep"); } if (d[0] == 0x1D) { // We are called in the ring unsigned char data[8]; uint8_t length; length = 8; canbus *comfBus; comfBus = m_can3; data[0] = 0x00; // As it seems that we are always the highest in the ring we always send to 0x400 if (ocu_working) { data[1] = 0x01; if (dev_mode) { ESP_LOGI(TAG, "OCU working"); } } else { data[1] = 0x11; // Ready to sleep. This is very important! if (dev_mode) { ESP_LOGI(TAG, "OCU ready to sleep"); } } data[2] = 0x02; data[3] = d[3]; // Not understood data[4] = 0x00; if (ocu_what) { // This is not understood and not implemented yet. data[5] = 0x14; } else { data[5] = 0x10; } data[6] = 0x00; data[7] = 0x00; vTaskDelay(50 / portTICK_PERIOD_MS); if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x43D, length, data); // We answer } ocu_awake = true; break; } break; default: // This will log all unknown incoming frames // ESP_LOGD(TAG, "IFC %03x 8 %02x %02x %02x %02x %02x %02x %02x %02x", p_frame->MsgID, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7]); break; } } OvmsVehicle::vehicle_command_t OvmsVehicleVWeUp::RemoteCommandHandler(RemoteCommand command) { ESP_LOGI(TAG, "RemoteCommandHandler"); vweup_remote_command = command; SendCommand(vweup_remote_command); if (signal_ok) { signal_ok = false; return Success; } return Fail; } //////////////////////////////////////////////////////////////////////// // Send a RemoteCommand on the CAN bus. // // Does nothing if @command is out of range // void OvmsVehicleVWeUp::SendCommand(RemoteCommand command) { switch (command) { case ENABLE_CLIMATE_CONTROL: if (StandardMetrics.ms_v_env_on->AsBool()) { ESP_LOGI(TAG, "Climate Control can't be enabled - car is on"); break; } if (fas_counter_off > 0 && fas_counter_off < 10) { ESP_LOGI(TAG, "Climate Control can't be enabled - CC off is still running"); break; } if (vweup_remote_climate_ticker == 0) { vweup_remote_climate_ticker = 1800; } else { ESP_LOGI(TAG, "Enable Climate Control - already enabled"); break; } vweup_cc_turning_on = true; CommandWakeup(); ESP_LOGI(TAG, "Enable Climate Control"); m_ccCountdown = xTimerCreate("VW e-Up CC Countdown", 1000 / portTICK_PERIOD_MS, pdTRUE, this, ccCountdown); xTimerStart(m_ccCountdown, 0); signal_ok = true; break; case DISABLE_CLIMATE_CONTROL: if (!vweup_cc_on) { ESP_LOGI(TAG, "Climate Control can't be disabled - CC is not enabled."); break; } if (StandardMetrics.ms_v_env_on->AsBool()) { ESP_LOGI(TAG, "Climate Control is already disabled - car is on"); break; } if ((fas_counter_on > 0 && fas_counter_on < 10) || ocu_wait) { // Should we implement a "wait for turn off" timer here? ESP_LOGI(TAG, "Climate Control can't be disabled - CC on is still running"); break; } if (vweup_remote_climate_ticker == 0) { ESP_LOGI(TAG, "Disable Climate Control - already disabled"); break; } ESP_LOGI(TAG, "Disable Climate Control"); vweup_remote_climate_ticker = 0; CCOff(); ocu_awake = true; signal_ok = true; break; case AUTO_DISABLE_CLIMATE_CONTROL: if (StandardMetrics.ms_v_env_on->AsBool()) { // Should not be possible ESP_LOGI(TAG, "Error: CC AUTO_DISABLE when car is on"); break; } vweup_remote_climate_ticker = 0; ESP_LOGI(TAG, "Auto Disable Climate Control"); CCOff(); ocu_awake = true; signal_ok = true; break; default: return; } } // Wakeup implentation over the VW ring commands // We need to register in the ring with a call to our self from 43D with 0x1D in the first byte // OvmsVehicle::vehicle_command_t OvmsVehicleVWeUp::CommandWakeup() { if (!vweup_enable_write) { return Fail; } if (!ocu_awake && !StandardMetrics.ms_v_env_on->AsBool()) { unsigned char data[8]; uint8_t length; length = 8; unsigned char data2[2]; uint8_t length2; length2 = 2; canbus *comfBus; comfBus = m_can3; // This is a very dirty workaround to get the wakup of the ring working. // We send 0x69E some values without knowing what they do. // This throws an AsynchronousInterruptHandler error, but wakes 0x400 // So we wait two seconds and then call us in the ring data[0] = 0x14; data[1] = 0x42; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length2, data2); } vTaskDelay(2000 / portTICK_PERIOD_MS); ESP_LOGI(TAG, "Sent Wakeup Command - stage 1"); data[0] = 0x1D; // We call ourself to wake up the ring data[1] = 0x02; data[2] = 0x02; data[3] = 0x00; data[4] = 0x00; data[5] = 0x14; // What does this do? data[6] = 0x00; data[7] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x43D, length, data); } vTaskDelay(50 / portTICK_PERIOD_MS); data[0] = 0x00; // We need to talk to 0x400 first to get accepted in the ring data[1] = 0x01; data[2] = 0x02; data[3] = 0x04; // This could be a problem data[4] = 0x00; data[5] = 0x14; // What does this do? data[6] = 0x00; data[7] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x43D, length, data); } ocu_working = true; ocu_awake = true; vTaskDelay(50 / portTICK_PERIOD_MS); m_sendOcuHeartbeat = xTimerCreate("VW e-Up OCU heartbeat", 1000 / portTICK_PERIOD_MS, pdTRUE, this, sendOcuHeartbeat); xTimerStart(m_sendOcuHeartbeat, 0); ESP_LOGI(TAG, "Sent Wakeup Command - stage 2"); StandardMetrics.ms_v_env_charging12v->SetValue(true); StandardMetrics.ms_v_env_aux12v->SetValue(true); t26_12v_boost_cnt = 0; t26_12v_wait_off = 0; if (!StandardMetrics.ms_v_charge_inprogress->AsBool()) { PollSetState(VWEUP_AWAKE); } else { PollSetState(VWEUP_CHARGING); } } // This can be done better. Gives always success, even when already awake. return Success; } void OvmsVehicleVWeUp::SendOcuHeartbeat() { if (!vweup_cc_on) { fas_counter_on = 0; if (fas_counter_off < 10 && !vweup_cc_turning_on) { fas_counter_off++; ocu_working = true; if (dev_mode) { ESP_LOGI(TAG, "OCU working"); } } else { if (vweup_cc_turning_on) { ocu_working = true; if (dev_mode) { ESP_LOGI(TAG, "OCU working"); } } else { ocu_working = false; if (dev_mode) { ESP_LOGI(TAG, "OCU ready to sleep"); } } } } else { fas_counter_off = 0; ocu_working = true; if (dev_mode) { ESP_LOGI(TAG, "OCU working"); } if (fas_counter_on < 10) { fas_counter_on++; } } unsigned char data[8]; uint8_t length; length = 8; canbus *comfBus; comfBus = m_can3; data[0] = 0x00; data[1] = 0x00; data[2] = 0x00; data[3] = 0x00; data[4] = 0x00; data[5] = 0x00; data[6] = 0x00; data[7] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x5A9, length, data); } if ((fas_counter_on > 0 && fas_counter_on < 10) || (fas_counter_off > 0 && fas_counter_off < 10)) { data[0] = 0x60; if (dev_mode) { ESP_LOGI(TAG, "OCU Heartbeat - 5A7 60"); } } else { data[0] = 0x00; if (dev_mode) { ESP_LOGI(TAG, "OCU Heartbeat - 5A7 00"); } } data[1] = 0x16; data[2] = 0x00; data[3] = 0x00; data[4] = 0x00; data[5] = 0x00; data[6] = 0x00; data[7] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x5A7, length, data); } } void OvmsVehicleVWeUp::CCCountdown() { cc_count++; ocu_wait = true; if (cc_count == 5) { CCOnP(); // This is weird. We first need to send a Climate Contol Off before it will turn on ??? } if (cc_count == 10) { CCOn(); xTimerStop(m_ccCountdown, 0); xTimerDelete(m_ccCountdown, 0); m_ccCountdown = NULL; ocu_wait = false; ocu_awake = true; cc_count = 0; } } void OvmsVehicleVWeUp::CCOn() { unsigned char data[8]; uint8_t length; length = 8; unsigned char data_s[4]; uint8_t length_s; length_s = 4; canbus *comfBus; comfBus = m_can3; data[0] = 0x60; data[1] = 0x16; data[2] = 0x00; data[3] = 0x00; data[4] = 0x00; data[5] = 0x00; data[6] = 0x00; data[7] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x5A7, length, data); } // d5 could be a counter? data[0] = 0x80; data[1] = 0x20; data[2] = 0x29; data[3] = 0x59; data[4] = 0x21; data[5] = 0x00; data[6] = 0x00; data[7] = 0x01; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length, data); } data[0] = 0xC0; data[1] = 0x06; data[2] = 0x00; data[3] = 0x10; data[4] = 0x1E; data[5] = 0xFF; data[6] = 0xFF; data[7] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length, data); } data[0] = 0xC1; data[1] = 0xFF; data[2] = 0xFF; data[3] = 0xFF; data[4] = 0xFF; data[5] = 0x01; data[6] = 0x78; // This is the target temperature. T = 10 + d6/10 if (vweup_cc_temp_int == 15) { data[6] = 0x32; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 15"); } } if (vweup_cc_temp_int == 16) { data[6] = 0x3C; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 16"); } } if (vweup_cc_temp_int == 17) { data[6] = 0x46; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 17"); } } if (vweup_cc_temp_int == 18) { data[6] = 0x50; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 18"); } } if (vweup_cc_temp_int == 19) { data[6] = 0x5A; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 19"); } } if (vweup_cc_temp_int == 20) { data[6] = 0x64; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 20"); } } if (vweup_cc_temp_int == 21) { data[6] = 0x6E; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 21"); } } if (vweup_cc_temp_int == 22) { data[6] = 0x78; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 22"); } } if (vweup_cc_temp_int == 23) { data[6] = 0x82; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 23"); } } if (vweup_cc_temp_int == 24) { data[6] = 0x8C; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 24"); } } if (vweup_cc_temp_int == 25) { data[6] = 0x96; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 25"); } } if (vweup_cc_temp_int == 26) { data[6] = 0xA0; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 26"); } } if (vweup_cc_temp_int == 27) { data[6] = 0xAA; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 27"); } } if (vweup_cc_temp_int == 28) { data[6] = 0xB4; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 28"); } } if (vweup_cc_temp_int == 29) { data[6] = 0xBE; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 29"); } } if (vweup_cc_temp_int == 30) { data[6] = 0xC8; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 30"); } } data[7] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length, data); } data[0] = 0xC2; data[1] = 0x1E; data[2] = 0x1E; data[3] = 0x0A; data[4] = 0x00; data[5] = 0x00; data[6] = 0x08; data[7] = 0x4F; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length, data); } data[0] = 0xC3; data[1] = 0x70; data[2] = 0x74; data[3] = 0x69; data[4] = 0x6F; data[5] = 0x6E; data[6] = 0x65; data[7] = 0x6E; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length, data); } data[0] = 0x60; data[1] = 0x16; data[2] = 0x00; data[3] = 0x00; data[4] = 0x00; data[5] = 0x00; data[6] = 0x00; data[7] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x5A7, length, data); } data_s[0] = 0x29; data_s[1] = 0x58; data_s[2] = 0x00; data_s[3] = 0x01; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length_s, data_s); } ESP_LOGI(TAG, "Wrote second stage Climate Control On Messages to Comfort CAN."); vweup_cc_on = true; vweup_cc_turning_on = false; StandardMetrics.ms_v_env_charging12v->SetValue(true); StandardMetrics.ms_v_env_aux12v->SetValue(true); } void OvmsVehicleVWeUp::CCOnP() { unsigned char data[8]; uint8_t length; length = 8; unsigned char data_s[4]; uint8_t length_s; length_s = 4; canbus *comfBus; comfBus = m_can3; data[0] = 0x60; data[1] = 0x16; data[2] = 0x00; data[3] = 0x00; data[4] = 0x00; data[5] = 0x00; data[6] = 0x00; data[7] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x5A7, length, data); } // d5 could be a counter data[0] = 0x80; data[1] = 0x20; data[2] = 0x29; data[3] = 0x59; data[4] = 0x22; data[5] = 0x00; data[6] = 0x00; data[7] = 0x01; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length, data); } data[0] = 0x60; data[1] = 0x16; data[2] = 0x00; data[3] = 0x00; data[4] = 0x00; data[5] = 0x00; data[6] = 0x00; data[7] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x5A7, length, data); } data[0] = 0xC0; data[1] = 0x06; data[2] = 0x00; data[3] = 0x10; data[4] = 0x1E; data[5] = 0xFF; data[6] = 0xFF; data[7] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length, data); } data[0] = 0xC1; data[1] = 0xFF; data[2] = 0xFF; data[3] = 0xFF; data[4] = 0xFF; data[5] = 0x01; data[6] = 0x78; // This is the target temperature. T = 10 + d6/10 if (vweup_cc_temp_int == 15) { data[6] = 0x32; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 15"); } } if (vweup_cc_temp_int == 16) { data[6] = 0x3C; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 16"); } } if (vweup_cc_temp_int == 17) { data[6] = 0x46; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 17"); } } if (vweup_cc_temp_int == 18) { data[6] = 0x50; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 18"); } } if (vweup_cc_temp_int == 19) { data[6] = 0x5A; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 19"); } } if (vweup_cc_temp_int == 20) { data[6] = 0x64; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 20"); } } if (vweup_cc_temp_int == 21) { data[6] = 0x6E; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 21"); } } if (vweup_cc_temp_int == 22) { data[6] = 0x78; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 22"); } } if (vweup_cc_temp_int == 23) { data[6] = 0x82; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 23"); } } if (vweup_cc_temp_int == 24) { data[6] = 0x8C; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 24"); } } if (vweup_cc_temp_int == 25) { data[6] = 0x96; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 25"); } } if (vweup_cc_temp_int == 26) { data[6] = 0xA0; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 26"); } } if (vweup_cc_temp_int == 27) { data[6] = 0xAA; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 27"); } } if (vweup_cc_temp_int == 28) { data[6] = 0xB4; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 28"); } } if (vweup_cc_temp_int == 29) { data[6] = 0xBE; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 29"); } } if (vweup_cc_temp_int == 30) { data[6] = 0xC8; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 30"); } } data[7] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length, data); } data[0] = 0xC2; data[1] = 0x1E; data[2] = 0x1E; data[3] = 0x0A; data[4] = 0x00; data[5] = 0x00; data[6] = 0x08; data[7] = 0x4F; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length, data); } data[0] = 0xC3; data[1] = 0x70; data[2] = 0x74; data[3] = 0x69; data[4] = 0x6F; data[5] = 0x6E; data[6] = 0x65; data[7] = 0x6E; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length, data); } data_s[0] = 0x29; data_s[1] = 0x58; data_s[2] = 0x00; data_s[3] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length_s, data_s); } ESP_LOGI(TAG, "Wrote first stage Climate Control On Messages to Comfort CAN."); } void OvmsVehicleVWeUp::CCOff() { unsigned char data[8]; uint8_t length; length = 8; unsigned char data_s[4]; uint8_t length_s; length_s = 4; canbus *comfBus; comfBus = m_can3; data[0] = 0x60; data[1] = 0x16; data[2] = 0x00; data[3] = 0x00; data[4] = 0x00; data[5] = 0x00; data[6] = 0x00; data[7] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x5A7, length, data); } // d5 could be a counter data[0] = 0x80; data[1] = 0x20; data[2] = 0x29; data[3] = 0x59; data[4] = 0x22; data[5] = 0x00; data[6] = 0x00; data[7] = 0x01; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length, data); } data[0] = 0x60; data[1] = 0x16; data[2] = 0x00; data[3] = 0x00; data[4] = 0x00; data[5] = 0x00; data[6] = 0x00; data[7] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x5A7, length, data); } data[0] = 0xC0; data[1] = 0x06; data[2] = 0x00; data[3] = 0x10; data[4] = 0x1E; data[5] = 0xFF; data[6] = 0xFF; data[7] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length, data); } data[0] = 0xC1; data[1] = 0xFF; data[2] = 0xFF; data[3] = 0xFF; data[4] = 0xFF; data[5] = 0x01; data[6] = 0x78; // This is the target temperature. T = 10 + d6/10 if (vweup_cc_temp_int == 19) { data[6] = 0x5A; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 19"); } } if (vweup_cc_temp_int == 20) { data[6] = 0x64; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 20"); } } if (vweup_cc_temp_int == 21) { data[6] = 0x6E; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 21"); } } if (vweup_cc_temp_int == 22) { data[6] = 0x78; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 22"); } } if (vweup_cc_temp_int == 23) { data[6] = 0x82; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 23"); } } if (vweup_cc_temp_int == 24) { data[6] = 0x8C; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 24"); } } if (vweup_cc_temp_int == 25) { data[6] = 0x96; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 25"); } } if (vweup_cc_temp_int == 26) { data[6] = 0xA0; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 26"); } } if (vweup_cc_temp_int == 27) { data[6] = 0xAA; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 27"); } } if (vweup_cc_temp_int == 28) { data[6] = 0xB4; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 28"); } } if (vweup_cc_temp_int == 29) { data[6] = 0xBE; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 29"); } } if (vweup_cc_temp_int == 30) { data[6] = 0xC8; if (dev_mode) { ESP_LOGI(TAG, "Cabin temperature set: 30"); } } data[0] = 0xC2; data[1] = 0x1E; data[2] = 0x1E; data[3] = 0x0A; data[4] = 0x00; data[5] = 0x00; data[6] = 0x08; data[7] = 0x4F; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length, data); } data[0] = 0xC3; data[1] = 0x70; data[2] = 0x74; data[3] = 0x69; data[4] = 0x6F; data[5] = 0x6E; data[6] = 0x65; data[7] = 0x6E; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length, data); } data_s[0] = 0x29; data_s[1] = 0x58; data_s[2] = 0x00; data_s[3] = 0x00; if (vweup_enable_write && !dev_mode) { comfBus->WriteStandard(0x69E, length_s, data_s); } ESP_LOGI(TAG, "Wrote Climate Control Off Message to Comfort CAN."); vweup_cc_on = false; } OvmsVehicle::vehicle_command_t OvmsVehicleVWeUp::CommandLock(const char *pin) { ESP_LOGI(TAG, "CommandLock"); return NotImplemented; } OvmsVehicle::vehicle_command_t OvmsVehicleVWeUp::CommandUnlock(const char *pin) { ESP_LOGI(TAG, "CommandUnlock"); return NotImplemented; } OvmsVehicle::vehicle_command_t OvmsVehicleVWeUp::CommandActivateValet(const char *pin) { ESP_LOGI(TAG, "CommandActivateValet"); return NotImplemented; } OvmsVehicle::vehicle_command_t OvmsVehicleVWeUp::CommandDeactivateValet(const char *pin) { ESP_LOGI(TAG, "CommandLDeactivateValet"); return NotImplemented; } OvmsVehicle::vehicle_command_t OvmsVehicleVWeUp::CommandStartCharge() { ESP_LOGI(TAG, "CommandStartCharge"); return NotImplemented; } OvmsVehicle::vehicle_command_t OvmsVehicleVWeUp::CommandStopCharge() { ESP_LOGI(TAG, "CommandStopCharge"); return NotImplemented; } OvmsVehicle::vehicle_command_t OvmsVehicleVWeUp::CommandHomelink(int button, int durationms) { // This is needed to enable climate control via Homelink for the iOS app ESP_LOGI(TAG, "CommandHomelink"); if (button == 0 && vweup_enable_write) { return RemoteCommandHandler(ENABLE_CLIMATE_CONTROL); } if (button == 1 && vweup_enable_write) { return RemoteCommandHandler(DISABLE_CLIMATE_CONTROL); } return NotImplemented; } OvmsVehicle::vehicle_command_t OvmsVehicleVWeUp::CommandClimateControl(bool climatecontrolon) { ESP_LOGI(TAG, "CommandClimateControl"); if (vweup_enable_write) { return RemoteCommandHandler(climatecontrolon ? ENABLE_CLIMATE_CONTROL : DISABLE_CLIMATE_CONTROL); } else { return NotImplemented; } }