- envelope: 7 fixed keys + conditional dataArea, no data key; int
type/snno, seconds timestamp, data setter auto len/crc (CRC32 upper);
SequenceNumber 1..9999 (increaseSN)
- crypto: encrypt is plaintext-header-first a||transform(b), key radix 32
at both tryParse sites; decrypt_data ports decryptData (dual-0x58
markers, s+'}' equipNo probe, s+','+T return); decrypt_request kept
for the device-side known-header split
- socket: raw send (no length prefix), accumulate-and-try receive
- battery: queryAPowerData shape (data={'update':2}), radix default 32,
--probe prints exact TX/RX bytes per variant; device_local.send_probe
- docs: RE_DART_FINDINGS ss3/5/8 + open items, BATTERY_POC host recipe
- tests: 19 new protocol tests, suite 44/44 green
55 lines
1.3 KiB
Python
55 lines
1.3 KiB
Python
"""
|
|
FranklinWH AGATE Device Client
|
|
|
|
Reverse engineered from com.Franklinwh.FamilyEnergy Android app.
|
|
Provides access to Bluetooth and Wi-Fi network I/O stack for AGATE devices.
|
|
"""
|
|
|
|
from .client import AgateClient
|
|
from .bluetooth import BluetoothManager
|
|
from .wifi import (
|
|
DEVICE_GATEWAY_IP,
|
|
FALLBACK_PASSWORD,
|
|
VALID_SN_LENGTH,
|
|
HotspotJoinError,
|
|
HotspotProfile,
|
|
NoOpWifiBackend,
|
|
NmcliWifiBackend,
|
|
WifiBackend,
|
|
WifiManager,
|
|
derive_hotspot_credentials,
|
|
)
|
|
from .api import ApiClient
|
|
|
|
__version__ = "0.1.0"
|
|
__all__ = [
|
|
"AgateClient",
|
|
"BluetoothManager",
|
|
"ApiClient",
|
|
"WifiManager",
|
|
"WifiBackend",
|
|
"NmcliWifiBackend",
|
|
"NoOpWifiBackend",
|
|
"HotspotProfile",
|
|
"HotspotJoinError",
|
|
"derive_hotspot_credentials",
|
|
"extract_soc",
|
|
"get_battery",
|
|
"probe_device",
|
|
"SOCKET_PORT_CANDIDATES",
|
|
"DEVICE_GATEWAY_IP",
|
|
"FALLBACK_PASSWORD",
|
|
"VALID_SN_LENGTH",
|
|
]
|
|
|
|
|
|
def __getattr__(name: str):
|
|
# Lazy so `python -m franklinwh_agate_client.battery` does not import
|
|
# the submodule twice (avoids a runpy RuntimeWarning).
|
|
if name in {"extract_soc", "get_battery", "probe_device",
|
|
"SOCKET_PORT_CANDIDATES"}:
|
|
from . import battery as _battery
|
|
|
|
return getattr(_battery, name)
|
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|