Hey all! I reverse-engineered the BLE commands and responses for core data for my LiTime MPPT solar charge controller:
I hope this helps someone; if you have questions, concerns, or suggestions, feel free to open an issue in the repo. Thanks!
Hey all! I reverse-engineered the BLE commands and responses for core data for my LiTime MPPT solar charge controller:
I hope this helps someone; if you have questions, concerns, or suggestions, feel free to open an issue in the repo. Thanks!
This is just great! Amazing job Mark, never thought it would be integrable with home assistant.
I created a ESPhome shunt that is connected to the MPTT, now i can combine the code of both sensors.
Awesome! I’m curious to see where to take it ![]()
I have this working and thanks for your work, but i was wondering about the text sensor in the litime_solar_mppt.yaml, have you managed to get this working or is there a work around, as this will not compile when i try to set it up giving a few errors ? those addition would be great in my setup.
# ESPHome Configuration for BT-LTMPPT4860 Solar Charge Controller
# specify values to be substitued into common components. todo: change these as desired.
substitutions:
location: solar
locationName: Battery Bay
device: litime-solar
deviceUnderscore: litime_solar
deviceName: LiTime Solar
deviceDescription: LiTime Solar BLE pull
liTimeMacAddress: AA:BB:CC:DD:EE:FF # todo: replace with the MAC for your device
# Enable Home Assistant API
api:
encryption:
key: !secret encryption_key
preferences:
flash_write_interval: 5min
logger:
level: DEBUG
baud_rate: 0 # disable logging over UART (maybe comment this out for initial upload)
ota:
password: !secret ota_password
platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_psk
power_save_mode: light
fast_connect: true # don't bother scanning. When doing a full scan, can get watchdog timeouts, and we know who we're connecting to anyway
use_address: !secret litime_solar_ip
manual_ip:
static_ip: !secret litime_solar_ip
gateway: 192.168.1.1 # todo: replace with your gateway IP if yours is different
subnet: 255.255.255.0
#region Buttons
button:
- platform: restart
name: $location $deviceName Restart
id: ${location}_${deviceUnderscore}_restart
#endregion
esphome:
includes:
- litime_solar_mppt.h
name: ${location}-${device}
comment: $deviceDescription
esp32:
board: nodemcu-32s
framework:
type: arduino
esp32_ble_tracker:
scan_parameters:
# We currently use the defaults to ensure Bluetooth
# can co-exist with WiFi In the future we may be able to
# enable the built-in coexistence logic in ESP-IDF
active: true
# Using ble_client to get notifications from the device
sensor:
- platform: wifi_signal
name: $location $deviceName WiFi Signal
id: ${location}_${deviceUnderscore}_wifi_signal
entity_category: diagnostic
update_interval: 60s
- platform: uptime
name: $location $deviceName Uptime
id: ${location}_${deviceUnderscore}_uptime
entity_category: diagnostic
accuracy_decimals: 0
unit_of_measurement: min
update_interval: 60s
filters:
- lambda: return x / 60;
- platform: template
name: "LiTime Battery Voltage"
unit_of_measurement: "V"
accuracy_decimals: 1
id: battery_voltage
device_class: voltage
- platform: template
name: "LiTime Battery Current"
unit_of_measurement: "A"
accuracy_decimals: 2
id: battery_current
device_class: current
- platform: template
name: "LiTime Battery Power"
unit_of_measurement: "W"
accuracy_decimals: 0
id: battery_power
device_class: "power"
- platform: template
name: "LiTime Controller Temperature"
unit_of_measurement: "°C"
accuracy_decimals: 0
id: controller_temp
device_class: "temperature"
- platform: template
name: "LiTime Load Voltage"
unit_of_measurement: "V"
accuracy_decimals: 1
id: load_voltage
device_class: voltage
- platform: template
name: "LiTime Load Current"
unit_of_measurement: "A"
accuracy_decimals: 2
id: load_current
device_class: current
- platform: template
name: "LiTime Load Power"
unit_of_measurement: "W"
accuracy_decimals: 0
id: load_power
device_class: "power"
- platform: template
name: "LiTime PV Input Voltage"
unit_of_measurement: "V"
accuracy_decimals: 1
id: pv_voltage
device_class: voltage
- platform: template
name: "LiTime Max Charging Power Today"
unit_of_measurement: "W"
accuracy_decimals: 0
id: max_charge_power
device_class: "power"
- platform: template
name: "LiTime Power Generation Today"
unit_of_measurement: "Wh"
accuracy_decimals: 0
id: energy_today
device_class: "energy"
state_class: total_increasing
- platform: template
name: "LiTime Running Days"
unit_of_measurement: "days"
accuracy_decimals: 0
id: running_days
- platform: template
name: "LiTime Total Power Generation"
unit_of_measurement: "Wh"
accuracy_decimals: 0
id: total_energy
device_class: "energy"
state_class: total_increasing
- platform: template
name: "LiTime Battery Level"
unit_of_measurement: "%"
accuracy_decimals: 0
update_interval: 60s
device_class: "battery"
lambda: |-
if (id(battery_voltage).has_state()) {
float voltage = id(battery_voltage).state;
// Adjust these values based on your battery type
float min_voltage = 11.0;
float max_voltage = 14.4;
return std::min(100.0f, std::max(0.0f, (voltage - min_voltage) / (max_voltage - min_voltage) * 100.0f));
} else {
return 0.0f;
}
# text_sensor:
# - platform: template
# name: "LiTime Controller Status"
# id: controller_status
# lambda: |-
# switch (id(mppt_data).controller_mode) {
# case 0: return {"Standby"};
# case 1: return {"Float"};
# case 2: return {"Boost"};
# case 3: return {"Equalization"};
# case 4: return {"Absorption"};
# default: return {"Unknown"};
# }
# Custom component to handle BLE notifications
- platform: ble_client
ble_client_id: ltmppt_client
id: ble_sensor
internal: true
service_uuid: "ffe0"
characteristic_uuid: "ffe1"
type: characteristic
notify: true
update_interval: never
# on_notify:
# then:
# - lambda: |-
# ESP_LOGW("ble_client.notify", "x: %.2f", x);
lambda: |-
// Display the hex string for debugging.
std::vector<uint8_t> data = x;
String hex_string = "";
for (size_t i = 0; i < x.size(); ++i) {
char hex_buffer[3];
snprintf(hex_buffer, sizeof(hex_buffer), "%02X", data[i]);
hex_string += hex_buffer;
if (i % 2 == 1 && i != data.size() - 1) {
hex_string += " "; // Insert a space every two bytes
}
}
uint8_t arr_size = x.size();
ESP_LOGD("LOG_TAG", "Hexadecimal string: %s", hex_string.c_str());
HandleResponseData(x);
return 0.0; // this sensor isn't actually used other than to hook into raw value and publish to template sensors
# Add a switch to control the DC load
switch:
- platform: template
name: "LiTime DC Load"
id: dc_load
optimistic: true
restore_mode: RESTORE_DEFAULT_ON
turn_on_action:
- ble_client.ble_write:
id: ltmppt_client
service_uuid: "FFE0"
characteristic_uuid: "FFE1"
value: !lambda |-
ESP_LOGI("mppt", "Sending load ON command");
// Command to turn on load: 010601200001483c
vector<uint8_t> request = {0x01, 0x06, 0x01, 0x20, 0x00, 0x01, 0x48, 0x3c};
return request;
turn_off_action:
- ble_client.ble_write:
id: ltmppt_client
service_uuid: "FFE0"
characteristic_uuid: "FFE1"
value: !lambda |-
ESP_LOGI("mppt", "Sending load OFF command");
// Command to turn off load: 01060120000089fc
vector<uint8_t> request = {0x01, 0x06, 0x01, 0x20, 0x00, 0x00, 0x89, 0xfc};
return request;
# Define the BLE client for the LTMPPT4860
ble_client:
- mac_address: ${liTimeMacAddress}
id: ltmppt_client
auto_connect: true
on_connect:
then:
- logger.log:
format: "BLE client connected to LiTime MPPT"
level: DEBUG
on_disconnect:
then:
- logger.log:
format: "BLE client disconnected from LiTime MPPT"
level: DEBUG
# Run this when we connect to the device
interval:
- interval: 10s
then:
- ble_client.ble_write:
id: ltmppt_client
service_uuid: "ffe0"
characteristic_uuid: "ffe1"
value: [0x01, 0x03, 0x01, 0x01, 0x00, 0x13, 0x54, 0x3B]
# example output: 0103 2600 6400 8E04 2E00 9719 FF00 0000 0000 0003 1803 6805 0400 0000 0400 0000 0300 0018 2A00 0000 00E1 65
I haven’t looked at it since I first wrote the integration, and I don’t know when I’ll have a chance. But if you are about to get it working, please feel free to submit a pull request to the project.
Thanks!
Thank you for the quick reply and i will do.