mirror of
https://github.com/ZaneThePython/openjibo-haint.git
synced 2026-06-21 13:46:00 +00:00
Change how connection works for minimal notifcations
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -16,6 +17,7 @@ from .const import (
|
||||
from .websocket_client import OpenJiboWebSocketClient
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_LIGHT_SUFFIXES = (" light", " lights", " lamp", " lamps")
|
||||
|
||||
|
||||
class JiboCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
@@ -32,6 +34,7 @@ class JiboCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
entry.data[CONF_SERVER_URL],
|
||||
entry.data[CONF_INSTANCE_ID],
|
||||
self._handle_message,
|
||||
entry.data.get(CONF_LINK_ID),
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -47,6 +50,10 @@ class JiboCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
async def _handle_message(self, payload: dict[str, Any]) -> None:
|
||||
message_type = payload.get("type")
|
||||
if message_type == "verification_code":
|
||||
if self.entry.data.get(CONF_LINK_ID):
|
||||
_LOGGER.debug("Ignoring verification code; Home Assistant is already paired")
|
||||
return
|
||||
|
||||
code = payload.get("code", "")
|
||||
notification_id = f"{NOTIFICATION_ID_PREFIX}{self.entry.data[CONF_INSTANCE_ID]}"
|
||||
await self.hass.services.async_call(
|
||||
@@ -100,32 +107,136 @@ class JiboCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
return
|
||||
|
||||
if message_type == "command":
|
||||
await self._handle_command(payload.get("command"))
|
||||
await self._handle_command(payload)
|
||||
return
|
||||
|
||||
async def _handle_command(self, command: str | None) -> None:
|
||||
async def _handle_command(self, payload: dict[str, Any]) -> None:
|
||||
command = payload.get("command")
|
||||
if command == "lights_off_current_room":
|
||||
await self._handle_lights_off_current_room()
|
||||
await self._handle_lights_room("turn_off")
|
||||
return
|
||||
|
||||
if command == "lights_on_current_room":
|
||||
await self._handle_lights_room("turn_on")
|
||||
return
|
||||
|
||||
if command == "lights_off_named":
|
||||
await self._handle_lights_named("turn_off", payload.get("targetName"))
|
||||
return
|
||||
|
||||
if command == "lights_on_named":
|
||||
await self._handle_lights_named("turn_on", payload.get("targetName"))
|
||||
return
|
||||
|
||||
_LOGGER.warning("OpenJibo server sent unknown command: %s", command)
|
||||
|
||||
async def _handle_lights_off_current_room(self) -> None:
|
||||
async def _handle_lights_room(self, service: str) -> None:
|
||||
area_id = self._get_jibo_area_id()
|
||||
if area_id is None:
|
||||
return
|
||||
|
||||
await self.hass.services.async_call(
|
||||
"light",
|
||||
service,
|
||||
target={"area_id": [area_id]},
|
||||
)
|
||||
_LOGGER.info("Called light.%s for area %s", service, area_id)
|
||||
|
||||
async def _handle_lights_named(self, service: str, target_name: str | None) -> None:
|
||||
if not target_name:
|
||||
_LOGGER.warning("OpenJibo named light command missing targetName")
|
||||
return
|
||||
|
||||
area_id = self._get_jibo_area_id()
|
||||
entity_id = self._find_matching_light(target_name, area_id)
|
||||
if entity_id is None:
|
||||
_LOGGER.warning("No light matched target %r", target_name)
|
||||
return
|
||||
|
||||
await self.hass.services.async_call(
|
||||
"light",
|
||||
service,
|
||||
{"entity_id": entity_id},
|
||||
)
|
||||
_LOGGER.info("Called light.%s for entity %s (target %r)", service, entity_id, target_name)
|
||||
|
||||
def _get_jibo_area_id(self) -> str | None:
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
device_registry = dr.async_get(self.hass)
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, self.entry.entry_id)})
|
||||
if device is None:
|
||||
_LOGGER.warning("OpenJibo device entry not found; cannot turn off room lights")
|
||||
return
|
||||
_LOGGER.warning("OpenJibo device entry not found; cannot control room lights")
|
||||
return None
|
||||
|
||||
if not device.area_id:
|
||||
_LOGGER.warning("OpenJibo device has no area assigned; cannot turn off room lights")
|
||||
return
|
||||
_LOGGER.warning("OpenJibo device has no area assigned; cannot control room lights")
|
||||
return None
|
||||
|
||||
await self.hass.services.async_call(
|
||||
"light",
|
||||
"turn_off",
|
||||
target={"area_id": [device.area_id]},
|
||||
)
|
||||
_LOGGER.info("Turned off lights in area %s for OpenJibo device", device.area_id)
|
||||
return device.area_id
|
||||
|
||||
def _find_matching_light(self, target_name: str, area_id: str | None) -> str | None:
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
entity_registry = er.async_get(self.hass)
|
||||
normalized_target = _normalize_light_name(target_name)
|
||||
if not normalized_target:
|
||||
return None
|
||||
|
||||
area_candidates = self._list_light_entity_ids(entity_registry, area_id)
|
||||
match = self._match_light_entity(normalized_target, area_candidates)
|
||||
if match is not None:
|
||||
return match
|
||||
|
||||
if area_id is not None:
|
||||
all_candidates = self._list_light_entity_ids(entity_registry, None)
|
||||
return self._match_light_entity(normalized_target, all_candidates)
|
||||
|
||||
return None
|
||||
|
||||
def _list_light_entity_ids(
|
||||
self,
|
||||
entity_registry: Any,
|
||||
area_id: str | None,
|
||||
) -> list[str]:
|
||||
candidates: list[str] = []
|
||||
for entity in entity_registry.entities.values():
|
||||
if entity.domain != "light":
|
||||
continue
|
||||
if area_id is not None and entity.area_id != area_id:
|
||||
continue
|
||||
candidates.append(entity.entity_id)
|
||||
return candidates
|
||||
|
||||
def _match_light_entity(self, normalized_target: str, entity_ids: list[str]) -> str | None:
|
||||
exact_match: str | None = None
|
||||
partial_match: str | None = None
|
||||
|
||||
for entity_id in entity_ids:
|
||||
state = self.hass.states.get(entity_id)
|
||||
friendly_name = state.name if state is not None else entity_id
|
||||
normalized_friendly = _normalize_light_name(friendly_name)
|
||||
if not normalized_friendly:
|
||||
continue
|
||||
|
||||
if normalized_friendly == normalized_target:
|
||||
exact_match = entity_id
|
||||
break
|
||||
|
||||
if (
|
||||
normalized_target in normalized_friendly
|
||||
or normalized_friendly in normalized_target
|
||||
) and partial_match is None:
|
||||
partial_match = entity_id
|
||||
|
||||
return exact_match or partial_match
|
||||
|
||||
|
||||
def _normalize_light_name(value: str) -> str:
|
||||
normalized = value.lower().strip()
|
||||
normalized = normalized.replace("'", "").replace("’", "")
|
||||
normalized = re.sub(r"\s+", " ", normalized)
|
||||
for suffix in _LIGHT_SUFFIXES:
|
||||
if normalized.endswith(suffix):
|
||||
normalized = normalized[: -len(suffix)].strip()
|
||||
return normalized
|
||||
|
||||
@@ -30,9 +30,11 @@ class OpenJiboWebSocketClient:
|
||||
server_url: str,
|
||||
instance_id: str,
|
||||
on_message: MessageHandler,
|
||||
link_id: str | None = None,
|
||||
) -> None:
|
||||
self._server_url = server_url
|
||||
self._instance_id = instance_id
|
||||
self._link_id = link_id
|
||||
self._on_message = on_message
|
||||
self._session: aiohttp.ClientSession | None = None
|
||||
self._ws: aiohttp.ClientWebSocketResponse | None = None
|
||||
@@ -99,6 +101,7 @@ class OpenJiboWebSocketClient:
|
||||
{
|
||||
"type": "register",
|
||||
"instanceId": self._instance_id,
|
||||
**({"linkId": self._link_id} if self._link_id else {}),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user