From 1cac80f7240f6b4561ab8f6f6942ac32f8c7b96f Mon Sep 17 00:00:00 2001 From: Alexander Ryazanov Date: Mon, 10 May 2021 13:18:09 +0300 Subject: [PATCH] Added optional arguments to decoding/encoding routines, bumped version number --- hekrapi/aioudp.py | 10 +++++++--- hekrapi/argument.py | 20 ++++++++++++-------- hekrapi/command.py | 6 +++++- hekrapi/device.py | 2 +- hekrapi/protocols/__init__.py | 3 +++ hekrapi/protocols/power_meter.py | 5 +++-- hekrapi/types.py | 8 +++++++- setup.py | 2 +- 8 files changed, 39 insertions(+), 17 deletions(-) diff --git a/hekrapi/aioudp.py b/hekrapi/aioudp.py index b3a001b..d9120b5 100644 --- a/hekrapi/aioudp.py +++ b/hekrapi/aioudp.py @@ -30,6 +30,8 @@ async def main(): # Datagram protocol +from typing import Callable + class DatagramEndpointProtocol(asyncio.DatagramProtocol): """Datagram protocol for the endpoint high-level interface.""" @@ -149,7 +151,7 @@ class RemoteEndpoint(Endpoint): It is initialized with an optional queue size for the incoming datagrams. """ - def send(self, data): + def send(self, data, **kwargs): """Send a datagram to the remote host.""" super().send(data, None) @@ -165,7 +167,7 @@ async def receive(self): # High-level coroutines async def open_datagram_endpoint( - host, port, *, endpoint_factory=Endpoint, remote=False, **kwargs): + host, port, *, endpoint_factory=Callable[[], Endpoint], remote=False, **kwargs): """Open and return a datagram endpoint. The default endpoint factory is the Endpoint class. @@ -209,10 +211,12 @@ async def open_remote_endpoint( # Testing try: + # noinspection PyPackageRequirements,PyUnresolvedReferences import pytest - pytestmark = pytest.mark.asyncio except ImportError: # pragma: no cover pass +else: + pytestmark = pytest.mark.asyncio async def test_standard_behavior(): diff --git a/hekrapi/argument.py b/hekrapi/argument.py index ef46919..48a12fd 100644 --- a/hekrapi/argument.py +++ b/hekrapi/argument.py @@ -12,13 +12,13 @@ class Argument: """Argument class for HekrAPI""" def __init__(self, name: str, - value_type: Union[Tuple[ArgumentType, ArgumentType], ArgumentType]=int, - byte_length: int=1, - variable: Optional[str]=None, - multiplier: Optional[Union[int, float]]=None, - decimals: Optional[int]=None, - value_min: Optional[Union[int, float]]=None, - value_max: Optional[Union[int, float]]=None, + value_type: Union[Tuple[ArgumentType, ArgumentType], ArgumentType] = int, + byte_length: int = 1, + variable: Optional[str] = None, + multiplier: Optional[Union[int, float]] = None, + decimals: Optional[int] = None, + value_min: Optional[Union[int, float]] = None, + value_max: Optional[Union[int, float]] = None, ): """Argument class constructor @@ -104,4 +104,8 @@ def variable(self) -> str: @variable.setter def variable(self, value): """Setter for variable name""" - self.__variable = value \ No newline at end of file + self.__variable = value + + +class OptionalArgument(Argument): + """Argument that is optional in datagrams""" diff --git a/hekrapi/command.py b/hekrapi/command.py index a5ea2e9..88aabd8 100644 --- a/hekrapi/command.py +++ b/hekrapi/command.py @@ -3,7 +3,7 @@ from typing import List -from .argument import Argument +from .argument import Argument, OptionalArgument from .const import FrameType from .exceptions import HekrTypeError, HekrValueError, InvalidDataMissingKeyException, \ InvalidDataGreaterThanException, InvalidDataLessThanException @@ -261,6 +261,8 @@ def encode(self, data: dict, use_variable_names: bool = False, filter_values: bo value_input = data.get(key, None) if value_input is None: + if isinstance(argument, OptionalArgument): + continue raise InvalidDataMissingKeyException(data_key=key) if argument.value_min is not None and argument.value_min > value_input: @@ -300,6 +302,8 @@ def decode(self, data: bytes, use_variable_names: bool = False, filter_values: b next_pos = current_pos + argument.byte_length if next_pos > data_length: + if isinstance(argument, OptionalArgument): + continue raise InvalidDataMissingKeyException(data_key=key) value_output = int.from_bytes(data[current_pos:next_pos], byteorder='big', signed=False) diff --git a/hekrapi/device.py b/hekrapi/device.py index 9e91814..930bb65 100644 --- a/hekrapi/device.py +++ b/hekrapi/device.py @@ -419,7 +419,7 @@ async def close_connection(self) -> None: async def send_request(self, request_str: str) -> None: _LOGGER.debug('Sending request via %s with content: %s' % (self, request_str)) - self._endpoint.send(str.encode(request_str)) + self._endpoint.send(str.encode(request_str), ) async def read_response(self) -> str: _LOGGER.debug('Starting receiving on %s' % self) diff --git a/hekrapi/protocols/__init__.py b/hekrapi/protocols/__init__.py index 5b5a64c..51ea3b8 100644 --- a/hekrapi/protocols/__init__.py +++ b/hekrapi/protocols/__init__.py @@ -1 +1,4 @@ """ Protocols implemented in Python for Hekr API """ +__all__ = [ + "power_meter" +] diff --git a/hekrapi/protocols/power_meter.py b/hekrapi/protocols/power_meter.py index 81d11d1..90f89a3 100644 --- a/hekrapi/protocols/power_meter.py +++ b/hekrapi/protocols/power_meter.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- """ Basic protocol definition for a smart power meter """ from enum import IntEnum +from typing import Union, Any -from ..argument import Argument +from ..argument import Argument, OptionalArgument from ..command import Command, FrameType from ..protocol import Protocol, TO_STR, TO_FLOAT, TO_BOOL, TO_SIGNED_FLOAT @@ -52,7 +53,7 @@ class SwitchState(IntEnum): Argument("warning_current", CurrentWarning, 1, "fault_Over_I"), Argument("delay_timer", int, 2, "tmCd_M", value_min=0, value_max=1440), Argument("delay_enabled", TO_BOOL, 1, "tmCdO_Sw"), - Argument("warning_battery", PowerSupplyWarning, 1, "fault_SurplusDeficiency") + OptionalArgument("warning_battery", PowerSupplyWarning, 1, "fault_SurplusDeficiency"), ]), Command(2, FrameType.SEND, "querySet", response_command_id=8), Command(3, FrameType.SEND, "setLimit", arguments=[ diff --git a/hekrapi/types.py b/hekrapi/types.py index 05bf175..f48043a 100644 --- a/hekrapi/types.py +++ b/hekrapi/types.py @@ -1,9 +1,15 @@ """Types for Hekr API project.""" -from typing import Tuple, Dict, Any, Union, Optional, Callable +from typing import Tuple, Dict, Any, Union, Optional, Callable, TYPE_CHECKING from .const import DeviceResponseState +if TYPE_CHECKING: + # noinspection PyUnresolvedReferences + from .command import Command + # noinspection PyUnresolvedReferences + from .device import Device + DecodeResult = Tuple['Command', Dict[str, Any], int] MessageID = int DeviceID = str diff --git a/setup.py b/setup.py index 53c22a3..8ee03ce 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from distutils.core import setup -version = 'v0.2.5' +version = 'v0.2.7' setup( name='hekrapi', packages=['hekrapi', 'hekrapi.protocols'],