Skip to content

Instantly share code, notes, and snippets.

@jnimmo
Forked from mukowman/wican.yaml
Last active June 17, 2024 04:39
Show Gist options
  • Save jnimmo/5182c59011a16c94293935d06c7857a9 to your computer and use it in GitHub Desktop.
Save jnimmo/5182c59011a16c94293935d06c7857a9 to your computer and use it in GitHub Desktop.
ESPHome configuration for the Ioniq EV 28kw with the MeatPi WiCAN interface
substitutions:
device_name: car
charging_voltage_threshold: '13.0'
low_voltage_threshold: '12.0'
low_voltage_sleep_duration: 30min
deep_sleep_duration: 3min # 5 * 60000
first_boot_run_duration: '300000'
abrp_key: !secret abrp_key
abrp_token: !secret abrp_token
hotspot1_ssid: !secret hotspot1_ssid
hotspot2_ssid: !secret hotspot2_ssid
hotspot3_ssid: !secret hotspot3_ssid
hotspot4_ssid: !secret hotspot4_ssid
hotspot1_password: !secret hotspot1_password
hotspot2_password: !secret hotspot2_password
hotspot3_password: !secret hotspot3_password
hotspot4_password: !secret hotspot4_password
esp32:
variant: ESP32C3
board: esp32-c3-devkitm-1
framework:
type: arduino
globals:
- id: sleep_mode_enabled
type: 'bool'
restore_value: yes
initial_value: 'true'
- id: added_wifi_hotspots
type: 'bool'
restore_value: no
initial_value: 'false'
- id: last_odometer_value
type: 'float'
restore_value: yes
- id: pending_status_update
type: 'bool'
restore_value: no
initial_value: 'false'
- id: last_hv_charging_state
type: 'bool'
restore_value: yes
initial_value: 'false'
- id: last_ac_present_state
type: 'bool'
restore_value: yes
initial_value: 'false'
- id: last_state_of_charge
type: uint8_t
restore_value: yes
initial_value: '0'
- id: frame_received
type: 'bool'
restore_value: no
initial_value: 'false'
- id: received_data
type: 'std::vector<uint8_t>'
restore_value: no
initial_value: 'std::vector<uint8_t>()'
- id: expected_length
type: 'int'
restore_value: no
initial_value: '0'
- id: next_frame_seq
type: 'uint8_t'
restore_value: no
initial_value: '0x21' # Start wfith 0x21 after FF
- id: can_soh_raw
type: 'float'
restore_value: no
initial_value: 'NAN'
- id: can_soh_display
type: 'float'
restore_value: no
initial_value: 'NAN'
- id: can_soc_bms
type: 'float'
restore_value: no
initial_value: 'NAN'
- id: can_battery_dc_voltage
type: 'float'
restore_value: no
initial_value: '0'
- id: can_battery_current_signed
type: 'float'
restore_value: no
initial_value: '0'
- id: can_battery_power
type: 'float'
restore_value: no
initial_value: '0'
- id: can_operating_hours_float
type: 'float'
restore_value: no
initial_value: 'NAN'
- id: can_speed_mph
type: 'float'
restore_value: no
initial_value: 'NAN'
- id: can_ignition_status
type: 'bool'
restore_value: no
initial_value: 'NAN'
- id: trip_startsoc
type: 'float'
restore_value: no
initial_value: '0'
- id: trip_startodo
type: 'float'
restore_value: no
initial_value: '0'
- id: last_canbus_packet
type: 'std::string'
restore_value: no
initial_value: '""'
- id: is_manual_request
type: 'bool'
restore_value: no
initial_value: 'false'
esphome:
name: car-wican
on_shutdown:
then:
- if:
condition:
lambda: return id(state_of_charge_bms).has_state();
then:
globals.set:
id: last_state_of_charge
value: !lambda 'return round(id(state_of_charge_bms).state);'
- if:
condition:
lambda: return id(odometer).has_state();
then:
globals.set:
id: last_odometer_value
value: !lambda 'return id(odometer).state;'
- if:
condition:
lambda: return id(ac_present).has_state();
then:
globals.set:
id: last_ac_present_state
value: !lambda 'return id(ac_present).state;'
- if:
condition:
lambda: return id(hv_charging).has_state();
then:
globals.set:
id: last_hv_charging_state
value: !lambda 'return id(hv_charging).state;'
on_boot:
priority: 210
then:
# Check if this is not a deep sleep wakeup
- light.turn_on:
id: blue_led
effect: "sleeping"
- script.execute: restore_sensor_values
# - wireguard.disable
- wait_until:
# If the battery voltage indicates the car is running, or the canbus responds that the car is charging, then wake up:
condition:
lambda: |-
return id(starter_battery_voltage).has_state();
timeout: 6s
- if:
condition:
and: # Deep sleep wakeup, check if we need to go back to sleep
- binary_sensor.is_off: first_boot
- sensor.in_range: # Low battery condition
id: starter_battery_voltage
below: '${low_voltage_threshold}'
then:
- logger.log: "Low voltage, going back to sleep."
- deep_sleep.enter:
id: deep_sleep_1
sleep_duration: ${low_voltage_sleep_duration}
- if:
condition:
binary_sensor.is_on: first_boot
then:
- lambda: id(deep_sleep_1)->set_run_duration(${first_boot_run_duration});
- text_sensor.template.publish:
id: boot_reason
state: "First boot"
- script.execute: update_car_sensors
- script.wait: update_car_sensors
- if: # Pending status update
condition: # If ignition is off, or has no state,
or:
- lambda: return (id(ignition).has_state() && id(ignition).state);
- lambda: return id(pending_status_update);
- lambda: return id(first_boot).state;
then:
- wifi.enable:
- light.turn_on:
id: blue_led
effect: "None"
- light.turn_on:
id: green_led
effect: "searching"
- script.execute: add_wifi_hotspots
# - script.execute: update_bthome_broadcast
else:
- logger.log: "No updates to send, going back to sleep."
- deep_sleep.enter:
id: deep_sleep_1
sleep_duration: ${deep_sleep_duration}
logger:
level: DEBUG #NONE# ERROR #INFO #DEBUG #VERBOSE
baud_rate: 0 #to disable logging via UART
# logs:
# text_sensor: ERROR
# homeassistant.sensor: ERROR
# canbus: INFO
# light: INFO
deep_sleep:
id: deep_sleep_1
run_duration: 50s
sleep_duration: ${deep_sleep_duration}
api:
reboot_timeout: 0s
on_client_connected:
- logger.log:
format: "Client %s connected to API with IP %s"
args: ["client_info.c_str()", "client_address.c_str()"]
services:
- service: send_manual_canbus_request
variables:
can_id: string
data: string
then:
- logger.log:
format: "Sending manual CAN bus request: can_id=0x%s, data=%s"
args: [ 'can_id.c_str()', 'data.c_str()' ]
- globals.set:
id: is_manual_request
value: 'true'
- globals.set:
id: frame_received
value: 'false'
- lambda: |-
std::vector<uint8_t> data_vector;
for (size_t i = 0; i < data.length(); i += 2) {
uint8_t byte = strtol(data.substr(i, 2).c_str(), NULL, 16);
data_vector.push_back(byte);
}
int can_id_int = strtol(can_id.c_str(), NULL, 16);
id(can0)->send_data(can_id_int, false, data_vector);
- wait_until:
condition:
lambda: return id(frame_received);
timeout: 1sec
- globals.set:
id: is_manual_request
value: 'false'
# esp32_ble:
# enable_on_boot: false
# esp32_ble_server:
# id: bleserver
# manufacturer: "ESPHome"
# model: "WiCAN"
# manufacturer_data: [0x4C, 0, 0x23, 77, 0xF0]
time:
- platform: homeassistant
id: ha_time
- platform: sntp
id: sntp_time
servers:
- 162.159.200.1
- 192.168.178.1
- 202.37.101.1
timezone: Pacific/Auckland
on_time_sync:
then:
- script.execute: send_abrp_telemetry_script
text_sensor:
- platform: template
id: boot_reason
name: "Wake reason"
- platform: wifi_info
ssid:
name: Connected SSID
id: connected_ssid
- platform: template
id: last_canbus_packet_sensor
name: "Last CANBus packet"
update_interval: 10s
wifi:
id: wifi_component
reboot_timeout: 0s
fast_connect: true
enable_on_boot: false
on_connect:
- light.turn_on:
id: green_led
effect: "None"
- if:
condition:
and:
- switch.is_on: send_abrp_telemetry
then:
- script.execute: send_abrp_telemetry_script
on_disconnect:
- light.turn_on:
id: green_led
effect: "searching"
networks:
- ssid: !secret wifi_ssid
password: !secret wifi_password
priority: 9
manual_ip:
static_ip: 192.168.178.210
gateway: 192.168.178.1
subnet: 255.255.255.0
dns1: 192.168.178.1
output:
- id: blue_led_output
platform: gpio
pin: 7
- id: green_led_output
platform: gpio
pin: { number: 8, inverted: true }
- id: yellow_led_output
platform: gpio
pin: { number: 9, inverted: true }
light:
- platform: binary
id: blue_led
output: blue_led_output
internal: True
effects:
- strobe:
name: "sleeping"
colors:
- state: True
duration: 500ms
- state: False
duration: 500ms
- strobe:
name: "sleep"
colors:
- state: True
duration: 2000ms
- state: False
duration: 2000ms
restore_mode: ALWAYS_ON
- platform: binary
id: green_led
output: green_led_output
effects:
- strobe:
name: "searching"
colors:
- state: True
duration: 100ms
- state: False
duration: 3000ms
- platform: binary
id: yellow_led
output: yellow_led_output
button:
- platform: restart
name: "${device_name} Restart"
- platform: template
name: Query Car Status
id: query_status
on_press:
- script.execute: update_car_sensors
canbus:
- platform: esp32_can
id: can0
tx_pin: 0
rx_pin: 3
bit_rate: 500kbps
can_id: 0 # mandatory but we do not use it
on_frame:
- can_id: 0
can_id_mask: 0
then:
- lambda: |-
auto data_pretty = remote_transmission_request ? "n/a" : format_hex_pretty(x).c_str();
ESP_LOGD("eup_dump", "can_id: 0x%08x, rtr: %d, length: %d, content: %s", can_id, remote_transmission_request, x.size(), data_pretty);
uint8_t frame_type = (x[0] & 0xF0) >> 4; // Extract the first 4 bits
uint8_t frame_index = x[0] & 0x0F; // Extract the next 4 bits
if (frame_type == 0x00) {
// This is a single-frame message
id(expected_length) = x[0] & 0x0F; // Extract the length from the first 4 bits
id(received_data).clear();
id(frame_received) = true;
id(next_frame_seq) = 0x01; // Reset for next multi-frame message
// Append the data (excluding the first byte which contains the length)
for (int i = 1; i < x.size(); i++) {
id(received_data).push_back(x[i]);
}
// Extract any single message data here
} else if (frame_type == 0x01) {
// This is the start of a multi-frame message
// Extract the message length from the next 12 bits
id(expected_length) = ((x[0] & 0x0F) << 8) | x[1];
ESP_LOGD("eup_dump", "multi-frame message header, total frame length: %d", id(expected_length));
id(received_data).clear();
id(next_frame_seq) = 0x01;
id(frame_received) = false;
// Append the first part of the data (after the size bytes)
for (int i = 2; i < 8; i++) {
id(received_data).push_back(x[i]);
}
// Send Flow Control frame to request Consecutive Frames
id(can0)->send_data(can_id - 0x08, false, {0x30, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
} else if (frame_type == 0x02) {
ESP_LOGD("eup_dump", "Received frame_index: %02X, Expected next_frame_seq: %02X", frame_index, id(next_frame_seq));
if (frame_index == id(next_frame_seq)) {
// This is a Consecutive Frame
for (int i = 1; i < 8; i++) {
id(received_data).push_back(x[i]);
}
id(next_frame_seq) = (id(next_frame_seq) == 0x0F) ? 0x00 : id(next_frame_seq) + 1;
// Check if reassembly is complete
if (id(received_data).size() >= id(expected_length)) {
// Convert the vector to a formatted string
std::string assembled_data;
for (uint8_t byte : id(received_data)) {
char buffer[4];
sprintf(buffer, "%02X.", byte);
assembled_data += buffer;
}
// Print the formatted string
ESP_LOGI("eup_dump", "Recieved completed canbus frame");
ESP_LOGD("AssembledFrame", "Complete Frame: %s", assembled_data.c_str());
if (id(is_manual_request)) {
id(last_canbus_packet) = assembled_data.c_str();
id(last_canbus_packet_sensor).publish_state(assembled_data.c_str());
}
if (can_id == 0x7EC && id(received_data)[1] == 0x05) {
if (id(received_data).size() >= 45) {
float soh_raw = ((id(received_data)[27] << 8) | id(received_data)[28]) / 10.0;
id(state_of_health).publish_state(soh_raw);
}
// State of Charge Display
float soc_display = (id(received_data).size() > 33) ? id(received_data)[33] / 2 : -1;
id(state_of_charge).publish_state(soc_display);
}
else if (can_id == 0x7EC && id(received_data)[1] == 0x01) {
// State of Charge BMS
float soc_bms = (id(received_data).size() > 6) ? id(received_data)[6] / 2 : -1;
id(state_of_charge_bms).publish_state(soc_bms);
id(state_of_charge_bms_calibrated).publish_state(soc_bms);
if (id(trip_startsoc) == 0) {
id(trip_startodo) = soc_bms;
}
// HV Battery Voltage
id(can_battery_dc_voltage) = ((id(received_data)[14] * 256) + id(received_data)[15]) / 10.0;
// Battery Current
int signed_byte_k = (id(received_data)[12] < 128) ? id(received_data)[12] : id(received_data)[12] - 256;
id(can_battery_current_signed) = (signed_byte_k * 256 + id(received_data)[13]) / 10.0;
// Battery temperatures
int battery_max_temp = (id(received_data)[16] < 128) ? id(received_data)[16] : id(received_data)[16] - 256;
int battery_min_temp = (id(received_data)[17] < 128) ? id(received_data)[17] : id(received_data)[17] - 256;
id(battery_temperature).publish_state((battery_min_temp + battery_max_temp)/2);
// Charging Status
bool hv_charging_status = (id(received_data)[11] >> 7) & 1;
id(hv_charging).publish_state(hv_charging_status);
bool ac_present_status = (id(received_data)[11] >> 5) & 1;
id(ac_present).publish_state(ac_present_status);
// CCS charging port
bool fast_charging_status = (id(received_data)[11] >> 6) & 1;
id(fast_charging).publish_state(fast_charging_status);
// BMS Ignition (published at end of lambda)
id(can_ignition_status) = (id(received_data)[52] >> 2) & 1;
// Operating hours
float operating_hours_float = ((id(received_data)[48] << 24) | (id(received_data)[49] << 16) | (id(received_data)[50] << 8) | id(received_data)[51]) / 3600.0;
id(operating_hours).publish_state(operating_hours_float);
// Extract and publish Cumulative Energy Charged
if (id(received_data).size() > 48) {
float cumulative_energy_charged_kw = ((id(received_data)[40] << 24) | (id(received_data)[41] << 16) | (id(received_data)[42] << 8) | id(received_data)[43]) / 10.0;
float cumulative_energy_discharged_kw = ((id(received_data)[44] << 24) | (id(received_data)[45] << 16) | (id(received_data)[46] << 8) | id(received_data)[47]) / 10.0;
id(cumulative_energy_charged).publish_state(cumulative_energy_charged_kw);
id(cumulative_energy_discharged).publish_state(cumulative_energy_discharged_kw);
}
// Boot flags
if (soc_bms != id(last_state_of_charge) || hv_charging_status != id(last_hv_charging_state) || ac_present_status != id(last_ac_present_state)) {
id(pending_status_update) = true;
id(pending_status_update_sensor).publish_state(true);
id(last_state_of_charge) = soc_bms;
}
}
else if (can_id == 0x7EA && id(received_data)[1] == 0x01) {
// response from 7E2 (published end of lambda)
id(can_speed_mph) = ((signed int)(id(received_data)[16]) * 256 + id(received_data)[15]) / 100.0;
}
else if (can_id == 0x7EE && id(received_data)[1] == 0x80 && id(received_data).size() > 14) {
// response to 0x7E6, containing ambient temperature
float ambient_temperature_float = (((id(received_data)[14] < 128) ? id(received_data)[14] : id(received_data)[14] - 256) - 80) / 2.0;
id(ambient_temperature).publish_state(ambient_temperature_float);
}
else if (can_id == 0x7CE && id(received_data)[1] == 0xB0 && id(received_data).size() > 11) {
// response to 7C6, containing odometer
uint32_t odometer_value = (static_cast<uint32_t>(id(received_data)[9]) << 16) |
(static_cast<uint32_t>(id(received_data)[10]) << 8) |
static_cast<uint32_t>(id(received_data)[11]);
id(odometer).publish_state(odometer_value);
if (id(trip_startodo) == 0) {
id(trip_startodo) = odometer_value;
}
}
id(next_frame_seq) = 0x01; // Reset for next multi-frame message
id(frame_received) = true;
}
}
} else {
ESP_LOGI("eup_dump", "Unexpected frame");
}
switch:
- platform: gpio
id: can_enabled
name: "Enable CAN Interface"
pin:
number: 6
inverted: true
restore_mode: ALWAYS_ON
- platform: template
id: sleep_mode
name: "${device_name} Sleep Mode"
optimistic: true
restore_mode: RESTORE_DEFAULT_ON
on_turn_on:
- logger.log: "Sleep mode: ON"
on_turn_off:
- logger.log: "Sleep mode: OFF"
- platform: template
id: send_abrp_telemetry
name: "Send ABRP telemetry"
optimistic: true
restore_mode: RESTORE_DEFAULT_OFF
- platform: template
id: advertise_bthome
name: "Advertise BLE Home packets"
optimistic: true
restore_mode: RESTORE_DEFAULT_ON
# on_turn_off:
# then:
# if:
# condition:
# ble.enabled
# then:
# ble.disable
interval:
# every 10 seconds while driving
- interval: 9s
then:
- if:
condition:
and:
- wifi.connected:
- binary_sensor.is_on: ignition
- switch.is_on: send_abrp_telemetry
- lambda: 'return !id(is_manual_request);'
then:
- script.execute: update_car_sensors
- script.wait: update_car_sensors
- script.execute: send_abrp_telemetry_script
- interval: 60s
then:
- script.execute: update_car_sensors
- script.wait: update_car_sensors
# - script.execute: update_bthome_broadcast
- if:
condition:
and:
- wifi.connected:
- switch.is_on: send_abrp_telemetry
then:
- script.execute: send_abrp_telemetry_script
- interval: 5min
then:
# - script.execute: update_bthome_broadcast
- if:
condition:
- wifi.connected:
then:
- script.execute: send_home_assistant_webhook
sensor:
- platform: template
id: state_of_health
name: "State of Health"
unit_of_measurement: "%"
accuracy_decimals: 2
state_class: measurement
filters:
- filter_out: nan
- platform: template
id: state_of_charge
name: "State of Charge (Display)"
unit_of_measurement: "%"
accuracy_decimals: 0
device_class: battery
state_class: measurement
filters:
- filter_out: nan
- delta: 1.0
- platform: template
id: state_of_charge_bms
name: "State of Charge (BMS)"
unit_of_measurement: "%"
accuracy_decimals: 1
device_class: battery
state_class: measurement
filters:
- filter_out: nan
- delta: 0.5
- platform: template
id: state_of_charge_bms_calibrated
name: "State of Charge"
unit_of_measurement: "%"
accuracy_decimals: 0
device_class: battery
state_class: measurement
filters:
- filter_out: nan
- delta: 0.5
- calibrate_linear:
method: least_squares
datapoints:
# Map 0.0 (from sensor) to 1.0 (true value)
- 12 -> 11
- 95 -> 99
- clamp:
max_value: 100.0
- platform: template
id: cumulative_energy_charged
name: "Cumulative Energy Charged"
unit_of_measurement: "kWh"
accuracy_decimals: 0
device_class: energy
state_class: total_increasing
filters:
- filter_out: nan
- delta: 1.0
- platform: template
id: cumulative_energy_discharged
name: "Cumulative Energy Discharged"
unit_of_measurement: "kWh"
accuracy_decimals: 0
device_class: energy
state_class: total_increasing
filters:
- filter_out: nan
- delta: 1.0
- platform: template
id: battery_current
name: "Battery Current"
unit_of_measurement: "A"
accuracy_decimals: 2
device_class: current
state_class: measurement
filters:
- filter_out: nan
- delta: 0.2
- platform: template
id: battery_power
name: "Battery Power"
unit_of_measurement: "kW"
accuracy_decimals: 2
device_class: power
state_class: measurement
filters:
- filter_out: nan
- platform: template
id: hv_battery_voltage
name: "HV Battery Voltage"
unit_of_measurement: "V"
accuracy_decimals: 0
device_class: voltage
state_class: measurement
filters:
- filter_out: nan
- delta: 1
- platform: template
id: real_vehicle_speed
name: "Real Vehicle Speed"
unit_of_measurement: "km/h"
accuracy_decimals: 0
device_class: speed
state_class: measurement
filters:
- filter_out: nan
- clamp:
min_value: 0
max_value: 140
ignore_out_of_range: true
- platform: template
id: operating_hours
name: "Operating hours"
unit_of_measurement: "h"
accuracy_decimals: 0
device_class: duration
state_class: total_increasing
update_interval: 5min
filters:
- filter_out: nan
- platform: template
id: odometer
name: "Odometer"
unit_of_measurement: "km"
accuracy_decimals: 0
device_class: distance
state_class: total_increasing
filters:
- filter_out: nan
# on_value:
# then:
# lambda: |-
# if (id(trip_start) >= 1) {
# return 1;
# }
- platform: template
id: ambient_temperature
name: "Ambient temperature"
unit_of_measurement: "°C"
device_class: temperature
state_class: measurement
update_interval: 1min
filters:
- filter_out: nan
- platform: adc
id: starter_battery_voltage
name: "${device_name} Battery Voltage"
pin: 4
attenuation: 11db # https://github.com/meatpiHQ/wican-fw/blob/bf212132f8e506f2c520e917daf86e53a1070302/main/sleep_mode.c#L234
filters:
- lambda: return x * 116 / 16; # https://github.com/meatpiHQ/wican-fw/blob/bf212132f8e506f2c520e917daf86e53a1070302/main/sleep_mode.c#L397
- sliding_window_moving_average:
window_size: 5 # Number of samples to average
send_every: 5 # How often to send the averaged value
send_first_at: 1 # When to send the first averaged value
update_interval: 5s # How often to update the sensor reading
unit_of_measurement: V
accuracy_decimals: 1
device_class: voltage
state_class: measurement
- platform: template
id: battery_temperature
name: "Average battery temperature"
unit_of_measurement: "°C"
device_class: temperature
accuracy_decimals: 1
state_class: measurement
filters:
- filter_out: nan
- platform: template
id: last_state_of_charge_sensor
name: "Last state of charge value"
lambda: |-
return id(last_state_of_charge);
binary_sensor:
# - platform: wireguard
# status:
# name: 'WireGuard Status'
- platform: template
id: pending_status_update_on_wake
name: "Pending Status Update on Wakeup"
- platform: template
id: first_boot
name: "First boot"
- platform: template
id: pending_status_update_sensor
name: "Pending Status Update"
lambda: |-
return id(pending_status_update);
- platform: template
id: wake
name: "Wake sensor"
filters:
- delayed_off: 2min
lambda: |-
if (id(ignition).has_state() || id(fast_charging).has_state() || !id(sleep_mode).state) {
return (id(ignition).state || id(fast_charging).state || !id(sleep_mode).state);
} else {
return {};
}
on_state:
then:
if:
condition:
and:
- binary_sensor.is_on: wake
- or:
- lambda: return (id(starter_battery_voltage).state >= ${charging_voltage_threshold});
- switch.is_off: sleep_mode
then:
- logger.log: "Wake sensor is on, preventing deep sleep"
- deep_sleep.prevent: deep_sleep_1
else:
- deep_sleep.allow: deep_sleep_1
- platform: status
id: statussensor
- platform: homeassistant
entity_id: input_boolean.disable_car_sleep
id: disable_sleep
publish_initial_state: true # This is important!
on_state:
then:
if:
condition:
lambda: return x;
then:
- switch.turn_off: sleep_mode
- platform: template
id: hv_charging
name: "High Voltage Battery Charging"
- platform: template
id: ac_present
name: "AC charger present"
- platform: template
id: fast_charging
name: "DC charger present"
- platform: template
id: ignition
name: "Ignition"
- platform: template
id: normal_voltage
name: "Starter battery normal voltage"
lambda: |-
return id(starter_battery_voltage).has_state() && id(starter_battery_voltage).state >= ${low_voltage_threshold};
script:
- id: restore_sensor_values
mode: single
then:
- lambda: id(first_boot).publish_state(esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_UNDEFINED);
- lambda: id(odometer).publish_state(id(last_odometer_value));
- lambda: id(ac_present).publish_state(id(last_ac_present_state));
- lambda: id(state_of_charge_bms).publish_state(id(last_state_of_charge));
- lambda: id(state_of_charge_bms_calibrated).publish_state(id(last_state_of_charge));
# Restore pending status update to a sensor so we can determine the boot reason
- lambda: id(pending_status_update_on_wake).publish_state(id(pending_status_update));
- id: add_wifi_hotspots
mode: single
then:
- lambda: |-
auto wific = id(wifi_component);
esphome::wifi::WiFiAP hotspot1;
hotspot1.set_ssid("${hotspot1_ssid}");
hotspot1.set_password("${hotspot1_password}");
hotspot1.set_priority(5.0f);
esphome::wifi::WiFiAP hotspot2;
hotspot2.set_ssid("${hotspot2_ssid}");
hotspot2.set_password("${hotspot2_password}");
hotspot2.set_priority(3.0f);
esphome::wifi::WiFiAP hotspot3;
hotspot3.set_ssid("${hotspot3_ssid}");
hotspot3.set_password("${hotspot3_password}");
hotspot3.set_priority(1.0f);
wific->add_sta(hotspot1);
wific->add_sta(hotspot2);
wific->add_sta(hotspot3);
wific->set_fast_connect(false);
- id: update_car_sensors
mode: single
then:
- logger.log:
format: "Sending canbus status request"
level: INFO
- globals.set:
id: frame_received
value: 'false'
- lambda: |-
id(can_battery_current_signed) = 0;
id(can_battery_power) = 0;
id(can_ignition_status) = false;
id(can_speed_mph) = 0;
id(can_battery_dc_voltage) = 0;
- canbus.send:
# 7E4 - 2101, returns on 7EC -
can_id: 0x7E4
data: [0x02, 0x21, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA]
- wait_until:
condition:
lambda: return id(frame_received);
timeout: 1sec
- globals.set:
id: frame_received
value: 'false'
- canbus.send:
# 7E4 - 2105, returns on 7EC
can_id: 0x7E4
data: [0x02, 0x21, 0x05, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA]
- wait_until:
condition:
lambda: return id(frame_received);
timeout: 1sec
- lambda: |-
// Update sensors
id(ignition).publish_state(id(can_ignition_status));
id(battery_current).publish_state(id(can_battery_current_signed));
id(battery_power).publish_state((id(can_battery_dc_voltage) * id(can_battery_current_signed))/1000);
id(hv_battery_voltage).publish_state(id(can_battery_dc_voltage));
- if:
condition:
binary_sensor.is_on: ignition
then:
- globals.set:
id: frame_received
value: 'false'
- canbus.send:
# 7E2 - 2101, Real vehicle speed, returns on 7EA
can_id: 0x7E2
data: [0x02, 0x21, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA]
- wait_until:
condition:
lambda: return id(frame_received);
timeout: 1sec
- globals.set:
id: frame_received
value: 'false'
- canbus.send:
# Ambient temperature returns on 7EE
can_id: 0x7E6
data: [0x02, 0x21, 0x80, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA]
- wait_until:
condition:
lambda: return id(frame_received);
timeout: 1sec
- globals.set:
id: frame_received
value: 'false'
- canbus.send:
# Odometer
can_id: 0x7C6 # Returns on 7CE
data: [0x03, 0x22, 0xB0, 0x02, 0xAA, 0xAA, 0xAA, 0xAA]
- wait_until:
condition:
lambda: return id(frame_received);
timeout: 1sec
- lambda: |-
id(real_vehicle_speed).publish_state(id(can_speed_mph) * 1.60934);
- id: send_abrp_telemetry_script
mode: single
then:
- if:
condition:
and:
- wifi.connected:
- time.has_time:
then:
- logger.log:
format: "Sending ABRP telemetry"
level: INFO
- http_request.post:
url: "https://api.iternio.com/1/tlm/send"
headers:
Content-Type: application/json
Authorization: !secret abrp_key
json: |-
root["tlm"]["utc"] = id(sntp_time).now().is_valid() ? id(sntp_time).now().timestamp : id(ha_time).now().timestamp;
root["tlm"]["soc"] = id(state_of_charge_bms_calibrated).has_state() ? id(state_of_charge_bms_calibrated).state : 0;
root["tlm"]["power"] = id(battery_power).state;
root["tlm"]["speed"] = id(real_vehicle_speed).state;
root["tlm"]["is_charging"] = id(hv_charging).state;
root["tlm"]["batt_temp"] = id(battery_temperature).state;
root["tlm"]["is_dcfc"] = id(fast_charging).state;
root["tlm"]["kwh_charged"] = id(cumulative_energy_charged).state;
root["tlm"]["odometer"] = id(odometer).has_state() ? id(odometer).state : id(last_odometer_value);
root["tlm"]["ext_temp"] = id(ambient_temperature).state;
root["tlm"]["voltage"] = id(hv_battery_voltage).state;
root["tlm"]["current"] = id(battery_current).state;
root["tlm"]["soh"] = id(state_of_health).state;
root["token"] = "${abrp_token}";
verify_ssl: false
- id: send_home_assistant_webhook
mode: single
then:
if:
condition:
and:
- wifi.connected:
then:
http_request.post:
headers:
Content-Type: application/json
url: !secret car_webhook_url
verify_ssl: false
json: |-
root["soc"] = id(state_of_charge_bms_calibrated).has_state() ? id(state_of_charge_bms_calibrated).state : id(last_state_of_charge);
root["is_charging"] = id(hv_charging).state;
root["is_dcfc"] = id(fast_charging).state;
root["speed"] = id(real_vehicle_speed).has_state() ? id(real_vehicle_speed).state : 0;
root["ac_present"] = id(ac_present).state;
root["power"] = id(battery_power).state;
root["odometer"] = id(odometer).has_state() ? id(odometer).state : id(last_odometer_value);
http_request:
useragent: esphome-wican
timeout: 3s
@jnimmo
Copy link
Author

jnimmo commented Apr 9, 2024

Glad to hear you got it working! Is your Home Assistant behind a proxy - i.e Cloudflare? I had to add some config because of that (XFF headers), but not aware of needing to make any other changes.

@djungelola
Copy link

djungelola commented Apr 9, 2024

would you like to share your config?
Edit: got it working

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment