From 1b2634757b60c63711b4dd087d5f960040a86f84 Mon Sep 17 00:00:00 2001 From: Bar Date: Tue, 28 Sep 2021 13:38:10 +0300 Subject: [PATCH] Fix #17 error due to unsupported event and log level of invalid matrix --- CHANGELOG.md | 5 + .../shinobi/api/shinobi_websocket.py | 95 ++++++++++++++----- custom_components/shinobi/helpers/const.py | 2 + .../shinobi/managers/event_manager.py | 5 +- custom_components/shinobi/manifest.json | 2 +- 5 files changed, 81 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44fa2a2..db590a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 1.1.13 + +- Fix error due to unsupported event messages [#17](https://github.com/elad-bar/ha-shinobi/issues/17) +- Change message's log level of empty matrices to `DEBUG` + ## 1.1.12 - Added support for single camera (response of an object instead of array) [#16](https://github.com/elad-bar/ha-shinobi/issues/16) diff --git a/custom_components/shinobi/api/shinobi_websocket.py b/custom_components/shinobi/api/shinobi_websocket.py index fdb55cb..00625ee 100644 --- a/custom_components/shinobi/api/shinobi_websocket.py +++ b/custom_components/shinobi/api/shinobi_websocket.py @@ -7,6 +7,7 @@ from datetime import datetime import json import logging +import sys from typing import Callable, Optional import aiohttp @@ -52,6 +53,12 @@ def __init__(self, self.is_connected = False self.api = api self.is_aborted = False + self._messages_handler: dict = { + SHINOBI_WS_CONNECTION_ESTABLISHED_MESSAGE: self.handle_connection_established_message, + SHINOBI_WS_PONG_MESSAGE: self.handle_pong_message, + SHINOBI_WS_CONNECTION_READY_MESSAGE: self.handle_ready_state_message, + SHINOBI_WS_ACTION_MESSAGE: self.handle_action_message + } self._handlers = { "log": self.handle_log, @@ -151,7 +158,7 @@ def handle_next_message(self, msg): else: self._last_update = datetime.now() - if msg.data == "close": + if msg.data is None or msg.data == "close": result = False else: self.hass.async_create_task(self.parse_message(msg.data)) @@ -160,42 +167,78 @@ def handle_next_message(self, msg): return result async def parse_message(self, message: str): - if message.startswith(SHINOBI_WS_CONNECTION_ESTABLISHED_MESSAGE): - _LOGGER.debug(f"Connected, Message: {message[1:]}") - - elif message.startswith(SHINOBI_WS_PONG_MESSAGE): - _LOGGER.debug(f"Pong received") + message_parts = message.split("[") + message_prefix = message_parts[0] - elif message.startswith(SHINOBI_WS_CONNECTION_READY_MESSAGE): - _LOGGER.debug(f"Back channel connected") - await self.send_connect_message() + message_handler = self._messages_handler.get(message_prefix) - elif message.startswith(SHINOBI_WS_ACTION_MESSAGE): - json_str = message[2:] - payload = json.loads(json_str) - await self.parse_payload(payload) + if message_handler is None: + _LOGGER.debug(f"No message handler available, Message: {message}") else: - _LOGGER.debug(f"No message handler available, Message: {message}") + message_data = message.replace(message_prefix, "") + + await message_handler(message_prefix, message_data) + + @staticmethod + async def handle_connection_established_message(prefix, data): + _LOGGER.debug(f"WebSocket connection established") + + @staticmethod + async def handle_pong_message(prefix, data): + _LOGGER.debug(f"Pong message received") + + async def handle_ready_state_message(self, prefix, data): + _LOGGER.debug(f"WebSocket connection state changed to ready") + + await self.send_connect_message() + + async def handle_action_message(self, prefix, data): + try: + payload = json.loads(data) + action = payload[0] + data = payload[1] - async def parse_payload(self, payload): - action = payload[0] - data = payload[1] + if action == "f": + func = data.get(action) - if action == "f": - func = data.get(action) + if func in self._handlers.keys(): + handler: Callable = self._handlers.get(func, None) - if func in self._handlers.keys(): - handler: Callable = self._handlers.get(func, None) + if handler is not None: + await handler(data) - if handler is not None: - await handler(data) + else: + _LOGGER.debug(f"Payload received, Type: {func}") else: - _LOGGER.debug(f"Payload received, Type: {func}") + _LOGGER.debug(f"No payload handler available, Payload: {payload}") - else: - _LOGGER.debug(f"No payload handler available, Payload: {payload}") + except Exception as ex: + exc_type, exc_obj, tb = sys.exc_info() + line_number = tb.tb_lineno + + supported_event = False + for key in self._handlers.keys(): + if key in data[0:50]: + supported_event = True + break + + if supported_event: + _LOGGER.error(f"Failed to parse message, Data: {data}, Error: {ex}, Line: {line_number}") + + else: + key = "Unknown" + unsupported_data = str(data[0:50]) + + if unsupported_data.startswith(TRIGGER_STARTS_WITH): + key_tmp = unsupported_data.replace(TRIGGER_STARTS_WITH, "") + key_arr = key_tmp.split("\"") + + if len(key_arr) > 0: + key = key_arr[0] + + _LOGGER.debug(f"Ignoring unsupported event message, Key: {key}, Data: {unsupported_data}") async def handle_log(self, data): monitor_id = data.get("mid") diff --git a/custom_components/shinobi/helpers/const.py b/custom_components/shinobi/helpers/const.py index e7d243a..977329a 100644 --- a/custom_components/shinobi/helpers/const.py +++ b/custom_components/shinobi/helpers/const.py @@ -212,6 +212,8 @@ ATTR_CAMERA_DETAILS_FPS: ATTR_FPS } +TRIGGER_STARTS_WITH = "[\"f\",{\"f\":\"" + TRIGGER_PLUG = "plug" TRIGGER_NAME = "name" TRIGGER_DETAILS = "details" diff --git a/custom_components/shinobi/managers/event_manager.py b/custom_components/shinobi/managers/event_manager.py index 5c55d1b..a4d351d 100644 --- a/custom_components/shinobi/managers/event_manager.py +++ b/custom_components/shinobi/managers/event_manager.py @@ -82,7 +82,7 @@ def _handle_sensor_event(self, topic, payload): for trigger_object in trigger_matrices: if trigger_object is None: - _LOGGER.warning(f"Invalid trigger object, payload: {payload}") + _LOGGER.debug(f"Ignoring empty trigger object") else: trigger_tag = trigger_object.get(TRIGGER_DETAILS_MATRICES_TAG) @@ -102,6 +102,9 @@ def _handle_sensor_event(self, topic, payload): TRIGGER_TOPIC: topic } + if len(trigger_tags) == 0 and sensor_type == REASON_MOTION: + _LOGGER.warning(f"No tags found for the event, Data: {payload}") + previous_data = self.get_state(topic, sensor_type) previous_state = previous_data.get(TRIGGER_STATE, STATE_OFF) diff --git a/custom_components/shinobi/manifest.json b/custom_components/shinobi/manifest.json index 5819a64..f900082 100644 --- a/custom_components/shinobi/manifest.json +++ b/custom_components/shinobi/manifest.json @@ -10,6 +10,6 @@ "codeowners": ["@elad-bar"], "requirements": [ ], "config_flow": true, - "version": "1.1.12", + "version": "1.1.13", "iot_class": "local_polling" }