📄 Configuration File

substitutions:
  name: jk-bms
  friendly_name: JK BMS
  device_description: "JK BMS - Generic Configuration"
  mac_address: AA:BB:CC:DD:EE:FF  # Înlocuiește cu adresa MAC a BMS-ului tău
  protocol_version: JK02_32S
  bms_name: "Battery"

esphome:
  name: ${name}
  friendly_name: ${friendly_name}

esp32:
  board: esp32dev
  framework:
    type: esp-idf

logger:
  level: WARN

api:
  encryption:
    key: "CHANGE_ME"  # Generează cheie nouă în ESPHome Dashboard
  reboot_timeout: 0s

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  use_address: 192.168.1.100  # Înlocuiește cu IP-ul dorit pentru ESP32
  power_save_mode: none
  fast_connect: on
  manual_ip:
    static_ip: 192.168.1.100  # Înlocuiește cu IP-ul dorit pentru ESP32
    gateway: 192.168.1.1      # Înlocuiește cu IP-ul gateway-ului tău
    subnet: 255.255.255.0
    dns1: 1.1.1.1

web_server:
  local: true
  port: 80
  version: 2
  include_internal: false
  auth:
    username: !secret web_server_username
    password: !secret web_server_password

ota:
  platform: esphome
  password: !secret ota_password

external_components:
  - source: github://syssi/esphome-jk-bms@main
    refresh: 5s

time:
  - platform: homeassistant
    id: homeassistant_time
    timezone: Europe/Bucharest
    on_time_sync:
      then:
        - logger.log: "Ora sincronizata cu Home Assistant"
  
  - platform: sntp
    id: sntp_time
    timezone: Europe/Bucharest
    servers:
      - time.google.com
      - 0.pool.ntp.org
      - 1.pool.ntp.org
    on_time_sync:
      then:
        - logger.log: "Ora sincronizata cu SNTP (backup)"

esp32_ble_tracker:
  on_ble_advertise:
    then:
      - lambda: |-
          if (x.get_name().rfind("JK-", 0) == 0) {
            ESP_LOGI("ble_adv", "JK-BMS detectat: %s [%s]", x.get_name().c_str(), x.address_str().c_str());
          }

ble_client:
  - mac_address: ${mac_address}
    id: client0

jk_bms_ble:
  - ble_client_id: client0
    protocol_version: ${protocol_version}
    throttle: 30s
    id: bms0

globals:
  - id: wifi_retry_count
    type: int
    restore_value: no
    initial_value: '0'
  
  - id: last_ble_update
    type: unsigned long
    restore_value: no
    initial_value: '0'

binary_sensor:
  - platform: template
    name: "wifi connected"
    id: wifi_connected
    device_class: connectivity
    entity_category: diagnostic
    lambda: |-
      return wifi::global_wifi_component->is_connected();
  
  - platform: template
    name: "${bms_name} ble alive"
    id: ble_alive
    device_class: connectivity
    entity_category: diagnostic
    lambda: |-
      unsigned long now = millis();
      unsigned long last = id(last_ble_update);
      
      bool voltage_valid = !isnan(id(batt_voltage).state);
      bool current_valid = !isnan(id(batt_current).state);
      bool soc_valid = !isnan(id(batt_soc).state);
      
      int valid_count = (voltage_valid ? 1 : 0) + (current_valid ? 1 : 0) + (soc_valid ? 1 : 0);
      
      if (valid_count >= 2) {
        id(last_ble_update) = now;
        return true;
      }
      
      if (last > 0 && (now - last) > 300000) {
        ESP_LOGW("watchdog", "${bms_name} BLE NU COMUNICA de 5 minute!");
        return false;
      }
      
      return (last > 0);
  
  - platform: jk_bms_ble
    jk_bms_ble_id: bms0
    balancing:
      name: "${bms_name} balancing"
    charging:
      name: "${bms_name} charging"
    discharging:
      name: "${bms_name} discharging"
    online_status:
      name: "${bms_name} online status"

