Files
franklinwh-agate-client/README.md
T
kfj001 2438a04ed5 Add SoftAP battery PoC (apower/BMS/EMS socket read) with docs and tests
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.
2026-10-15 01:26:28 +00:00

113 lines
4.1 KiB
Markdown

# FranklinWH AGATE Client
Python client library for interacting with FranklinWH AGATE energy storage device.
## Repository
**Gitea:** http://192.168.1.74:30008/WesternTechnologies/franklinwh-agate-client
## Documentation
- **[Dart AOT Reverse-Engineering Findings](docs/RE_DART_FINDINGS.md)** — detailed protocol
analysis recovered from the app's `libapp.so` with [blutter](https://github.com/worawit/blutter)
(transports, wire envelope, command/topic enums, crypto/CRC, BLE framing, hotspot credentials).
- **[Battery PoC](docs/BATTERY_POC.md)** — read charge % over the SoftAP socket
(`python -m franklinwh_agate_client.battery --sn <SN>`).
## Reverse Engineering Summary
This library was reverse engineered from the official FranklinWH Family Energy Android app (`com.Franklinwh.FamilyEnergy`).
### Network I/O Stack
The device communicates via:
- **Bluetooth LE**: Direct connection to AGATE device
- **Onboard Wi-Fi**: Local network connection via device hotspot
- **Cloud APIs**: `https://e-api.franklinwh.com`, `https://energy.franklinwh.com`
### Key Operations Discovered
- `queryAgateInfo` - Get device information
- `queryDeviceInfoHttp` - HTTP device info query
- `queryModesByHttp` - Query operating modes
- `queryConnectWifi` - Wi-Fi connection management
- `connectViaBluetooth` - Bluetooth connection
- `startBluetoothHeartbeat` - Keepalive
- `BluetoothManager` - Bluetooth service management
- `WifiOnboarding` - Wi-Fi setup flows
### Endpoints Found
- `https://e-api.franklinwh.com/api/appapi`
- `https://energy.franklinwh.com/api-energy/deviceGroup/electric`
- `https://energy.franklinwh.com/hes-gateway`
- `https://dev.franklinwh.com/hes-gateway`
- `https://uat.franklinwh.com/api-vpp-portal`
## Usage
```python
from franklinwh_agate_client import AgateClient
client = AgateClient()
device_info = client.query_agate_info(device_id)
```
## Battery PoC (SoftAP socket)
```bash
python -m franklinwh_agate_client.battery --sn <20-char-SN> --show-hotspot
python -m franklinwh_agate_client.battery --sn <20-char-SN> # e.g. 78%
python -m franklinwh_agate_client.battery --sn <20-char-SN> --raw
```
Probes `10.100.1.1:9000,18000`, sends `0x19/0x1D/0x1E`, prints
`currentBatterySoc → soc → avg(socArray)`. Must run where the Wi-Fi adapter
is on the device AP — see [Battery PoC](docs/BATTERY_POC.md).
## Local connect (device hotspot)
The aGate runs its own SoftAP (gateway `10.100.1.1`); joining it is what
lets the client talk to the device directly. The client derives the hotspot
credentials from the 20-char SN (`AP_<sn[11:]>` / `<sn[8:]>`, or the
`AP_<sn>` / `12345678` fallback for malformed SNs), joins, **verifies the
association**, then talks to the device over `https://10.100.1.1:18000`
and the encrypted socket channel.
```python
from franklinwh_agate_client import (
AgateClient, NmcliWifiBackend, NoOpWifiBackend, derive_hotspot_credentials,
)
# Linux desktop/laptop with NetworkManager:
client = AgateClient(wifi_backend=NmcliWifiBackend())
profile = client.join_hotspot(sn) # joins + verifies; raises HotspotJoinError on failure
local = client.attach_local(sn) # LocalDeviceClient -> https://10.100.1.1:18000
info = local.obtain_agate_info() # or local.query_bms_info(), ...
# Container, or a host where you join the hotspot manually:
wm = AgateClient(wifi_backend=NoOpWifiBackend(local_ip="10.100.1.2")).wifi_manager
wm.assume_connected(derive_hotspot_credentials(sn))
```
`WifiManager.join_*` are fail-closed: they poll the backend's `is_connected`
against `settle_timeout` (default 30 s) and raise `HotspotJoinError` if the
association never confirms (`verify=False` for fire-and-forget). On Linux,
`NmcliWifiBackend` uses `nmcli ... device wifi list -f SSID,ACTIVE` for the
association check (a merely *visible* AP does not count) and per-Wi-Fi-
interface `IP4.ADDRESS` lookups for the host IP.
## Development
This library is a work in progress. Additional operations will be added as reverse engineering continues.
Tests (no radio or NetworkManager required):
```
python -m unittest discover -s tests
```
## License
Internal use only. Reverse engineering findings for research purposes.