Add Berger LFP BLE command notes and client

This commit is contained in:
2026-07-27 01:20:56 +02:00
commit b390792e34
10 changed files with 861 additions and 0 deletions
+105
View File
@@ -0,0 +1,105 @@
# Berger LFP BLE Flow
This is the app flow used to obtain battery information such as discharge
current, SOC, voltage, temperatures, cell voltages, MOS state, and estimated
charge/discharge time.
## Evidence
- App package: `com.jbd.berger`
- Version: `1.0.3`
- Main bundle: `assets/apps/__UNI__F61076D/www/app-service.js`
- Source-map style log labels embedded in bundle:
- `utils/BLE.ts`
- `App.vue`
- dashboard/control/parameter pages
## Connection Sequence
1. `uni.openBluetoothAdapter()`
2. `uni.startBluetoothDevicesDiscovery()` with service filter `ff00`
3. `uni.onBluetoothDeviceFound()` records devices and RSSI
4. `uni.createBLEConnection()` with 10 second timeout
5. `uni.getBLEDeviceServices()`
6. `uni.getBLEDeviceCharacteristics()`
7. `uni.notifyBLECharacteristicValueChange()` on `ff01`
8. `uni.onBLECharacteristicValueChange()` buffers incoming bytes
9. `uni.writeBLECharacteristicValue()` sends command chunks to `ff02` with
`writeType: "writeNoResponse"`
The app chunks outgoing buffers into 20-byte pieces before writing.
## Services And Characteristics
- Service: `0000ff00-0000-1000-8000-00805f9b34fb`
- Notify/read: `0000ff01-0000-1000-8000-00805f9b34fb`
- Write: `0000ff02-0000-1000-8000-00805f9b34fb`
## Pairing / Bonding
- Needs Android bond before normal telemetry: likely no.
- Evidence: the app uses uni-app BLE APIs for connection, service discovery,
notifications, and writes. I did not find a normal-path bond request in the
app bundle.
- Caveat: a peripheral firmware could still enforce encryption independently,
but the app flow itself does not show pairing as a telemetry prerequisite.
## Getting Live Values
The app sends:
```text
DD A5 03 00 FF FD 77
```
The response uses:
```text
DD 03 <status> <length> <payload> <checksum> 77
```
Important payload fields for command `0x03`:
| Payload bytes | Formula | Unit | Field |
| ---: | --- | --- | --- |
| `0..1` | big-endian / 100 | V | pack voltage |
| `2..3` | signed big-endian / 100 | A | current |
| `4..5` | big-endian / 100 | Ah | remaining capacity |
| `6..7` | big-endian / 100 | Ah | nominal capacity |
| `8..9` | raw | cycles | cycle count |
| `10..11` | JBD bitfield | date | production date |
| `12..13` | nonzero bitmap | boolean | balancing |
| `16..17` | nonzero bitmap | boolean | protection |
| `18` | decimal digits | version | software version |
| `19` | raw | % | SOC |
| `20` | `1 = charge`, `2 = discharge`, `3 = both` | boolean | MOS state |
| `22` | raw count | count | temperature sensor count |
| `23..` | `(raw - 2731) / 10` | deg C | temperatures |
Production date bitfield:
```text
day = value & 0x1F
month = (value >> 5) & 0x0F
year = 2000 + (value >> 9)
```
Estimated time is computed locally:
```text
if current_A > 0:
time_to_full = (nominal_Ah - remaining_Ah) / current_A
if current_A < 0:
time_to_empty = remaining_Ah / abs(current_A)
```
## Cell Voltages
The app sends:
```text
DD A5 04 00 FF FC 77
```
The response command `0x04` payload is a sequence of big-endian 16-bit cell
voltages scaled by `0.001 V`.