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

4.1 KiB

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.so with 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 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

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.