Optional aGate address on the API (home-LAN direct connect)
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.
This commit is contained in:
1 parent
eda78dd7d1
commit
b60b32830f
4 files changed
+70
-20
No files matched your search
@@ -20,7 +20,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from .device_local import LocalDeviceClient
|
||||
from .protocol import CommunicationCmd
|
||||
from .wifi import derive_hotspot_credentials
|
||||
from .wifi import DEVICE_GATEWAY_IP, derive_hotspot_credentials
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -144,7 +144,7 @@ def _is_empty_pack(reply: Dict[str, Any], result: Dict[str, Any]) -> bool:
|
||||
|
||||
def get_battery(
|
||||
sn: str,
|
||||
host: str = "10.100.1.1",
|
||||
host: str = DEVICE_GATEWAY_IP,
|
||||
ports: Tuple[int, ...] = SOCKET_PORT_CANDIDATES,
|
||||
key_radix: int = 16,
|
||||
timeout: float = 5.0,
|
||||
@@ -197,7 +197,7 @@ PROBE_VARIANTS: Tuple[Tuple[str, CommunicationCmd, Any], ...] = (
|
||||
|
||||
def probe_device(
|
||||
sn: str,
|
||||
host: str = "10.100.1.1",
|
||||
host: str = DEVICE_GATEWAY_IP,
|
||||
ports: Tuple[int, ...] = SOCKET_PORT_CANDIDATES,
|
||||
key_radix: int = 16,
|
||||
timeout: float = 5.0,
|
||||
@@ -262,7 +262,7 @@ _MQTT_CONNECT = bytes.fromhex("101200044d5154540402003c000670726f62653031")
|
||||
|
||||
def probe_transport(
|
||||
sn: str,
|
||||
host: str = "10.100.1.1",
|
||||
host: str = DEVICE_GATEWAY_IP,
|
||||
ports: Tuple[int, ...] = (9000,),
|
||||
key_radix: int = 16,
|
||||
timeout: float = 5.0,
|
||||
@@ -327,7 +327,7 @@ def main(argv: Optional[List[str]] = None) -> int:
|
||||
description="Read aGate battery percentage over the device SoftAP socket."
|
||||
)
|
||||
parser.add_argument("--sn", required=True, help="20-char device serial")
|
||||
parser.add_argument("--host", default="10.100.1.1", help="aGate gateway IP")
|
||||
parser.add_argument("--host", default=DEVICE_GATEWAY_IP, help="aGate gateway IP")
|
||||
parser.add_argument(
|
||||
"--ports", default=",".join(map(str, SOCKET_PORT_CANDIDATES)),
|
||||
help="comma-separated socket ports to probe",
|
||||
|
||||
@@ -7,22 +7,39 @@ from __future__ import annotations
|
||||
from typing import Any, Optional
|
||||
|
||||
from .bluetooth import BluetoothManager
|
||||
from .wifi import HotspotProfile, WifiBackend, WifiManager
|
||||
from .wifi import DEVICE_GATEWAY_IP, HotspotProfile, WifiBackend, WifiManager
|
||||
|
||||
|
||||
class AgateClient:
|
||||
"""Main client for FranklinWH AGATE device operations."""
|
||||
"""Main client for FranklinWH AGATE device operations.
|
||||
|
||||
:param device_id: optional default device identifier.
|
||||
:param wifi_backend: platform backend used for hotspot joins.
|
||||
:param address: optional default aGate address (IP or hostname).
|
||||
Overrides the ``10.100.1.1`` SoftAP default for direct-connect
|
||||
calls — e.g. the home-LAN address (``192.168.1.x``) once the unit
|
||||
is on the router. Per-call ``address``/``host``/``gateway_ip``
|
||||
arguments take precedence over this default.
|
||||
"""
|
||||
|
||||
def __init__(self, device_id: Optional[str] = None,
|
||||
wifi_backend: Optional[WifiBackend] = None) -> None:
|
||||
wifi_backend: Optional[WifiBackend] = None,
|
||||
address: Optional[str] = None) -> None:
|
||||
self.device_id = device_id
|
||||
self.bluetooth_manager: Optional[BluetoothManager] = None
|
||||
self.wifi_manager = WifiManager(backend=wifi_backend)
|
||||
self.address = address
|
||||
|
||||
# -- device hotspot (local connect) -------------------------------------
|
||||
def join_hotspot(self, sn: str, **kwargs: Any) -> HotspotProfile:
|
||||
"""Join the device SoftAP (derives creds, joins, verifies)."""
|
||||
return self.wifi_manager.join_device_hotspot(sn, **kwargs)
|
||||
def join_hotspot(self, sn: str, gateway_ip: Optional[str] = None,
|
||||
**kwargs: Any) -> HotspotProfile:
|
||||
"""Join the device SoftAP (derives creds, joins, verifies).
|
||||
|
||||
``gateway_ip`` overrides the address recorded on the profile
|
||||
without changing the radio join.
|
||||
"""
|
||||
return self.wifi_manager.join_device_hotspot(
|
||||
sn, gateway_ip=gateway_ip or self.address, **kwargs)
|
||||
|
||||
def leave_hotspot(self) -> None:
|
||||
"""Disconnect from the device hotspot."""
|
||||
@@ -30,9 +47,23 @@ class AgateClient:
|
||||
|
||||
def attach_local(self, sn: str, *, verify_join: bool = True,
|
||||
settle_timeout: float = 30.0, poll_interval: float = 1.0,
|
||||
address: Optional[str] = None,
|
||||
host: Optional[str] = None,
|
||||
gateway_ip: Optional[str] = None,
|
||||
join: Optional[bool] = None,
|
||||
**local_kwargs: Any):
|
||||
"""Join the device hotspot, then return a ``LocalDeviceClient``
|
||||
aimed at the device gateway (``https://10.100.1.1:18000`` by default).
|
||||
"""Join the device hotspot, then return a ``LocalDeviceClient``.
|
||||
|
||||
Without an explicit address this keeps the existing behaviour:
|
||||
join the SoftAP and aim at the profile gateway
|
||||
(``https://10.100.1.1:18000`` by default).
|
||||
|
||||
Pass ``address`` (``host``/``gateway_ip`` are aliases) to reach the
|
||||
aGate elsewhere — e.g. its home-LAN IP once it is on the router.
|
||||
An explicit address defaults to *skipping* the hotspot radio join
|
||||
(pass ``join=True`` to force it); ``join=False`` never touches the
|
||||
radio. The client-level ``address`` default applies when no
|
||||
per-call address is given.
|
||||
|
||||
``verify_join`` gates the association check (TLS verification of the
|
||||
device API is a separate concern — pass ``verify=`` in
|
||||
@@ -45,13 +76,21 @@ class AgateClient:
|
||||
(``port``, ``scheme``, ``verify``, ``socket_port``, ``key_radix``,
|
||||
``timeout``, ``extra_headers``, ``session``).
|
||||
"""
|
||||
explicit = address if address is not None else (
|
||||
host if host is not None else gateway_ip)
|
||||
target = explicit if explicit is not None else self.address
|
||||
do_join = (target is None) if join is None else join
|
||||
if do_join:
|
||||
profile = self.wifi_manager.join_device_hotspot(
|
||||
sn, verify=verify_join, settle_timeout=settle_timeout,
|
||||
poll_interval=poll_interval,
|
||||
gateway_ip=target,
|
||||
)
|
||||
target = profile.gateway_ip
|
||||
from .device_local import LocalDeviceClient
|
||||
|
||||
return LocalDeviceClient(sn=sn, host=profile.gateway_ip, **local_kwargs)
|
||||
return LocalDeviceClient(sn=sn, host=target or DEVICE_GATEWAY_IP,
|
||||
**local_kwargs)
|
||||
|
||||
# -- cloud / stub operations (pending RE) -------------------------------
|
||||
def query_agate_info(self, device_id: Optional[str] = None):
|
||||
|
||||
@@ -17,10 +17,11 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from .battery import _walk
|
||||
from .device_local import LocalDeviceClient
|
||||
from .wifi import DEVICE_GATEWAY_IP
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
HOST = "10.100.1.1"
|
||||
HOST = DEVICE_GATEWAY_IP
|
||||
PORTS: Tuple[int, ...] = (9000, 18000)
|
||||
|
||||
#: Reply keys holding watt readings, in priority order.
|
||||
|
||||
@@ -65,7 +65,8 @@ class HotspotJoinError(RuntimeError):
|
||||
"""A join was issued but the association never verified in time."""
|
||||
|
||||
|
||||
def derive_hotspot_credentials(sn: str) -> "HotspotProfile":
|
||||
def derive_hotspot_credentials(sn: str,
|
||||
gateway_ip: Optional[str] = None) -> "HotspotProfile":
|
||||
"""``getDeviceApSsid`` / ``getDeviceApDefaultPwd``.
|
||||
|
||||
Offline fallback: for a *20-character* SN the SSID is ``"AP_" + sn[11:]``
|
||||
@@ -74,6 +75,9 @@ def derive_hotspot_credentials(sn: str) -> "HotspotProfile":
|
||||
|
||||
The authoritative values come from the live device
|
||||
(``/terminal/getHotSpotInfo`` + ``/terminal/getHosSpotPassword``).
|
||||
|
||||
``gateway_ip`` overrides the default SoftAP gateway (``10.100.1.1``) —
|
||||
e.g. when the aGate is reached on its home-LAN address instead.
|
||||
"""
|
||||
if len(sn) == VALID_SN_LENGTH:
|
||||
ssid = "AP_" + sn[11:]
|
||||
@@ -83,7 +87,8 @@ def derive_hotspot_credentials(sn: str) -> "HotspotProfile":
|
||||
len(sn), VALID_SN_LENGTH)
|
||||
ssid = "AP_" + sn
|
||||
password = FALLBACK_PASSWORD
|
||||
return HotspotProfile(ssid=ssid, password=password)
|
||||
return HotspotProfile(ssid=ssid, password=password,
|
||||
gateway_ip=gateway_ip or DEVICE_GATEWAY_IP)
|
||||
|
||||
|
||||
class WifiBackend(ABC):
|
||||
@@ -308,9 +313,14 @@ class WifiManager:
|
||||
# -- device hotspot ------------------------------------------------------
|
||||
def join_device_hotspot(self, sn: str, *, verify: bool = True,
|
||||
settle_timeout: float = 30.0,
|
||||
poll_interval: float = 1.0) -> HotspotProfile:
|
||||
"""Derive the profile from the SN, join, and verify the association."""
|
||||
profile = derive_hotspot_credentials(sn)
|
||||
poll_interval: float = 1.0,
|
||||
gateway_ip: Optional[str] = None) -> HotspotProfile:
|
||||
"""Derive the profile from the SN, join, and verify the association.
|
||||
|
||||
``gateway_ip`` overrides the device address recorded on the
|
||||
profile (default ``10.100.1.1``) without changing the radio join.
|
||||
"""
|
||||
profile = derive_hotspot_credentials(sn, gateway_ip=gateway_ip)
|
||||
return self._join(profile, verify=verify,
|
||||
settle_timeout=settle_timeout,
|
||||
poll_interval=poll_interval)
|
||||
|
||||
Reference in new issue
Block a user