1
0
mirror of https://github.com/natekspencer/hacs-oasis_mini.git synced 2025-12-06 18:44:14 -05:00
Files
hacs-oasis_mini/custom_components/oasis_mini/pyoasiscontrol/clients/http_client.py
2025-11-22 20:51:17 +00:00

181 lines
5.8 KiB
Python

"""Oasis HTTP client (per-device)."""
from __future__ import annotations
import logging
from typing import Any
from aiohttp import ClientSession
from ..device import OasisDevice
from .transport import OasisClientProtocol
_LOGGER = logging.getLogger(__name__)
class OasisHttpClient(OasisClientProtocol):
"""HTTP-based Oasis transport.
This client is typically used per-device (per host/IP).
It implements the OasisClientProtocol so OasisDevice can delegate
all commands through it.
"""
def __init__(self, host: str, session: ClientSession | None = None) -> None:
self._host = host
self._session: ClientSession | None = session
self._owns_session: bool = session is None
@property
def session(self) -> ClientSession:
if self._session is None or self._session.closed:
self._session = ClientSession()
self._owns_session = True
return self._session
async def async_close(self) -> None:
"""Close owned session."""
if self._session and not self._session.closed and self._owns_session:
await self._session.close()
@property
def url(self) -> str:
# These devices are plain HTTP, no TLS
return f"http://{self._host}/"
async def _async_request(self, method: str, url: str, **kwargs: Any) -> Any:
"""Low-level HTTP helper."""
session = self.session
_LOGGER.debug(
"%s %s",
method,
session._build_url(url).update_query( # pylint: disable=protected-access
kwargs.get("params"),
),
)
resp = await session.request(method, url, **kwargs)
if resp.status == 200:
if resp.content_type == "text/plain":
return await resp.text()
if resp.content_type == "application/json":
return await resp.json()
return None
resp.raise_for_status()
async def _async_get(self, **kwargs: Any) -> str | None:
return await self._async_request("GET", self.url, **kwargs)
async def _async_command(self, **kwargs: Any) -> str | None:
result = await self._async_get(**kwargs)
_LOGGER.debug("Result: %s", result)
return result
async def async_get_mac_address(self, device: OasisDevice) -> str | None:
"""Fetch MAC address via HTTP GETMAC."""
try:
mac = await self._async_get(params={"GETMAC": ""})
if isinstance(mac, str):
return mac.strip()
except Exception: # noqa: BLE001
_LOGGER.exception(
"Failed to get MAC address via HTTP for %s", device.serial_number
)
return None
async def async_send_ball_speed_command(
self,
device: OasisDevice,
speed: int,
) -> None:
await self._async_command(params={"WRIOASISSPEED": speed})
async def async_send_led_command(
self,
device: OasisDevice,
led_effect: str,
color: str,
led_speed: int,
brightness: int,
) -> None:
payload = f"{led_effect};0;{color};{led_speed};{brightness}"
await self._async_command(params={"WRILED": payload})
async def async_send_sleep_command(self, device: OasisDevice) -> None:
await self._async_command(params={"CMDSLEEP": ""})
async def async_send_move_job_command(
self,
device: OasisDevice,
from_index: int,
to_index: int,
) -> None:
await self._async_command(params={"MOVEJOB": f"{from_index};{to_index}"})
async def async_send_change_track_command(
self,
device: OasisDevice,
index: int,
) -> None:
await self._async_command(params={"CMDCHANGETRACK": index})
async def async_send_add_joblist_command(
self,
device: OasisDevice,
tracks: list[int],
) -> None:
# The old code passed the list directly; if the device expects CSV:
await self._async_command(params={"ADDJOBLIST": ",".join(map(str, tracks))})
async def async_send_set_playlist_command(
self,
device: OasisDevice,
playlist: list[int],
) -> None:
await self._async_command(params={"WRIJOBLIST": ",".join(map(str, playlist))})
# optional: optimistic state update
device.update_from_status_dict({"playlist": playlist})
async def async_send_set_repeat_playlist_command(
self,
device: OasisDevice,
repeat: bool,
) -> None:
await self._async_command(params={"WRIREPEATJOB": 1 if repeat else 0})
async def async_send_set_autoplay_command(
self,
device: OasisDevice,
option: str,
) -> None:
await self._async_command(params={"WRIWAITAFTER": option})
async def async_send_upgrade_command(
self,
device: OasisDevice,
beta: bool,
) -> None:
await self._async_command(params={"CMDUPGRADE": 1 if beta else 0})
async def async_send_play_command(self, device: OasisDevice) -> None:
await self._async_command(params={"CMDPLAY": ""})
async def async_send_pause_command(self, device: OasisDevice) -> None:
await self._async_command(params={"CMDPAUSE": ""})
async def async_send_stop_command(self, device: OasisDevice) -> None:
await self._async_command(params={"CMDSTOP": ""})
async def async_send_reboot_command(self, device: OasisDevice) -> None:
await self._async_command(params={"CMDBOOT": ""})
async def async_get_status(self, device: OasisDevice) -> None:
"""Fetch status via GETSTATUS and update the device."""
raw_status = await self._async_get(params={"GETSTATUS": ""})
if raw_status is None:
return
_LOGGER.debug("Status for %s: %s", device.serial_number, raw_status)
device.update_from_status_string(raw_status)