sensor:
  - platform: wifi_signal
    name: "wifi signal db"
    id: wifi_signal_db
    update_interval: 60s
    entity_category: diagnostic
    internal: true
  
  - platform: template
    name: "wifi signal percent"
    unit_of_measurement: "%"
    icon: "mdi:wifi"
    update_interval: 60s
    accuracy_decimals: 0
    entity_category: diagnostic
    lambda: |-
      float dbm = id(wifi_signal_db).state;
      if (isnan(dbm)) return 0;
      if (dbm <= -100) return 0;
      if (dbm >= -50) return 100;
      return 2 * (dbm + 100);
  
  - platform: jk_bms_ble
    jk_bms_ble_id: bms0
    
    min_cell_voltage:
      name: "${bms_name} min cell voltage"
    max_cell_voltage:
      name: "${bms_name} max cell voltage"
    min_voltage_cell:
      name: "${bms_name} min voltage cell"
    max_voltage_cell:
      name: "${bms_name} max voltage cell"
    delta_cell_voltage:
      name: "${bms_name} delta cell voltage"
    average_cell_voltage:
      name: "${bms_name} average cell voltage"
    
    cell_voltage_1:
      name: "${bms_name} cell 1"
    cell_voltage_2:
      name: "${bms_name} cell 2"
    cell_voltage_3:
      name: "${bms_name} cell 3"
    cell_voltage_4:
      name: "${bms_name} cell 4"
    cell_voltage_5:
      name: "${bms_name} cell 5"
    cell_voltage_6:
      name: "${bms_name} cell 6"
    cell_voltage_7:
      name: "${bms_name} cell 7"
    cell_voltage_8:
      name: "${bms_name} cell 8"
    cell_voltage_9:
      name: "${bms_name} cell 9"
    cell_voltage_10:
      name: "${bms_name} cell 10"
    cell_voltage_11:
      name: "${bms_name} cell 11"
    cell_voltage_12:
      name: "${bms_name} cell 12"
    cell_voltage_13:
      name: "${bms_name} cell 13"
    cell_voltage_14:
      name: "${bms_name} cell 14"
    cell_voltage_15:
      name: "${bms_name} cell 15"
    cell_voltage_16:
      name: "${bms_name} cell 16"
    
    total_voltage:
      name: "${bms_name} total voltage"
      id: batt_voltage
    current:
      name: "${bms_name} current"
      id: batt_current
    power:
      name: "${bms_name} power"
    charging_power:
      name: "${bms_name} charging power"
    discharging_power:
      name: "${bms_name} discharging power"
    
    temperature_sensor_1:
      name: "${bms_name} temperature 1"
    temperature_sensor_2:
      name: "${bms_name} temperature 2"
    
    state_of_charge:
      name: "${bms_name} SOC"
      id: batt_soc
    capacity_remaining:
      name: "${bms_name} capacity remaining"
    total_battery_capacity_setting:
      name: "${bms_name} total battery capacity"
    charging_cycles:
      name: "${bms_name} charging cycles"
    balancing_current:
      name: "${bms_name} balancing current"

text_sensor:
  - platform: template
    name: "wifi status"
    id: wifi_status_sensor
    icon: "mdi:wifi"
    update_interval: 60s
    entity_category: diagnostic
    lambda: |-
      if (wifi::global_wifi_component->is_connected()) {
        return {"WiFi OK"};
      } else {
        int count = id(wifi_retry_count);
        char status[20];
        sprintf(status, "Retry (%d/5)", count);
        return {(std::string)status};
      }
  
  - platform: template
    name: "esp time"
    icon: "mdi:clock-outline"
    update_interval: 1s
    entity_category: diagnostic
    lambda: |-
      char time_str[20];
      auto time = id(homeassistant_time).now();
      if (time.is_valid()) {
        sprintf(time_str, "%02d:%02d %02d.%02d.%04d", 
                time.hour, time.minute,
                time.day_of_month, time.month, time.year);
        return {(std::string)time_str};
      }
      return {"N/A"};

switch:
  - platform: jk_bms_ble
    jk_bms_ble_id: bms0
    charging:
      name: "${bms_name} charging"
    discharging:
      name: "${bms_name} discharging"
    balancer:
      name: "${bms_name} balancer"
  
  - platform: ble_client
    ble_client_id: client0
    name: "${bms_name} BLE connection"

number:
  - platform: jk_bms_ble
    jk_bms_ble_id: bms0
    
    max_charge_current:
      name: "${bms_name} max charge current"
      mode: box
    max_discharge_current:
      name: "${bms_name} max discharge current"
      mode: box
    balance_trigger_voltage:
      name: "${bms_name} balance trigger voltage"
      mode: box
    balance_starting_voltage:
      name: "${bms_name} balance starting voltage"
      mode: box
    cell_voltage_overvoltage_protection:
      name: "${bms_name} overvoltage protection"
      mode: box
    cell_voltage_undervoltage_protection:
      name: "${bms_name} undervoltage protection"
      mode: box
    power_off_voltage:
      name: "${bms_name} power off voltage"
      mode: box

interval:
  - interval: 60s
    then:
      - lambda: |-
          bool wifi_ok = wifi::global_wifi_component->is_connected();
          
          if (wifi_ok) {
            if (id(wifi_retry_count) != 0) {
              ESP_LOGI("watchdog", "WiFi reconectat!");
            }
            id(wifi_retry_count) = 0;
          } else {
            id(wifi_retry_count) += 1;
            int count = id(wifi_retry_count);
            ESP_LOGW("watchdog", "WiFi deconectat - Retry %d/5", count);
            if (count >= 5) {
              ESP_LOGW("watchdog", "=== WIFI DECONECTAT 5 MINUTE! RESTART! ===");
              App.safe_reboot();
            }
          }
  
  - interval: 60s
    then:
      - lambda: |-
          unsigned long now = millis();
          unsigned long last = id(last_ble_update);
          if (last > 0 && (now - last) > 300000) {
            ESP_LOGW("watchdog", "=== ${bms_name} BLE BLOCAT 5 MIN! RESTART! ===");
            App.safe_reboot();
          }

button:
  - platform: restart
    name: "restart"
    icon: "mdi:restart"
    entity_category: diagnostic
  
  - platform: jk_bms_ble
    jk_bms_ble_id: bms0
    retrieve_settings:
      name: "${bms_name} retrieve settings"
    retrieve_device_info:
      name: "${bms_name} retrieve device info"