Skip to content

Commit

Permalink
Support for new attribute without causing warnings
Browse files Browse the repository at this point in the history
Support for CCR/CEE (as switches)
Partial Support for 24v thermostat (honeywell / resideo) (as a regular thermostat).. still work to be done
Support for Floor thermostat (as a regular thermostat)
Bugfix: Gracefuly handle cases where the websocket client stacks multiple messages in a single receive call
Wait 5s on websocket reconnect, instead of 1s.
  • Loading branch information
nlz242 committed Nov 10, 2023
1 parent 783a541 commit 5c72d37
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 19 deletions.
7 changes: 6 additions & 1 deletion pyhilo/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
TOKEN_EXPIRATION_PADDING: Final = 300
VERIFY: Final = True
DEVICE_REFRESH_TIME: Final = 1800
PYHILO_VERSION: Final = "2023.08.03"
PYHILO_VERSION: Final = "2023.11.01"
# TODO: Find a way to keep previous line in sync with pyproject.toml automatically

CONTENT_TYPE_FORM: Final = "application/x-www-form-urlencoded"
Expand Down Expand Up @@ -170,6 +170,7 @@
"zigbee_channel",
"zig_bee_pairing_activated",
"gateway_asset_id",
"e_tag",
]

HILO_LIST_ATTRIBUTES: Final = [
Expand All @@ -190,6 +191,10 @@
"Outlet": "Switch",
"SmokeDetector": "Sensor",
"Thermostat": "Climate",
"FloorThermostat": "Climate",
"Ccr": "Switch",
"Cee": "Switch",
"Thermostat24V": "Climate",
"Tracker": "Sensor",
}

Expand Down
14 changes: 10 additions & 4 deletions pyhilo/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,16 @@ async def update(self) -> None:
# Don't do anything with unpaired device for now.
# self.devices.remove(device)

async def update_gateway(self) -> None:
gateway = await self._api.get_gateway(self.location_id)
LOG.debug(f"Generating device (gateway) {gateway}")
self.generate_device(gateway)
async def update_devicelist_from_signalr(self, values: list[dict[str, Any]]) -> list[HiloDevice]:
new_devices = []
for raw_device in values:
LOG.debug(f"Generating device {raw_device}")
dev = self.generate_device(raw_device)
if dev not in self.devices:
self.devices.append(dev)
new_devices.append(dev)

return new_devices

async def async_init(self) -> None:
"""Initialize the Hilo "manager" class."""
Expand Down
1 change: 1 addition & 0 deletions pyhilo/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def __init__(self, **event: dict[str, Any]):
if allowed_wH > 0:
self.used_percentage = round(used_wH / allowed_wH * 100, 2)
self.dict_items = [
"event_id",
"participating",
"configurable",
"period",
Expand Down
39 changes: 25 additions & 14 deletions pyhilo/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,31 +162,38 @@ def remove() -> None:
async def _async_receive_json(self) -> dict[str, Any]:
"""Receive a JSON response from the websocket server."""
assert self._client
msg = await self._client.receive(300)
if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSED, WSMsgType.CLOSING):
LOG.error(f"Websocket: Received event to close connection: {msg.type}")

response = await self._client.receive(300)

if response.type in (WSMsgType.CLOSE, WSMsgType.CLOSED, WSMsgType.CLOSING):
LOG.error(f"Websocket: Received event to close connection: {response.type}")
raise ConnectionClosedError("Connection was closed.")

if msg.type == WSMsgType.ERROR:
LOG.error(f"Websocket: Received error event, Connection failed: {msg.type}")
if response.type == WSMsgType.ERROR:
LOG.error(f"Websocket: Received error event, Connection failed: {response.type}")
raise ConnectionFailedError

if msg.type != WSMsgType.TEXT:
LOG.error(f"Websocket: Received invalid message: {msg}")
raise InvalidMessageError(f"Received non-text message: {msg.type}")
if response.type != WSMsgType.TEXT:
LOG.error(f"Websocket: Received invalid message: {response}")
raise InvalidMessageError(f"Received non-text message: {response.type}")

try:
data = json.loads(msg.data[:-1])
messages = []
# Sometimes the http lib stacks multiple messages in the buffer, we need to split them to process.
received_messages = response.data.strip().split("\x1e")
for msg in received_messages:
data = json.loads(msg)
messages.append(data)
except ValueError as v_exc:
raise InvalidMessageError("Received invalid JSON") from v_exc
except json.decoder.JSONDecodeError as j_exc:
LOG.error(f"Received invalid JSON: {msg.data}")
LOG.error(f"Received invalid JSON: {msg}")
LOG.exception(j_exc)
data = {}

self._watchdog.trigger()

return cast(Dict[str, Any], data)
return cast(Dict[str, Any], messages)

async def _async_send_json(self, payload: dict[str, Any]) -> None:
"""Send a JSON message to the websocket server.
Expand Down Expand Up @@ -314,12 +321,16 @@ async def async_listen(self) -> None:
LOG.info("Websocket: Listen started.")
try:
while not self._client.closed:
message = await self._async_receive_json()
self._parse_message(message)
messages = await self._async_receive_json()
for msg in messages:
self._parse_message(msg)
except ConnectionClosedError as err:
LOG.error(f"Websocket: Closed while listening: {err}")
LOG.exception(err)
pass
except InvalidMessageError as err:
LOG.warning(f"Websocket: Received invalid json : {message}")
pass
finally:
LOG.info("Websocket: Listen completed; cleaning up")
self._watchdog.cancel()
Expand All @@ -332,7 +343,7 @@ async def async_reconnect(self) -> None:
"""Reconnect (and re-listen, if appropriate) to the websocket."""
LOG.warning("Websocket: Reconnecting")
await self.async_disconnect()
await asyncio.sleep(1)
await asyncio.sleep(5)
await self.async_connect()

async def send_status(self) -> None:
Expand Down

0 comments on commit 5c72d37

Please sign in to comment.