From f81ff04e9ec3c137c62fa8ed9bfc603e72081630 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Wed, 22 Nov 2023 07:10:00 +0000 Subject: [PATCH] Adds typing support (#338) * Initial basic type checking * Fix untyped calls and defs * Add mypy to CI * Add py.typed marker for PEP 561 compat * Fix flake8 spacing in setup.py * pylint didn't like some line lengths * Ignore TYPE_CHECKING blocks from code coverage --- .coveragerc | 2 + .github/workflows/ci.yml | 2 + mypy.ini | 6 + old_api/pyvlx/blind.py | 2 +- old_api/pyvlx/exception.py | 2 +- old_api/pyvlx/rollershutter.py | 2 +- old_api/pyvlx/scene.py | 2 +- old_api/pyvlx/window.py | 2 +- pyvlx/api/activate_scene.py | 18 ++- pyvlx/api/api_event.py | 31 +++-- pyvlx/api/command_send.py | 33 ++++-- pyvlx/api/factory_default.py | 14 ++- pyvlx/api/frame_creation.py | 8 +- pyvlx/api/frames/alias_array.py | 12 +- pyvlx/api/frames/frame.py | 15 +-- pyvlx/api/frames/frame_activate_scene.py | 27 +++-- .../frames/frame_activation_log_updated.py | 2 +- pyvlx/api/frames/frame_command_send.py | 73 +++++++----- pyvlx/api/frames/frame_discover_nodes.py | 18 +-- pyvlx/api/frames/frame_error_notification.py | 8 +- pyvlx/api/frames/frame_facory_default.py | 8 +- .../frames/frame_get_all_nodes_information.py | 27 +++-- pyvlx/api/frames/frame_get_limitation.py | 57 +++++---- pyvlx/api/frames/frame_get_local_time.py | 10 +- pyvlx/api/frames/frame_get_network_setup.py | 18 +-- .../api/frames/frame_get_node_information.py | 33 +++--- .../api/frames/frame_get_protocol_version.py | 12 +- pyvlx/api/frames/frame_get_scene_list.py | 22 ++-- pyvlx/api/frames/frame_get_state.py | 12 +- pyvlx/api/frames/frame_get_version.py | 18 +-- pyvlx/api/frames/frame_helper.py | 6 +- .../frame_house_status_monitor_disable_cfm.py | 2 +- .../frame_house_status_monitor_disable_req.py | 2 +- .../frame_house_status_monitor_enable_cfm.py | 2 +- .../frame_house_status_monitor_enable_req.py | 2 +- pyvlx/api/frames/frame_leave_learn_state.py | 12 +- .../frames/frame_node_information_changed.py | 19 +-- ...ode_state_position_changed_notification.py | 10 +- pyvlx/api/frames/frame_password_change.py | 25 ++-- pyvlx/api/frames/frame_password_enter.py | 17 +-- pyvlx/api/frames/frame_reboot.py | 8 +- pyvlx/api/frames/frame_set_node_name.py | 18 +-- pyvlx/api/frames/frame_set_utc.py | 12 +- pyvlx/api/frames/frame_status_request.py | 45 ++++--- pyvlx/api/get_all_nodes_information.py | 15 ++- pyvlx/api/get_limitation.py | 33 ++++-- pyvlx/api/get_local_time.py | 16 ++- pyvlx/api/get_network_setup.py | 13 +- pyvlx/api/get_node_information.py | 17 ++- pyvlx/api/get_protocol_version.py | 16 ++- pyvlx/api/get_scene_list.py | 17 ++- pyvlx/api/get_state.py | 18 ++- pyvlx/api/get_version.py | 14 ++- pyvlx/api/house_status_monitor.py | 23 ++-- pyvlx/api/leave_learn_state.py | 15 ++- pyvlx/api/password_enter.py | 13 +- pyvlx/api/reboot.py | 14 ++- pyvlx/api/session_id.py | 4 +- pyvlx/api/set_node_name.py | 13 +- pyvlx/api/set_utc.py | 12 +- pyvlx/api/status_request.py | 17 ++- pyvlx/config.py | 20 +++- pyvlx/connection.py | 48 ++++---- pyvlx/dataobjects.py | 40 ++++--- pyvlx/exception.py | 7 +- pyvlx/heartbeat.py | 24 ++-- pyvlx/klf200gateway.py | 57 +++++---- pyvlx/lightening_device.py | 17 ++- pyvlx/node.py | 23 ++-- pyvlx/node_helper.py | 15 ++- pyvlx/node_updater.py | 14 ++- pyvlx/nodes.py | 29 +++-- pyvlx/on_off_switch.py | 17 ++- pyvlx/opening_device.py | 79 ++++++------ pyvlx/parameter.py | 112 ++++++++++-------- pyvlx/py.typed | 0 pyvlx/pyvlx.py | 25 ++-- pyvlx/scene.py | 13 +- pyvlx/scenes.py | 21 ++-- pyvlx/slip.py | 12 +- pyvlx/string_helper.py | 4 +- requirements/testing.txt | 2 + setup.py | 3 + test/frame_get_limitation_status_ntf_test.py | 6 +- 84 files changed, 921 insertions(+), 613 deletions(-) create mode 100644 mypy.ini create mode 100644 pyvlx/py.typed diff --git a/.coveragerc b/.coveragerc index 27e42bd0..34aa4b25 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,3 +5,5 @@ omit = [report] +exclude_lines = + if TYPE_CHECKING: \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a70674ba..c9c3ad6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,8 @@ jobs: run: flake8 - name: Linter Pylint run: PYTHONPATH=. pylint --rcfile=.pylintrc pyvlx test/*.py *.py examples/*.py + - name: Mypy + run: mypy pyvlx - name: Tests run: PYTHONPATH=. pytest --cov pyvlx --cov-report xml - name: Upload coverage artifact diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..e6edeb46 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,6 @@ +[mypy] +show_error_codes = True +warn_unused_ignores = True +disallow_untyped_defs = True +disallow_untyped_calls = True +check_untyped_defs = True \ No newline at end of file diff --git a/old_api/pyvlx/blind.py b/old_api/pyvlx/blind.py index 00342742..02bf7326 100644 --- a/old_api/pyvlx/blind.py +++ b/old_api/pyvlx/blind.py @@ -24,7 +24,7 @@ def from_config(cls, pyvlx, item): typeid = item['typeId'] return cls(pyvlx, ident, name, subtype, typeid) - def __str__(self): + def __str__(self) -> str: """Return object as readable string.""" return ' str: """Return object as readable string.""" return '' \ .format(self.description) diff --git a/old_api/pyvlx/rollershutter.py b/old_api/pyvlx/rollershutter.py index 3fa29820..b7b2c469 100644 --- a/old_api/pyvlx/rollershutter.py +++ b/old_api/pyvlx/rollershutter.py @@ -24,7 +24,7 @@ def from_config(cls, pyvlx, item): typeid = item['typeId'] return cls(pyvlx, ident, name, subtype, typeid) - def __str__(self): + def __str__(self) -> str: """Return object as readable string.""" return ' str: """Return object as readable string.""" return '' \ diff --git a/old_api/pyvlx/window.py b/old_api/pyvlx/window.py index c6fb9fa3..f53fd7ca 100644 --- a/old_api/pyvlx/window.py +++ b/old_api/pyvlx/window.py @@ -24,7 +24,7 @@ def from_config(cls, pyvlx, item): typeid = item['typeId'] return cls(pyvlx, ident, name, subtype, typeid) - def __str__(self): + def __str__(self) -> str: """Return object as readable string.""" return ' bool: """Handle incoming API frame, return True if this was the expected frame.""" if ( isinstance(frame, FrameActivateSceneConfirmation) @@ -49,7 +55,7 @@ async def handle_frame(self, frame): return True return False - def request_frame(self): + def request_frame(self) -> FrameActivateSceneRequest: """Construct initiating frame.""" self.session_id = get_new_session_id() return FrameActivateSceneRequest( diff --git a/pyvlx/api/api_event.py b/pyvlx/api/api_event.py index d3402aaa..ead02e64 100644 --- a/pyvlx/api/api_event.py +++ b/pyvlx/api/api_event.py @@ -1,11 +1,17 @@ """Base class for waiting for a specific answer frame from velux ap..""" import asyncio +from typing import TYPE_CHECKING, Optional + +from .frames import FrameBase + +if TYPE_CHECKING: + from pyvlx import PyVLX class ApiEvent: """Base class for waiting a specific frame from API connection.""" - def __init__(self, pyvlx, timeout_in_seconds=10): + def __init__(self, pyvlx: "PyVLX", timeout_in_seconds: int = 10): """Initialize ApiEvent.""" self.pyvlx = pyvlx self.response_received_or_timeout = asyncio.Event() @@ -13,9 +19,9 @@ def __init__(self, pyvlx, timeout_in_seconds=10): self.success = False self.timeout_in_seconds = timeout_in_seconds self.timeout_callback = None - self.timeout_handle = None + self.timeout_handle: Optional[asyncio.TimerHandle] = None - async def do_api_call(self): + async def do_api_call(self) -> None: """Start. Sending and waiting for answer.""" self.pyvlx.connection.register_frame_received_cb(self.response_rec_callback) await self.send_frame() @@ -24,33 +30,34 @@ async def do_api_call(self): await self.stop_timeout() self.pyvlx.connection.unregister_frame_received_cb(self.response_rec_callback) - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" raise NotImplementedError("handle_frame has to be implemented") - async def send_frame(self): + async def send_frame(self) -> None: """Send frame to API connection.""" await self.pyvlx.send_frame(self.request_frame()) - def request_frame(self): - """Construct initiating framw.""" + def request_frame(self) -> FrameBase: + """Construct initiating frame.""" raise NotImplementedError("send_frame has to be implemented") - async def response_rec_callback(self, frame): + async def response_rec_callback(self, frame: FrameBase) -> None: """Handle frame. Callback from internal api connection.""" if await self.handle_frame(frame): self.response_received_or_timeout.set() - def timeout(self): + def timeout(self) -> None: """Handle timeout for not having received expected frame.""" self.response_received_or_timeout.set() - async def start_timeout(self): + async def start_timeout(self) -> None: """Start timeout.""" self.timeout_handle = self.pyvlx.connection.loop.call_later( self.timeout_in_seconds, self.timeout ) - async def stop_timeout(self): + async def stop_timeout(self) -> None: """Stop timeout.""" - self.timeout_handle.cancel() + if self.timeout_handle is not None: + self.timeout_handle.cancel() diff --git a/pyvlx/api/command_send.py b/pyvlx/api/command_send.py index d047a73b..32804b28 100644 --- a/pyvlx/api/command_send.py +++ b/pyvlx/api/command_send.py @@ -1,24 +1,31 @@ """Module for retrieving scene list from API.""" +from typing import TYPE_CHECKING, Any, Optional + +from ..parameter import Parameter from .api_event import ApiEvent from .frames import ( - CommandSendConfirmationStatus, FrameCommandRemainingTimeNotification, - FrameCommandRunStatusNotification, FrameCommandSendConfirmation, - FrameCommandSendRequest, FrameSessionFinishedNotification) + CommandSendConfirmationStatus, FrameBase, + FrameCommandRemainingTimeNotification, FrameCommandRunStatusNotification, + FrameCommandSendConfirmation, FrameCommandSendRequest, + FrameSessionFinishedNotification) from .session_id import get_new_session_id +if TYPE_CHECKING: + from pyvlx import PyVLX + class CommandSend(ApiEvent): """Class for sending command to API.""" def __init__( self, - pyvlx, - node_id, - parameter, - active_parameter=0, - wait_for_completion=True, - timeout_in_seconds=60, - **functional_parameter + pyvlx: "PyVLX", + node_id: int, + parameter: Parameter, + active_parameter: int = 0, + wait_for_completion: bool = True, + timeout_in_seconds: int = 60, + **functional_parameter: Any ): """Initialize SceneList class.""" super().__init__(pyvlx=pyvlx, timeout_in_seconds=timeout_in_seconds) @@ -28,9 +35,9 @@ def __init__( self.active_parameter = active_parameter self.functional_parameter = functional_parameter self.wait_for_completion = wait_for_completion - self.session_id = None + self.session_id: Optional[int] = None - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if ( isinstance(frame, FrameCommandSendConfirmation) @@ -59,7 +66,7 @@ async def handle_frame(self, frame): return True return False - def request_frame(self): + def request_frame(self) -> FrameCommandSendRequest: """Construct initiating frame.""" self.session_id = get_new_session_id() return FrameCommandSendRequest( diff --git a/pyvlx/api/factory_default.py b/pyvlx/api/factory_default.py index 991d09e8..11bbab30 100644 --- a/pyvlx/api/factory_default.py +++ b/pyvlx/api/factory_default.py @@ -1,21 +1,27 @@ """Module for handling the FactoryDefault to API.""" +from typing import TYPE_CHECKING + from pyvlx.log import PYVLXLOG from .api_event import ApiEvent from .frames import ( - FrameGatewayFactoryDefaultConfirmation, FrameGatewayFactoryDefaultRequest) + FrameBase, FrameGatewayFactoryDefaultConfirmation, + FrameGatewayFactoryDefaultRequest) + +if TYPE_CHECKING: + from pyvlx import PyVLX class FactoryDefault(ApiEvent): """Class for handling Factory reset API.""" - def __init__(self, pyvlx): + def __init__(self, pyvlx: "PyVLX"): """Initialize facotry default class.""" super().__init__(pyvlx=pyvlx) self.pyvlx = pyvlx self.success = False - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if isinstance(frame, FrameGatewayFactoryDefaultConfirmation): PYVLXLOG.warning("KLF200 is factory resetting") @@ -23,6 +29,6 @@ async def handle_frame(self, frame): return True return False - def request_frame(self): + def request_frame(self) -> FrameGatewayFactoryDefaultRequest: """Construct initiating frame.""" return FrameGatewayFactoryDefaultRequest() diff --git a/pyvlx/api/frame_creation.py b/pyvlx/api/frame_creation.py index 8c5acd6e..20aa5f74 100644 --- a/pyvlx/api/frame_creation.py +++ b/pyvlx/api/frame_creation.py @@ -1,10 +1,12 @@ """Helper module for creating a frame out of raw data.""" +from typing import Optional + from pyvlx.const import Command from pyvlx.log import PYVLXLOG from .frames import ( FrameActivateSceneConfirmation, FrameActivateSceneRequest, - FrameActivationLogUpdatedNotification, + FrameActivationLogUpdatedNotification, FrameBase, FrameCommandRemainingTimeNotification, FrameCommandRunStatusNotification, FrameCommandSendConfirmation, FrameCommandSendRequest, FrameDiscoverNodesConfirmation, FrameDiscoverNodesNotification, @@ -38,7 +40,7 @@ FrameStatusRequestRequest, extract_from_frame) -def frame_from_raw(raw): +def frame_from_raw(raw: bytes) -> Optional[FrameBase]: """Create and return frame from raw bytes.""" command, payload = extract_from_frame(raw) frame = create_frame(command) @@ -54,7 +56,7 @@ def frame_from_raw(raw): return frame -def create_frame(command): +def create_frame(command: Command) -> Optional[FrameBase]: """Create and return empty Frame from Command.""" # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements if command == Command.GW_ERROR_NTF: diff --git a/pyvlx/api/frames/alias_array.py b/pyvlx/api/frames/alias_array.py index c08f9345..b358967b 100644 --- a/pyvlx/api/frames/alias_array.py +++ b/pyvlx/api/frames/alias_array.py @@ -1,24 +1,26 @@ """Module for storing alias array.""" +from typing import List, Optional, Tuple + from pyvlx.exception import PyVLXException class AliasArray: """Object for storing alias array.""" - def __init__(self, raw=None): + def __init__(self, raw: Optional[bytes] = None): """Initialize alias array.""" - self.alias_array_ = [] + self.alias_array_: List[Tuple[bytes, bytes]] = [] if raw is not None: self.parse_raw(raw) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return ", ".join( "{:02x}{:02x}={:02x}{:02x}".format(c[0][0], c[0][1], c[1][0], c[1][1]) for c in self.alias_array_ ) - def __bytes__(self): + def __bytes__(self) -> bytes: """Get raw bytes of alias array.""" ret = bytes([len(self.alias_array_)]) for alias in self.alias_array_: @@ -26,7 +28,7 @@ def __bytes__(self): ret += bytes((5 - len(self.alias_array_)) * 4) return ret - def parse_raw(self, raw): + def parse_raw(self, raw: bytes) -> None: """Parse alias array from raw bytes.""" if not isinstance(raw, bytes): raise PyVLXException("AliasArray::invalid_type_if_raw", type_raw=type(raw)) diff --git a/pyvlx/api/frames/frame.py b/pyvlx/api/frames/frame.py index 827b8bd2..b4114360 100644 --- a/pyvlx/api/frames/frame.py +++ b/pyvlx/api/frames/frame.py @@ -1,6 +1,7 @@ """Module for Frames.""" import struct +from pyvlx.const import Command from pyvlx.exception import PyVLXException from .frame_helper import calc_crc @@ -9,17 +10,17 @@ class FrameBase: """Class for Base Frame.""" - def __init__(self, command): + def __init__(self, command: Command): """Initialize Base Frame.""" self.command = command - def __bytes__(self): + def __bytes__(self) -> bytes: """Get raw bytes of Frame.""" payload = self.get_payload() self.validate_payload_len(payload) return self.build_frame(self.command, payload) - def validate_payload_len(self, payload): + def validate_payload_len(self, payload: bytes) -> None: """Validate payload len.""" if not hasattr(self, "PAYLOAD_LEN"): # No fixed payload len, e.g. within FrameGetSceneListNotification @@ -33,19 +34,19 @@ def validate_payload_len(self, payload): frame_type=type(self).__name__, ) - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" return b"" - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return "<{}/>".format(type(self).__name__) @staticmethod - def build_frame(command, payload): + def build_frame(command: Command, payload: bytes) -> bytes: """Build raw bytes from command and payload.""" packet_length = 2 + len(payload) + 1 ret = struct.pack("BB", 0, packet_length) diff --git a/pyvlx/api/frames/frame_activate_scene.py b/pyvlx/api/frames/frame_activate_scene.py index 865971fc..110b045b 100644 --- a/pyvlx/api/frames/frame_activate_scene.py +++ b/pyvlx/api/frames/frame_activate_scene.py @@ -1,5 +1,6 @@ """Module for sending command to gw.""" from enum import Enum +from typing import Optional from pyvlx.const import Command, Originator, Priority, Velocity @@ -13,10 +14,10 @@ class FrameActivateSceneRequest(FrameBase): def __init__( self, - scene_id=None, - session_id=None, - originator=Originator.USER, - velocity=Velocity.DEFAULT, + scene_id: Optional[int] = None, + session_id: Optional[int] = None, + originator: Originator = Originator.USER, + velocity: Velocity = Velocity.DEFAULT, ): """Init Frame.""" super().__init__(Command.GW_ACTIVATE_SCENE_REQ) @@ -26,8 +27,10 @@ def __init__( self.priority = Priority.USER_LEVEL_2 self.velocity = velocity - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" + assert self.session_id is not None + assert self.scene_id is not None ret = bytes([self.session_id >> 8 & 255, self.session_id & 255]) ret += bytes([self.originator.value]) ret += bytes([self.priority.value]) @@ -35,7 +38,7 @@ def get_payload(self): ret += bytes([self.velocity.value]) return ret - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.session_id = payload[0] * 256 + payload[1] self.originator = Originator(payload[2]) @@ -43,7 +46,7 @@ def from_payload(self, payload): self.scene_id = payload[4] self.velocity = Velocity(payload[5]) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} scene_id="{}" session_id="{}" originator="{}" velocity="{}"/>'.format( type(self).__name__, self.scene_id, self.session_id, self.originator, self.velocity @@ -63,24 +66,26 @@ class FrameActivateSceneConfirmation(FrameBase): PAYLOAD_LEN = 3 - def __init__(self, session_id=None, status=None): + def __init__(self, session_id: Optional[int] = None, status: Optional[ActivateSceneConfirmationStatus] = None): """Init Frame.""" super().__init__(Command.GW_ACTIVATE_SCENE_CFM) self.session_id = session_id self.status = status - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" + assert self.status is not None + assert self.session_id is not None ret = bytes([self.status.value]) ret += bytes([self.session_id >> 8 & 255, self.session_id & 255]) return ret - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.status = ActivateSceneConfirmationStatus(payload[0]) self.session_id = payload[1] * 256 + payload[2] - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} session_id="{}" status="{}"/>'.format( type(self).__name__, self.session_id, self.status diff --git a/pyvlx/api/frames/frame_activation_log_updated.py b/pyvlx/api/frames/frame_activation_log_updated.py index a8154653..42950584 100644 --- a/pyvlx/api/frames/frame_activation_log_updated.py +++ b/pyvlx/api/frames/frame_activation_log_updated.py @@ -9,6 +9,6 @@ class FrameActivationLogUpdatedNotification(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_ACTIVATION_LOG_UPDATED_NTF) diff --git a/pyvlx/api/frames/frame_command_send.py b/pyvlx/api/frames/frame_command_send.py index f99fdbc4..a0387551 100644 --- a/pyvlx/api/frames/frame_command_send.py +++ b/pyvlx/api/frames/frame_command_send.py @@ -1,5 +1,6 @@ """Module for sending command to gw.""" from enum import Enum +from typing import List, Optional from pyvlx.const import Command, Originator, Priority from pyvlx.exception import PyVLXException @@ -15,16 +16,16 @@ class FrameCommandSendRequest(FrameBase): def __init__( self, - node_ids=None, - parameter=Parameter(), - active_parameter=0, - session_id=None, - originator=Originator.USER, - **functional_parameter + node_ids: Optional[List[int]] = None, + parameter: Parameter = Parameter(), + active_parameter: int = 0, + session_id: Optional[int] = None, + originator: Originator = Originator.USER, + **functional_parameter: bytes ): """Init Frame.""" super().__init__(Command.GW_COMMAND_SEND_REQ) - self.node_ids = node_ids + self.node_ids = node_ids if node_ids is not None else [] self.parameter = parameter self.active_parameter = active_parameter self.fpi1 = 0 @@ -48,9 +49,10 @@ def __init__( else: self.functional_parameter[key] = bytes(2) - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" # Session id + assert self.session_id is not None ret = bytes([self.session_id >> 8 & 255, self.session_id & 255]) ret += bytes([self.originator.value]) ret += bytes([self.priority.value]) @@ -80,7 +82,7 @@ def get_payload(self): ret += bytes([0]) return ret - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.session_id = payload[0] * 256 + payload[1] self.originator = Originator(payload[2]) @@ -95,7 +97,7 @@ def from_payload(self, payload): self.parameter = Parameter(payload[7:9]) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" functional_parameter = "" for key, value in self.functional_parameter.items(): @@ -125,24 +127,26 @@ class FrameCommandSendConfirmation(FrameBase): PAYLOAD_LEN = 3 - def __init__(self, session_id=None, status=None): + def __init__(self, session_id: Optional[int] = None, status: Optional[CommandSendConfirmationStatus] = None): """Init Frame.""" super().__init__(Command.GW_COMMAND_SEND_CFM) self.session_id = session_id self.status = status - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" + assert self.session_id is not None + assert self.status is not None ret = bytes([self.session_id >> 8 & 255, self.session_id & 255]) ret += bytes([self.status.value]) return ret - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.session_id = payload[0] * 256 + payload[1] self.status = CommandSendConfirmationStatus(payload[2]) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} session_id="{}" status="{}"/>'.format( type(self).__name__, self.session_id, self.status @@ -156,11 +160,11 @@ class FrameCommandRunStatusNotification(FrameBase): def __init__( self, - session_id=None, - status_id=None, - index_id=None, - node_parameter=None, - parameter_value=None, + session_id: Optional[int] = None, + status_id: Optional[int] = None, + index_id: Optional[int] = None, + node_parameter: Optional[int] = None, + parameter_value: Optional[int] = None, ): """Init Frame.""" super().__init__(Command.GW_COMMAND_RUN_STATUS_NTF) @@ -170,19 +174,24 @@ def __init__( self.node_parameter = node_parameter self.parameter_value = parameter_value - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" + assert self.session_id is not None ret = bytes([self.session_id >> 8 & 255, self.session_id & 255]) + assert self.status_id is not None ret += bytes([self.status_id]) + assert self.index_id is not None ret += bytes([self.index_id]) + assert self.node_parameter is not None ret += bytes([self.node_parameter]) + assert self.parameter_value is not None ret += bytes([self.parameter_value >> 8 & 255, self.parameter_value & 255]) # XXX: Missing implementation of run_status, status_reply and information_code ret += bytes(6) return ret - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.session_id = payload[0] * 256 + payload[1] self.status_id = payload[2] @@ -190,7 +199,7 @@ def from_payload(self, payload): self.node_parameter = payload[4] self.parameter_value = payload[5] * 256 + payload[6] - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return ( '<{} session_id="{}" status_id="{}" ' @@ -207,7 +216,7 @@ class FrameCommandRemainingTimeNotification(FrameBase): PAYLOAD_LEN = 6 - def __init__(self, session_id=None, index_id=None, node_parameter=None, seconds=0): + def __init__(self, session_id: Optional[int] = None, index_id: Optional[int] = None, node_parameter: Optional[int] = None, seconds: int = 0): """Init Frame.""" super().__init__(Command.GW_COMMAND_REMAINING_TIME_NTF) self.session_id = session_id @@ -215,22 +224,25 @@ def __init__(self, session_id=None, index_id=None, node_parameter=None, seconds= self.node_parameter = node_parameter self.seconds = seconds - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" + assert self.session_id is not None ret = bytes([self.session_id >> 8 & 255, self.session_id & 255]) + assert self.index_id is not None ret += bytes([self.index_id]) + assert self.node_parameter is not None ret += bytes([self.node_parameter]) ret += bytes([self.seconds >> 8 & 255, self.seconds & 255]) return ret - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.session_id = payload[0] * 256 + payload[1] self.index_id = payload[2] self.node_parameter = payload[3] self.seconds = payload[4] * 256 + payload[5] - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return ( '<{} session_id="{}" index_id="{}" ' @@ -246,21 +258,22 @@ class FrameSessionFinishedNotification(FrameBase): PAYLOAD_LEN = 2 - def __init__(self, session_id=None): + def __init__(self, session_id: Optional[int] = None): """Init Frame.""" super().__init__(Command.GW_SESSION_FINISHED_NTF) self.session_id = session_id - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" + assert self.session_id is not None ret = bytes([self.session_id >> 8 & 255, self.session_id & 255]) return ret - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.session_id = payload[0] * 256 + payload[1] - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} session_id="{}"/>'.format( type(self).__name__, self.session_id diff --git a/pyvlx/api/frames/frame_discover_nodes.py b/pyvlx/api/frames/frame_discover_nodes.py index b505add4..db5be261 100644 --- a/pyvlx/api/frames/frame_discover_nodes.py +++ b/pyvlx/api/frames/frame_discover_nodes.py @@ -9,21 +9,21 @@ class FrameDiscoverNodesRequest(FrameBase): PAYLOAD_LEN = 1 - def __init__(self, node_type=NodeType.NO_TYPE): + def __init__(self, node_type: NodeType = NodeType.NO_TYPE): """Init Frame.""" super().__init__(Command.GW_CS_DISCOVER_NODES_REQ) self.node_type = node_type - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" ret = bytes([self.node_type.value]) return ret - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.node_type = NodeType(payload[0]) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} node_type="{}"/>'.format(type(self).__name__, self.node_type) @@ -33,7 +33,7 @@ class FrameDiscoverNodesConfirmation(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_CS_DISCOVER_NODES_CFM) @@ -43,20 +43,20 @@ class FrameDiscoverNodesNotification(FrameBase): PAYLOAD_LEN = 131 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_CS_DISCOVER_NODES_NTF) self.payload = b"\0" * 131 - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" return self.payload - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.payload = payload - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} payload="{}"/>'.format( type(self).__name__, diff --git a/pyvlx/api/frames/frame_error_notification.py b/pyvlx/api/frames/frame_error_notification.py index df1c08a4..7fb01c31 100644 --- a/pyvlx/api/frames/frame_error_notification.py +++ b/pyvlx/api/frames/frame_error_notification.py @@ -23,20 +23,20 @@ class FrameErrorNotification(FrameBase): PAYLOAD_LEN = 1 - def __init__(self, error_type=ErrorType.NotFurtherDefined): + def __init__(self, error_type: ErrorType = ErrorType.NotFurtherDefined): """Init Frame.""" super().__init__(Command.GW_ERROR_NTF) self.error_type = error_type - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" ret = bytes([self.error_type.value]) return ret - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.error_type = ErrorType(payload[0]) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} error_type="{}"/>'.format(type(self).__name__, self.error_type) diff --git a/pyvlx/api/frames/frame_facory_default.py b/pyvlx/api/frames/frame_facory_default.py index 7768f674..7e6310ec 100644 --- a/pyvlx/api/frames/frame_facory_default.py +++ b/pyvlx/api/frames/frame_facory_default.py @@ -9,11 +9,11 @@ class FrameGatewayFactoryDefaultRequest(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_SET_FACTORY_DEFAULT_REQ) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{}/>'.format(type(self).__name__) @@ -23,10 +23,10 @@ class FrameGatewayFactoryDefaultConfirmation(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_SET_FACTORY_DEFAULT_CFM) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{}/>'.format(type(self).__name__) diff --git a/pyvlx/api/frames/frame_get_all_nodes_information.py b/pyvlx/api/frames/frame_get_all_nodes_information.py index 1c0ae071..2fc13dd9 100644 --- a/pyvlx/api/frames/frame_get_all_nodes_information.py +++ b/pyvlx/api/frames/frame_get_all_nodes_information.py @@ -2,6 +2,7 @@ import struct from datetime import datetime from enum import Enum +from typing import Optional from pyvlx.const import Command, NodeTypeWithSubtype, NodeVariation, Velocity from pyvlx.exception import PyVLXException @@ -17,7 +18,7 @@ class FrameGetAllNodesInformationRequest(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_GET_ALL_NODES_INFORMATION_REQ) @@ -35,22 +36,22 @@ class FrameGetAllNodesInformationConfirmation(FrameBase): PAYLOAD_LEN = 2 - def __init__(self, status=AllNodesInformationStatus.OK, number_of_nodes=0): + def __init__(self, status: AllNodesInformationStatus = AllNodesInformationStatus.OK, number_of_nodes: int = 0): """Init Frame.""" super().__init__(Command.GW_GET_ALL_NODES_INFORMATION_CFM) self.status = status self.number_of_nodes = number_of_nodes - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" return bytes([self.status.value, self.number_of_nodes]) - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.status = AllNodesInformationStatus(payload[0]) self.number_of_nodes = payload[1] - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} status="{}" number_of_nodes="{}"/>'.format( type(self).__name__, self.status, self.number_of_nodes @@ -62,7 +63,7 @@ class FrameGetAllNodesInformationNotification(FrameBase): PAYLOAD_LEN = 124 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_GET_ALL_NODES_INFORMATION_NTF) self.node_id = 0 @@ -89,14 +90,14 @@ def __init__(self): self.alias_array = AliasArray() @property - def serial_number(self): + def serial_number(self) -> Optional[str]: """Property for serial number in a human readable way.""" if self._serial_number == bytes(8): return None return ":".join("{:02x}".format(c) for c in self._serial_number) @serial_number.setter - def serial_number(self, serial_number): + def serial_number(self, serial_number: str) -> None: """Set serial number.""" if serial_number is None: self._serial_number = bytes(8) @@ -107,7 +108,7 @@ def serial_number(self, serial_number): if len(self._serial_number) != 8: raise PyVLXException("could_not_parse_serial_number") - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" payload = bytes() payload += bytes([self.node_id]) @@ -135,7 +136,7 @@ def get_payload(self): return payload - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.node_id = payload[0] self.order = payload[1] * 256 + payload[2] @@ -161,11 +162,11 @@ def from_payload(self, payload): self.alias_array = AliasArray(payload[103:125]) @property - def timestamp_formatted(self): + def timestamp_formatted(self) -> str: """Return time as human readable string.""" return datetime.fromtimestamp(self.timestamp).strftime("%Y-%m-%d %H:%M:%S") - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return ( '<{} node_id="{}" order="{}" ' @@ -207,6 +208,6 @@ class FrameGetAllNodesInformationFinishedNotification(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_GET_ALL_NODES_INFORMATION_FINISHED_NTF) diff --git a/pyvlx/api/frames/frame_get_limitation.py b/pyvlx/api/frames/frame_get_limitation.py index a6310dbe..a7a85342 100644 --- a/pyvlx/api/frames/frame_get_limitation.py +++ b/pyvlx/api/frames/frame_get_limitation.py @@ -1,5 +1,7 @@ """Module for get local time classes.""" +from typing import List, Optional + from pyvlx.const import Command, LimitationType, Originator, Priority from .frame import FrameBase @@ -10,19 +12,23 @@ class FrameGetLimitationStatus(FrameBase): PAYLOAD_LEN = 25 - def __init__(self, node_ids=None, session_id=None, limitation_type=LimitationType.MIN_LIMITATION): + def __init__(self, + node_ids: Optional[List[int]] = None, + session_id: Optional[int] = None, + limitation_type: LimitationType = LimitationType.MIN_LIMITATION): """Init Frame.""" super().__init__(Command.GW_GET_LIMITATION_STATUS_REQ) self.session_id = session_id self.originator = Originator.USER self.priority = Priority.USER_LEVEL_2 - self.node_ids = node_ids + self.node_ids = node_ids if node_ids is not None else [] self.parameter_id = 0 # Main Parameter self.limitations_type = limitation_type - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" + assert self.session_id is not None ret = bytes([self.session_id >> 8 & 255, self.session_id & 255]) ret += bytes([len(self.node_ids)]) # index array count ret += bytes(self.node_ids) + bytes(20 - len(self.node_ids)) @@ -30,7 +36,7 @@ def get_payload(self): ret += bytes([self.limitations_type.value]) return ret - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return f'<{type(self).__name__} node_ids="{self.node_ids}" ' \ f'session_id="{self.session_id}" originator="{self.originator}" />' @@ -41,24 +47,26 @@ class FrameGetLimitationStatusConfirmation(FrameBase): PAYLOAD_LEN = 3 - def __init__(self, session_id=None, data=None): + def __init__(self, session_id: Optional[int] = None, data: Optional[int] = None): """Init Frame.""" super().__init__(Command.GW_GET_LIMITATION_STATUS_CFM) self.session_id = session_id self.data = data - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" + assert self.session_id is not None ret = bytes([self.session_id >> 8 & 255, self.session_id & 255]) + assert self.data is not None ret += bytes([self.data]) return ret - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.session_id = payload[0] * 256 + payload[1] self.data = payload[2] - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} session_id="{}" status="{}"/>'.format( type(self).__name__, self.session_id, self.data @@ -70,29 +78,34 @@ class FrameGetLimitationStatusNotification(FrameBase): PAYLOAD_LEN = 10 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_LIMITATION_STATUS_NTF) - self.session_id = None + self.session_id: Optional[int] = None self.node_id = 0 self.parameter_id = 0 - self.min_value = None - self.max_value = None - self.limit_originator = None - self.limit_time = None + self.min_value: Optional[bytes] = None + self.max_value: Optional[bytes] = None + self.limit_originator: Optional[Originator] = None + self.limit_time: Optional[int] = None - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" + assert self.session_id is not None + assert self.min_value is not None + assert self.max_value is not None + assert self.limit_originator is not None + assert self.limit_time is not None payload = bytes([self.session_id >> 8 & 255, self.session_id & 255]) payload += bytes([self.node_id]) payload += bytes([self.parameter_id]) - payload += bytes([self.min_value >> 8 & 255, self.min_value & 255]) - payload += bytes([self.max_value >> 8 & 255, self.max_value & 255]) - payload += bytes([self.limit_originator]) + payload += self.min_value + payload += self.max_value + payload += bytes([self.limit_originator.value]) payload += bytes([self.limit_time]) return payload - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.session_id = payload[0] * 256 + payload[1] self.node_id = payload[2] @@ -102,11 +115,11 @@ def from_payload(self, payload): self.limit_originator = Originator(payload[8]) self.limit_time = payload[9] - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return ( - '<{} node_id="{}" session_id="{}" min_value="{}" ' - 'max_value="{}" originator="{}" limit_time="{}"/>'.format( + '<{} node_id="{}" session_id="{}" min_value="{!r}" ' + 'max_value="{!r}" originator="{}" limit_time="{}"/>'.format( type(self).__name__, self.node_id, self.session_id, self.min_value, self.max_value, self.limit_originator, self.limit_time diff --git a/pyvlx/api/frames/frame_get_local_time.py b/pyvlx/api/frames/frame_get_local_time.py index 8342ea5c..13a27548 100644 --- a/pyvlx/api/frames/frame_get_local_time.py +++ b/pyvlx/api/frames/frame_get_local_time.py @@ -10,7 +10,7 @@ class FrameGetLocalTimeRequest(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_GET_LOCAL_TIME_REQ) @@ -20,19 +20,19 @@ class FrameGetLocalTimeConfirmation(FrameBase): PAYLOAD_LEN = 15 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_GET_LOCAL_TIME_CFM) self.time = DtoLocalTime() - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" return self.time.to_payload() - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.time.from_payload(payload) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{0}>{1}'.format(type(self).__name__, self.time) diff --git a/pyvlx/api/frames/frame_get_network_setup.py b/pyvlx/api/frames/frame_get_network_setup.py index f2743d1b..b272f296 100644 --- a/pyvlx/api/frames/frame_get_network_setup.py +++ b/pyvlx/api/frames/frame_get_network_setup.py @@ -9,7 +9,7 @@ class FrameGetNetworkSetupRequest(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_GET_NETWORK_SETUP_REQ) @@ -19,8 +19,8 @@ class FrameGetNetworkSetupConfirmation(FrameBase): PAYLOAD_LEN = 13 - def __init__(self, ipaddress=bytes(4), netmask=bytes(4), gateway=bytes(4), - dhcp=DHCPParameter.DISABLE): + def __init__(self, ipaddress: bytes = bytes(4), netmask: bytes = bytes(4), gateway: bytes = bytes(4), + dhcp: DHCPParameter = DHCPParameter.DISABLE): """Init Frame.""" super().__init__(Command.GW_GET_NETWORK_SETUP_CFM) self._ipaddress = ipaddress @@ -29,21 +29,21 @@ def __init__(self, ipaddress=bytes(4), netmask=bytes(4), gateway=bytes(4), self.dhcp = dhcp @property - def ipaddress(self): + def ipaddress(self) -> str: """Return ipaddress as human readable string.""" return ".".join(str(c) for c in self._ipaddress) @property - def netmask(self): + def netmask(self) -> str: """Return ipaddress as human readable string.""" return ".".join(str(c) for c in self._netmask) @property - def gateway(self): + def gateway(self) -> str: """Return ipaddress as human readable string.""" return ".".join(str(c) for c in self._gateway) - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" payload = self._ipaddress payload += self._netmask @@ -51,14 +51,14 @@ def get_payload(self): payload += bytes(self.dhcp.value) return payload - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self._ipaddress = payload[0:4] self._netmask = payload[4:8] self._gateway = payload[8:12] self.dhcp = DHCPParameter(payload[12]) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} ipaddress="{}" netmask="{}" gateway="{}" dhcp="{}"/>'.format( type(self).__name__, self.ipaddress, self.netmask, self.gateway, self.dhcp) diff --git a/pyvlx/api/frames/frame_get_node_information.py b/pyvlx/api/frames/frame_get_node_information.py index 8d2597cd..d4138af7 100644 --- a/pyvlx/api/frames/frame_get_node_information.py +++ b/pyvlx/api/frames/frame_get_node_information.py @@ -2,6 +2,7 @@ import struct from datetime import datetime from enum import Enum +from typing import Optional from pyvlx.const import Command, NodeTypeWithSubtype, NodeVariation, Velocity from pyvlx.exception import PyVLXException @@ -17,20 +18,21 @@ class FrameGetNodeInformationRequest(FrameBase): PAYLOAD_LEN = 1 - def __init__(self, node_id=None): + def __init__(self, node_id: Optional[int] = None): """Init Frame.""" super().__init__(Command.GW_GET_NODE_INFORMATION_REQ) self.node_id = node_id - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" + assert self.node_id is not None return bytes([self.node_id]) - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.node_id = payload[0] - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} node_id="{}"/>'.format(type(self).__name__, self.node_id) @@ -49,22 +51,23 @@ class FrameGetNodeInformationConfirmation(FrameBase): PAYLOAD_LEN = 2 - def __init__(self, status=NodeInformationStatus.OK, node_id=None): + def __init__(self, status: NodeInformationStatus = NodeInformationStatus.OK, node_id: Optional[int] = None): """Init Frame.""" super().__init__(Command.GW_GET_NODE_INFORMATION_CFM) self.status = status self.node_id = node_id - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" + assert self.node_id is not None return bytes([self.status.value, self.node_id]) - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.status = NodeInformationStatus(payload[0]) self.node_id = payload[1] - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} node_id="{}" status="{}"/>'.format( type(self).__name__, self.node_id, self.status @@ -76,7 +79,7 @@ class FrameGetNodeInformationNotification(FrameBase): PAYLOAD_LEN = 124 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_GET_NODE_INFORMATION_NTF) self.node_id = 0 @@ -103,14 +106,14 @@ def __init__(self): self.alias_array = AliasArray() @property - def serial_number(self): + def serial_number(self) -> Optional[str]: """Property for serial number in a human readable way.""" if self._serial_number == bytes(8): return None return ":".join("{:02x}".format(c) for c in self._serial_number) @serial_number.setter - def serial_number(self, serial_number): + def serial_number(self, serial_number: Optional[str]) -> None: """Set serial number.""" if serial_number is None: self._serial_number = bytes(8) @@ -121,7 +124,7 @@ def serial_number(self, serial_number): if len(self._serial_number) != 8: raise PyVLXException("could_not_parse_serial_number") - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" payload = bytes() payload += bytes([self.node_id]) @@ -150,7 +153,7 @@ def get_payload(self): payload += bytes(self.alias_array) return payload - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.node_id = payload[0] self.order = payload[1] * 256 + payload[2] @@ -178,11 +181,11 @@ def from_payload(self, payload): self.alias_array = AliasArray(payload[103:125]) @property - def timestamp_formatted(self): + def timestamp_formatted(self) -> str: """Return time as human readable string.""" return datetime.fromtimestamp(self.timestamp).strftime("%Y-%m-%d %H:%M:%S") - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return ( '<{} node_id="{}" order="{}" ' diff --git a/pyvlx/api/frames/frame_get_protocol_version.py b/pyvlx/api/frames/frame_get_protocol_version.py index 0ec9ac1b..6ae73dd8 100644 --- a/pyvlx/api/frames/frame_get_protocol_version.py +++ b/pyvlx/api/frames/frame_get_protocol_version.py @@ -9,7 +9,7 @@ class FrameGetProtocolVersionRequest(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_GET_PROTOCOL_VERSION_REQ) @@ -19,18 +19,18 @@ class FrameGetProtocolVersionConfirmation(FrameBase): PAYLOAD_LEN = 4 - def __init__(self, major_version=0, minor_version=0): + def __init__(self, major_version: int = 0, minor_version: int = 0): """Init Frame.""" super().__init__(Command.GW_GET_PROTOCOL_VERSION_CFM) self.major_version = major_version self.minor_version = minor_version @property - def version(self): + def version(self) -> str: """Return formatted protocol version.""" return "{}.{}".format(self.major_version, self.minor_version) - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" return bytes( [ @@ -41,12 +41,12 @@ def get_payload(self): ] ) - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.major_version = payload[0] * 256 + payload[1] self.minor_version = payload[2] * 256 + payload[3] - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} version="{}"/>'.format( type(self).__name__, self.version diff --git a/pyvlx/api/frames/frame_get_scene_list.py b/pyvlx/api/frames/frame_get_scene_list.py index 1b722b69..5038e53c 100644 --- a/pyvlx/api/frames/frame_get_scene_list.py +++ b/pyvlx/api/frames/frame_get_scene_list.py @@ -1,4 +1,6 @@ """Module for get scene list frame classes.""" +from typing import List, Tuple + from pyvlx.const import Command from pyvlx.exception import PyVLXException from pyvlx.string_helper import bytes_to_string, string_to_bytes @@ -11,7 +13,7 @@ class FrameGetSceneListRequest(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_GET_SCENE_LIST_REQ) @@ -21,20 +23,20 @@ class FrameGetSceneListConfirmation(FrameBase): PAYLOAD_LEN = 1 - def __init__(self, count_scenes=0): + def __init__(self, count_scenes: int = 0): """Init Frame.""" super().__init__(Command.GW_GET_SCENE_LIST_CFM) self.count_scenes = count_scenes - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" return bytes([self.count_scenes]) - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.count_scenes = payload[0] - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} count_scenes="{}"/>'.format( type(self).__name__, self.count_scenes @@ -44,13 +46,13 @@ def __str__(self): class FrameGetSceneListNotification(FrameBase): """Frame for scene list notification.""" - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_GET_SCENE_LIST_NTF) - self.scenes = [] + self.scenes: List[Tuple[int, str]] = [] self.remaining_scenes = 0 - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" ret = bytes([len(self.scenes)]) for number, name in self.scenes: @@ -59,7 +61,7 @@ def get_payload(self): ret += bytes([self.remaining_scenes]) return ret - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" number_of_objects = payload[0] self.remaining_scenes = payload[-1] @@ -73,7 +75,7 @@ def from_payload(self, payload): name = bytes_to_string(scene[1:]) self.scenes.append((number, name)) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} scenes="{}" remaining_scenes="{}">'.format( type(self).__name__, self.scenes, self.remaining_scenes diff --git a/pyvlx/api/frames/frame_get_state.py b/pyvlx/api/frames/frame_get_state.py index c6cf9547..f023f369 100644 --- a/pyvlx/api/frames/frame_get_state.py +++ b/pyvlx/api/frames/frame_get_state.py @@ -9,7 +9,7 @@ class FrameGetStateRequest(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_GET_STATE_REQ) @@ -21,26 +21,26 @@ class FrameGetStateConfirmation(FrameBase): def __init__( self, - gateway_state=GatewayState.TEST_MODE, - gateway_sub_state=GatewaySubState.IDLE, + gateway_state: GatewayState = GatewayState.TEST_MODE, + gateway_sub_state: GatewaySubState = GatewaySubState.IDLE, ): """Init Frame.""" super().__init__(Command.GW_GET_STATE_CFM) self.gateway_state = gateway_state self.gateway_sub_state = gateway_sub_state - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" payload = bytes([self.gateway_state.value, self.gateway_sub_state.value]) payload += bytes(4) # State date, reserved for future use return payload - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.gateway_state = GatewayState(payload[0]) self.gateway_sub_state = GatewaySubState(payload[1]) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} gateway_state="{}" gateway_sub_state="{}"/>'.format( type(self).__name__, self.gateway_state, self.gateway_sub_state diff --git a/pyvlx/api/frames/frame_get_version.py b/pyvlx/api/frames/frame_get_version.py index b5d0b06f..0de34443 100644 --- a/pyvlx/api/frames/frame_get_version.py +++ b/pyvlx/api/frames/frame_get_version.py @@ -1,4 +1,6 @@ """Module for get version frame classes.""" +from typing import Union + from pyvlx.const import Command from .frame import FrameBase @@ -9,7 +11,7 @@ class FrameGetVersionRequest(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_GET_VERSION_REQ) @@ -19,7 +21,7 @@ class FrameGetVersionConfirmation(FrameBase): PAYLOAD_LEN = 9 - def __init__(self, software_version=bytes(6), hardware_version=0): + def __init__(self, software_version: Union[bytes, str] = bytes(6), hardware_version: int = 0): """Init Frame.""" super().__init__(Command.GW_GET_VERSION_CFM) if isinstance(software_version, str): @@ -30,38 +32,38 @@ def __init__(self, software_version=bytes(6), hardware_version=0): self.product_type = 3 @property - def version(self): + def version(self) -> str: """Return formatted version.""" return "{}: Software version: {}, hardware version: {}".format( self.product, self.software_version, self.hardware_version ) @property - def software_version(self): + def software_version(self) -> str: """Return software version as human readable string.""" return ".".join(str(c) for c in self._software_version) @property - def product(self): + def product(self) -> str: """Return product as human readable string.""" if self.product_group == 14 and self.product_type == 3: return "KLF 200" return "Unknown Product: {}:{}".format(self.product_group, self.product_type) - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" ret = self._software_version ret += bytes([self.hardware_version, self.product_group, self.product_type]) return ret - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self._software_version = payload[0:6] self.hardware_version = payload[6] self.product_group = payload[7] self.product_type = payload[8] - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return ( '<{} software_version="{}" hardware_version="{}" product="{}"/>'.format( diff --git a/pyvlx/api/frames/frame_helper.py b/pyvlx/api/frames/frame_helper.py index 7df6dbe0..d80102e0 100644 --- a/pyvlx/api/frames/frame_helper.py +++ b/pyvlx/api/frames/frame_helper.py @@ -1,9 +1,11 @@ """Helper module for SLIP Frames.""" +from typing import Tuple + from pyvlx.const import Command from pyvlx.exception import PyVLXException -def calc_crc(raw): +def calc_crc(raw: bytes) -> int: """Calculate cyclic redundancy check (CRC).""" crc = 0 for sym in raw: @@ -11,7 +13,7 @@ def calc_crc(raw): return crc -def extract_from_frame(data): +def extract_from_frame(data: bytes) -> Tuple[Command, bytes]: """Extract payload and command from frame.""" if len(data) <= 4: raise PyVLXException("could_not_extract_from_frame_too_short", data=data) diff --git a/pyvlx/api/frames/frame_house_status_monitor_disable_cfm.py b/pyvlx/api/frames/frame_house_status_monitor_disable_cfm.py index 3cf5c2b7..1ffa1e7f 100644 --- a/pyvlx/api/frames/frame_house_status_monitor_disable_cfm.py +++ b/pyvlx/api/frames/frame_house_status_monitor_disable_cfm.py @@ -9,6 +9,6 @@ class FrameHouseStatusMonitorDisableConfirmation(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_HOUSE_STATUS_MONITOR_DISABLE_CFM) diff --git a/pyvlx/api/frames/frame_house_status_monitor_disable_req.py b/pyvlx/api/frames/frame_house_status_monitor_disable_req.py index 38052eb6..8f57c354 100644 --- a/pyvlx/api/frames/frame_house_status_monitor_disable_req.py +++ b/pyvlx/api/frames/frame_house_status_monitor_disable_req.py @@ -9,6 +9,6 @@ class FrameHouseStatusMonitorDisableRequest(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_HOUSE_STATUS_MONITOR_DISABLE_REQ) diff --git a/pyvlx/api/frames/frame_house_status_monitor_enable_cfm.py b/pyvlx/api/frames/frame_house_status_monitor_enable_cfm.py index dc5b6d1f..8a6f449e 100644 --- a/pyvlx/api/frames/frame_house_status_monitor_enable_cfm.py +++ b/pyvlx/api/frames/frame_house_status_monitor_enable_cfm.py @@ -9,6 +9,6 @@ class FrameHouseStatusMonitorEnableConfirmation(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_HOUSE_STATUS_MONITOR_ENABLE_CFM) diff --git a/pyvlx/api/frames/frame_house_status_monitor_enable_req.py b/pyvlx/api/frames/frame_house_status_monitor_enable_req.py index 941f42f5..94321d73 100644 --- a/pyvlx/api/frames/frame_house_status_monitor_enable_req.py +++ b/pyvlx/api/frames/frame_house_status_monitor_enable_req.py @@ -9,6 +9,6 @@ class FrameHouseStatusMonitorEnableRequest(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_HOUSE_STATUS_MONITOR_ENABLE_REQ) diff --git a/pyvlx/api/frames/frame_leave_learn_state.py b/pyvlx/api/frames/frame_leave_learn_state.py index 85842a1e..3a343051 100644 --- a/pyvlx/api/frames/frame_leave_learn_state.py +++ b/pyvlx/api/frames/frame_leave_learn_state.py @@ -9,11 +9,11 @@ class FrameLeaveLearnStateRequest(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_LEAVE_LEARN_STATE_REQ) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{}/>'.format(type(self).__name__) @@ -23,19 +23,19 @@ class FrameLeaveLearnStateConfirmation(FrameBase): PAYLOAD_LEN = 1 - def __init__(self, status=0): + def __init__(self, status: int = 0): """Init Frame.""" super().__init__(Command.GW_LEAVE_LEARN_STATE_CFM) self.status = LeaveLearnStateConfirmationStatus(status) - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" return bytes([self.status.value]) - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.status = LeaveLearnStateConfirmationStatus(payload[0]) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} status="{}"/>'.format(type(self).__name__, self.status) diff --git a/pyvlx/api/frames/frame_node_information_changed.py b/pyvlx/api/frames/frame_node_information_changed.py index 48efb436..56b3725d 100644 --- a/pyvlx/api/frames/frame_node_information_changed.py +++ b/pyvlx/api/frames/frame_node_information_changed.py @@ -1,4 +1,6 @@ """Module for requesting change of node name.""" +from typing import Optional + from pyvlx.const import Command, NodeVariation from pyvlx.string_helper import bytes_to_string, string_to_bytes @@ -12,11 +14,11 @@ class FrameNodeInformationChangedNotification(FrameBase): def __init__( self, - node_id=0, - name=None, - order=0, - placement=0, - node_variation=NodeVariation.NOT_SET, + node_id: int = 0, + name: Optional[str] = None, + order: int = 0, + placement: int = 0, + node_variation: NodeVariation = NodeVariation.NOT_SET, ): """Init Frame.""" super().__init__(Command.GW_NODE_INFORMATION_CHANGED_NTF) @@ -26,16 +28,17 @@ def __init__( self.placement = placement self.node_variation = node_variation - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" payload = bytes([self.node_id]) + assert self.name is not None payload += string_to_bytes(self.name, 64) payload += bytes([self.order >> 8 & 255, self.order & 255]) payload += bytes([self.placement]) payload += bytes([self.node_variation.value]) return payload - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.node_id = payload[0] self.name = bytes_to_string(payload[1:65]) @@ -43,7 +46,7 @@ def from_payload(self, payload): self.placement = payload[67] self.node_variation = NodeVariation(payload[68]) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return ( '<{} node_id="{}" name="{}" order="{}" ' diff --git a/pyvlx/api/frames/frame_node_state_position_changed_notification.py b/pyvlx/api/frames/frame_node_state_position_changed_notification.py index 58c2b7b5..d8703e86 100644 --- a/pyvlx/api/frames/frame_node_state_position_changed_notification.py +++ b/pyvlx/api/frames/frame_node_state_position_changed_notification.py @@ -13,7 +13,7 @@ class FrameNodeStatePositionChangedNotification(FrameBase): PAYLOAD_LEN = 20 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_NODE_STATE_POSITION_CHANGED_NTF) self.node_id = 0 @@ -27,7 +27,7 @@ def __init__(self): self.remaining_time = 0 self.timestamp = 0 - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" payload = bytes([self.node_id]) payload += bytes([self.state]) @@ -41,7 +41,7 @@ def get_payload(self): payload += struct.pack(">I", self.timestamp) return payload - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.node_id = payload[0] self.state = payload[1] @@ -57,11 +57,11 @@ def from_payload(self, payload): self.timestamp = struct.unpack(">I", payload[16:20])[0] @property - def timestamp_formatted(self): + def timestamp_formatted(self) -> str: """Return time as human readable string.""" return datetime.fromtimestamp(self.timestamp).strftime("%Y-%m-%d %H:%M:%S") - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return ( '<{} node_id="{}" ' diff --git a/pyvlx/api/frames/frame_password_change.py b/pyvlx/api/frames/frame_password_change.py index 7e40ac8c..7106ac60 100644 --- a/pyvlx/api/frames/frame_password_change.py +++ b/pyvlx/api/frames/frame_password_change.py @@ -1,5 +1,6 @@ """Module for password enter frame classes.""" from enum import Enum +from typing import Optional from pyvlx.const import Command from pyvlx.exception import PyVLXException @@ -14,13 +15,13 @@ class FramePasswordChangeRequest(FrameBase): MAX_SIZE = 32 PAYLOAD_LEN = 64 - def __init__(self, currentpassword=None, newpassword=None): + def __init__(self, currentpassword: Optional[str] = None, newpassword: Optional[str] = None): """Init Frame.""" super().__init__(Command.GW_PASSWORD_CHANGE_REQ) self.currentpassword = currentpassword self.newpassword = newpassword - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" if self.currentpassword is None: raise PyVLXException("currentpassword is none") @@ -34,12 +35,12 @@ def get_payload(self): return string_to_bytes(self.currentpassword, self.MAX_SIZE)+string_to_bytes(self.newpassword, self.MAX_SIZE) - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.currentpassword = bytes_to_string(payload[0:32]) self.newpassword = bytes_to_string(payload[32:]) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" currentpassword_esc = ( None if self.currentpassword is None else "{}****".format(self.currentpassword[:2]) @@ -63,20 +64,20 @@ class FramePasswordChangeConfirmation(FrameBase): PAYLOAD_LEN = 1 - def __init__(self, status=PasswordChangeConfirmationStatus.SUCCESSFUL): + def __init__(self, status: PasswordChangeConfirmationStatus = PasswordChangeConfirmationStatus.SUCCESSFUL): """Init Frame.""" super().__init__(Command.GW_PASSWORD_CHANGE_CFM) self.status = status - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" return bytes([self.status.value]) - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.status = PasswordChangeConfirmationStatus(payload[0]) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} status="{}"/>'.format(type(self).__name__, self.status) @@ -87,12 +88,12 @@ class FramePasswordChangeNotification(FrameBase): MAX_SIZE = 32 PAYLOAD_LEN = 32 - def __init__(self, newpassword=None): + def __init__(self, newpassword: Optional[str] = None): """Init Frame.""" super().__init__(Command.GW_PASSWORD_CHANGE_NTF) self.newpassword = newpassword - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" if self.newpassword is None: raise PyVLXException("newpassword is none") @@ -101,11 +102,11 @@ def get_payload(self): return string_to_bytes(self.newpassword, self.MAX_SIZE) - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.newpassword = bytes_to_string(payload) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" newpassword_esc = ( None if self.newpassword is None else "{}****".format(self.newpassword[:2]) diff --git a/pyvlx/api/frames/frame_password_enter.py b/pyvlx/api/frames/frame_password_enter.py index 3ee1328d..d64e46ee 100644 --- a/pyvlx/api/frames/frame_password_enter.py +++ b/pyvlx/api/frames/frame_password_enter.py @@ -1,5 +1,6 @@ """Module for password enter frame classes.""" from enum import Enum +from typing import Optional from pyvlx.const import Command from pyvlx.exception import PyVLXException @@ -14,12 +15,12 @@ class FramePasswordEnterRequest(FrameBase): MAX_SIZE = 32 PAYLOAD_LEN = 32 - def __init__(self, password=None): + def __init__(self, password: Optional[str] = None): """Init Frame.""" super().__init__(Command.GW_PASSWORD_ENTER_REQ) self.password = password - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" if self.password is None: raise PyVLXException("password is none") @@ -27,11 +28,11 @@ def get_payload(self): raise PyVLXException("password is too long") return string_to_bytes(self.password, self.MAX_SIZE) - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.password = bytes_to_string(payload) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" password_esc = ( None if self.password is None else "{}****".format(self.password[:2]) @@ -51,19 +52,19 @@ class FramePasswordEnterConfirmation(FrameBase): PAYLOAD_LEN = 1 - def __init__(self, status=PasswordEnterConfirmationStatus.SUCCESSFUL): + def __init__(self, status: PasswordEnterConfirmationStatus = PasswordEnterConfirmationStatus.SUCCESSFUL): """Init Frame.""" super().__init__(Command.GW_PASSWORD_ENTER_CFM) self.status = status - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" return bytes([self.status.value]) - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.status = PasswordEnterConfirmationStatus(payload[0]) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} status="{}"/>'.format(type(self).__name__, self.status) diff --git a/pyvlx/api/frames/frame_reboot.py b/pyvlx/api/frames/frame_reboot.py index ad71e41f..d77f2eeb 100644 --- a/pyvlx/api/frames/frame_reboot.py +++ b/pyvlx/api/frames/frame_reboot.py @@ -9,11 +9,11 @@ class FrameGatewayRebootRequest(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_REBOOT_REQ) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{}/>'.format(type(self).__name__) @@ -23,10 +23,10 @@ class FrameGatewayRebootConfirmation(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_REBOOT_CFM) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{}/>'.format(type(self).__name__) diff --git a/pyvlx/api/frames/frame_set_node_name.py b/pyvlx/api/frames/frame_set_node_name.py index f7cb1f64..471b3e09 100644 --- a/pyvlx/api/frames/frame_set_node_name.py +++ b/pyvlx/api/frames/frame_set_node_name.py @@ -1,5 +1,6 @@ """Module for requesting change of node name.""" from enum import Enum +from typing import Optional from pyvlx.const import Command from pyvlx.string_helper import bytes_to_string, string_to_bytes @@ -12,24 +13,25 @@ class FrameSetNodeNameRequest(FrameBase): PAYLOAD_LEN = 65 - def __init__(self, node_id=0, name=None): + def __init__(self, node_id: int = 0, name: Optional[str] = None): """Init Frame.""" super().__init__(Command.GW_SET_NODE_NAME_REQ) self.node_id = node_id self.name = name - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" ret = bytes([self.node_id]) + assert self.name is not None ret += string_to_bytes(self.name, 64) return ret - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.node_id = payload[0] self.name = bytes_to_string(payload[1:65]) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} node_id="{}" name="{}"/>'.format( type(self).__name__, self.node_id, self.name @@ -49,22 +51,22 @@ class FrameSetNodeNameConfirmation(FrameBase): PAYLOAD_LEN = 2 - def __init__(self, status=SetNodeNameConfirmationStatus.OK, node_id=0): + def __init__(self, status: SetNodeNameConfirmationStatus = SetNodeNameConfirmationStatus.OK, node_id: int = 0): """Init Frame.""" super().__init__(Command.GW_SET_NODE_NAME_CFM) self.status = status self.node_id = node_id - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" return bytes([self.status.value, self.node_id]) - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.status = SetNodeNameConfirmationStatus(payload[0]) self.node_id = payload[1] - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} node_id="{}" status="{}"/>'.format( type(self).__name__, self.node_id, self.status diff --git a/pyvlx/api/frames/frame_set_utc.py b/pyvlx/api/frames/frame_set_utc.py index a29d916a..54dc9f21 100644 --- a/pyvlx/api/frames/frame_set_utc.py +++ b/pyvlx/api/frames/frame_set_utc.py @@ -12,7 +12,7 @@ class FrameSetUTCConfirmation(FrameBase): PAYLOAD_LEN = 0 - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_SET_UTC_CFM) @@ -22,24 +22,24 @@ class FrameSetUTCRequest(FrameBase): PAYLOAD_LEN = 4 - def __init__(self, timestamp=0): + def __init__(self, timestamp: float = 0): """Init Frame.""" super().__init__(Command.GW_SET_UTC_REQ) self.timestamp = timestamp @property - def timestamp_formatted(self): + def timestamp_formatted(self) -> str: """Return time as human readable string.""" return datetime.fromtimestamp(self.timestamp).strftime("%Y-%m-%d %H:%M:%S") - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" return struct.pack(">I", self.timestamp) - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.timestamp = struct.unpack(">I", payload[0:4])[0] - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} time="{}"/>'.format(type(self).__name__, self.timestamp_formatted) diff --git a/pyvlx/api/frames/frame_status_request.py b/pyvlx/api/frames/frame_status_request.py index 8d510834..48a8decb 100644 --- a/pyvlx/api/frames/frame_status_request.py +++ b/pyvlx/api/frames/frame_status_request.py @@ -1,5 +1,6 @@ """Module for get node information from gateway.""" from enum import Enum +from typing import Dict, List, Optional from pyvlx.const import ( Command, NodeParameter, RunStatus, StatusReply, StatusType) @@ -14,17 +15,18 @@ class FrameStatusRequestRequest(FrameBase): PAYLOAD_LEN = 26 - def __init__(self, session_id=None, node_ids=None): + def __init__(self, session_id: Optional[int] = None, node_ids: Optional[List[int]] = None): """Init Frame.""" super().__init__(Command.GW_STATUS_REQUEST_REQ) self.session_id = session_id - self.node_ids = node_ids + self.node_ids = node_ids if node_ids is not None else [] self.status_type = StatusType.REQUEST_CURRENT_POSITION self.fpi1 = 254 # Request FP1 to FP7 self.fpi2 = 0 - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" + assert self.session_id is not None ret = bytes([self.session_id >> 8 & 255, self.session_id & 255]) ret += bytes([len(self.node_ids)]) # index array count ret += bytes(self.node_ids) + bytes(20 - len(self.node_ids)) @@ -33,7 +35,7 @@ def get_payload(self): ret += bytes([self.fpi2]) return ret - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.session_id = payload[0] * 256 + payload[1] len_node_ids = payload[2] @@ -47,7 +49,7 @@ def from_payload(self, payload): self.fpi1 = payload[24] self.fpi2 = payload[25] - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return ( '<{} session_id="{}" node_ids="{}" ' @@ -71,24 +73,26 @@ class FrameStatusRequestConfirmation(FrameBase): PAYLOAD_LEN = 3 - def __init__(self, session_id=None, status=None): + def __init__(self, session_id: Optional[int] = None, status: Optional[StatusRequestStatus] = None): """Init Frame.""" super().__init__(Command.GW_STATUS_REQUEST_CFM) self.session_id = session_id self.status = status - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" + assert self.session_id is not None ret = bytes([self.session_id >> 8 & 255, self.session_id & 255]) + assert self.status is not None ret += bytes([self.status.value]) return ret - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.session_id = payload[0] * 256 + payload[1] self.status = StatusRequestStatus(payload[2]) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} session_id="{}" status="{}"/>'.format( type(self).__name__, self.session_id, self.status @@ -101,7 +105,7 @@ class FrameStatusRequestNotification(FrameBase): # PAYLOAD_LEN = 59 # No PAYLOAD_LEN because it is variable depending on StatusType - def __init__(self): + def __init__(self) -> None: """Init Frame.""" super().__init__(Command.GW_STATUS_REQUEST_NTF) self.session_id = 0 @@ -111,14 +115,14 @@ def __init__(self): self.status_reply = StatusReply.UNKNOWN_STATUS_REPLY self.status_type = StatusType.REQUEST_TARGET_POSITION self.status_count = 0 - self.parameter_data = {} + self.parameter_data: Dict[NodeParameter, Parameter] = {} self.target_position = Parameter() self.current_position = Parameter() self.remaining_time = 0 - self.last_master_execution_address = 0 + self.last_master_execution_address = b'' self.last_command_originator = 0 - def get_payload(self): + def get_payload(self) -> bytes: """Return Payload.""" payload = bytes() payload += bytes([self.session_id >> 8 & 255, self.session_id & 255]) @@ -131,24 +135,19 @@ def get_payload(self): payload += bytes(self.target_position.raw) payload += bytes(self.current_position.raw) payload += bytes([self.remaining_time >> 8 & 255, self.remaining_time & 255]) - payload += bytes([ - self.last_master_execution_address >> 16 & 255, - self.last_master_execution_address >> 8 & 255, - self.last_master_execution_address & 255 - ]) - + payload += self.last_master_execution_address payload += bytes([self.last_command_originator]) else: payload += bytes([self.status_count]) keys = self.parameter_data.keys() for key in keys: - payload += bytes([key]) + payload += bytes([key.value]) payload += bytes(self.parameter_data[key].raw) payload += bytes(51 - len(self.parameter_data)) return payload - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Init frame from binary data.""" self.session_id = payload[0] * 256 + payload[1] self.status_id = payload[2] @@ -167,13 +166,13 @@ def from_payload(self, payload): for i in range(8, 8 + self.status_count*3, 3): self.parameter_data.update({NodeParameter(payload[i]): Parameter(payload[i+1:i+3])}) - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" if self.status_type == StatusType.REQUEST_MAIN_INFO: return ( '<{} session_id="{}" status_id="{}" ' 'node_id="{}" run_status="{}" status_reply="{}" status_type="{}" target_position="{}" ' - 'current_position="{}" remaining_time="{}" last_master_execution_address="{}" last_command_originator="{}"/>'.format( + 'current_position="{}" remaining_time="{}" last_master_execution_address="{!r}" last_command_originator="{}"/>'.format( type(self).__name__, self.session_id, self.status_id, diff --git a/pyvlx/api/get_all_nodes_information.py b/pyvlx/api/get_all_nodes_information.py index 782433ff..7e98359e 100644 --- a/pyvlx/api/get_all_nodes_information.py +++ b/pyvlx/api/get_all_nodes_information.py @@ -1,25 +1,30 @@ """Module for retrieving node information from API.""" +from typing import TYPE_CHECKING, List + from pyvlx.log import PYVLXLOG from .api_event import ApiEvent from .frames import ( - FrameGetAllNodesInformationConfirmation, + FrameBase, FrameGetAllNodesInformationConfirmation, FrameGetAllNodesInformationFinishedNotification, FrameGetAllNodesInformationNotification, FrameGetAllNodesInformationRequest) +if TYPE_CHECKING: + from pyvlx import PyVLX + class GetAllNodesInformation(ApiEvent): """Class for retrieving node informationfrom API.""" - def __init__(self, pyvlx): + def __init__(self, pyvlx: "PyVLX"): """Initialize SceneList class.""" super().__init__(pyvlx=pyvlx) self.number_of_nodes = 0 self.success = False - self.notification_frames = [] + self.notification_frames: List[FrameGetAllNodesInformationNotification] = [] - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if isinstance(frame, FrameGetAllNodesInformationConfirmation): self.number_of_nodes = frame.number_of_nodes @@ -36,6 +41,6 @@ async def handle_frame(self, frame): return True return False - def request_frame(self): + def request_frame(self) -> FrameGetAllNodesInformationRequest: """Construct initiating frame.""" return FrameGetAllNodesInformationRequest() diff --git a/pyvlx/api/get_limitation.py b/pyvlx/api/get_limitation.py index 36d25aa8..f1b1f332 100644 --- a/pyvlx/api/get_limitation.py +++ b/pyvlx/api/get_limitation.py @@ -1,41 +1,48 @@ """Module for retrieving limitation value from API.""" -from ..const import LimitationType +from typing import TYPE_CHECKING, Optional + +from ..const import LimitationType, Originator from ..parameter import Position from .api_event import ApiEvent from .frames import ( - FrameGetLimitationStatus, FrameGetLimitationStatusConfirmation, + FrameBase, FrameGetLimitationStatus, FrameGetLimitationStatusConfirmation, FrameGetLimitationStatusNotification) from .session_id import get_new_session_id +if TYPE_CHECKING: + from pyvlx import PyVLX + class GetLimitation(ApiEvent): """Class for retrieving gateway state from API.""" - def __init__(self, pyvlx, node_id, limitation_type=LimitationType.MIN_LIMITATION): + def __init__(self, pyvlx: "PyVLX", node_id: int, limitation_type: LimitationType = LimitationType.MIN_LIMITATION): """Initialize SceneList class.""" super().__init__(pyvlx=pyvlx) self.node_id = node_id self.limitation_type = limitation_type self.success = False - self.notification_frame = None - self.session_id = None - self.min_value_raw = None - self.max_value_raw = None - self.originator = None - self.limit_time = None + self.notification_frame: Optional[FrameGetLimitationStatusNotification] = None + self.session_id: Optional[int] = None + self.min_value_raw: Optional[bytes] = None + self.max_value_raw: Optional[bytes] = None + self.originator: Optional[Originator] = None + self.limit_time: Optional[int] = None @property - def max_value(self): + def max_value(self) -> int: """Return max value.""" + assert self.max_value_raw is not None return Position.to_percent(self.max_value_raw) @property - def min_value(self): + def min_value(self) -> int: """Return min value.""" + assert self.min_value_raw is not None return Position.to_percent(self.min_value_raw) - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if isinstance(frame, FrameGetLimitationStatusConfirmation): return False # Wait for Notification Frame @@ -50,7 +57,7 @@ async def handle_frame(self, frame): return True return False - def request_frame(self): + def request_frame(self) -> FrameGetLimitationStatus: """Construct initiating frame.""" self.session_id = get_new_session_id() return FrameGetLimitationStatus(node_ids=[self.node_id], session_id=self.session_id, diff --git a/pyvlx/api/get_local_time.py b/pyvlx/api/get_local_time.py index 431ee580..3d1197d2 100644 --- a/pyvlx/api/get_local_time.py +++ b/pyvlx/api/get_local_time.py @@ -1,28 +1,34 @@ """Module for local time firmware version from API.""" +from typing import TYPE_CHECKING + from pyvlx.dataobjects import DtoLocalTime from .api_event import ApiEvent -from .frames import FrameGetLocalTimeConfirmation, FrameGetLocalTimeRequest +from .frames import ( + FrameBase, FrameGetLocalTimeConfirmation, FrameGetLocalTimeRequest) + +if TYPE_CHECKING: + from pyvlx import PyVLX class GetLocalTime(ApiEvent): """Class for retrieving firmware version from API.""" - def __init__(self, pyvlx): + def __init__(self, pyvlx: "PyVLX"): """Initialize GetLocalTime class.""" super().__init__(pyvlx=pyvlx) self.success = False self.localtime = DtoLocalTime() - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if not isinstance(frame, FrameGetLocalTimeConfirmation): return False - self.localtime = frame.localtime + self.localtime = frame.time self.success = True return True - def request_frame(self): + def request_frame(self) -> FrameGetLocalTimeRequest: """Construct initiating frame.""" return FrameGetLocalTimeRequest() diff --git a/pyvlx/api/get_network_setup.py b/pyvlx/api/get_network_setup.py index 441a68c6..46fd5f5d 100644 --- a/pyvlx/api/get_network_setup.py +++ b/pyvlx/api/get_network_setup.py @@ -1,21 +1,26 @@ """Module for retrieving gateway state from API.""" +from typing import TYPE_CHECKING + from pyvlx.dataobjects import DtoNetworkSetup from .api_event import ApiEvent from .frames import ( - FrameGetNetworkSetupConfirmation, FrameGetNetworkSetupRequest) + FrameBase, FrameGetNetworkSetupConfirmation, FrameGetNetworkSetupRequest) + +if TYPE_CHECKING: + from pyvlx import PyVLX class GetNetworkSetup(ApiEvent): """Class for retrieving gateway state from API.""" - def __init__(self, pyvlx): + def __init__(self, pyvlx: "PyVLX"): """Initialize GetNetworkSetup class.""" super().__init__(pyvlx=pyvlx) self.success = False self.networksetup = DtoNetworkSetup() - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if not isinstance(frame, FrameGetNetworkSetupConfirmation): return False @@ -24,6 +29,6 @@ async def handle_frame(self, frame): frame.ipaddress, frame.gateway, frame.netmask, frame.dhcp) return True - def request_frame(self): + def request_frame(self) -> FrameGetNetworkSetupRequest: """Construct initiating frame.""" return FrameGetNetworkSetupRequest() diff --git a/pyvlx/api/get_node_information.py b/pyvlx/api/get_node_information.py index e7fcce7d..74aebb6d 100644 --- a/pyvlx/api/get_node_information.py +++ b/pyvlx/api/get_node_information.py @@ -1,21 +1,26 @@ """Module for retrieving node information from API.""" +from typing import TYPE_CHECKING, Optional + from .api_event import ApiEvent from .frames import ( - FrameGetNodeInformationConfirmation, FrameGetNodeInformationNotification, - FrameGetNodeInformationRequest) + FrameBase, FrameGetNodeInformationConfirmation, + FrameGetNodeInformationNotification, FrameGetNodeInformationRequest) + +if TYPE_CHECKING: + from pyvlx import PyVLX class GetNodeInformation(ApiEvent): """Class for retrieving node informationfrom API.""" - def __init__(self, pyvlx, node_id): + def __init__(self, pyvlx: "PyVLX", node_id: int): """Initialize SceneList class.""" super().__init__(pyvlx=pyvlx) self.node_id = node_id self.success = False - self.notification_frame = None + self.notification_frame: Optional[FrameGetNodeInformationNotification] = None - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if ( isinstance(frame, FrameGetNodeInformationConfirmation) @@ -32,6 +37,6 @@ async def handle_frame(self, frame): return True return False - def request_frame(self): + def request_frame(self) -> FrameGetNodeInformationRequest: """Construct initiating frame.""" return FrameGetNodeInformationRequest(node_id=self.node_id) diff --git a/pyvlx/api/get_protocol_version.py b/pyvlx/api/get_protocol_version.py index 7de2ee6f..9e8ea33a 100644 --- a/pyvlx/api/get_protocol_version.py +++ b/pyvlx/api/get_protocol_version.py @@ -1,22 +1,28 @@ """Module for retrieving protocol version from API.""" +from typing import TYPE_CHECKING + from pyvlx.dataobjects import DtoProtocolVersion from .api_event import ApiEvent from .frames import ( - FrameGetProtocolVersionConfirmation, FrameGetProtocolVersionRequest) + FrameBase, FrameGetProtocolVersionConfirmation, + FrameGetProtocolVersionRequest) + +if TYPE_CHECKING: + from pyvlx import PyVLX class GetProtocolVersion(ApiEvent): """Class for retrieving protocol version from API.""" - def __init__(self, pyvlx): + def __init__(self, pyvlx: "PyVLX"): """Initialize GetProtocolVersion class.""" super().__init__(pyvlx=pyvlx) self.success = False self.protocolversion = DtoProtocolVersion() - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if not isinstance(frame, FrameGetProtocolVersionConfirmation): return False @@ -24,11 +30,11 @@ async def handle_frame(self, frame): self.success = True return True - def request_frame(self): + def request_frame(self) -> FrameGetProtocolVersionRequest: """Construct initiating frame.""" return FrameGetProtocolVersionRequest() @property - def version(self): + def version(self) -> str: """Return Protocol Version as human readable string.""" return "{}.{}".format(self.protocolversion.majorversion, self.protocolversion.minorversion) diff --git a/pyvlx/api/get_scene_list.py b/pyvlx/api/get_scene_list.py index afc76794..9d7351e5 100644 --- a/pyvlx/api/get_scene_list.py +++ b/pyvlx/api/get_scene_list.py @@ -1,23 +1,28 @@ """Module for retrieving scene list from API.""" +from typing import TYPE_CHECKING, List, Optional, Tuple + from pyvlx.log import PYVLXLOG from .api_event import ApiEvent from .frames import ( - FrameGetSceneListConfirmation, FrameGetSceneListNotification, + FrameBase, FrameGetSceneListConfirmation, FrameGetSceneListNotification, FrameGetSceneListRequest) +if TYPE_CHECKING: + from pyvlx import PyVLX + class GetSceneList(ApiEvent): """Class for retrieving scene list from API.""" - def __init__(self, pyvlx): + def __init__(self, pyvlx: "PyVLX"): """Initialize SceneList class.""" super().__init__(pyvlx=pyvlx) self.success = False - self.count_scenes = None - self.scenes = [] + self.count_scenes: Optional[int] = None + self.scenes: List[Tuple[int, str]] = [] - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if isinstance(frame, FrameGetSceneListConfirmation): self.count_scenes = frame.count_scenes @@ -39,6 +44,6 @@ async def handle_frame(self, frame): return True return False - def request_frame(self): + def request_frame(self) -> FrameGetSceneListRequest: """Construct initiating frame.""" return FrameGetSceneListRequest() diff --git a/pyvlx/api/get_state.py b/pyvlx/api/get_state.py index e0eb5f05..a5b0d77f 100644 --- a/pyvlx/api/get_state.py +++ b/pyvlx/api/get_state.py @@ -1,20 +1,26 @@ """Module for retrieving gateway state from API.""" +from typing import TYPE_CHECKING, Optional + +from pyvlx.const import GatewayState, GatewaySubState from pyvlx.dataobjects import DtoState from .api_event import ApiEvent -from .frames import FrameGetStateConfirmation, FrameGetStateRequest +from .frames import FrameBase, FrameGetStateConfirmation, FrameGetStateRequest + +if TYPE_CHECKING: + from pyvlx import PyVLX class GetState(ApiEvent): """Class for retrieving gateway state from API.""" - def __init__(self, pyvlx): + def __init__(self, pyvlx: "PyVLX"): """Initialize GetState class.""" super().__init__(pyvlx=pyvlx) self.success = False self.state = DtoState() - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if not isinstance(frame, FrameGetStateConfirmation): return False @@ -22,16 +28,16 @@ async def handle_frame(self, frame): self.state = DtoState(frame.gateway_state, frame.gateway_sub_state) return True - def request_frame(self): + def request_frame(self) -> FrameGetStateRequest: """Construct initiating frame.""" return FrameGetStateRequest() @property - def gateway_state(self): + def gateway_state(self) -> Optional[GatewayState]: """Return Gateway State as human readable string. Deprecated.""" return self.state.gateway_state @property - def gateway_sub_state(self): + def gateway_sub_state(self) -> Optional[GatewaySubState]: """Return Gateway Sub State as human readable string. Deprecated.""" return self.state.gateway_sub_state diff --git a/pyvlx/api/get_version.py b/pyvlx/api/get_version.py index e3afbc85..2807640c 100644 --- a/pyvlx/api/get_version.py +++ b/pyvlx/api/get_version.py @@ -1,20 +1,26 @@ """Module for retrieving firmware version from API.""" +from typing import TYPE_CHECKING + from pyvlx.dataobjects import DtoVersion from .api_event import ApiEvent -from .frames import FrameGetVersionConfirmation, FrameGetVersionRequest +from .frames import ( + FrameBase, FrameGetVersionConfirmation, FrameGetVersionRequest) + +if TYPE_CHECKING: + from pyvlx import PyVLX class GetVersion(ApiEvent): """Class for retrieving firmware version from API.""" - def __init__(self, pyvlx): + def __init__(self, pyvlx: "PyVLX"): """Initialize GetVersion class.""" super().__init__(pyvlx=pyvlx) self.success = False self.version = DtoVersion() - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if not isinstance(frame, FrameGetVersionConfirmation): return False @@ -23,6 +29,6 @@ async def handle_frame(self, frame): self.success = True return True - def request_frame(self): + def request_frame(self) -> FrameGetVersionRequest: """Construct initiating frame.""" return FrameGetVersionRequest() diff --git a/pyvlx/api/house_status_monitor.py b/pyvlx/api/house_status_monitor.py index 9b7ce746..2f4b60c1 100644 --- a/pyvlx/api/house_status_monitor.py +++ b/pyvlx/api/house_status_monitor.py @@ -1,30 +1,35 @@ """Module for house status monitor.""" +from typing import TYPE_CHECKING + from pyvlx.exception import PyVLXException from .api_event import ApiEvent from .frames import ( - FrameHouseStatusMonitorDisableConfirmation, + FrameBase, FrameHouseStatusMonitorDisableConfirmation, FrameHouseStatusMonitorDisableRequest, FrameHouseStatusMonitorEnableConfirmation, FrameHouseStatusMonitorEnableRequest) +if TYPE_CHECKING: + from pyvlx import PyVLX + class HouseStatusMonitorEnable(ApiEvent): """Class for enabling house status monotor.""" - def __init__(self, pyvlx): + def __init__(self, pyvlx: "PyVLX"): """Initialize HouseStatusMonitorEnable class.""" super().__init__(pyvlx=pyvlx) self.success = False - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if not isinstance(frame, FrameHouseStatusMonitorEnableConfirmation): return False self.success = True return True - def request_frame(self): + def request_frame(self) -> FrameHouseStatusMonitorEnableRequest: """Construct initiating frame.""" return FrameHouseStatusMonitorEnableRequest() @@ -32,24 +37,24 @@ def request_frame(self): class HouseStatusMonitorDisable(ApiEvent): """Class for disabling house status monotor.""" - def __init__(self, pyvlx): + def __init__(self, pyvlx: "PyVLX"): """Initialize HouseStatusMonitorEnable class.""" super().__init__(pyvlx=pyvlx) self.success = False - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if not isinstance(frame, FrameHouseStatusMonitorDisableConfirmation): return False self.success = True return True - def request_frame(self): + def request_frame(self) -> FrameHouseStatusMonitorDisableRequest: """Construct initiating frame.""" return FrameHouseStatusMonitorDisableRequest() -async def house_status_monitor_enable(pyvlx): +async def house_status_monitor_enable(pyvlx: "PyVLX") -> None: """Enable house status monitor.""" status_monitor_enable = HouseStatusMonitorEnable(pyvlx=pyvlx) await status_monitor_enable.do_api_call() @@ -57,7 +62,7 @@ async def house_status_monitor_enable(pyvlx): raise PyVLXException("Unable enable house status monitor.") -async def house_status_monitor_disable(pyvlx): +async def house_status_monitor_disable(pyvlx: "PyVLX") -> None: """Disable house status monitor.""" status_monitor_disable = HouseStatusMonitorDisable(pyvlx=pyvlx) await status_monitor_disable.do_api_call() diff --git a/pyvlx/api/leave_learn_state.py b/pyvlx/api/leave_learn_state.py index dbbe1ce5..baa91fc6 100644 --- a/pyvlx/api/leave_learn_state.py +++ b/pyvlx/api/leave_learn_state.py @@ -1,28 +1,33 @@ """Module for handling the login to API.""" +from typing import TYPE_CHECKING + from pyvlx.dataobjects import DtoLeaveLearnState from .api_event import ApiEvent from .frames import ( - FrameLeaveLearnStateConfirmation, FrameLeaveLearnStateRequest) + FrameBase, FrameLeaveLearnStateConfirmation, FrameLeaveLearnStateRequest) + +if TYPE_CHECKING: + from pyvlx import PyVLX class LeaveLearnState(ApiEvent): """Class for handling leave learn state to API.""" - def __init__(self, pyvlx): + def __init__(self, pyvlx: "PyVLX"): """Initialize leave learn state class.""" super().__init__(pyvlx=pyvlx) self.status = DtoLeaveLearnState() self.success = False - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if not isinstance(frame, FrameLeaveLearnStateConfirmation): return False - self.status = frame.status + self.status.status = frame.status self.success = True return True - def request_frame(self): + def request_frame(self) -> FrameLeaveLearnStateRequest: """Construct initiating frame.""" return FrameLeaveLearnStateRequest() diff --git a/pyvlx/api/password_enter.py b/pyvlx/api/password_enter.py index 477d3104..283e62e3 100644 --- a/pyvlx/api/password_enter.py +++ b/pyvlx/api/password_enter.py @@ -1,22 +1,27 @@ """Module for handling the login to API.""" +from typing import TYPE_CHECKING + from pyvlx.log import PYVLXLOG from .api_event import ApiEvent from .frames import ( - FramePasswordEnterConfirmation, FramePasswordEnterRequest, + FrameBase, FramePasswordEnterConfirmation, FramePasswordEnterRequest, PasswordEnterConfirmationStatus) +if TYPE_CHECKING: + from pyvlx import PyVLX + class PasswordEnter(ApiEvent): """Class for handling login to API.""" - def __init__(self, pyvlx, password): + def __init__(self, pyvlx: "PyVLX", password: str): """Initialize login class.""" super().__init__(pyvlx=pyvlx) self.password = password self.success = False - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if not isinstance(frame, FramePasswordEnterConfirmation): return False @@ -29,6 +34,6 @@ async def handle_frame(self, frame): self.success = True return True - def request_frame(self): + def request_frame(self) -> FramePasswordEnterRequest: """Construct initiating frame.""" return FramePasswordEnterRequest(password=self.password) diff --git a/pyvlx/api/reboot.py b/pyvlx/api/reboot.py index f79d63f8..4ea885ff 100644 --- a/pyvlx/api/reboot.py +++ b/pyvlx/api/reboot.py @@ -1,20 +1,26 @@ """Module for handling the Reboot to API.""" +from typing import TYPE_CHECKING + from pyvlx.log import PYVLXLOG from .api_event import ApiEvent -from .frames import FrameGatewayRebootConfirmation, FrameGatewayRebootRequest +from .frames import ( + FrameBase, FrameGatewayRebootConfirmation, FrameGatewayRebootRequest) + +if TYPE_CHECKING: + from pyvlx import PyVLX class Reboot(ApiEvent): """Class for handling Reboot to API.""" - def __init__(self, pyvlx): + def __init__(self, pyvlx: "PyVLX"): """Initialize Reboot class.""" super().__init__(pyvlx=pyvlx) self.pyvlx = pyvlx self.success = False - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if isinstance(frame, FrameGatewayRebootConfirmation): PYVLXLOG.warning("KLF200 is rebooting") @@ -22,6 +28,6 @@ async def handle_frame(self, frame): return True return False - def request_frame(self): + def request_frame(self) -> FrameGatewayRebootRequest: """Construct initiating frame.""" return FrameGatewayRebootRequest() diff --git a/pyvlx/api/session_id.py b/pyvlx/api/session_id.py index c2b6eae0..79ed7bd7 100644 --- a/pyvlx/api/session_id.py +++ b/pyvlx/api/session_id.py @@ -5,7 +5,7 @@ MAX_SESSION_ID = 65535 -def get_new_session_id(): +def get_new_session_id() -> int: """Generate new session_id.""" global LAST_SESSION_ID # pylint: disable=global-statement LAST_SESSION_ID = LAST_SESSION_ID + 1 @@ -14,7 +14,7 @@ def get_new_session_id(): return LAST_SESSION_ID -def set_session_id(session_id): +def set_session_id(session_id: int) -> None: """Set session id to value.""" global LAST_SESSION_ID # pylint: disable=global-statement LAST_SESSION_ID = session_id diff --git a/pyvlx/api/set_node_name.py b/pyvlx/api/set_node_name.py index e8bfdc88..f47edb96 100644 --- a/pyvlx/api/set_node_name.py +++ b/pyvlx/api/set_node_name.py @@ -1,27 +1,32 @@ """Module for changing a node name.""" +from typing import TYPE_CHECKING + from .api_event import ApiEvent from .frames import ( - FrameSetNodeNameConfirmation, FrameSetNodeNameRequest, + FrameBase, FrameSetNodeNameConfirmation, FrameSetNodeNameRequest, SetNodeNameConfirmationStatus) +if TYPE_CHECKING: + from pyvlx import PyVLX + class SetNodeName(ApiEvent): """Class for changing the name of a node via API.""" - def __init__(self, pyvlx, node_id, name): + def __init__(self, pyvlx: "PyVLX", node_id: int, name: str): """Initialize class.""" super().__init__(pyvlx=pyvlx) self.node_id = node_id self.name = name self.success = False - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if not isinstance(frame, FrameSetNodeNameConfirmation): return False self.success = frame.status == SetNodeNameConfirmationStatus.OK return True - def request_frame(self): + def request_frame(self) -> FrameSetNodeNameRequest: """Construct initiating frame.""" return FrameSetNodeNameRequest(node_id=self.node_id, name=self.name) diff --git a/pyvlx/api/set_utc.py b/pyvlx/api/set_utc.py index d5018459..3bd27ff3 100644 --- a/pyvlx/api/set_utc.py +++ b/pyvlx/api/set_utc.py @@ -1,27 +1,31 @@ """Module for setting UTC time within gateway.""" import time +from typing import TYPE_CHECKING from .api_event import ApiEvent -from .frames import FrameSetUTCConfirmation, FrameSetUTCRequest +from .frames import FrameBase, FrameSetUTCConfirmation, FrameSetUTCRequest + +if TYPE_CHECKING: + from pyvlx import PyVLX class SetUTC(ApiEvent): """Class for setting UTC time within gateway.""" - def __init__(self, pyvlx, timestamp=time.time()): + def __init__(self, pyvlx: "PyVLX", timestamp: float = time.time()): """Initialize SetUTC class.""" super().__init__(pyvlx=pyvlx) self.timestamp = timestamp self.success = False - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if not isinstance(frame, FrameSetUTCConfirmation): return False self.success = True return True - def request_frame(self): + def request_frame(self) -> FrameSetUTCRequest: """Construct initiating frame.""" timestamp = int(self.timestamp) return FrameSetUTCRequest(timestamp=timestamp) diff --git a/pyvlx/api/status_request.py b/pyvlx/api/status_request.py index 66d59a72..7b7f2f73 100644 --- a/pyvlx/api/status_request.py +++ b/pyvlx/api/status_request.py @@ -1,23 +1,28 @@ """Module for retrieving node information from API.""" +from typing import TYPE_CHECKING, Optional + from .api_event import ApiEvent from .frames import ( - FrameStatusRequestConfirmation, FrameStatusRequestNotification, + FrameBase, FrameStatusRequestConfirmation, FrameStatusRequestNotification, FrameStatusRequestRequest) from .session_id import get_new_session_id +if TYPE_CHECKING: + from pyvlx import PyVLX + class StatusRequest(ApiEvent): """Class for retrieving node informationfrom API.""" - def __init__(self, pyvlx, node_id): + def __init__(self, pyvlx: "PyVLX", node_id: int): """Initialize SceneList class.""" super().__init__(pyvlx=pyvlx) self.node_id = node_id self.success = False - self.notification_frame = None - self.session_id = None + self.notification_frame: Optional[FrameStatusRequestNotification] = None + self.session_id: Optional[int] = None - async def handle_frame(self, frame): + async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming API frame, return True if this was the expected frame.""" if ( isinstance(frame, FrameStatusRequestConfirmation) @@ -34,7 +39,7 @@ async def handle_frame(self, frame): return True return False - def request_frame(self): + def request_frame(self) -> FrameStatusRequestRequest: """Construct initiating frame.""" self.session_id = get_new_session_id() return FrameStatusRequestRequest( diff --git a/pyvlx/config.py b/pyvlx/config.py index e143c051..e694177c 100644 --- a/pyvlx/config.py +++ b/pyvlx/config.py @@ -1,16 +1,26 @@ """Module for configuration.""" +from typing import TYPE_CHECKING, Any, Optional, cast + import yaml from .exception import PyVLXException from .log import PYVLXLOG +if TYPE_CHECKING: + from pyvlx import PyVLX + class Config: """Object for configuration.""" DEFAULT_PORT = 51200 - def __init__(self, pyvlx, path=None, host=None, password=None, port=None): + def __init__(self, + pyvlx: "PyVLX", + path: Optional[str] = None, + host: Optional[str] = None, + password: Optional[str] = None, + port: Optional[int] = None): """Initialize Config class.""" self.pyvlx = pyvlx self.host = host @@ -19,22 +29,22 @@ def __init__(self, pyvlx, path=None, host=None, password=None, port=None): if path is not None: self.read_config(path) - def read_config(self, path): + def read_config(self, path: str) -> None: """Read configuration file.""" PYVLXLOG.info("Reading config file: %s", path) try: with open(path, "r", encoding="utf-8") as filehandle: doc = yaml.safe_load(filehandle) self.test_configuration(doc, path) - self.host = doc["config"]["host"] - self.password = doc["config"]["password"] + self.host = cast(str, doc["config"]["host"]) + self.password = cast(str, doc["config"]["password"]) if "port" in doc["config"]: self.port = doc["config"]["port"] except FileNotFoundError as not_found: raise PyVLXException("file does not exist: {0}".format(not_found)) from not_found @staticmethod - def test_configuration(doc, path): + def test_configuration(doc: Any, path: str) -> None: """Test if configuration file is sane.""" if "config" not in doc: raise PyVLXException("no element config found in: {0}".format(path)) diff --git a/pyvlx/connection.py b/pyvlx/connection.py index 626efe5e..ca45bc1b 100644 --- a/pyvlx/connection.py +++ b/pyvlx/connection.py @@ -1,9 +1,11 @@ """Module for handling the TCP connection with Gateway.""" import asyncio import ssl +from typing import Callable, Coroutine, List, Optional from .api.frame_creation import frame_from_raw from .api.frames import FrameBase +from .config import Config from .exception import PyVLXException from .log import PYVLXLOG from .slip import get_next_slip, is_slip, slip_pack @@ -12,21 +14,21 @@ class SlipTokenizer: """Helper class for splitting up binary stream to slip packets.""" - def __init__(self): + def __init__(self) -> None: """Init Tokenizer.""" self.data = bytes() - def feed(self, chunk): + def feed(self, chunk: bytes) -> None: """Feed chunk to tokenizer.""" if not chunk: return self.data += chunk - def has_tokens(self): + def has_tokens(self) -> bool: """Return True if Tokenizer has tokens.""" return is_slip(self.data) - def get_next_token(self): + def get_next_token(self) -> Optional[bytes]: """Get next token from Tokenizer.""" slip, self.data = get_next_slip(self.data) return slip @@ -35,54 +37,59 @@ def get_next_token(self): class TCPTransport(asyncio.Protocol): """Class for handling asyncio connection transport.""" - def __init__(self, frame_received_cb, connection_closed_cb): + def __init__(self, frame_received_cb: Callable[[FrameBase], None], connection_closed_cb: Callable[[], None]): """Init TCPTransport.""" self.frame_received_cb = frame_received_cb self.connection_closed_cb = connection_closed_cb self.tokenizer = SlipTokenizer() - def connection_made(self, transport): + def connection_made(self, transport: object) -> None: """Handle sucessful connection.""" - def data_received(self, data): + def data_received(self, data: bytes) -> None: """Handle data received.""" self.tokenizer.feed(data) while self.tokenizer.has_tokens(): raw = self.tokenizer.get_next_token() + assert raw is not None frame = frame_from_raw(raw) if frame is not None: self.frame_received_cb(frame) - def connection_lost(self, exc): + def connection_lost(self, exc: object) -> None: """Handle lost connection.""" self.connection_closed_cb() +CallbackType = Callable[[FrameBase], Coroutine] + + class Connection: """Class for handling TCP connection.""" - def __init__(self, loop, config): + def __init__(self, loop: asyncio.AbstractEventLoop, config: Config): """Init TCP connection.""" self.loop = loop self.config = config - self.transport = None - self.frame_received_cbs = [] + self.transport: Optional[asyncio.Transport] = None + self.frame_received_cbs: List[CallbackType] = [] self.connected = False self.connection_counter = 0 - def __del__(self): + def __del__(self) -> None: """Destruct connection.""" self.disconnect() - def disconnect(self): + def disconnect(self) -> None: """Disconnect connection.""" if self.transport is not None: self.transport.close() self.transport = None - async def connect(self): + async def connect(self) -> None: """Connect to gateway via SSL.""" tcp_client = TCPTransport(self.frame_received_cb, self.connection_closed_cb) + assert self.config.host is not None self.transport, _ = await self.loop.create_connection( lambda: tcp_client, host=self.config.host, @@ -92,36 +99,37 @@ async def connect(self): self.connected = True self.connection_counter += 1 - def register_frame_received_cb(self, callback): + def register_frame_received_cb(self, callback: CallbackType) -> None: """Register frame received callback.""" self.frame_received_cbs.append(callback) - def unregister_frame_received_cb(self, callback): + def unregister_frame_received_cb(self, callback: CallbackType) -> None: """Unregister frame received callback.""" self.frame_received_cbs.remove(callback) - def write(self, frame): + def write(self, frame: FrameBase) -> None: """Write frame to Bus.""" if not isinstance(frame, FrameBase): raise PyVLXException("Frame not of type FrameBase", frame_type=type(frame)) PYVLXLOG.debug("SEND: %s", frame) + assert self.transport is not None self.transport.write(slip_pack(bytes(frame))) @staticmethod - def create_ssl_context(): + def create_ssl_context() -> ssl.SSLContext: """Create and return SSL Context.""" ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE return ssl_context - def frame_received_cb(self, frame): + def frame_received_cb(self, frame: FrameBase) -> None: """Received message.""" PYVLXLOG.debug("REC: %s", frame) for frame_received_cb in self.frame_received_cbs: # pylint: disable=not-callable self.loop.create_task(frame_received_cb(frame)) - def connection_closed_cb(self): + def connection_closed_cb(self) -> None: """Server closed connection.""" self.connected = False diff --git a/pyvlx/dataobjects.py b/pyvlx/dataobjects.py index eaac7fd2..bd8ce892 100644 --- a/pyvlx/dataobjects.py +++ b/pyvlx/dataobjects.py @@ -1,12 +1,17 @@ """Module for Dataobjects.""" import time from datetime import datetime +from typing import Optional + +from .const import ( + DHCPParameter, GatewayState, GatewaySubState, + LeaveLearnStateConfirmationStatus) class DtoLocalTime: """Dataobject to hold KLF200 Time Data.""" - def __init__(self, utctime=None, localtime=None): + def __init__(self, utctime: Optional[datetime] = None, localtime: Optional[datetime] = None): """Initialize DtoLocalTime class.""" if utctime is None: utctime = datetime.fromtimestamp(0) @@ -15,14 +20,14 @@ def __init__(self, utctime=None, localtime=None): self.utctime = utctime self.localtime = localtime - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return ( '<{} utctime="{}" localtime="{}"/>'.format( type(self).__name__, self.utctime, self.localtime) ) - def from_payload(self, payload): + def from_payload(self, payload: bytes) -> None: """Build the Dto From Data.""" self.utctime = datetime.fromtimestamp(int.from_bytes(payload[0:4], "big")) weekday = payload[11] - 1 @@ -40,7 +45,7 @@ def from_payload(self, payload): int.from_bytes(payload[12:14], byteorder='big'), # day of year int.from_bytes(payload[14:15], byteorder='big', signed=True)))) - def to_payload(self): + def to_payload(self) -> bytes: """Build the Dto From Data.""" payload = b'' payload = int(self.utctime.timestamp()).to_bytes(4, byteorder='big') @@ -62,14 +67,18 @@ def to_payload(self): class DtoNetworkSetup: """Dataobject to hold KLF200 Network Setup.""" - def __init__(self, ipaddress=None, gateway=None, netmask=None, dhcp=None): + def __init__(self, + ipaddress: Optional[str] = None, + gateway: Optional[str] = None, + netmask: Optional[str] = None, + dhcp: Optional[DHCPParameter] = None): """Initialize DtoNetworkSetup class.""" self.ipaddress = ipaddress self.gateway = gateway self.netmask = netmask self.dhcp = dhcp - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return '<{} ipaddress="{}" gateway="{}" gateway="{}" dhcp="{}"/>'.format( type(self).__name__, self.ipaddress, self.gateway, @@ -80,12 +89,12 @@ def __str__(self): class DtoProtocolVersion: """KLF 200 Dataobject for Protocol version.""" - def __init__(self, majorversion=None, minorversion=None): + def __init__(self, majorversion: Optional[int] = None, minorversion: Optional[int] = None): """Initialize DtoProtocolVersion class.""" self.majorversion = majorversion self.minorversion = minorversion - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return ( '<{} majorversion="{}" minorversion="{}"/>'.format( @@ -97,12 +106,12 @@ def __str__(self): class DtoState: """Data Object for Gateway State.""" - def __init__(self, gateway_state=None, gateway_sub_state=None): + def __init__(self, gateway_state: Optional[GatewayState] = None, gateway_sub_state: Optional[GatewaySubState] = None): """Initialize DtoState class.""" self.gateway_state = gateway_state self.gateway_sub_state = gateway_sub_state - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return ( '<{} gateway_state="{}" gateway_sub_state="{}"/>'.format( @@ -115,14 +124,17 @@ class DtoVersion: """Object for KLF200 Version Information.""" def __init__(self, - softwareversion=None, hardwareversion=None, productgroup=None, producttype=None): + softwareversion: Optional[str] = None, + hardwareversion: Optional[int] = None, + productgroup: Optional[int] = None, + producttype: Optional[int] = None): """Initialize DtoVersion class.""" self.softwareversion = softwareversion self.hardwareversion = hardwareversion self.productgroup = productgroup self.producttype = producttype - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return ( '<{} softwareversion="{}" hardwareversion="{}" ' @@ -136,11 +148,11 @@ def __str__(self): class DtoLeaveLearnState: """Dataobject to hold KLF200 Data.""" - def __init__(self, status=None): + def __init__(self, status: Optional[LeaveLearnStateConfirmationStatus] = None): """Initialize DtoLeaveLearnState class.""" self.status = status - def __str__(self): + def __str__(self) -> str: """Return human readable string.""" return ( '<{} status="{}"/>'.format( diff --git a/pyvlx/exception.py b/pyvlx/exception.py index cb70e6f2..f0ea4685 100644 --- a/pyvlx/exception.py +++ b/pyvlx/exception.py @@ -1,16 +1,17 @@ """Module for PyVLX Exceptions.""" +from typing import Any class PyVLXException(Exception): """Default PyVLX Exception.""" - def __init__(self, description, **kwargs): + def __init__(self, description: str, **kwargs: Any): """Initialize PyVLXException class.""" super().__init__(description) self.description = description self.parameter = kwargs - def _format_parameter(self): + def _format_parameter(self) -> str: return " ".join( [ '%s="%s"' % (key, value) @@ -18,7 +19,7 @@ def _format_parameter(self): ] ) - def __str__(self): + def __str__(self) -> str: """Return object as readable string.""" return '<{} description="{}" {}/>'.format( type(self).__name__, self.description, self._format_parameter() diff --git a/pyvlx/heartbeat.py b/pyvlx/heartbeat.py index 4f8b3330..38b694f3 100644 --- a/pyvlx/heartbeat.py +++ b/pyvlx/heartbeat.py @@ -1,43 +1,47 @@ """Module for sending get state requests to API in regular periods.""" import asyncio +from typing import TYPE_CHECKING, Optional from .api import GetState from .api.status_request import StatusRequest from .exception import PyVLXException from .opening_device import Blind +if TYPE_CHECKING: + from pyvlx import PyVLX + class Heartbeat: """Class for sending heartbeats to API.""" - def __init__(self, pyvlx, timeout_in_seconds=60): + def __init__(self, pyvlx: "PyVLX", timeout_in_seconds: int = 60): """Initialize Heartbeat object.""" self.pyvlx = pyvlx self.timeout_in_seconds = timeout_in_seconds self.loop_event = asyncio.Event() self.stopped = False - self.run_task = None - self.timeout_handle = None + self.run_task: Optional[asyncio.Task[None]] = None + self.timeout_handle: Optional[asyncio.TimerHandle] = None self.stopped_event = asyncio.Event() - def __del__(self): + def __del__(self) -> None: """Cleanup heartbeat.""" self.cancel_loop_timeout() - def start(self): + def start(self) -> None: """Create loop task.""" self.stopped = False self.stopped_event.clear() self.run_task = self.pyvlx.loop.create_task(self.loop()) - async def stop(self): + async def stop(self) -> None: """Stop heartbeat.""" self.stopped = True self.loop_event.set() # Waiting for shutdown of loop() await self.stopped_event.wait() - async def loop(self): + async def loop(self) -> None: """Pulse every timeout seconds until stopped.""" while not self.stopped: self.timeout_handle = self.pyvlx.connection.loop.call_later( @@ -53,17 +57,17 @@ async def loop(self): self.cancel_loop_timeout() self.stopped_event.set() - def loop_timeout(self): + def loop_timeout(self) -> None: """Handle loop timeout.""" self.loop_event.set() - def cancel_loop_timeout(self): + def cancel_loop_timeout(self) -> None: """Cancel loop timeout.""" if self.timeout_handle is not None: self.timeout_handle.cancel() self.timeout_handle = None - async def pulse(self): + async def pulse(self) -> None: """Send get state request to API to keep the connection alive.""" get_state = GetState(pyvlx=self.pyvlx) await get_state.do_api_call() diff --git a/pyvlx/klf200gateway.py b/pyvlx/klf200gateway.py index ac9026d9..2dedb239 100644 --- a/pyvlx/klf200gateway.py +++ b/pyvlx/klf200gateway.py @@ -1,40 +1,49 @@ """Module for basic klf200 gateway functions.""" +from typing import TYPE_CHECKING, Awaitable, Callable, List, Optional + from .api import ( FactoryDefault, GetLocalTime, GetNetworkSetup, GetProtocolVersion, GetState, GetVersion, LeaveLearnState, PasswordEnter, Reboot, SetUTC) +from .dataobjects import ( + DtoLocalTime, DtoNetworkSetup, DtoProtocolVersion, DtoState, DtoVersion) from .exception import PyVLXException +if TYPE_CHECKING: + from pyvlx import PyVLX + +CallbackType = Callable[["Klf200Gateway"], Awaitable[None]] + class Klf200Gateway: """Class for node abstraction.""" - def __init__(self, pyvlx): + def __init__(self, pyvlx: "PyVLX"): """Initialize Node object.""" self.pyvlx = pyvlx - self.state = None - self.network_setup = None - self.password = None - self.time = None - self.protocol_version = None - self.version = None - self.device_updated_cbs = [] - - def register_device_updated_cb(self, device_updated_cb): + self.state: Optional[DtoState] = None + self.network_setup: Optional[DtoNetworkSetup] = None + self.password: Optional[str] = None + self.time: Optional[DtoLocalTime] = None + self.protocol_version: Optional[DtoProtocolVersion] = None + self.version: Optional[DtoVersion] = None + self.device_updated_cbs: List[CallbackType] = [] + + def register_device_updated_cb(self, device_updated_cb: CallbackType) -> None: """Register device updated callback.""" self.device_updated_cbs.append(device_updated_cb) - def unregister_device_updated_cb(self, device_updated_cb): + def unregister_device_updated_cb(self, device_updated_cb: CallbackType) -> None: """Unregister device updated callback.""" self.device_updated_cbs.remove(device_updated_cb) - async def after_update(self): + async def after_update(self) -> None: """Execute callbacks after internal state has been changed.""" for device_updated_cb in self.device_updated_cbs: # pylint: disable=not-callable await device_updated_cb(self) - async def get_state(self): + async def get_state(self) -> bool: """Retrieve state from API.""" get_state = GetState(pyvlx=self.pyvlx) await get_state.do_api_call() @@ -43,7 +52,7 @@ async def get_state(self): self.state = get_state.state return get_state.success - async def get_network_setup(self): + async def get_network_setup(self) -> bool: """Retrieve network setup from API.""" get_network_setup = GetNetworkSetup(pyvlx=self.pyvlx) await get_network_setup.do_api_call() @@ -52,7 +61,7 @@ async def get_network_setup(self): self.network_setup = get_network_setup.networksetup return get_network_setup.success - async def get_version(self): + async def get_version(self) -> bool: """Retrieve version from API.""" get_version = GetVersion(pyvlx=self.pyvlx) await get_version.do_api_call() @@ -61,7 +70,7 @@ async def get_version(self): self.version = get_version.version return get_version.success - async def get_protocol_version(self): + async def get_protocol_version(self) -> bool: """Retrieve protocol version from API.""" get_protocol_version = GetProtocolVersion(pyvlx=self.pyvlx) await get_protocol_version.do_api_call() @@ -70,7 +79,7 @@ async def get_protocol_version(self): self.protocol_version = get_protocol_version.protocolversion return get_protocol_version.success - async def leave_learn_state(self): + async def leave_learn_state(self) -> bool: """Leave Learn state from API.""" leave_learn_state = LeaveLearnState(pyvlx=self.pyvlx) await leave_learn_state.do_api_call() @@ -78,7 +87,7 @@ async def leave_learn_state(self): raise PyVLXException("Unable to leave learn state") return leave_learn_state.success - async def set_utc(self): + async def set_utc(self) -> bool: """Set UTC Clock.""" setutc = SetUTC(pyvlx=self.pyvlx) await setutc.do_api_call() @@ -86,13 +95,13 @@ async def set_utc(self): raise PyVLXException("Unable to set utc.") return setutc.success - async def set_rtc_time_zone(self): + async def set_rtc_time_zone(self) -> None: """Set the RTC Time Zone.""" # idontwant = setrtctimezone(pyvlx=self.pyvlx) raise PyVLXException("KLF 200 RTC Timezone Set not implemented") # return setrtctimezone.success - async def reboot(self): + async def reboot(self) -> bool: """Reboot gateway.""" reboot = Reboot(pyvlx=self.pyvlx) await reboot.do_api_call() @@ -100,7 +109,7 @@ async def reboot(self): raise PyVLXException("Unable to reboot gateway.") return reboot.success - async def set_factory_default(self): + async def set_factory_default(self) -> bool: """Set Gateway to Factory Default.""" factorydefault = FactoryDefault(pyvlx=self.pyvlx) await factorydefault.do_api_call() @@ -108,7 +117,7 @@ async def set_factory_default(self): raise PyVLXException("Unable to factory Default Reset gateway.") return factorydefault.success - async def get_local_time(self): + async def get_local_time(self) -> bool: """Get local time from gateway.""" getlocaltime = GetLocalTime(pyvlx=self.pyvlx) await getlocaltime.do_api_call() @@ -117,7 +126,7 @@ async def get_local_time(self): self.time = getlocaltime.localtime return getlocaltime.success - async def password_enter(self, password): + async def password_enter(self, password: str) -> bool: """Get enter Password for gateway.""" self.password = password passwordenter = PasswordEnter(pyvlx=self.pyvlx, password=self.password) @@ -126,7 +135,7 @@ async def password_enter(self, password): raise PyVLXException("Login to KLF 200 failed, check credentials") return passwordenter.success - def __str__(self): + def __str__(self) -> str: """Return object as readable string.""" return '<{} state="{}" network_setup="{}" version="{}" protocol_version="{}"/>'.format( type(self).__name__, diff --git a/pyvlx/lightening_device.py b/pyvlx/lightening_device.py index da370d7e..56e0d81a 100644 --- a/pyvlx/lightening_device.py +++ b/pyvlx/lightening_device.py @@ -1,14 +1,19 @@ """Module for lights.""" +from typing import TYPE_CHECKING + from .api import CommandSend from .exception import PyVLXException from .node import Node from .parameter import Intensity +if TYPE_CHECKING: + from pyvlx import PyVLX + class LighteningDevice(Node): """Meta class for turning on device with one main parameter for intensity.""" - def __init__(self, pyvlx, node_id, name, serial_number): + def __init__(self, pyvlx: "PyVLX", node_id: int, name: str, serial_number: str): """Initialize turning on device. Parameters: @@ -24,7 +29,7 @@ def __init__(self, pyvlx, node_id, name, serial_number): ) self.intensity = Intensity() - async def set_intensity(self, intensity, wait_for_completion=True): + async def set_intensity(self, intensity: Intensity, wait_for_completion: bool = True) -> None: """Set light to desired intensity. Parameters: @@ -44,7 +49,7 @@ async def set_intensity(self, intensity, wait_for_completion=True): raise PyVLXException("Unable to send command") await self.after_update() - async def turn_on(self, wait_for_completion=True): + async def turn_on(self, wait_for_completion: bool = True) -> None: """Turn on light. Parameters: @@ -57,7 +62,7 @@ async def turn_on(self, wait_for_completion=True): wait_for_completion=wait_for_completion, ) - async def turn_off(self, wait_for_completion=True): + async def turn_off(self, wait_for_completion: bool = True) -> None: """Turn off light. Parameters: @@ -74,7 +79,7 @@ async def turn_off(self, wait_for_completion=True): class Light(LighteningDevice): """Light object.""" - def __init__(self, pyvlx, node_id, name, serial_number): + def __init__(self, pyvlx: "PyVLX", node_id: int, name: str, serial_number: str): """Initialize Light class. Parameters: @@ -89,7 +94,7 @@ def __init__(self, pyvlx, node_id, name, serial_number): pyvlx=pyvlx, node_id=node_id, name=name, serial_number=serial_number ) - def __str__(self): + def __str__(self) -> str: """Return object as readable string.""" return ( '<{} name="{}" ' diff --git a/pyvlx/node.py b/pyvlx/node.py index 848256b3..92859ecf 100644 --- a/pyvlx/node.py +++ b/pyvlx/node.py @@ -5,36 +5,43 @@ be derived by other objects like window openers and roller shutters. """ +from typing import TYPE_CHECKING, Any, Awaitable, Callable, List + from .api import SetNodeName from .exception import PyVLXException +if TYPE_CHECKING: + from pyvlx import PyVLX + +CallbackType = Callable[["Node"], Awaitable[None]] + class Node: """Class for node abstraction.""" - def __init__(self, pyvlx, node_id, name, serial_number): + def __init__(self, pyvlx: "PyVLX", node_id: int, name: str, serial_number: str): """Initialize Node object.""" self.pyvlx = pyvlx self.node_id = node_id self.name = name self.serial_number = serial_number - self.device_updated_cbs = [] + self.device_updated_cbs: List[CallbackType] = [] - def register_device_updated_cb(self, device_updated_cb): + def register_device_updated_cb(self, device_updated_cb: CallbackType) -> None: """Register device updated callback.""" self.device_updated_cbs.append(device_updated_cb) - def unregister_device_updated_cb(self, device_updated_cb): + def unregister_device_updated_cb(self, device_updated_cb: CallbackType) -> None: """Unregister device updated callback.""" self.device_updated_cbs.remove(device_updated_cb) - async def after_update(self): + async def after_update(self) -> None: """Execute callbacks after internal state has been changed.""" for device_updated_cb in self.device_updated_cbs: # pylint: disable=not-callable await device_updated_cb(self) - async def rename(self, name): + async def rename(self, name: str) -> None: """Change name of node.""" set_node_name = SetNodeName(pyvlx=self.pyvlx, node_id=self.node_id, name=name) await set_node_name.do_api_call() @@ -42,7 +49,7 @@ async def rename(self, name): raise PyVLXException("Unable to rename node") self.name = name - def __str__(self): + def __str__(self) -> str: """Return object as readable string.""" return ( '<{} name="{}" ' @@ -52,7 +59,7 @@ def __str__(self): ) ) - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: """Equal operator.""" return ( type(self).__name__ == type(other).__name__ diff --git a/pyvlx/node_helper.py b/pyvlx/node_helper.py index cc971262..4718c50c 100644 --- a/pyvlx/node_helper.py +++ b/pyvlx/node_helper.py @@ -1,15 +1,28 @@ """Helper module for Node objects.""" +from typing import TYPE_CHECKING, Optional, Union + +from .api.frames import ( + FrameGetAllNodesInformationNotification, + FrameGetNodeInformationNotification) from .const import NodeTypeWithSubtype from .lightening_device import Light from .log import PYVLXLOG +from .node import Node from .on_off_switch import OnOffSwitch from .opening_device import ( Awning, Blade, Blind, GarageDoor, Gate, RollerShutter, Window) +if TYPE_CHECKING: + from pyvlx import PyVLX + -def convert_frame_to_node(pyvlx, frame): +def convert_frame_to_node(pyvlx: "PyVLX", + frame: Union[FrameGetNodeInformationNotification, FrameGetAllNodesInformationNotification]) -> Optional[Node]: """Convert FrameGet[All]Node[s]InformationNotification into Node object.""" # pylint: disable=too-many-return-statements + + assert frame.serial_number is not None + if frame.node_type == NodeTypeWithSubtype.WINDOW_OPENER: return Window( pyvlx=pyvlx, diff --git a/pyvlx/node_updater.py b/pyvlx/node_updater.py index fcbfd125..88dc54fe 100644 --- a/pyvlx/node_updater.py +++ b/pyvlx/node_updater.py @@ -1,6 +1,9 @@ """Module for updating nodes via frames.""" + +from typing import TYPE_CHECKING + from .api.frames import ( - FrameGetAllNodesInformationNotification, + FrameBase, FrameGetAllNodesInformationNotification, FrameNodeStatePositionChangedNotification, FrameStatusRequestNotification) from .const import NodeParameter from .lightening_device import LighteningDevice @@ -8,15 +11,18 @@ from .opening_device import Blind, OpeningDevice from .parameter import Intensity, Parameter, Position +if TYPE_CHECKING: + from pyvlx import PyVLX + class NodeUpdater: """Class for updating nodes via incoming frames, usually received by house monitor.""" - def __init__(self, pyvlx): + def __init__(self, pyvlx: "PyVLX"): """Initialize NodeUpdater object.""" self.pyvlx = pyvlx - async def process_frame_status_request_notification(self, frame: FrameStatusRequestNotification): + async def process_frame_status_request_notification(self, frame: FrameStatusRequestNotification) -> None: """Process FrameStatusRequestNotification.""" PYVLXLOG.debug("NodeUpdater process frame: %s", frame) if frame.node_id not in self.pyvlx.nodes: @@ -40,7 +46,7 @@ async def process_frame_status_request_notification(self, frame: FrameStatusRequ await node.after_update() - async def process_frame(self, frame): + async def process_frame(self, frame: FrameBase) -> None: """Update nodes via frame, usually received by house monitor.""" if isinstance( frame, diff --git a/pyvlx/nodes.py b/pyvlx/nodes.py index 66ec851e..8ed2c937 100644 --- a/pyvlx/nodes.py +++ b/pyvlx/nodes.py @@ -1,23 +1,28 @@ """Module for storing nodes.""" +from typing import TYPE_CHECKING, Iterator, List, Optional, Union + from .api import GetAllNodesInformation, GetNodeInformation from .exception import PyVLXException from .node import Node from .node_helper import convert_frame_to_node +if TYPE_CHECKING: + from pyvlx import PyVLX + class Nodes: """Object for storing node objects.""" - def __init__(self, pyvlx): + def __init__(self, pyvlx: "PyVLX"): """Initialize Nodes object.""" self.pyvlx = pyvlx - self.__nodes = [] + self.__nodes: List[Node] = [] - def __iter__(self): + def __iter__(self) -> Iterator[Node]: """Iterate.""" yield from self.__nodes - def __getitem__(self, key): + def __getitem__(self, key: Union[str, int]) -> Node: """Return node by name or by index.""" if isinstance(key, int): for node in self.__nodes: @@ -28,7 +33,7 @@ def __getitem__(self, key): return node raise KeyError - def __contains__(self, key): + def __contains__(self, key: Union[str, int, Node]) -> bool: """Check if key is in index.""" if isinstance(key, int): for node in self.__nodes: @@ -43,11 +48,11 @@ def __contains__(self, key): return True return False - def __len__(self): + def __len__(self) -> int: """Return number of nodes.""" return len(self.__nodes) - def add(self, node): + def add(self, node: Node) -> None: """Add Node, replace existing node if node with node_id is present.""" if not isinstance(node, Node): raise TypeError() @@ -57,29 +62,31 @@ def add(self, node): return self.__nodes.append(node) - def clear(self): + def clear(self) -> None: """Clear internal node array.""" self.__nodes = [] - async def load(self, node_id=None): + async def load(self, node_id: Optional[int] = None) -> None: """Load nodes from KLF 200, if no node_id is specified all nodes are loaded.""" if node_id is not None: await self._load_node(node_id=node_id) else: await self._load_all_nodes() - async def _load_node(self, node_id): + async def _load_node(self, node_id: int) -> None: """Load single node via API.""" get_node_information = GetNodeInformation(pyvlx=self.pyvlx, node_id=node_id) await get_node_information.do_api_call() if not get_node_information.success: raise PyVLXException("Unable to retrieve node information") notification_frame = get_node_information.notification_frame + if notification_frame is None: + return node = convert_frame_to_node(self.pyvlx, notification_frame) if node is not None: self.add(node) - async def _load_all_nodes(self): + async def _load_all_nodes(self) -> None: """Load all nodes via API.""" get_all_nodes_information = GetAllNodesInformation(pyvlx=self.pyvlx) await get_all_nodes_information.do_api_call() diff --git a/pyvlx/on_off_switch.py b/pyvlx/on_off_switch.py index de3f523e..ef403627 100644 --- a/pyvlx/on_off_switch.py +++ b/pyvlx/on_off_switch.py @@ -1,21 +1,26 @@ """Module for on/off switches.""" +from typing import TYPE_CHECKING + from .api.command_send import CommandSend from .exception import PyVLXException from .node import Node from .parameter import SwitchParameter, SwitchParameterOff, SwitchParameterOn +if TYPE_CHECKING: + from pyvlx import PyVLX + class OnOffSwitch(Node): """Class for controlling on-off switches.""" - def __init__(self, pyvlx, node_id, name, serial_number): + def __init__(self, pyvlx: "PyVLX", node_id: int, name: str, serial_number: str): """Initialize opening device.""" super().__init__( pyvlx=pyvlx, node_id=node_id, name=name, serial_number=serial_number ) self.parameter = SwitchParameter() - async def set_state(self, parameter): + async def set_state(self, parameter: SwitchParameter) -> None: """Set switch to desired state.""" command_send = CommandSend( pyvlx=self.pyvlx, node_id=self.node_id, parameter=parameter @@ -26,18 +31,18 @@ async def set_state(self, parameter): self.parameter = parameter await self.after_update() - async def set_on(self): + async def set_on(self) -> None: """Set switch on.""" await self.set_state(SwitchParameterOn()) - async def set_off(self): + async def set_off(self) -> None: """Set switch off.""" await self.set_state(SwitchParameterOff()) - def is_on(self): + def is_on(self) -> bool: """Return if switch is set to on.""" return self.parameter.is_on() - def is_off(self): + def is_off(self) -> bool: """Return if switch is set to off.""" return self.parameter.is_off() diff --git a/pyvlx/opening_device.py b/pyvlx/opening_device.py index b0a1d022..9b9c9a73 100644 --- a/pyvlx/opening_device.py +++ b/pyvlx/opening_device.py @@ -1,4 +1,6 @@ """Module for window openers.""" +from typing import TYPE_CHECKING, Optional + from .api.command_send import CommandSend from .api.get_limitation import GetLimitation from .exception import PyVLXException @@ -6,12 +8,15 @@ from .parameter import ( CurrentPosition, IgnorePosition, Parameter, Position, TargetPosition) +if TYPE_CHECKING: + from pyvlx import PyVLX + class OpeningDevice(Node): """Meta class for opening device with one main parameter for position.""" def __init__( - self, pyvlx, node_id, name, serial_number, position_parameter=Parameter() + self, pyvlx: "PyVLX", node_id: int, name: str, serial_number: str, position_parameter: Parameter = Parameter() ): """Initialize opening device. @@ -29,7 +34,7 @@ def __init__( ) self.position = Position(parameter=position_parameter) - async def set_position(self, position, wait_for_completion=True): + async def set_position(self, position: Position, wait_for_completion: bool = True) -> None: """Set window to desired position. Parameters: @@ -49,7 +54,7 @@ async def set_position(self, position, wait_for_completion=True): raise PyVLXException("Unable to send command") await self.after_update() - async def open(self, wait_for_completion=True): + async def open(self, wait_for_completion: bool = True) -> None: """Open window. Parameters: @@ -62,7 +67,7 @@ async def open(self, wait_for_completion=True): wait_for_completion=wait_for_completion, ) - async def close(self, wait_for_completion=True): + async def close(self, wait_for_completion: bool = True) -> None: """Close window. Parameters: @@ -75,7 +80,7 @@ async def close(self, wait_for_completion=True): wait_for_completion=wait_for_completion, ) - async def stop(self, wait_for_completion=True): + async def stop(self, wait_for_completion: bool = True) -> None: """Stop window. Parameters: @@ -87,7 +92,7 @@ async def stop(self, wait_for_completion=True): position=CurrentPosition(), wait_for_completion=wait_for_completion ) - def __str__(self): + def __str__(self) -> str: """Return object as readable string.""" return ( '<{} name="{}" node_id="{}" serial_number="{}" position="{}"/>'.format( @@ -101,12 +106,12 @@ class Window(OpeningDevice): def __init__( self, - pyvlx, - node_id, - name, - serial_number, - position_parameter=Parameter(), - rain_sensor=False, + pyvlx: "PyVLX", + node_id: int, + name: str, + serial_number: str, + position_parameter: Parameter = Parameter(), + rain_sensor: bool = False, ): """Initialize Window class. @@ -130,7 +135,7 @@ def __init__( ) self.rain_sensor = rain_sensor - def __str__(self): + def __str__(self) -> str: """Return object as readable string.""" return ( '<{} name="{}" node_id="{}" rain_sensor={} serial_number="{}" position="{}"/>'.format( @@ -139,7 +144,7 @@ def __str__(self): ) ) - async def get_limitation(self): + async def get_limitation(self) -> GetLimitation: """Return limitaation.""" get_limitation = GetLimitation(pyvlx=self.pyvlx, node_id=self.node_id) await get_limitation.do_api_call() @@ -152,7 +157,7 @@ class Blind(OpeningDevice): """Blind objects.""" def __init__( - self, pyvlx, node_id, name, serial_number, position_parameter=Parameter() + self, pyvlx: "PyVLX", node_id: int, name: str, serial_number: str, position_parameter: Parameter = Parameter() ): """Initialize Blind class. @@ -173,10 +178,13 @@ def __init__( self.orientation = Position(position_percent=0) self.target_orientation = TargetPosition() self.target_position = TargetPosition() - self.open_orientation_target: float = 50 - self.close_orientation_target: float = 100 + self.open_orientation_target: int = 50 + self.close_orientation_target: int = 100 - async def set_position_and_orientation(self, position, wait_for_completion=True, orientation=None): + async def set_position_and_orientation(self, + position: Position, + wait_for_completion: bool = True, + orientation: Optional[Position] = None) -> None: """Set window to desired position. Parameters: @@ -190,31 +198,30 @@ async def set_position_and_orientation(self, position, wait_for_completion=True, Note, that, if the position is set to 0, the orientation will be set to 0 too. """ - self.target_position = position + self.target_position = TargetPosition.from_position(position) self.position = position - kwargs = {} - + fp3: Position if orientation is not None: - kwargs['fp3'] = orientation + fp3 = orientation elif self.target_position == Position(position_percent=0): - kwargs['fp3'] = Position(position_percent=0) + fp3 = Position(position_percent=0) else: - kwargs['fp3'] = IgnorePosition() + fp3 = IgnorePosition() command_send = CommandSend( pyvlx=self.pyvlx, - wait_for_completion=wait_for_completion, node_id=self.node_id, parameter=position, - **kwargs + wait_for_completion=wait_for_completion, + fp3=fp3 ) await command_send.do_api_call() if not command_send.success: raise PyVLXException("Unable to send command") await self.after_update() - async def set_position(self, position, wait_for_completion=True): + async def set_position(self, position: Position, wait_for_completion: bool = True) -> None: """Set window to desired position. Parameters: @@ -227,7 +234,7 @@ async def set_position(self, position, wait_for_completion=True): """ await self.set_position_and_orientation(position, wait_for_completion) - async def open(self, wait_for_completion=True): + async def open(self, wait_for_completion: bool = True) -> None: """Open window. Parameters: @@ -239,7 +246,7 @@ async def open(self, wait_for_completion=True): wait_for_completion=wait_for_completion, ) - async def close(self, wait_for_completion=True): + async def close(self, wait_for_completion: bool = True) -> None: """Close window. Parameters: @@ -251,25 +258,25 @@ async def close(self, wait_for_completion=True): wait_for_completion=wait_for_completion, ) - async def stop(self, wait_for_completion=True): + async def stop(self, wait_for_completion: bool = True) -> None: """Stop Blind position.""" await self.set_position_and_orientation( position=CurrentPosition(), wait_for_completion=wait_for_completion, orientation=self.target_orientation ) - async def set_orientation(self, orientation, wait_for_completion=True): + async def set_orientation(self, orientation: Position, wait_for_completion: bool = True) -> None: """Set Blind shades to desired orientation. Parameters: * orientation: Position object containing the target orientation. + target_orientation: Position object holding the target orientation - which allows to ajust the orientation while the blind is in movement + which allows to adjust the orientation while the blind is in movement without stopping the blind (if the position has been changed.) * wait_for_completion: If set, function will return after device has reached target position. """ - self.target_orientation = orientation + self.target_orientation = TargetPosition.from_position(orientation) self.orientation = orientation fp3 = Position(position_percent=0)\ @@ -291,7 +298,7 @@ async def set_orientation(self, orientation, wait_for_completion=True): # KLF200 always send UNKNOWN position for functional parameter, # so orientation is set directly and not via GW_NODE_STATE_POSITION_CHANGED_NTF - async def open_orientation(self, wait_for_completion=True): + async def open_orientation(self, wait_for_completion: bool = True) -> None: """Open Blind slats orientation. Blind slats with ±90° orientation are open at 50% @@ -301,14 +308,14 @@ async def open_orientation(self, wait_for_completion=True): wait_for_completion=wait_for_completion, ) - async def close_orientation(self, wait_for_completion=True): + async def close_orientation(self, wait_for_completion: bool = True) -> None: """Close Blind slats.""" await self.set_orientation( orientation=Position(position_percent=self.close_orientation_target), wait_for_completion=wait_for_completion, ) - async def stop_orientation(self, wait_for_completion=True): + async def stop_orientation(self, wait_for_completion: bool = True) -> None: """Stop Blind slats.""" await self.set_orientation( orientation=CurrentPosition(), wait_for_completion=wait_for_completion diff --git a/pyvlx/parameter.py b/pyvlx/parameter.py index c80d4f9c..2bc2c05d 100644 --- a/pyvlx/parameter.py +++ b/pyvlx/parameter.py @@ -1,4 +1,6 @@ """Module for Position class.""" +from typing import Optional + from .exception import PyVLXException @@ -14,20 +16,20 @@ class Parameter: TARGET = 0xD100 # D1 00 IGNORE = 0xD400 # D4 00 - def __init__(self, raw=None): + def __init__(self, raw: Optional[bytes] = None): """Initialize Parameter class.""" self.raw = self.from_int(Position.UNKNOWN_VALUE) if raw is not None: self.raw = self.from_raw(raw) - def from_parameter(self, parameter): + def from_parameter(self, parameter: "Parameter") -> None: """Set internal raw state from parameter.""" if not isinstance(parameter, Parameter): raise PyVLXException("parameter::from_parameter_wrong_object") self.raw = parameter.raw @staticmethod - def from_int(value): + def from_int(value: int) -> bytes: """Create raw out of position vlaue.""" if not isinstance(value, int): raise PyVLXException("value_has_to_be_int") @@ -36,7 +38,7 @@ def from_int(value): return bytes([value >> 8 & 255, value & 255]) @staticmethod - def is_valid_int(value): + def is_valid_int(value: int) -> bool: """Test if value can be rendered out of int.""" if 0 <= value <= Parameter.MAX: # This includes ON and OFF return True @@ -51,7 +53,7 @@ def is_valid_int(value): return False @staticmethod - def from_raw(raw): + def from_raw(raw: bytes) -> bytes: """Test if raw packets are valid for initialization of Position.""" if not isinstance(raw, bytes): raise PyVLXException("Position::raw_must_be_bytes") @@ -67,37 +69,43 @@ def from_raw(raw): return Position.from_int(Position.UNKNOWN_VALUE) return raw - def __eq__(self, other): + def __eq__(self, other: object) -> bool: """Equal operator.""" + if not isinstance(other, Parameter): + return NotImplemented return self.raw == other.raw - def __str__(self): + def __str__(self) -> str: """Return string representation of object.""" return "0x" + "".join("{:02X}".format(x) for x in self.raw) + def __bytes__(self) -> bytes: + """Return raw bytes of object.""" + return self.raw + class SwitchParameter(Parameter): """Class for storing On or Off values.""" - def __init__(self, parameter=None): + def __init__(self, parameter: Optional[Parameter] = None): """Initialize Parameter class.""" super().__init__() if parameter is not None: self.from_parameter(parameter) - def set_on(self): + def set_on(self) -> None: """Set parameter to 'on' state.""" self.raw = self.from_int(Parameter.ON) - def set_off(self): + def set_off(self) -> None: """Set parameter to 'off' state.""" self.raw = self.from_int(Parameter.OFF) - def is_on(self): + def is_on(self) -> bool: """Return True if parameter is in 'on' state.""" return self.raw == self.from_int(Parameter.ON) - def is_off(self): + def is_off(self) -> bool: """Return True if parameter is in 'off' state.""" return self.raw == self.from_int(Parameter.OFF) @@ -105,7 +113,7 @@ def is_off(self): class SwitchParameterOn(SwitchParameter): """Switch Parameter in switched 'on' state.""" - def __init__(self): + def __init__(self) -> None: """Initialize SwitchParameterOn class.""" super().__init__() self.set_on() @@ -114,7 +122,7 @@ def __init__(self): class SwitchParameterOff(SwitchParameter): """Switch Parameter in switched 'off' state.""" - def __init__(self): + def __init__(self) -> None: """Initialize SwitchParameterOff class.""" super().__init__() self.set_off() @@ -123,7 +131,7 @@ def __init__(self): class Position(Parameter): """Class for storing a position.""" - def __init__(self, parameter=None, position=None, position_percent=None): + def __init__(self, parameter: Optional[Parameter] = None, position: Optional[int] = None, position_percent: Optional[int] = None): """Initialize Position class.""" super().__init__() if parameter is not None: @@ -133,54 +141,54 @@ def __init__(self, parameter=None, position=None, position_percent=None): elif position_percent is not None: self.position_percent = position_percent - def __bytes__(self): + def __bytes__(self) -> bytes: """Convert object in byte representation.""" return self.raw @property - def known(self): + def known(self) -> bool: """Known property, true if position is not in an unknown position.""" return self.raw != self.from_int(Position.UNKNOWN_VALUE) @property - def open(self): + def open(self) -> bool: """Return true if position is set to fully open.""" return self.raw == self.from_int(Position.MIN) @property - def closed(self): + def closed(self) -> bool: """Return true if position is set to fully closed.""" # Consider closed even if raw is not exactly 51200 (tolerance for devices like Velux SML) return self.position_percent == 100 @property - def position(self): + def position(self) -> int: """Position property.""" return self.to_int(self.raw) @position.setter - def position(self, position): + def position(self, position: int) -> None: """Setter of internal raw via position.""" self.raw = self.from_int(position) @property - def position_percent(self): + def position_percent(self) -> int: """Position percent property.""" - # inclear why it returns a here + # unclear why it returns a here return int(self.to_percent(self.raw)) @position_percent.setter - def position_percent(self, position_percent): + def position_percent(self, position_percent: int) -> None: """Setter of internal raw via percent position.""" self.raw = self.from_percent(position_percent) @staticmethod - def to_int(raw): + def to_int(raw: bytes) -> int: """Create int position value out of raw.""" return raw[0] * 256 + raw[1] @staticmethod - def from_percent(position_percent): + def from_percent(position_percent: int) -> bytes: """Create raw value out of percent position.""" if not isinstance(position_percent, int): raise PyVLXException("Position::position_percent_has_to_be_int") @@ -191,14 +199,14 @@ def from_percent(position_percent): return bytes([position_percent * 2, 0]) @staticmethod - def to_percent(raw): + def to_percent(raw: bytes) -> int: """Create percent position value out of raw.""" # The first byte has the vlue from 0 to 200. Ignoring the second one. # Adding 0.5 allows a slight tolerance for devices (e.g. Velux SML) that # do not return exactly 51200 as final position when closed. return int(raw[0] / 2 + 0.5) - def __str__(self): + def __str__(self) -> str: """Return string representation of object.""" if self.raw == self.from_int(Position.UNKNOWN_VALUE): return "UNKNOWN" @@ -208,7 +216,7 @@ def __str__(self): class UnknownPosition(Position): """Unknown position.""" - def __init__(self): + def __init__(self) -> None: """Initialize UnknownPosition class.""" super().__init__(position=Position.UNKNOWN_VALUE) @@ -216,7 +224,7 @@ def __init__(self): class CurrentPosition(Position): """Current position, used to stop devices.""" - def __init__(self): + def __init__(self) -> None: """Initialize CurrentPosition class.""" super().__init__(position=Position.CURRENT) @@ -230,15 +238,23 @@ class TargetPosition(Position): """ - def __init__(self): - """Initialize CurrentPosition class.""" + def __init__(self) -> None: + """Initialize TargetPosition class.""" super().__init__(position=Position.TARGET) + @staticmethod + def from_position(from_position: Position) -> "TargetPosition": + """Create TargetPosition from an existing position.""" + target = TargetPosition() + target.position = from_position.position + target.position_percent = from_position.position_percent + return target + class IgnorePosition(Position): """The Ignore is used where a parameter in the frame is to be ignored.""" - def __init__(self): + def __init__(self) -> None: """Initialize CurrentPosition class.""" super().__init__(position=Position.IGNORE) @@ -246,7 +262,7 @@ def __init__(self): class Intensity(Parameter): """Class for storing an intensity.""" - def __init__(self, parameter=None, intensity=None, intensity_percent=None): + def __init__(self, parameter: Optional[Parameter] = None, intensity: Optional[int] = None, intensity_percent: Optional[int] = None): """Initialize Intensity class.""" super().__init__() if parameter is not None: @@ -256,53 +272,53 @@ def __init__(self, parameter=None, intensity=None, intensity_percent=None): elif intensity_percent is not None: self.intensity_percent = intensity_percent - def __bytes__(self): + def __bytes__(self) -> bytes: """Convert object in byte representation.""" return self.raw @property - def known(self): + def known(self) -> bool: """Known property, true if intensity is not in an unknown intensity.""" return self.raw != self.from_int(Intensity.UNKNOWN_VALUE) @property - def on(self): # pylint: disable=invalid-name + def on(self) -> bool: # pylint: disable=invalid-name """Return true if intensity is set to fully turn on.""" return self.raw == self.from_int(Intensity.MIN) @property - def off(self): + def off(self) -> bool: """Return true if intensity is set to fully turn off.""" return self.raw == bytes([self.MAX >> 8 & 255, self.MAX & 255]) @property - def intensity(self): + def intensity(self) -> int: """Intensity property.""" return self.to_int(self.raw) @intensity.setter - def intensity(self, intensity): + def intensity(self, intensity: int) -> None: """Setter of internal raw via intensity.""" self.raw = self.from_int(intensity) @property - def intensity_percent(self): + def intensity_percent(self) -> int: """Intensity percent property.""" # inclear why it returns a here return int(self.to_percent(self.raw)) @intensity_percent.setter - def intensity_percent(self, intensity_percent): + def intensity_percent(self, intensity_percent: int) -> None: """Setter of internal raw via percent intensity.""" self.raw = self.from_percent(intensity_percent) @staticmethod - def to_int(raw): + def to_int(raw: bytes) -> int: """Create int intensity value out of raw.""" return raw[0] * 256 + raw[1] @staticmethod - def from_percent(intensity_percent): + def from_percent(intensity_percent: int) -> bytes: """Create raw value out of percent intensity.""" if not isinstance(intensity_percent, int): raise PyVLXException("Intensity::intensity_percent_has_to_be_int") @@ -313,12 +329,12 @@ def from_percent(intensity_percent): return bytes([intensity_percent * 2, 0]) @staticmethod - def to_percent(raw): + def to_percent(raw: bytes) -> int: """Create percent intensity value out of raw.""" # The first byte has the value from 0 to 200. Ignoring the second one. return int(raw[0] / 2) - def __str__(self): + def __str__(self) -> str: """Return string representation of object.""" if self.raw == self.from_int(Intensity.UNKNOWN_VALUE): return "UNKNOWN" @@ -328,7 +344,7 @@ def __str__(self): class UnknownIntensity(Intensity): """Unknown intensity.""" - def __init__(self): + def __init__(self) -> None: """Initialize UnknownIntensity class.""" super().__init__(intensity=Intensity.UNKNOWN_VALUE) @@ -336,6 +352,6 @@ def __init__(self): class CurrentIntensity(Intensity): """Current intensity, used to stop devices.""" - def __init__(self): + def __init__(self) -> None: """Initialize CurrentIntensity class.""" super().__init__(intensity=Intensity.CURRENT) diff --git a/pyvlx/py.typed b/pyvlx/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/pyvlx/pyvlx.py b/pyvlx/pyvlx.py index 4e5e6ed9..065feb75 100644 --- a/pyvlx/pyvlx.py +++ b/pyvlx/pyvlx.py @@ -6,9 +6,11 @@ and roller shutters. """ import asyncio +from typing import Optional from .api import ( get_limitation, house_status_monitor_disable, house_status_monitor_enable) +from .api.frames import FrameBase from .config import Config from .connection import Connection from .heartbeat import Heartbeat @@ -22,7 +24,11 @@ class PyVLX: """Class for PyVLX.""" - def __init__(self, path=None, host=None, password=None, loop=None): + def __init__(self, + path: Optional[str] = None, + host: Optional[str] = None, + password: Optional[str] = None, + loop: Optional[asyncio.AbstractEventLoop] = None): """Initialize PyVLX class.""" self.loop = loop or asyncio.get_event_loop() self.config = Config(self, path, host, password) @@ -36,11 +42,12 @@ def __init__(self, path=None, host=None, password=None, loop=None): self.protocol_version = None self.klf200 = Klf200Gateway(pyvlx=self) - async def connect(self): + async def connect(self) -> None: """Connect to KLF 200.""" PYVLXLOG.debug("Connecting to KLF 200.") self.heartbeat.start() await self.connection.connect() + assert self.config.password is not None await self.klf200.password_enter(password=self.config.password) await self.klf200.get_version() await self.klf200.get_protocol_version() @@ -55,33 +62,33 @@ async def connect(self): await self.klf200.get_network_setup() await house_status_monitor_enable(pyvlx=self) - async def reboot_gateway(self): + async def reboot_gateway(self) -> None: """For Compatibility: Reboot the KLF 200.""" PYVLXLOG.warning("KLF 200 reboot initiated") await self.klf200.reboot() - async def send_frame(self, frame): + async def send_frame(self, frame: FrameBase) -> None: """Send frame to API via connection.""" if not self.connection.connected: await self.connect() self.connection.write(frame) - async def disconnect(self): + async def disconnect(self) -> None: """Disconnect from KLF 200.""" # If the connection will be closed while house status monitor is enabled, a reconnection will fail on SSL handshake. await house_status_monitor_disable(pyvlx=self) await self.heartbeat.stop() self.connection.disconnect() - async def load_nodes(self, node_id=None): + async def load_nodes(self, node_id: Optional[int] = None) -> None: """Load devices from KLF 200, if no node_id is specified all nodes are loaded.""" await self.nodes.load(node_id) - async def load_scenes(self): + async def load_scenes(self) -> None: """Load scenes from KLF 200.""" await self.scenes.load() - async def get_limitation(self, node_id): + async def get_limitation(self, node_id: int) -> None: """Return limitation.""" - limit = get_limitation.GetLimitation(self, [node_id]) + limit = get_limitation.GetLimitation(self, node_id) await limit.do_api_call() diff --git a/pyvlx/scene.py b/pyvlx/scene.py index a497c9f1..00a9fe42 100644 --- a/pyvlx/scene.py +++ b/pyvlx/scene.py @@ -1,12 +1,17 @@ """Module for scene.""" +from typing import TYPE_CHECKING, Any + from .api import ActivateScene from .exception import PyVLXException +if TYPE_CHECKING: + from pyvlx import PyVLX + class Scene: """Object for scene.""" - def __init__(self, pyvlx, scene_id, name): + def __init__(self, pyvlx: "PyVLX", scene_id: int, name: str): """Initialize Scene object. Parameters: @@ -20,7 +25,7 @@ def __init__(self, pyvlx, scene_id, name): self.scene_id = scene_id self.name = name - async def run(self, wait_for_completion=True): + async def run(self, wait_for_completion: bool = True) -> None: """Run scene. Parameters: @@ -37,12 +42,12 @@ async def run(self, wait_for_completion=True): if not activate_scene.success: raise PyVLXException("Unable to activate scene") - def __str__(self): + def __str__(self) -> str: """Return object as readable string.""" return '<{} name="{}" id="{}"/>'.format( type(self).__name__, self.name, self.scene_id ) - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: """Equal operator.""" return self.__dict__ == other.__dict__ diff --git a/pyvlx/scenes.py b/pyvlx/scenes.py index d98264bc..c2d8bcb0 100644 --- a/pyvlx/scenes.py +++ b/pyvlx/scenes.py @@ -1,22 +1,27 @@ """Module for storing and accessing scene list.""" +from typing import TYPE_CHECKING, Iterator, List, Union + from .api import GetSceneList from .exception import PyVLXException from .scene import Scene +if TYPE_CHECKING: + from pyvlx import PyVLX + class Scenes: """Class for storing and accessing .""" - def __init__(self, pyvlx): + def __init__(self, pyvlx: "PyVLX"): """Initialize Scenes class.""" self.pyvlx = pyvlx - self.__scenes = [] + self.__scenes: List[Scene] = [] - def __iter__(self): + def __iter__(self) -> Iterator[Scene]: """Iterate.""" yield from self.__scenes - def __getitem__(self, key): + def __getitem__(self, key: Union[str, int]) -> Scene: """Return scene by name or by index.""" if isinstance(key, int): for scene in self.__scenes: @@ -27,11 +32,11 @@ def __getitem__(self, key): return scene raise KeyError - def __len__(self): + def __len__(self) -> int: """Return number of scenes.""" return len(self.__scenes) - def add(self, scene): + def add(self, scene: Scene) -> None: """Add scene, replace existing scene if scene with scene_id is present.""" if not isinstance(scene, Scene): raise TypeError() @@ -41,11 +46,11 @@ def add(self, scene): return self.__scenes.append(scene) - def clear(self): + def clear(self) -> None: """Clear internal scenes array.""" self.__scenes = [] - async def load(self): + async def load(self) -> None: """Load scenes from KLF 200.""" get_scene_list = GetSceneList(pyvlx=self.pyvlx) await get_scene_list.do_api_call() diff --git a/pyvlx/slip.py b/pyvlx/slip.py index f48623cf..ee258db6 100644 --- a/pyvlx/slip.py +++ b/pyvlx/slip.py @@ -1,33 +1,35 @@ """Module for Serial Line Internet Protocol (SLIP).""" +from typing import Optional, Tuple + SLIP_END = 0xC0 SLIP_ESC = 0xDB SLIP_ESC_END = 0xDC SLIP_ESC_ESC = 0xDD -def is_slip(raw): +def is_slip(raw: bytes) -> bool: """Check if raw is a SLIP packet.""" if len(raw) < 2: return False return raw[0] == SLIP_END and SLIP_END in raw[1:] -def decode(raw): +def decode(raw: bytes) -> bytes: """Decode SLIP message.""" return raw.replace(bytes([SLIP_ESC, SLIP_ESC_END]), bytes([SLIP_END])).replace( bytes([SLIP_ESC, SLIP_ESC_ESC]), bytes([SLIP_ESC]) ) -def encode(raw): +def encode(raw: bytes) -> bytes: """Encode SLIP message.""" return raw.replace(bytes([SLIP_ESC]), bytes([SLIP_ESC, SLIP_ESC_ESC])).replace( bytes([SLIP_END]), bytes([SLIP_ESC, SLIP_ESC_END]) ) -def get_next_slip(raw): +def get_next_slip(raw: bytes) -> Tuple[Optional[bytes], bytes]: """ Get the next slip packet from raw data. @@ -41,6 +43,6 @@ def get_next_slip(raw): return slip_packet, new_raw -def slip_pack(raw): +def slip_pack(raw: bytes) -> bytes: """Pack raw message to complete slip message.""" return bytes([SLIP_END]) + encode(raw) + bytes([SLIP_END]) diff --git a/pyvlx/string_helper.py b/pyvlx/string_helper.py index e4f683b2..a170d849 100644 --- a/pyvlx/string_helper.py +++ b/pyvlx/string_helper.py @@ -2,7 +2,7 @@ from .exception import PyVLXException -def string_to_bytes(string, size): +def string_to_bytes(string: str, size: int) -> bytes: """Convert string to bytes add padding.""" if len(string) > size: raise PyVLXException("string_to_bytes::string_to_large") @@ -10,7 +10,7 @@ def string_to_bytes(string, size): return encoded + bytes(size - len(encoded)) -def bytes_to_string(raw): +def bytes_to_string(raw: bytes) -> str: """Convert bytes to string.""" ret = bytes() for byte in raw: diff --git a/requirements/testing.txt b/requirements/testing.txt index f4c9a605..c56ee887 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -10,3 +10,5 @@ pytest-cov==4.0.0 pytest-timeout==2.1.0 setuptools==67.8.0 twine==4.0.2 +mypy==1.5.1 +types-pyyaml==6.0.12.12 \ No newline at end of file diff --git a/setup.py b/setup.py index 86af2188..3513099d 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,9 @@ def get_long_description(): "Programming Language :: Python :: 3.6", ], packages=find_packages(), + package_data={ + 'pyvlx': ['py.typed'], + }, install_requires=REQUIRES, keywords="velux KLF 200 home automation", zip_safe=False, diff --git a/test/frame_get_limitation_status_ntf_test.py b/test/frame_get_limitation_status_ntf_test.py index af604d0b..954c8759 100644 --- a/test/frame_get_limitation_status_ntf_test.py +++ b/test/frame_get_limitation_status_ntf_test.py @@ -18,9 +18,9 @@ def test_bytes(self): frame.session_id = 1 frame.node_id = 1 frame.parameter_id = 0 - frame.min_value = 47668 - frame.max_value = 63487 - frame.limit_originator = Originator.USER.value + frame.min_value = int.to_bytes(47668, 2, byteorder='big') + frame.max_value = int.to_bytes(63487, 2, byteorder='big') + frame.limit_originator = Originator.USER frame.limit_time = 0 self.assertEqual(bytes(frame), b'\x00\r\x03\x14\x00\x01\x01\x00\xba4\xf7\xff\x01\x00\x9d')