I have it working for the most part. I only have access to X210 batteries, worked on all of them. So i am unsure about what you guys want to do. But from the code of the app it looks more or less the same, maybe the characteristic uuid’s etc are different on other devices.
You can find it two ways. connect the nRF app. Then check for the notify services and set them to “notify” by pressing the button. This will update their value once new data comes in.
Triggering the notify can be done by pressing the battery state indicator on the battery itself, (in case of the x210 at least). Or by sending a 1 to one of the other characteristics that are receive.
You want to find the long chain of bytes that come back like in the earlyer post. In my case this is Service FFF0 and characteristic FFF1
Things i have to figure out, the status things dont make sense to me yet. And when i pull new data the indicator lights for the charge state light up on the battery as well. This should not be needed
Let me know if there are questions.
Edit:
i see that the nrf connect on android looks a bit different and also shows the mac adress right at the top (this is not possible on ios). I guess from the layout the 3 down arrows mean enable notify in this case, on ios its an arrow down with a line under it.
esp32_ble_tracker:
scan_parameters:
duration: 300s
# Define the BLE client
ble_client:
- mac_address: "DC:0D:30:0C:5D:DF" # Replace with the MAC address of your battery
id: my_ble_client
## To find the mac adress, most easy way is to enable the part down below saying:
## - platform: ble_scanner
## name: "BLE Devices Scanner"
## This will show all found BLE devices in the area with their mac adres and name if they have one in the esphome log.
## In case of a X210 battery the macs start consistently with: DC:0D:30:
text_sensor:
- platform: ble_client
ble_client_id: my_ble_client
name: "BLE Service FFF0 Characteristic FFF1"
service_uuid: 'FFF0'
characteristic_uuid: 'FFF1'
notify: true
id: ble_raw_data
on_value:
then:
- lambda: |-
std::string raw_data = id(ble_raw_data).state;
if (raw_data.length() >= 38) {
// Strip the first character '^'
raw_data = raw_data.substr(1);
auto char_to_int = [](char c) -> int {
if ('0' <= c && c <= '9') {
return c - '0';
} else if ('A' <= c && c <= 'F') {
return (c - 'A') + 10;
} else {
return 0;
}
};
auto asciitochar = [&](char b, char b2) -> int {
return ((char_to_int(b) << 4) & 0xF0) + (char_to_int(b2) & 0x0F);
};
int voltage = (((((asciitochar(raw_data[6], raw_data[7]) << 8) + asciitochar(raw_data[4], raw_data[5])) << 8) + asciitochar(raw_data[2], raw_data[3])) << 8) + asciitochar(raw_data[0], raw_data[1]);
int current = (((((asciitochar(raw_data[14], raw_data[15]) << 8) + asciitochar(raw_data[12], raw_data[13])) << 8) + asciitochar(raw_data[10], raw_data[11])) << 8) + asciitochar(raw_data[8], raw_data[9]);
int soc = (asciitochar(raw_data[30], raw_data[31]) << 8) + asciitochar(raw_data[28], raw_data[29]);
int temperature = (asciitochar(raw_data[34], raw_data[35]) << 8) + asciitochar(raw_data[32], raw_data[33]);
int status = asciitochar(raw_data[36], raw_data[37]);
int afe_status = asciitochar(raw_data[40], raw_data[41]);
float temperature_celsius = (temperature - 0xAAB) / 10.0;
float voltage_display = voltage / 1000.0;
float current_display = current / 10.0;
id(global_voltage) = voltage_display;
id(global_current) = current_display;
id(global_soc) = soc;
id(global_temperature) = temperature_celsius;
id(global_status) = status;
id(global_afe_status) = afe_status;
} else {
ESP_LOGE("main", "Received raw data is too short.");
}
- platform: ble_scanner
name: "BLE Devices Scanner"
sensor:
- platform: template
name: "Battery voltage"
unit_of_measurement: "V"
update_interval: 30s
accuracy_decimals: 2
lambda: |-
return id(global_voltage);
- platform: template
name: "Battery Current"
update_interval: 30s
unit_of_measurement: "A"
accuracy_decimals: 1
lambda: |-
return id(global_current);
- platform: template
name: "Battery SOC"
update_interval: 30s
unit_of_measurement: "%"
accuracy_decimals: 0
lambda: |-
return id(global_soc);
- platform: template
name: "Battery Temperature"
update_interval: 30s
unit_of_measurement: "°C"
accuracy_decimals: 1
lambda: |-
return id(global_temperature);
#Unsure about these two yet if they actually work and what they mean
- platform: template
name: "Battery Status"
update_interval: 30s
accuracy_decimals: 0
lambda: |-
return id(global_status);
- platform: template
name: "Battery AFE Status"
update_interval: 30s
accuracy_decimals: 0
lambda: |-
return id(global_afe_status);
globals:
- id: global_voltage
type: float
restore_value: no
initial_value: '0.0'
- id: global_current
type: float
restore_value: no
initial_value: '0.0'
- id: global_soc
type: int
restore_value: no
initial_value: '0'
- id: global_temperature
type: float
restore_value: no
initial_value: '0.0'
- id: global_status
type: int
restore_value: no
initial_value: '0'
- id: global_afe_status
type: int
restore_value: no
initial_value: '0'
#this fetches data every minute by pushing a Number 1 to the FFF2 characteristic, causing an update on the Notify FFF1
interval:
- interval: 1min
then:
- ble_client.ble_write:
id: my_ble_client
service_uuid: 'FFF0'
characteristic_uuid: 'FFF2'
value: [0x31]
#this button manually fetches the data by pushing a Number 1 to the FFF2 characteristic, causing an update on the Notify FFF1
button:
- platform: template
name: "Force update values"
on_press:
then:
- ble_client.ble_write:
id: my_ble_client
service_uuid: 'FFF0'
characteristic_uuid: 'FFF2'
value: [0x31]