""" Agate Client - Main interface for AGATE device operations. """ from __future__ import annotations from typing import Any, Optional from .bluetooth import BluetoothManager from .wifi import DEVICE_GATEWAY_IP, HotspotProfile, WifiBackend, WifiManager class AgateClient: """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, 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, 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.""" self.wifi_manager.leave_device_hotspot() 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``. 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 ``local_kwargs``). Raises :class:`~franklinwh_agate_client.wifi.HotspotJoinError` if the association never verifies. (``LocalDeviceClient`` imports ``requests``; install it before calling this on a cloud-less host.) Extra keyword arguments are passed through to ``LocalDeviceClient`` (``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=target or DEVICE_GATEWAY_IP, **local_kwargs) # -- cloud / stub operations (pending RE) ------------------------------- def query_agate_info(self, device_id: Optional[str] = None): """Query AGATE device information.""" # Reverse engineered operation: queryAgateInfo pass def query_device_info_http(self, device_id: Optional[str] = None): """Query device info via HTTP.""" # Reverse engineered operation: queryDeviceInfoHttp pass def query_modes_by_http(self, device_id: Optional[str] = None): """Query operating modes via HTTP.""" # Reverse engineered operation: queryModesByHttp pass