From 1c092e7b71d3791e4c28604c0391c711116193d2 Mon Sep 17 00:00:00 2001 From: fuatakgun Date: Mon, 9 Jan 2023 10:27:23 +0100 Subject: [PATCH] feat: improve message generation function --- .../eufy_security/coordinator.py | 8 +- .../eufy_security_api/api_client.py | 252 ++++++++---------- .../eufy_security/eufy_security_api/const.py | 20 +- .../eufy_security_api/outgoing_message.py | 134 +++++----- 4 files changed, 199 insertions(+), 215 deletions(-) diff --git a/custom_components/eufy_security/coordinator.py b/custom_components/eufy_security/coordinator.py index 28aa571..6fe6e74 100644 --- a/custom_components/eufy_security/coordinator.py +++ b/custom_components/eufy_security/coordinator.py @@ -56,22 +56,26 @@ def platforms(self): @property def devices(self) -> dict: + """get devices from API""" return self._api.devices @property def stations(self) -> dict: + """get stations from API""" return self._api.stations async def set_mfa_and_connect(self, mfa_input: str): + """set mfa and connect""" await self._api.set_mfa_and_connect(mfa_input) async def set_captcha_and_connect(self, captcha_id: str, captcha_input: str): + """set captcha and connect""" await self._api.set_captcha_and_connect(captcha_id, captcha_input) - async def send_message(self, message: dict) -> None: + async def send_message(self, message: str) -> None: """send message to websocket api""" _LOGGER.debug(f"send_message - {message}") - await self._api.send_message(json.dumps(message)) + await self._api.send_message(message) async def set_log_level(self, log_level: str) -> None: """set log level of websocket server""" diff --git a/custom_components/eufy_security/eufy_security_api/api_client.py b/custom_components/eufy_security/eufy_security_api/api_client.py index e5b30bc..561fd53 100644 --- a/custom_components/eufy_security/eufy_security_api/api_client.py +++ b/custom_components/eufy_security/eufy_security_api/api_client.py @@ -16,6 +16,7 @@ from .const import ( SCHEMA_VERSION, EventNameToHandler, + EventSourceType, MessageField, ProductCommand, ProductType, @@ -53,12 +54,12 @@ def __init__(self, config, session: aiohttp.ClientSession) -> None: @property def devices(self) -> dict: - """ initialized devices """ + """initialized devices""" return self._devices @property def stations(self) -> dict: - """ initialized stations """ + """initialized stations""" return self._stations async def ws_connect(self): @@ -74,36 +75,36 @@ async def connect(self): async def _check_interactive_mode(self): # driver is not connected, wait for captcha event try: - _LOGGER.debug(f"_start_listening 2") + _LOGGER.debug(f"_check_interactive_mode 1") await asyncio.wait_for(self._captcha_future, timeout=10) event = self._captcha_future.result() raise CaptchaRequiredException(event.data[MessageField.CAPTCHA_ID.value], event.data[MessageField.CAPTCHA_IMG.value]) except (asyncio.exceptions.TimeoutError, asyncio.exceptions.CancelledError): pass - _LOGGER.debug(f"_start_listening 2") + _LOGGER.debug(f"_check_interactive_mode 2") # driver is not connected and captcha exception is not thrown, wait for mfa event try: - _LOGGER.debug(f"_start_listening 3") + _LOGGER.debug(f"_check_interactive_mode 3") await asyncio.wait_for(self._mfa_future, timeout=5) event = self._mfa_future.result() raise MultiFactorCodeRequiredException() except (asyncio.exceptions.TimeoutError, asyncio.exceptions.CancelledError) as exc: - _LOGGER.debug(f"_start_listening 4") + _LOGGER.debug(f"_check_interactive_mode 4") await self._connect_driver() raise DriverNotConnectedError() from exc async def _set_products(self) -> None: - _LOGGER.debug(f"_start_listening 1") + _LOGGER.debug(f"_set_products 1") self._captcha_future = asyncio.get_event_loop().create_future() self._mfa_future = asyncio.get_event_loop().create_future() - result = await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.start_listening)) + result = await self._start_listening() if result[MessageField.STATE.value][EventSourceType.driver.name][MessageField.CONNECTED.value] is False: await self._check_interactive_mode() - self._devices = await self._get_product(ProductType.device, result[MessageField.STATE.value]["devices"]) - self._stations = await self._get_product(ProductType.station, result[MessageField.STATE.value]["stations"]) + self._devices = await self._get_products(ProductType.device, result[MessageField.STATE.value]["devices"]) + self._stations = await self._get_products(ProductType.station, result[MessageField.STATE.value]["stations"]) - async def _get_product(self, product_type: ProductType, products: list) -> dict: + async def _get_products(self, product_type: ProductType, products: list) -> dict: response = {} for serial_no in products: product: Product = None @@ -139,80 +140,116 @@ async def set_mfa_and_connect(self, mfa_input: str): await asyncio.sleep(10) await self._set_products() - # general api commands + # server level commands + async def _start_listening(self): + return await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.start_listening)) + + async def _set_schema(self, schema_version: int) -> None: + await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.set_api_schema, schema_version=schema_version)) + + # driver level commands + async def _connect_driver(self) -> None: + await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.connect)) + + async def set_log_level(self, log_level: str) -> None: + """set log level of websocket server""" + await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.set_log_level, log_level=log_level)) + + async def poll_refresh(self) -> None: + """Poll cloud data for latest changes""" + await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.poll_refresh)) + async def _set_captcha(self, captcha_id: str, captcha_input: str) -> None: - command_type = OutgoingMessageType.set_captcha - command = EventSourceType.driver.name + "." + command_type.name await self._send_message_get_response( - OutgoingMessage(command_type, command=command, captcha_id=captcha_id, captcha_input=captcha_input) + OutgoingMessage(OutgoingMessageType.set_captcha, captcha_id=captcha_id, captcha_input=captcha_input) ) async def _set_mfa_code(self, mfa_input: str) -> None: - command_type = OutgoingMessageType.set_verify_code - command = EventSourceType.driver.name + "." + command_type.name - await self._send_message_get_response(OutgoingMessage(command_type, command=command, verify_code=mfa_input)) + await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.set_verify_code, verify_code=mfa_input)) - async def _connect_driver(self) -> None: - command_type = OutgoingMessageType.driver_connect - command = EventSourceType.driver.name + "." + "connect" - await self._send_message_get_response(OutgoingMessage(command_type, command=command)) + # product (both device and station) level commands + async def _get_metadata(self, product_type: ProductType, serial_no: str) -> dict: + result = await self._send_message_get_response( + OutgoingMessage(OutgoingMessageType.get_properties_metadata, domain=product_type.name, serial_no=serial_no) + ) + return result[MessageField.PROPERTIES.value] - async def _set_schema(self, schema_version: int) -> None: - await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.set_api_schema, schema_version=schema_version)) + async def _get_properties(self, product_type: ProductType, serial_no: str) -> dict: + result = await self._send_message_get_response( + OutgoingMessage(OutgoingMessageType.get_properties, domain=product_type.name, serial_no=serial_no) + ) + return result[MessageField.PROPERTIES.value] - async def set_log_level(self, log_level: str) -> None: - """set log level of websocket server""" - command_type = OutgoingMessageType.set_log_level - command = EventSourceType.driver.name + "." + "connect" - await self._send_message_get_response(OutgoingMessage(command_type, command=command, log_level=log_level)) + async def _get_commands(self, product_type: ProductType, serial_no: str) -> dict: + result = await self._send_message_get_response( + OutgoingMessage(OutgoingMessageType.get_commands, domain=product_type.name, serial_no=serial_no) + ) + return result[MessageField.COMMANDS.value] + + async def set_property(self, product_type: ProductType, serial_no: str, name: str, value: Any) -> None: + """Process set property call""" + await self._send_message_get_response( + OutgoingMessage(OutgoingMessageType.set_property, domain=product_type.name, serial_no=serial_no, name=name, value=value) + ) + + async def trigger_alarm(self, product_type: ProductType, serial_no: str, duration: int) -> None: + """Process trigger alarm call""" + await self._send_message_get_response( + OutgoingMessage(OutgoingMessageType.trigger_alarm, domain=product_type.name, serial_no=serial_no, seconds=duration) + ) + + async def reset_alarm(self, product_type: ProductType, serial_no: str) -> None: + """Process reset alarm call""" + await self._send_message_get_response( + OutgoingMessage(OutgoingMessageType.reset_alarm, domain=product_type.name, serial_no=serial_no) + ) + + # device level commands + async def pan_and_tilt(self, product_type: ProductType, serial_no: str, direction: int) -> None: + """Process start pan tilt rotate zoom""" + await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.pan_and_tilt, serial_no=serial_no, direction=direction)) + + async def start_rtsp_livestream(self, product_type: ProductType, serial_no: str) -> None: + """Process start rtsp livestream call""" + await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.start_rtsp_livestream, serial_no=serial_no)) + + async def stop_rtsp_livestream(self, product_type: ProductType, serial_no: str) -> None: + """Process stop rtsp livestream call""" + await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.stop_rtsp_livestream, serial_no=serial_no)) - # device and station functions async def _get_is_rtsp_streaming(self, product_type: ProductType, serial_no: str) -> bool: - command_type = OutgoingMessageType.is_rtsp_livestreaming - command = product_type.name + "." + command_type.name - result = await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no)) + result = await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.is_rtsp_livestreaming, serial_no=serial_no)) return result[MessageField.LIVE_STREAMING.value] + async def start_livestream(self, product_type: ProductType, serial_no: str) -> None: + """Process start p2p livestream call""" + await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.start_livestream, serial_no=serial_no)) + + async def stop_livestream(self, product_type: ProductType, serial_no: str) -> None: + """Process stop p2p livestream call""" + await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.stop_livestream, serial_no=serial_no)) + async def _get_is_p2p_streaming(self, product_type: ProductType, serial_no: str) -> bool: - command_type = OutgoingMessageType.is_livestreaming - command = product_type.name + "." + command_type.name - result = await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no)) + result = await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.is_livestreaming, serial_no=serial_no)) return result[MessageField.LIVE_STREAMING.value] - async def pan_and_tilt(self, product_type: ProductType, serial_no: str, direction: int) -> None: - """Process start pan tilt rotate zoom""" - command_type = OutgoingMessageType.pan_and_tilt - command = product_type.name + "." + command_type.name - await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no, direction=direction)) + async def _get_voices(self, product_type: ProductType, serial_no: str) -> dict: + result = await self._send_message_get_response( + OutgoingMessage(OutgoingMessageType.get_voices, domain=product_type.name, serial_no=serial_no) + ) + return result[MessageField.VOICES.value] async def quick_response(self, product_type: ProductType, serial_no: str, voice_id: int) -> None: """Process quick response for doorbell""" - command_type = OutgoingMessageType.quick_response - command = product_type.name + "." + command_type.name - await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no, voice_id=voice_id)) - - async def chime(self, product_type: ProductType, serial_no: str, ringtone: int) -> None: - """Process chme call""" - command_type = OutgoingMessageType.chime - command = product_type.name + "." + command_type.name - await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no, ringtone=ringtone)) - - async def reboot(self, product_type: ProductType, serial_no: str) -> None: - """Process reboot call""" - command_type = OutgoingMessageType.reboot - command = product_type.name + "." + command_type.name - await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no)) + await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.quick_response, serial_no=serial_no, voice_id=voice_id)) async def snooze( self, product_type: ProductType, serial_no: str, snooze_time: int, snooze_chime: bool, snooze_motion: bool, snooze_homebase: bool ) -> None: """Process snooze for devices ans stations""" - command_type = OutgoingMessageType.snooze - command = product_type.name + "." + command_type.name await self._send_message_get_response( OutgoingMessage( - command_type, - command=command, + OutgoingMessageType.snooze, serial_no=serial_no, snooze_time=snooze_time, snooze_chime=snooze_chime, @@ -223,81 +260,21 @@ async def snooze( async def verify_pin(self, product_type: ProductType, serial_no: str, pin: str) -> None: """verify pin for safe product""" - command_type = OutgoingMessageType.verify_pin - command = product_type.name + "." + command_type.name - await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no, pin=pin)) + await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.verify_pin, serial_no=serial_no, pin=pin)) async def unlock(self, product_type: ProductType, serial_no: str) -> None: """unlock for safe product""" - command_type = OutgoingMessageType.unlock - command = product_type.name + "." + command_type.name - await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no)) + await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.unlock, serial_no=serial_no)) - async def start_rtsp_livestream(self, product_type: ProductType, serial_no: str) -> None: - """Process start rtsp livestream call""" - command_type = OutgoingMessageType.start_rtsp_livestream - command = product_type.name + "." + command_type.name - await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no)) - - async def stop_rtsp_livestream(self, product_type: ProductType, serial_no: str) -> None: - """Process stop rtsp livestream call""" - command_type = OutgoingMessageType.stop_rtsp_livestream - command = product_type.name + "." + command_type.name - await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no)) - - async def start_livestream(self, product_type: ProductType, serial_no: str) -> None: - """Process start p2p livestream call""" - command_type = OutgoingMessageType.start_livestream - command = product_type.name + "." + command_type.name - await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no)) - - async def stop_livestream(self, product_type: ProductType, serial_no: str) -> None: - """Process stop p2p livestream call""" - command_type = OutgoingMessageType.stop_livestream - command = product_type.name + "." + command_type.name - await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no)) - - async def trigger_alarm(self, product_type: ProductType, serial_no: str, duration: int) -> None: - """Process trigger alarm call""" - command_type = OutgoingMessageType.trigger_alarm - command = product_type.name + "." + command_type.name - await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no, seconds=duration)) + # station level commands - async def reset_alarm(self, product_type: ProductType, serial_no: str) -> None: - """Process reset alarm call""" - command_type = OutgoingMessageType.reset_alarm - command = product_type.name + "." + command_type.name - await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no)) - - async def set_property(self, product_type: ProductType, serial_no: str, name: str, value: Any) -> None: - """Process set property call""" - command_type = OutgoingMessageType.set_property - command = product_type.name + "." + command_type.name - await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no, name=name, value=value)) - - async def _get_voices(self, product_type: ProductType, serial_no: str) -> dict: - command_type = OutgoingMessageType.get_voices - command = product_type.name + "." + command_type.name - result = await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no)) - return result[MessageField.VOICES.value] - - async def _get_properties(self, product_type: ProductType, serial_no: str) -> dict: - command_type = OutgoingMessageType.get_properties - command = product_type.name + "." + command_type.name - result = await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no)) - return result[MessageField.PROPERTIES.value] - - async def _get_metadata(self, product_type: ProductType, serial_no: str) -> dict: - command_type = OutgoingMessageType.get_properties_metadata - command = product_type.name + "." + command_type.name - result = await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no)) - return result[MessageField.PROPERTIES.value] + async def chime(self, product_type: ProductType, serial_no: str, ringtone: int) -> None: + """Process chme call""" + await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.chime, serial_no=serial_no, ringtone=ringtone)) - async def _get_commands(self, product_type: ProductType, serial_no: str) -> dict: - command_type = OutgoingMessageType.get_commands - command = product_type.name + "." + command_type.name - result = await self._send_message_get_response(OutgoingMessage(command_type, command=command, serial_no=serial_no)) - return result[MessageField.COMMANDS.value] + async def reboot(self, product_type: ProductType, serial_no: str) -> None: + """Process reboot call""" + await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.reboot, serial_no=serial_no)) async def _on_message(self, message: dict) -> None: message_str = str(message)[0:200] @@ -333,7 +310,7 @@ async def _handle_event(self, event: Event): # handle device or statino specific events through specific instances plural_product = "_" + event.data[MessageField.SOURCE.value] + "s" try: - product = self.__dict__[plural_product][event.data[MessageField.SERIAL_NUMBER.value]] + product = self.__dict__[plural_product][event.data[MessageField.SERIAL_NO.value]] await product.process_event(event) except (KeyError, TypeError) as exc: raise DeviceNotInitializedYetException(event) from exc @@ -374,12 +351,6 @@ async def send_message(self, message: dict) -> None: _LOGGER.debug(f"send_message - {message}") await self._client.send_message(json.dumps(message)) - async def poll_refresh(self) -> None: - """Poll cloud data for latest changes""" - command_type = OutgoingMessageType.poll_refresh - command = EventSourceType.driver.name + "." + command_type.name - await self._send_message_get_response(OutgoingMessage(command_type, command=command)) - async def disconnect(self): """Disconnect the web socket and destroy it""" await self._client.disconnect() @@ -391,12 +362,3 @@ class IncomingMessageType(Enum): version = "version" result = "result" event = "event" - - -class EventSourceType(Enum): - """Event type""" - - station = "station" - device = "device" - driver = "driver" - server = "server" diff --git a/custom_components/eufy_security/eufy_security_api/const.py b/custom_components/eufy_security/eufy_security_api/const.py index be4ad6a..6190639 100644 --- a/custom_components/eufy_security/eufy_security_api/const.py +++ b/custom_components/eufy_security/eufy_security_api/const.py @@ -13,8 +13,10 @@ class MessageField(Enum): """Incoming or outgoing message field types""" # general fields + DUMMY = "dummy" MESSAGE_ID = "messageId" ERROR_CODE = "errorCode" + DOMAIN = "domain" COMMAND = "command" TYPE = "type" SUCCESS = "success" @@ -23,8 +25,8 @@ class MessageField(Enum): SOURCE = "source" SCHEMA_VERSION = "schemaVersion" LOG_LEVEL = "level" - BIND_AT_RUNTIME = "bindAtRuntime" - SERIAL_NUMBER = "serialNumber" + RUNTIME = "runtime" + SERIAL_NO = "serialNumber" PROPERTIES = "properties" COMMANDS = "commands" NAME = "name" @@ -75,6 +77,10 @@ class MessageField(Enum): SNOOZE_MOTION = "snoozeMotion" SNOOZE_HOMEBASE = "snoozeHomebase" + # get events + MAX_RESULTS = "maxResults" + DEVICE_SERIAL_NO = "deviceSN" + # https://bropat.github.io/eufy-security-ws/#/api_events?id=device-level-events class EventNameToHandler(Enum): @@ -124,3 +130,13 @@ class ProductCommand(Enum): reset_alarm = CommandDescription("Reset Alarm") verify_pin = CommandDescription("Verify Pin", "verify_p_i_n") reboot = CommandDescription("Reboot", "stationReboot") + + +class EventSourceType(Enum): + """Event type""" + + station = "station" + device = "device" + driver = "driver" + server = "server" + product = "product" diff --git a/custom_components/eufy_security/eufy_security_api/outgoing_message.py b/custom_components/eufy_security/eufy_security_api/outgoing_message.py index b4ec146..280f016 100644 --- a/custom_components/eufy_security/eufy_security_api/outgoing_message.py +++ b/custom_components/eufy_security/eufy_security_api/outgoing_message.py @@ -2,7 +2,7 @@ import logging import uuid -from .const import MessageField +from .const import MessageField, EventSourceType _LOGGER: logging.Logger = logging.getLogger(__package__) @@ -32,69 +32,60 @@ class OutgoingMessageToParameter(Enum): class OutgoingMessageType(Enum): """Outgoing message types""" - driver_connect = {MessageField.COMMAND.value: auto()} - set_api_schema = {MessageField.COMMAND.value: auto(), MessageField.SCHEMA_VERSION.value: MessageField.BIND_AT_RUNTIME} - set_log_level = {MessageField.COMMAND.value: auto(), MessageField.LOG_LEVEL.value: MessageField.BIND_AT_RUNTIME} - start_listening = {MessageField.COMMAND.value: auto()} - get_properties_metadata = {MessageField.COMMAND.value: auto(), MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME} - get_properties = {MessageField.COMMAND.value: auto(), MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME} - get_commands = {MessageField.COMMAND.value: auto(), MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME} - poll_refresh = {MessageField.COMMAND.value: auto()} - set_property = { - MessageField.COMMAND.value: auto(), - MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME, - MessageField.NAME.value: MessageField.BIND_AT_RUNTIME, - MessageField.VALUE.value: MessageField.BIND_AT_RUNTIME, - } - trigger_alarm = { - MessageField.COMMAND.value: auto(), - MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME, - MessageField.SECONDS.value: MessageField.BIND_AT_RUNTIME, - } - pan_and_tilt = { - MessageField.COMMAND.value: auto(), - MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME, - MessageField.DIRECTION.value: MessageField.BIND_AT_RUNTIME, - } - reset_alarm = {MessageField.COMMAND.value: auto(), MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME} - start_rtsp_livestream = {MessageField.COMMAND.value: auto(), MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME} - stop_rtsp_livestream = {MessageField.COMMAND.value: auto(), MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME} - is_rtsp_livestreaming = {MessageField.COMMAND.value: auto(), MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME} - start_livestream = {MessageField.COMMAND.value: auto(), MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME} - stop_livestream = {MessageField.COMMAND.value: auto(), MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME} - is_livestreaming = {MessageField.COMMAND.value: auto(), MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME} + # server level commands + start_listening = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.server} + set_api_schema = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.server, MessageField.SCHEMA_VERSION: None} + + # driver level commands + connect = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.driver} + set_log_level = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.driver, MessageField.LOG_LEVEL: None} + poll_refresh = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.driver} set_captcha = { - MessageField.COMMAND.value: auto(), - MessageField.CAPTCHA_ID.value: MessageField.BIND_AT_RUNTIME, - MessageField.CAPTCHA_IMG.value: MessageField.BIND_AT_RUNTIME, + MessageField.DUMMY: auto(), + MessageField.DOMAIN: EventSourceType.driver, + MessageField.CAPTCHA_ID: None, + MessageField.CAPTCHA_IMG: None, } - set_verify_code = {MessageField.COMMAND.value: auto(), MessageField.VERIFY_CODE.value: MessageField.BIND_AT_RUNTIME} - get_voices = {MessageField.COMMAND.value: auto(), MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME} - quick_response = { - MessageField.COMMAND.value: auto(), - MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME, - MessageField.VOICE_ID.value: MessageField.BIND_AT_RUNTIME, + set_verify_code = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.driver, MessageField.VERIFY_CODE: None} + get_video_events = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.driver, MessageField.MAX_RESULTS: None} + + # product (both device and station) level commands + get_properties_metadata = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.product} + get_properties = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.product} + get_commands = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.product} + set_property = { + MessageField.DUMMY: auto(), + MessageField.DOMAIN: EventSourceType.product, + MessageField.NAME: None, + MessageField.VALUE: None, } + trigger_alarm = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.product, MessageField.SECONDS: None} + reset_alarm = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.product} + + # device level commands + pan_and_tilt = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.device, MessageField.DIRECTION: None} + start_rtsp_livestream = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.device} + stop_rtsp_livestream = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.device} + is_rtsp_livestreaming = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.device} + start_livestream = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.device} + stop_livestream = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.device} + is_livestreaming = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.device} + get_voices = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.device} + quick_response = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.device, MessageField.VOICE_ID: None} snooze = { - MessageField.COMMAND.value: auto(), - MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME, - MessageField.SNOOZE_TIME.value: MessageField.BIND_AT_RUNTIME, - MessageField.SNOOZE_CHIME.value: MessageField.BIND_AT_RUNTIME, - MessageField.SNOOZE_MOTION.value: MessageField.BIND_AT_RUNTIME, - MessageField.SNOOZE_HOMEBASE.value: MessageField.BIND_AT_RUNTIME, - } - chime = { - MessageField.COMMAND.value: auto(), - MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME, - MessageField.RINGTONE.value: MessageField.BIND_AT_RUNTIME, - } - verify_pin = { - MessageField.COMMAND.value: auto(), - MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME, - MessageField.PIN.value: MessageField.BIND_AT_RUNTIME, + MessageField.DUMMY: auto(), + MessageField.DOMAIN: EventSourceType.device, + MessageField.SNOOZE_TIME: None, + MessageField.SNOOZE_CHIME: None, + MessageField.SNOOZE_MOTION: None, + MessageField.SNOOZE_HOMEBASE: None, } - unlock = {MessageField.COMMAND.value: auto(), MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME} - reboot = {MessageField.COMMAND.value: auto(), MessageField.SERIAL_NUMBER.value: MessageField.BIND_AT_RUNTIME} + verify_pin = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.device, MessageField.PIN: None} + unlock = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.device} + + # station level commands + chime = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.station, MessageField.RINGTONE: None} + reboot = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.station} class OutgoingMessage: @@ -102,13 +93,24 @@ class OutgoingMessage: def __init__(self, message_type: OutgoingMessageType, **kwargs) -> None: self._type = message_type - self._message = self.type.value.copy() - self._message[MessageField.COMMAND.value] = kwargs.get("command", self.type.name) - self._message[MessageField.MESSAGE_ID.value] = kwargs.get("id", self.command + "." + uuid.uuid4().hex) - - for key, value in self._message.items(): - if value == MessageField.BIND_AT_RUNTIME: - self._message[key] = kwargs.get(OutgoingMessageToParameter[key].value) + self._message = {} + + for key in message_type.value.keys(): + try: + self._message[key.value] = kwargs.get(OutgoingMessageToParameter[key.value].value) + except KeyError: + pass + + default_domain = message_type.value[MessageField.DOMAIN] + if default_domain in [EventSourceType.product, EventSourceType.station, EventSourceType.device]: + self._message[MessageField.SERIAL_NO.value] = kwargs.get(OutgoingMessageToParameter[MessageField.SERIAL_NO.value].value) + + domain = default_domain.value if default_domain != EventSourceType.product else kwargs.get(MessageField.DOMAIN.value, "") + command = self.type.name if domain == EventSourceType.server.value else domain + "." + self.type.name + _LOGGER.debug(f"domain - {domain} - {default_domain} - {command} - {kwargs} - {self._message}") + self._message[MessageField.COMMAND.value] = command + self._message[MessageField.MESSAGE_ID.value] = self.command + "." + uuid.uuid4().hex + _LOGGER.debug(self._message) @property def id(self) -> str: