Probes 10.100.1.1:9000,18000, sends 0x19/0x1D/0x1E envelopes, extracts currentBatterySoc/soc/avg(socArray). Includes offline extract_soc tests, BATTERY_POC docs, README usage, and empirical port-9000 RE notes.
59 lines
2.1 KiB
Markdown
59 lines
2.1 KiB
Markdown
# Battery PoC — direct-connect charge state
|
||
|
||
Reads the aggregate battery percentage straight from the aGate over its own
|
||
SoftAP. No cloud, no MQTT, no Bluetooth. Recovered from the Dart AOT layer;
|
||
see `RE_DART_FINDINGS.md` §§3–5, 8, 10.
|
||
|
||
## Requirements
|
||
|
||
- Host whose Wi-Fi adapter is associated to the device AP
|
||
(`AP_<sn[11:]>` / `<sn[8:]>`, `wifi.derive_hotspot_credentials`).
|
||
- L2 reachability to `10.100.1.1`. In a container this means a bridged
|
||
`macvlan`/`ipvlan` address on `10.100.1.0/24` or `network_mode: host`
|
||
(see repo-root `.devcontainer/docker-compose.yml`). A default Docker
|
||
bridge (`172.17.0.0/16`) is NATed to a non-`10.100.1.x` source and gets
|
||
`ECONNREFUSED`.
|
||
- Python `requests` for the HTTP helpers in `device_local.py`.
|
||
|
||
## What it does
|
||
|
||
1. Probes TCP ports `9000, 18000` in order (`9000` was open on the tested
|
||
unit; `0x4650 = 18000` is the app constant but was closed there).
|
||
2. Sends `DEVICE_APOWER_INFO (0x19)`, then `BMS (0x1D)`, `EMS (0x1E)`:
|
||
compact-JSON `CommunicationObject` → `encrypt_object` (key
|
||
`hex(sum(sn) & 0xFF)`) → int16LE length framing.
|
||
3. Decrypts the reply and extracts `currentBatterySoc` → `soc` →
|
||
`avg(socArray)`.
|
||
|
||
## Usage
|
||
|
||
```bash
|
||
# show derived hotspot creds only
|
||
python -m franklinwh_agate_client.battery --sn <20-char-SN> --show-hotspot
|
||
|
||
# print just the percentage
|
||
python -m franklinwh_agate_client.battery --sn <20-char-SN>
|
||
|
||
# full reply envelope
|
||
python -m franklinwh_agate_client.battery --sn <20-char-SN> --raw
|
||
```
|
||
|
||
Options: `--host` (default `10.100.1.1`), `--ports 9000,18000`,
|
||
`--key-radix {16,32}` (default 16; 32 is the alternate candidate in the
|
||
disassembly), `--timeout`.
|
||
|
||
Programmatic:
|
||
|
||
```python
|
||
from franklinwh_agate_client import get_battery
|
||
out = get_battery("...", host="10.100.1.1")
|
||
print(out["value"], out["source"]) # e.g. 78.0 currentBatterySoc
|
||
```
|
||
|
||
## Limits
|
||
|
||
- No live round-trip has succeeded yet from an isolated container netns
|
||
(all probes `ECONNREFUSED`); first success needs a `10.100.1.x` source.
|
||
- `key_radix`, BLE field order, and `X-Device-*` binding remain open
|
||
(`RE_DART_FINDINGS.md` Open items).
|