AgateClient takes address= default; attach_local accepts address/host/gateway_ip per call with join control. An explicit address skips the SoftAP radio join by default (join=True forces it). derive_hotspot_credentials and join_device_hotspot accept a gateway_ip override. battery/power host defaults now use DEVICE_GATEWAY_IP. Verified live at 192.168.1.142:9000.
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 — detailed protocol
analysis recovered from the app's
libapp.sowith blutter (transports, wire envelope, command/topic enums, crypto/CRC, BLE framing, hotspot credentials). - Battery PoC — 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 informationqueryDeviceInfoHttp- HTTP device info queryqueryModesByHttp- Query operating modesqueryConnectWifi- Wi-Fi connection managementconnectViaBluetooth- Bluetooth connectionstartBluetoothHeartbeat- KeepaliveBluetoothManager- Bluetooth service managementWifiOnboarding- Wi-Fi setup flows
Endpoints Found
https://e-api.franklinwh.com/api/appapihttps://energy.franklinwh.com/api-energy/deviceGroup/electrichttps://energy.franklinwh.com/hes-gatewayhttps://dev.franklinwh.com/hes-gatewayhttps://uat.franklinwh.com/api-vpp-portal
Usage
from franklinwh_agate_client import AgateClient
client = AgateClient()
device_info = client.query_agate_info(device_id)
Battery PoC (SoftAP socket)
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.
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.
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.