diff --git a/tests/__init__.py b/tests/__init__.py index b4a5ed89..99bb51fc 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +1,57 @@ -"""Contains test suite.""" +"""Contains a test suite.""" + + +import importlib +import json +import os +import pathlib +from typing import Final + +import pytest + +TESTDATA_DIR: Final = "testdata" + + +def load_and_create_class_instance(module_name: str, class_name: str, **kwargs): + """Load a class and creates it's instance.""" + return getattr(importlib.import_module(module_name), class_name)(**kwargs) + + +def try_int(key): + """Try to convert key to integer or return key unchanged + on error. + """ + try: + return int(key) + except ValueError: + return key + + +def decode_hinted_objects(d): + """Decode a hinted JSON objects.""" + if "__module__" in d and "__class__" in d: + module_name = d.pop("__module__") + class_name = d.pop("__class__") + return load_and_create_class_instance(module_name, class_name, **d) + + if "__bytearray__" in d: + return bytearray.fromhex("".join(d["items"])) + + if "__tuple__" in d: + return tuple(d["items"]) + + return {try_int(k): v for k, v in d.items()} + + +def load_json_test_data(path: str): + """Load test data from JSON file.""" + abs_path = "/".join([os.path.dirname(__file__), TESTDATA_DIR, path]) + file = pathlib.Path(abs_path) + with open(file, encoding="utf-8") as fp: + return json.load(fp, object_hook=decode_hinted_objects) + + +def load_json_parameters(path: str): + """Prepare JSON test data for parametrization.""" + test_data = load_json_test_data(path) + return [pytest.param(x["message"], x["data"], id=x["id"]) for x in test_data] diff --git a/tests/conftest.py b/tests/conftest.py index 4b877e3c..d15a82d6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,469 +1,14 @@ -"""Fixtures for PyPlumIO test suite.""" +"""Fixtures for the PyPlumIO test suite.""" import asyncio -from datetime import datetime from unittest.mock import patch import pytest -from pyplumio.const import ( - ATTR_DEVICE_INDEX, - ATTR_INDEX, - ATTR_OFFSET, - ATTR_PARAMETER, - ATTR_PASSWORD, - ATTR_SCHEDULE, - ATTR_SIZE, - ATTR_SWITCH, - ATTR_TYPE, - ATTR_VALUE, - AlertType, - FrameType, - ProductType, -) +from pyplumio.const import ProductType from pyplumio.devices.ecomax import EcoMAX -from pyplumio.helpers.parameter import ParameterValues -from pyplumio.helpers.typing import EventDataType -from pyplumio.structures.alerts import ATTR_ALERTS, Alert -from pyplumio.structures.ecomax_parameters import ATTR_ECOMAX_PARAMETERS -from pyplumio.structures.mixer_parameters import ATTR_MIXER_PARAMETERS -from pyplumio.structures.modules import ConnectedModules -from pyplumio.structures.network_info import ( - ATTR_NETWORK, - EthernetParameters, - NetworkInfo, - WirelessParameters, -) +from pyplumio.structures.network_info import NetworkInfo from pyplumio.structures.product_info import ATTR_PRODUCT, ProductInfo -from pyplumio.structures.program_version import ATTR_VERSION, VersionInfo -from pyplumio.structures.schedules import ATTR_SCHEDULE_PARAMETERS, ATTR_SCHEDULES -from pyplumio.structures.thermostat_parameters import ( - ATTR_THERMOSTAT_PARAMETERS, - ATTR_THERMOSTAT_PROFILE, -) -from pyplumio.structures.thermostat_sensors import ATTR_THERMOSTAT_COUNT - -TEST_SCHEDULE: list[bool] = [ - False, - False, - False, - False, - False, - False, - False, - False, - False, - False, - False, - False, - False, - False, - False, - False, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - True, - False, -] - - -@pytest.fixture(name="data") -def fixture_data() -> dict[FrameType, EventDataType]: - """Return response data keyed by frame type.""" - return { - FrameType.RESPONSE_PROGRAM_VERSION: { - ATTR_VERSION: VersionInfo(software="1.0.0") - }, - FrameType.RESPONSE_DEVICE_AVAILABLE: { - ATTR_NETWORK: NetworkInfo( - eth=EthernetParameters( - ip="192.168.1.2", - netmask="255.255.255.0", - gateway="192.168.1.1", - status=True, - ), - wlan=WirelessParameters( - ip="192.168.2.2", - netmask="255.255.255.0", - gateway="192.168.2.1", - status=True, - ssid="tests", - ), - ) - }, - FrameType.RESPONSE_UID: { - ATTR_PRODUCT: ProductInfo( - type=ProductType.ECOMAX_P, - id=90, - uid="D251PAKR3GCPZ1K8G05G0", - logo=23040, - image=2816, - model="EM350P2-ZF", - ) - }, - FrameType.RESPONSE_PASSWORD: {ATTR_PASSWORD: "0000"}, - FrameType.RESPONSE_ECOMAX_PARAMETERS: { - ATTR_ECOMAX_PARAMETERS: [ - (0, ParameterValues(61, 61, 100)), - (1, ParameterValues(60, 41, 60)), - (2, ParameterValues(40, 20, 59)), - (14, ParameterValues(20, 1, 250)), - (15, ParameterValues(3, 1, 30)), - (16, ParameterValues(1, 1, 30)), - (17, ParameterValues(5, 1, 30)), - (18, ParameterValues(1, 0, 1)), - (19, ParameterValues(0, 0, 60)), - (20, ParameterValues(60, 0, 100)), - (23, ParameterValues(20, 10, 100)), - (35, ParameterValues(30, 20, 100)), - (36, ParameterValues(4, 1, 30)), - (37, ParameterValues(0, 0, 100)), - (38, ParameterValues(8, 1, 250)), - (39, ParameterValues(50, 40, 85)), - (40, ParameterValues(10, 10, 30)), - (41, ParameterValues(30, 20, 50)), - (44, ParameterValues(10, 10, 240)), - (47, ParameterValues(15, 10, 20)), - (50, ParameterValues(50, 40, 150)), - (53, ParameterValues(2, 1, 15)), - (54, ParameterValues(3, 1, 10)), - (55, ParameterValues(40, 20, 60)), - (60, ParameterValues(60, 1, 250)), - (61, ParameterValues(30, 20, 50)), - (85, ParameterValues(125, 1, 250)), - (87, ParameterValues(2, 1, 100)), - (88, ParameterValues(47, 1, 250)), - (89, ParameterValues(10, 10, 30)), - (98, ParameterValues(65, 50, 80)), - (99, ParameterValues(50, 30, 80)), - (100, ParameterValues(80, 60, 90)), - (101, ParameterValues(50, 30, 80)), - (102, ParameterValues(0, 0, 99)), - (105, ParameterValues(5, 3, 15)), - (106, ParameterValues(0, 0, 1)), - (107, ParameterValues(13, 1, 40)), - (108, ParameterValues(20, 0, 40)), - (111, ParameterValues(0, 0, 1)), - (112, ParameterValues(5, 0, 30)), - (114, ParameterValues(90, 85, 95)), - (115, ParameterValues(60, 40, 90)), - (119, ParameterValues(51, 40, 70)), - (120, ParameterValues(40, 20, 55)), - (121, ParameterValues(70, 40, 80)), - (122, ParameterValues(2, 0, 2)), - (123, ParameterValues(10, 1, 30)), - (124, ParameterValues(0, 0, 1)), - (125, ParameterValues(0, 0, 2)), - (126, ParameterValues(16, 5, 30)), - (127, ParameterValues(10, 1, 15)), - (128, ParameterValues(3, 0, 99)), - ] - }, - FrameType.RESPONSE_MIXER_PARAMETERS: { - ATTR_MIXER_PARAMETERS: { - 0: [ - (0, ParameterValues(40, 30, 60)), - (1, ParameterValues(20, 30, 40)), - (2, ParameterValues(80, 70, 90)), - (3, ParameterValues(20, 10, 30)), - (4, ParameterValues(1, 0, 1)), - (5, ParameterValues(13, 10, 30)), - ], - } - }, - FrameType.RESPONSE_THERMOSTAT_PARAMETERS: { - ATTR_THERMOSTAT_COUNT: 3, - ATTR_THERMOSTAT_PROFILE: ParameterValues(0, 0, 5), - ATTR_THERMOSTAT_PARAMETERS: { - 0: [ - (0, ParameterValues(0, 0, 7)), - (1, ParameterValues(220, 100, 350)), - (2, ParameterValues(150, 100, 350)), - (3, ParameterValues(100, 60, 140)), - (4, ParameterValues(2, 0, 60)), - (5, ParameterValues(1, 0, 60)), - (6, ParameterValues(1, 0, 60)), - (7, ParameterValues(10, 0, 60)), - (8, ParameterValues(9, 0, 50)), - (9, ParameterValues(222, 100, 350)), - (10, ParameterValues(212, 100, 350)), - (11, ParameterValues(90, 50, 300)), - ] - }, - }, - FrameType.RESPONSE_ALERTS: { - ATTR_ALERTS: [ - Alert( - code=26, - from_dt=datetime(2022, 7, 23, 16, 27), - to_dt=datetime(2022, 7, 23, 16, 32, 27), - ), - Alert( - code=AlertType.POWER_LOSS, - from_dt=datetime(2022, 7, 22, 22, 33), - to_dt=None, - ), - ] - }, - FrameType.RESPONSE_SCHEDULES: { - ATTR_SCHEDULES: [(0, [TEST_SCHEDULE] * 7)], - ATTR_SCHEDULE_PARAMETERS: [ - (0, ParameterValues(0, 0, 1)), - (1, ParameterValues(5, 0, 30)), - ], - }, - FrameType.REQUEST_SET_ECOMAX_PARAMETER: { - ATTR_INDEX: 0, - ATTR_VALUE: 80, - }, - FrameType.REQUEST_SET_MIXER_PARAMETER: { - ATTR_INDEX: 0, - ATTR_VALUE: 40, - ATTR_DEVICE_INDEX: 0, - }, - FrameType.REQUEST_SET_THERMOSTAT_PARAMETER: { - ATTR_INDEX: 1, - ATTR_VALUE: 42, - ATTR_OFFSET: 12, - ATTR_SIZE: 2, - }, - FrameType.REQUEST_ECOMAX_CONTROL: {ATTR_VALUE: 1}, - FrameType.REQUEST_SET_SCHEDULE: { - ATTR_TYPE: "heating", - ATTR_SWITCH: 0, - ATTR_PARAMETER: 5, - ATTR_SCHEDULE: [TEST_SCHEDULE] * 7, - }, - FrameType.MESSAGE_SENSOR_DATA: { - "sensors": { - "frame_versions": { - 85: 45559, - 84: 48672, - 86: 64152, - 54: 1, - 56: 2, - 57: 1, - 61: 12568, - }, - "state": 0, - "fan": False, - "feeder": False, - "heating_pump": False, - "water_heater_pump": False, - "circulation_pump": False, - "lighter": False, - "alarm": False, - "outer_boiler": False, - "fan2_exhaust": False, - "feeder2": False, - "outer_feeder": False, - "solar_pump": False, - "fireplace_pump": False, - "gcz_contact": False, - "blow_fan1": False, - "blow_fan2": False, - "heating_pump_flag": True, - "water_heater_pump_flag": True, - "circulation_pump_flag": True, - "solar_pump_flag": False, - "heating_temp": 22.384185791015625, - "optical_temp": 0.0, - "heating_target": 41, - "heating_status": 0, - "water_heater_target": 45, - "water_heater_status": 128, - "pending_alerts": 0, - "fuel_level": 32, - "transmission": 0, - "fan_power": 0.0, - "load": 0, - "power": 0.0, - "fuel_consumption": 0.0, - "thermostat": 1, - "modules": ConnectedModules( - module_a="18.11.58.K1", - module_b=None, - module_c=None, - ecolambda=None, - ecoster=None, - panel="18.10.72", - ), - "lambda_sensor": {"state": 1, "target": 2, "level": 40}, - "thermostat_sensors": [ - ( - 0, - { - "state": 3, - "current_temp": 43.5, - "target_temp": 50.0, - "contacts": True, - "schedule": False, - }, - ) - ], - "thermostat_count": 1, - "mixer_sensors": [ - (4, {"current_temp": 20.0, "target_temp": 40, "pump": False}) - ], - "mixer_count": 5, - } - }, - } - - -@pytest.fixture(name="messages") -def fixture_messages() -> dict[FrameType, bytearray]: - """Return response messages keyed by frame type.""" - return { - FrameType.RESPONSE_PROGRAM_VERSION: bytearray.fromhex( - "FFFF057A0000000001000000000056" - ), - FrameType.RESPONSE_DEVICE_AVAILABLE: bytearray.fromhex( - """01C0A80102FFFFFF00C0A8010101C0A80202FFFFFF00C0A80201010164010000000005746 -5737473""".replace( - "\n", "" - ) - ), - FrameType.RESPONSE_UID: bytearray.fromhex( - "005A000B001600110D3833383655395A0000000A454D33353050322D5A46" - ), - FrameType.RESPONSE_PASSWORD: bytearray.fromhex("0430303030"), - FrameType.RESPONSE_ECOMAX_PARAMETERS: bytearray.fromhex( - """00008b3d3d643c293c28143bfffffffffffffffffffffffffffffffffffffffffffffffff -fffffffffffffffff1401fa03011e01011e05011e01000100003c3c0064ffffffffffff140a64fffffffffff -fffffffffffffffffffffffffffffffffffffffffffffffffffffff1e146404011e0000640801fa3228550a0 -a1e1e1432ffffffffffff0a0af0ffffffffffff0f0a14ffffffffffff322896ffffffffffff02010f03010a2 -8143cffffffffffffffffffffffff3c01fa1e1432fffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -fff7d01faffffff0201642f01fa0a0a1effffffffffffffffffffffffffffffffffffffffffffffff4132503 -21e50503c5a321e50000063ffffffffffff05030f0000010d0128140028ffffffffffff00000105001efffff -f5a555f3c285affffffffffffffffff3328462814374628500200020a011e00000100000210051e0a010f030 -063ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff""".replace( - "\n", "" - ) - ), - FrameType.RESPONSE_MIXER_PARAMETERS: bytearray.fromhex( - "00000601281E3C141E2850465A140A1E0100010D0A1E" - ), - FrameType.RESPONSE_THERMOSTAT_PARAMETERS: bytearray.fromhex( - """000025000005000007DC0064005E01960064005E01643C8C02003C01003C01003C0A003C0 -90032DE0064005E01D40064005E015A0032002C01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF""".replace( - "\n", "" - ) - ), - FrameType.RESPONSE_DATA_SCHEMA: bytearray.fromhex( - """01010400070A02060A00060A01060A02000A01000A0 -3060A07060A05060A06060A08060A09060A0A060A03000A04060A0B060A0C060A0D060A0E060A0F060A10060 -A04000A11060A11060A12060A13060A14060A15060A16060A0500050800050B00050C00050D00050E00050A0 -0050900050700050F00051200050600051000051100050600050600051400051500051600051700051800051 -900051A00070104070704070304071C00070604070204070004071B000704040705040706000709040708040 -70600070600071E00071F00070B04070A0407200007210007220004010504070504030504240004060504020 -5040005042300040405040505040600040905040805040600040600042600042700040B05040A05042800042 -900042A00042C00042F00043000043100043200042E00042D00042B000433000436000406000434000435000 -40600040600043800043900043A00043B00043C00043D00043E000A40000A43000A44000A45000A46000A420 -00A41000A3F000A47000A53000A53000A48000A49000A4A000A53000A4C000A4D000A4E000A4F000A50000A5 -1000A52000A53000A53000A53000A53000A53000A53000A53000A53000A55000A56000A57000A58000A59000 -A5A000A5B000A5C000A5D000A5E000A5F000A5F000A5F000A5F000A5F000A5F000461000462000400080A630 -00A64000401080A66000A67000A68000A69000A6A000A6B000A6C000A6D000A6E000A6F000A70000A71000A7 -2000A72000A72000A72000473000474000402070A75000A76000A06070A77000A53000A78000A79000A53000 -A7A000A7B000A7C000A7D000A7E000A7F0004800004830004840004910004920004930004940004950004960 -0049700049800049900049A000A9B000A9C000A9D000A9E000A9F0004A40004A50004A60004A00004A10007A -20007A30004030704600007010707650004AA0004AD0005B00005B10005B20005B30005B50005B60007AB000 -5AC0006AE0006AF000FB7000FB8000FB90004BA000FBB000FBC000FBD0004BE0004BF0004C00004C10004C20 -00CC300 - """.replace( - "\n", "" - ) - ), - FrameType.RESPONSE_ALERTS: bytearray.fromhex( - "6400021a5493382B9B94382B009C97372B00000000" - ), - FrameType.RESPONSE_SCHEDULES: bytearray.fromhex( - """100101000005001E0000FFFFFFFE0000FFFFFFFE0000FFFFFFFE0000FFFFFFFE0000FFFFF -FFE0000FFFFFFFE0000FFFFFFFE -""".replace( - "\n", "" - ) - ), - FrameType.MESSAGE_REGULATOR_DATA: bytearray.fromhex( - """626400010855F7B15420BE6101003D183136010064010040041C5698FA0000000000FF0FF -F0FFF0FFF0FFF0FFF0F9F04080FFF0FFF0F0000000000000000000000000000000000000000000000000000C -07F0000C07F0000C07F0000C07F0000C07F0000C07FD012B341000000000000C07F0000C07F0000C07F0000C -07F0000C07F0000C07F0000C07F0000C07F0000C07F0000C07F0000C07F0000C07F0000C07F0000C07F2D280 -000000029000000002828000000002828000000800000000000000000000000000000000000000000003FFF7 -F00000000200000000000404000403F124B01000000000000000000000002020100000000000000000000000 -000000000000000000000150009001A000D000C001D00000000000000000000000000000000000000FFFFFF0 -0000000000000000000FFFFFF0000000000010164000000 -""".replace( - "\n", "" - ) - ), - FrameType.MESSAGE_SENSOR_DATA: bytearray.fromhex( - """0755F7B15420BE5698FA3601003802003901003D18310000000000FF0300000900D012B34 -101FFFFFFFF02FFFFFFFF03FFFFFFFF04FFFFFFFF05FFFFFFFF060000000007FFFFFFFF08FFFFFFFF29002D8 -00020000000000000000000000000000001120B3A4B01FFFFFFFF120A480102280005010300002E420000484 -205FFFFFFFF28000800FFFFFFFF28000800FFFFFFFF28000800FFFFFFFF280008000000A04128000800 -""".replace( - "\n", "" - ) - ), - FrameType.REQUEST_ECOMAX_PARAMETERS: bytearray.fromhex("FF00"), - FrameType.REQUEST_SET_ECOMAX_PARAMETER: bytearray.fromhex("0050"), - FrameType.REQUEST_SET_MIXER_PARAMETER: bytearray.fromhex("000028"), - FrameType.REQUEST_SET_THERMOSTAT_PARAMETER: bytearray.fromhex("0d2a00"), - FrameType.REQUEST_ECOMAX_CONTROL: bytearray.fromhex("01"), - FrameType.REQUEST_SET_SCHEDULE: bytearray.fromhex( - """010000050000FFFFFFFE0000FFFFFFFE0000FFFFFFFE0000FFFFFFFE0000FFFFFFFE0000F -FFFFFFE0000FFFFFFFE -""".replace( - "\n", "" - ) - ), - } - - -@pytest.fixture(name="sensor_data_without_thermostats") -def fixture_sensor_data_without_thermostats() -> bytearray: - """Return device sensor data without thermostat data.""" - return bytearray.fromhex( - """0755F7B15420BE5698FA3601003802003901003D18310000000000FF0300000900D012B34101F -FFFFFFF02FFFFFFFF03FFFFFFFF04FFFFFFFF05FFFFFFFF060000000007FFFFFFFF08FFFFFFFF29002D80002 -0000000000000000000000000000001120B3A4B01FFFFFFFF120A4801022800FF05FFFFFFFF28000800FFFFF -FFF28000800FFFFFFFF28000800FFFFFFFF28000800FFFFFFFF28000800 - """.replace( - "\n", "" - ) - ) @pytest.fixture(name="ecomax") diff --git a/tests/frames/test_messages.py b/tests/frames/test_messages.py index 4c651c68..1eb8e469 100644 --- a/tests/frames/test_messages.py +++ b/tests/frames/test_messages.py @@ -1,182 +1,63 @@ """Test PyPlumIO message frames.""" + from typing import Final -from pyplumio.const import ( - ATTR_CURRENT_TEMP, - ATTR_SCHEDULE, - ATTR_SENSORS, - ATTR_STATE, - ATTR_TARGET_TEMP, - BYTE_UNDEFINED, - DeviceState, - DeviceType, - FrameType, -) +import pytest + +from pyplumio.const import ATTR_SENSORS, ATTR_STATE, DeviceType from pyplumio.frames.messages import RegulatorDataMessage, SensorDataMessage -from pyplumio.structures.fan_power import ATTR_FAN_POWER from pyplumio.structures.frame_versions import ATTR_FRAME_VERSIONS -from pyplumio.structures.fuel_consumption import ATTR_FUEL_CONSUMPTION -from pyplumio.structures.fuel_level import ATTR_FUEL_LEVEL -from pyplumio.structures.lambda_sensor import ( - ATTR_LAMBDA_LEVEL, - ATTR_LAMBDA_STATE, - ATTR_LAMBDA_TARGET, -) -from pyplumio.structures.load import ATTR_LOAD -from pyplumio.structures.mixer_sensors import ATTR_MIXER_SENSORS, ATTR_PUMP -from pyplumio.structures.modules import ATTR_MODULES -from pyplumio.structures.outputs import ATTR_HEATING_PUMP -from pyplumio.structures.pending_alerts import ATTR_PENDING_ALERTS -from pyplumio.structures.power import ATTR_POWER -from pyplumio.structures.regulator_data import ATTR_REGDATA_DECODER -from pyplumio.structures.statuses import ATTR_HEATING_STATUS, ATTR_HEATING_TARGET -from pyplumio.structures.temperatures import ATTR_HEATING_TEMP -from pyplumio.structures.thermostat_sensors import ( - ATTR_CONTACTS, - ATTR_THERMOSTAT_SENSORS, -) +from pyplumio.structures.regulator_data import ATTR_REGDATA, ATTR_REGDATA_DECODER +from tests import load_json_parameters, load_json_test_data -INDEX_VERSION_MINOR: Final = 3 -INDEX_VERSION_MAJOR: Final = 4 INDEX_STATE: Final = 22 -INDEX_FUEL_LEVEL: Final = 82 -INDEX_FAN_POWER: Final = 84 -INDEX_LOAD: Final = 88 -INDEX_POWER: Final = 89 -INDEX_FUEL_CONSUMPTION: Final = 93 -INDEX_LAMBDA_SENSOR: Final = 110 def test_messages_type() -> None: """Test if response is instance of frame class.""" - for response in ( - RegulatorDataMessage, - SensorDataMessage, - ): + for response in (RegulatorDataMessage, SensorDataMessage): frame = response(recipient=DeviceType.ALL, sender=DeviceType.ECONET) assert isinstance(frame, response) -def test_regdata_decode_message(messages: dict[FrameType, bytearray]) -> None: - """Test parsing of regdata message.""" - frame = RegulatorDataMessage(message=messages[FrameType.MESSAGE_REGULATOR_DATA]) - decoder = frame.data[ATTR_REGDATA_DECODER] - data = decoder.decode(frame.message)[0] - assert ATTR_FRAME_VERSIONS in data - - -def test_regdata_decode_message_with_unknown_version( - messages: dict[FrameType, bytearray] -) -> None: - """Test parsing of regdata message with unknown message version.""" - test_message = messages[FrameType.MESSAGE_REGULATOR_DATA] - test_message[INDEX_VERSION_MAJOR], test_message[INDEX_VERSION_MINOR] = (2, 0) - frame = RegulatorDataMessage(message=test_message) +@pytest.mark.parametrize( + "schema, regdata", + zip( + load_json_test_data("responses/data_schema.json"), + load_json_test_data("messages/regulator_data.json"), + ), + ids=[ + "unknown_regulator_data_version", + "EM350P2_regulator_data", + "incomplete_boolean", + ], +) +async def test_regulator_data_message(schema, regdata) -> None: + """Test a regulator data message.""" + frame = RegulatorDataMessage(message=regdata["message"]) decoder = frame.data[ATTR_REGDATA_DECODER] - assert not decoder.decode(frame.message)[0] - - -def test_sensor_data_decode_message(messages: dict[FrameType, bytearray]) -> None: - """Test parsing sensor data message.""" - test_message = messages[FrameType.MESSAGE_SENSOR_DATA] - frame = SensorDataMessage(message=test_message) - data = frame.data[ATTR_SENSORS] - assert ATTR_FRAME_VERSIONS in data - assert data[ATTR_FRAME_VERSIONS][85] == 45559 - assert len(data[ATTR_FRAME_VERSIONS]) == 7 - assert data[ATTR_STATE] == DeviceState.OFF - assert round(data[ATTR_HEATING_TEMP], 2) == 22.38 - assert data[ATTR_HEATING_TARGET] == 41 - assert not data[ATTR_HEATING_PUMP] - assert data[ATTR_HEATING_STATUS] == 0 - assert data[ATTR_MODULES].module_a == "18.11.58.K1" - assert data[ATTR_MODULES].panel == "18.10.72" - assert data[ATTR_LAMBDA_LEVEL] == 4.0 - assert data[ATTR_PENDING_ALERTS] == 0 - assert data[ATTR_FUEL_LEVEL] == 32 - assert data[ATTR_MIXER_SENSORS] == { - 4: { - ATTR_CURRENT_TEMP: 20.0, - ATTR_TARGET_TEMP: 40, - ATTR_PUMP: False, - }, - } - assert data[ATTR_THERMOSTAT_SENSORS] == { - 0: { - ATTR_STATE: 3, - ATTR_CURRENT_TEMP: 43.5, - ATTR_TARGET_TEMP: 50.0, - ATTR_CONTACTS: True, - ATTR_SCHEDULE: False, - }, - } - - # Test with extra state. - test_message[INDEX_STATE] = 12 - frame = SensorDataMessage(message=test_message) - assert frame.data[ATTR_SENSORS][ATTR_STATE] == DeviceState.STABILIZATION - - # Test with the unknown state. - test_message[INDEX_STATE] = 99 - frame = SensorDataMessage(message=test_message) - assert frame.data[ATTR_SENSORS][ATTR_STATE] == 99 - - -def test_sensor_data_without_fuel_level_and_load( - messages: dict[FrameType, bytearray] -) -> None: - """Test that fuel level and load keys are not present in the device data - if they are unavailable. - """ - test_message = messages[FrameType.MESSAGE_SENSOR_DATA] - for index in (INDEX_FUEL_LEVEL, INDEX_LOAD): - test_message[index] = BYTE_UNDEFINED - - frame = SensorDataMessage(message=test_message) - assert ATTR_FUEL_LEVEL not in frame.data - assert ATTR_LOAD not in frame.data - - -def test_sensor_data_without_lambda_sensor( - messages: dict[FrameType, bytearray] -) -> None: - """Test that lambda sensor dict are not present in the device data - if it is unavailable. - """ - test_message = messages[FrameType.MESSAGE_SENSOR_DATA] - test_message[INDEX_LAMBDA_SENSOR] = BYTE_UNDEFINED - for byte in range(1, 6): - del test_message[INDEX_LAMBDA_SENSOR + byte] - - frame = SensorDataMessage(message=test_message) - assert ATTR_LAMBDA_STATE not in frame.data - assert ATTR_LAMBDA_TARGET not in frame.data - assert ATTR_LAMBDA_LEVEL not in frame.data - - -def test_sensor_data_without_fan_power_and_fuel_consumption( - messages: dict[FrameType, bytearray] -) -> None: - """Test that power, fan power and fuel consumption keys are not - present in the device data if they are unavailable. - """ - test_message = messages[FrameType.MESSAGE_SENSOR_DATA] - for index in (INDEX_FAN_POWER, INDEX_FUEL_CONSUMPTION, INDEX_POWER): - for byte in range(4): - test_message[index + byte] = 0xFF - - frame = SensorDataMessage(message=test_message) - assert ATTR_FAN_POWER not in frame.data - assert ATTR_FUEL_CONSUMPTION not in frame.data - assert ATTR_POWER not in frame.data + result = decoder.decode(frame.message, data=schema["data"])[0] + if regdata["id"] == "unknown_regulator_data_version": + assert ATTR_FRAME_VERSIONS not in result + assert ATTR_REGDATA not in result + else: + assert result[ATTR_FRAME_VERSIONS] == regdata["data"][ATTR_FRAME_VERSIONS] + assert result[ATTR_REGDATA].data == regdata["data"][ATTR_REGDATA] + + +@pytest.mark.parametrize( + "message, data", + load_json_parameters("messages/sensor_data.json"), +) +def test_sensor_data_message(message, data) -> None: + """Test a sensor data message.""" + assert SensorDataMessage(message=message).data == data -def test_current_data_without_thermostats( - sensor_data_without_thermostats: bytearray, -) -> None: - """Test that thermostats key is not present in the device data - if thermostats data is unavailable. - """ - frame = SensorDataMessage(message=sensor_data_without_thermostats) - assert ATTR_THERMOSTAT_SENSORS not in frame.data +def test_sensor_data_message_with_unknown_state() -> None: + """Test a sensor data message with an unknown device state.""" + test_data = load_json_test_data("messages/sensor_data.json")[0] + message = test_data["message"] + message[INDEX_STATE] = 99 + assert SensorDataMessage(message=message).data[ATTR_SENSORS][ATTR_STATE] == 99 diff --git a/tests/frames/test_requests.py b/tests/frames/test_requests.py index d18a0bc9..bc7b4030 100644 --- a/tests/frames/test_requests.py +++ b/tests/frames/test_requests.py @@ -2,7 +2,7 @@ import pytest -from pyplumio.const import DeviceType, FrameType +from pyplumio.const import DeviceType from pyplumio.exceptions import FrameDataError from pyplumio.frames import Request from pyplumio.frames.requests import ( @@ -24,7 +24,7 @@ UIDRequest, ) from pyplumio.frames.responses import DeviceAvailableResponse, ProgramVersionResponse -from pyplumio.helpers.typing import EventDataType +from tests import load_json_parameters def test_base_class_response() -> None: @@ -65,79 +65,85 @@ def test_check_device_response_recipient_and_type() -> None: assert frame.response().recipient == DeviceType.ECONET -def test_parameters(messages: dict[FrameType, bytearray]) -> None: - """Test parameters request bytes.""" - frame = EcomaxParametersRequest() - assert frame.message == messages[FrameType.REQUEST_ECOMAX_PARAMETERS] +@pytest.mark.parametrize( + "message, data", + load_json_parameters("requests/ecomax_control.json"), +) +def test_ecomax_control(message, data) -> None: + """Test an ecoMAX control parameter request.""" + assert EcomaxControlRequest(data=data).message == message -def test_set_parameter( - data: dict[FrameType, EventDataType], messages: dict[FrameType, bytearray] -) -> None: - """Test set parameter request bytes.""" - frame = SetEcomaxParameterRequest(data=data[FrameType.REQUEST_SET_ECOMAX_PARAMETER]) - assert frame.message == messages[FrameType.REQUEST_SET_ECOMAX_PARAMETER] +def test_ecomax_control_without_data() -> None: + """Test an ecoMAX control request without any data.""" + with pytest.raises(FrameDataError): + EcomaxControlRequest() -def test_set_parameter_with_no_data() -> None: - """Test set parameter request with no data.""" - with pytest.raises(FrameDataError): - SetEcomaxParameterRequest() +@pytest.mark.parametrize( + "message, data", + load_json_parameters("requests/ecomax_parameters.json"), +) +def test_ecomax_parameters(message, data) -> None: + """Test an ecoMAX parameters request.""" + assert EcomaxParametersRequest(data=data).message == message -def test_set_mixer_parameter( - data: dict[FrameType, EventDataType], messages: dict[FrameType, bytearray] -) -> None: - """Test set mixer parameter request bytes.""" - frame = SetMixerParameterRequest(data=data[FrameType.REQUEST_SET_MIXER_PARAMETER]) - assert frame.message == messages[FrameType.REQUEST_SET_MIXER_PARAMETER] +@pytest.mark.parametrize( + "message, data", + load_json_parameters("requests/set_ecomax_parameter.json"), +) +def test_set_ecomax_parameter(message, data) -> None: + """Test a set ecoMAX parameter request.""" + assert SetEcomaxParameterRequest(data=data).message == message -def test_set_mixer_parameter_with_no_data() -> None: - """Test set mixer parameter request with no data.""" +def test_set_ecomax_parameter_without_data() -> None: + """Test a set ecoMAX parameter request without any data.""" with pytest.raises(FrameDataError): - SetMixerParameterRequest() + SetEcomaxParameterRequest() -def test_set_thermostat_parameter( - data: dict[FrameType, EventDataType], messages: dict[FrameType, bytearray] -) -> None: - """Test set thermostat parameter request bytes.""" - frame = SetThermostatParameterRequest( - data=data[FrameType.REQUEST_SET_THERMOSTAT_PARAMETER] - ) - assert frame.message == messages[FrameType.REQUEST_SET_THERMOSTAT_PARAMETER] +@pytest.mark.parametrize( + "message, data", + load_json_parameters("requests/set_mixer_parameter.json"), +) +def test_set_mixer_parameter(message, data) -> None: + """Test a set mixer parameter request.""" + assert SetMixerParameterRequest(data=data).message == message -def test_set_thermostat_parameter_with_no_data() -> None: - """Test set thermostat parameter request with no data.""" +def test_set_mixer_parameter_without_data() -> None: + """Test a set mixer parameter request without any data.""" with pytest.raises(FrameDataError): - SetThermostatParameterRequest() + SetMixerParameterRequest() -def test_ecomax_control( - data: dict[FrameType, EventDataType], messages: dict[FrameType, bytearray] -) -> None: - """Test ecoMAX control parameter request bytes.""" - frame = EcomaxControlRequest(data=data[FrameType.REQUEST_ECOMAX_CONTROL]) - assert frame.message == messages[FrameType.REQUEST_ECOMAX_CONTROL] +@pytest.mark.parametrize( + "message, data", + load_json_parameters("requests/set_schedule.json"), +) +def test_set_schedule(message, data) -> None: + """Test a set schedule request bytes.""" + assert SetScheduleRequest(data=data).message == message -def test_ecomax_control_with_no_data() -> None: - """Test ecoMAX control request with no data.""" +def test_set_schedule_without_data() -> None: + """Test a set schedule request without any data.""" with pytest.raises(FrameDataError): - EcomaxControlRequest() + SetScheduleRequest() -def test_set_schedule( - data: dict[FrameType, EventDataType], messages: dict[FrameType, bytearray] -) -> None: - """Test set schedule request bytes.""" - frame = SetScheduleRequest(data=data[FrameType.REQUEST_SET_SCHEDULE]) - assert frame.message == messages[FrameType.REQUEST_SET_SCHEDULE] +@pytest.mark.parametrize( + "message, data", + load_json_parameters("requests/set_thermostat_parameter.json"), +) +def test_set_thermostat_parameter(message, data) -> None: + """Test a set thermostat parameter request.""" + assert SetThermostatParameterRequest(data=data).message == message -def test_set_schedule_with_no_data() -> None: - """Test set schedule request with no data.""" +def test_set_thermostat_parameter_without_data() -> None: + """Test a set thermostat parameter request without any data.""" with pytest.raises(FrameDataError): - SetScheduleRequest() + SetThermostatParameterRequest() diff --git a/tests/frames/test_responses.py b/tests/frames/test_responses.py index bac1f12d..642a81ed 100644 --- a/tests/frames/test_responses.py +++ b/tests/frames/test_responses.py @@ -1,6 +1,8 @@ """Test PyPlumIO response frames.""" -from pyplumio.const import DeviceType, FrameType +import pytest + +from pyplumio.const import DeviceType from pyplumio.frames.responses import ( AlertsResponse, DataSchemaResponse, @@ -13,16 +15,9 @@ ThermostatParametersResponse, UIDResponse, ) -from pyplumio.helpers.typing import EventDataType -from pyplumio.structures.alerts import ATTR_ALERTS -from pyplumio.structures.data_schema import ATTR_SCHEMA -from pyplumio.structures.mixer_parameters import ATTR_MIXER_PARAMETERS -from pyplumio.structures.thermostat_parameters import ( - ATTR_THERMOSTAT_PARAMETERS, - ATTR_THERMOSTAT_PARAMETERS_DECODER, - ATTR_THERMOSTAT_PROFILE, -) +from pyplumio.structures.thermostat_parameters import ATTR_THERMOSTAT_PARAMETERS_DECODER from pyplumio.structures.thermostat_sensors import ATTR_THERMOSTAT_COUNT +from tests import load_json_parameters def test_responses_type() -> None: @@ -42,155 +37,108 @@ def test_responses_type() -> None: assert isinstance(frame, response) -def test_program_version_response( - data: dict[FrameType, EventDataType], messages: dict[FrameType, bytearray] -) -> None: - """Test creating program version message.""" - frame1 = ProgramVersionResponse(data=data[FrameType.RESPONSE_PROGRAM_VERSION]) - frame2 = ProgramVersionResponse( - message=messages[FrameType.RESPONSE_PROGRAM_VERSION] - ) - assert frame1.message == messages[FrameType.RESPONSE_PROGRAM_VERSION] - assert frame2.data == data[FrameType.RESPONSE_PROGRAM_VERSION] - - -def test_device_available_response( - data: dict[FrameType, EventDataType], - messages: dict[FrameType, bytearray], -) -> None: - """Test creating device available message.""" - frame1 = DeviceAvailableResponse(data=data[FrameType.RESPONSE_DEVICE_AVAILABLE]) - frame2 = DeviceAvailableResponse( - message=messages[FrameType.RESPONSE_DEVICE_AVAILABLE] - ) - assert frame1.message == messages[FrameType.RESPONSE_DEVICE_AVAILABLE] - assert frame2.data == data[FrameType.RESPONSE_DEVICE_AVAILABLE] +@pytest.mark.parametrize( + "message, data", + load_json_parameters("responses/alerts.json"), +) +def test_alerts_response(message, data) -> None: + """Test an alerts response.""" + assert AlertsResponse(message=message).data == data + assert not AlertsResponse(data=data).message + + +@pytest.mark.parametrize( + "message, data", + load_json_parameters("responses/data_schema.json"), +) +def test_data_schema_response(message, data) -> None: + """Test a data schema response.""" + assert DataSchemaResponse(message=message).data == data + assert not DataSchemaResponse(data=data).message -def test_uid_response( - data: dict[FrameType, EventDataType], - messages: dict[FrameType, bytearray], -) -> None: - """Test parsing UID message.""" - frame1 = UIDResponse(message=messages[FrameType.RESPONSE_UID]) - frame2 = UIDResponse(data=data[FrameType.RESPONSE_UID]) - assert frame1.data == data[FrameType.RESPONSE_UID] - assert not frame2.message +@pytest.mark.parametrize( + "message, data", + load_json_parameters("responses/device_available.json"), +) +def test_device_available_response(message, data) -> None: + """Test a device available response.""" + assert DeviceAvailableResponse(data=data).message == message + assert DeviceAvailableResponse(message=message).data == data + + +@pytest.mark.parametrize( + "message, data", + load_json_parameters("responses/ecomax_parameters.json"), +) +def test_ecomax_parameters_response(message, data) -> None: + """Test a ecoMAX parameters response.""" + assert EcomaxParametersResponse(message=message).data == data + assert not EcomaxParametersResponse(data=data).message -def test_password_response( - data: dict[FrameType, EventDataType], - messages: dict[FrameType, bytearray], -) -> None: +@pytest.mark.parametrize( + "message, data", + load_json_parameters("responses/mixer_parameters.json"), +) +def test_mixer_parameters_response(message, data) -> None: + """Test a mixer parameters response.""" + assert MixerParametersResponse(message=message).data == data + assert not MixerParametersResponse(data=data).message + + +@pytest.mark.parametrize( + "message, data", + load_json_parameters("responses/password.json"), +) +def test_password_response(message, data) -> None: """Test parsing password message.""" - frame1 = PasswordResponse(message=messages[FrameType.RESPONSE_PASSWORD]) - frame2 = PasswordResponse(data=data[FrameType.RESPONSE_PASSWORD]) - assert frame1.data == data[FrameType.RESPONSE_PASSWORD] - assert not frame2.message - - -def test_ecomax_parameters_response( - data: dict[FrameType, EventDataType], - messages: dict[FrameType, bytearray], -) -> None: - """Test parsing ecoMAX parameters message.""" - frame1 = EcomaxParametersResponse( - message=messages[FrameType.RESPONSE_ECOMAX_PARAMETERS] - ) - frame2 = EcomaxParametersResponse(data=data[FrameType.RESPONSE_ECOMAX_PARAMETERS]) - assert frame1.data == data[FrameType.RESPONSE_ECOMAX_PARAMETERS] - assert not frame2.message - - -def test_mixer_parameters_response( - data: dict[FrameType, EventDataType], - messages: dict[FrameType, bytearray], -) -> None: - """Test parsing message for mixer parameters response.""" - frame1 = MixerParametersResponse( - message=messages[FrameType.RESPONSE_MIXER_PARAMETERS] - ) - frame2 = MixerParametersResponse(data=data[FrameType.RESPONSE_MIXER_PARAMETERS]) - assert frame1.data == data[FrameType.RESPONSE_MIXER_PARAMETERS] - assert not frame2.message - - # Test with empty parameters. - frame1 = MixerParametersResponse(message=bytearray.fromhex("00000201")) - assert frame1.data == {ATTR_MIXER_PARAMETERS: {}} - - -def test_thermostat_parameters_response( - data: dict[FrameType, EventDataType], - messages: dict[FrameType, bytearray], -) -> None: - """Test parsing message for thermostat parameters response.""" - frame = ThermostatParametersResponse( - message=messages[FrameType.RESPONSE_THERMOSTAT_PARAMETERS] - ) + assert PasswordResponse(message=message).data == data + assert not PasswordResponse(data=data).message + + +@pytest.mark.parametrize( + "message, data", + load_json_parameters("responses/program_version.json"), +) +def test_program_version_response(message, data) -> None: + """Test a program version response.""" + assert ProgramVersionResponse(data=data).message == message + assert ProgramVersionResponse(message=message).data == data + + +@pytest.mark.parametrize( + "message, data", + load_json_parameters("responses/schedules.json"), +) +def test_schedules_response(message, data) -> None: + """Test a schedules response.""" + assert SchedulesResponse(message=message).data == data + assert not SchedulesResponse(data=data).message + + +@pytest.mark.parametrize( + "message, data", + load_json_parameters("responses/thermostat_parameters.json"), +) +def test_thermostat_parameters_response(message, data) -> None: + """Test a thermostat parameters response.""" + frame = ThermostatParametersResponse(message=message) decoder = frame.data[ATTR_THERMOSTAT_PARAMETERS_DECODER] - frame_data = decoder.decode( - message=frame.message, - data={ATTR_THERMOSTAT_COUNT: 3}, - )[0] - assert frame_data == data[FrameType.RESPONSE_THERMOSTAT_PARAMETERS] - - -def test_thermostat_parameters_response_with_no_parameters() -> None: - """Test parsing message for the thermosat parameters response - with no parameters.""" - frame = ThermostatParametersResponse( - message=bytearray.fromhex("00000300FFFFFFFFFFFFFFFFFF") + assert ( + decoder.decode( + message=frame.message, + data={ATTR_THERMOSTAT_COUNT: 3}, + )[0] + == data ) - decoder = frame.data[ATTR_THERMOSTAT_PARAMETERS_DECODER] - frame_data = decoder.decode( - message=frame.message, - data={ATTR_THERMOSTAT_COUNT: 2}, - )[0] - assert frame_data == { - ATTR_THERMOSTAT_COUNT: 2, - ATTR_THERMOSTAT_PROFILE: None, - ATTR_THERMOSTAT_PARAMETERS: {}, - } - - -def test_data_schema_response(messages: dict[FrameType, bytearray]) -> None: - """Test parsing message for data schema response.""" - frame = DataSchemaResponse(message=messages[FrameType.RESPONSE_DATA_SCHEMA]) - assert ATTR_SCHEMA in frame.data - assert len(frame.data[ATTR_SCHEMA]) == 257 - - -def test_data_schema_response_with_no_parameters() -> None: - """Test parsing message for data schema with no parameters.""" - frame = DataSchemaResponse(message=bytearray.fromhex("0000")) - assert ATTR_SCHEMA not in frame.data - - -def test_alerts_response( - data: dict[FrameType, EventDataType], - messages: dict[FrameType, bytearray], -) -> None: - """Test alerts response.""" - frame1 = AlertsResponse(message=messages[FrameType.RESPONSE_ALERTS]) - frame2 = AlertsResponse(data=data[FrameType.RESPONSE_ALERTS]) - assert frame1.data == data[FrameType.RESPONSE_ALERTS] - assert not frame2.message - - -def test_alerts_response_with_no_alerts( - data: dict[FrameType, EventDataType], - messages: dict[FrameType, bytearray], -) -> None: - """Test alerts response with no alerts.""" - frame = AlertsResponse(message=bytearray.fromhex("000000")) - assert ATTR_ALERTS not in frame.data - - -def test_schedule_response( - data: dict[FrameType, EventDataType], messages: dict[FrameType, bytearray] -) -> None: - """Test schedule response.""" - frame1 = SchedulesResponse(message=messages[FrameType.RESPONSE_SCHEDULES]) - frame2 = SchedulesResponse(data=data[FrameType.RESPONSE_SCHEDULES]) - assert frame1.data == data[FrameType.RESPONSE_SCHEDULES] - assert not frame2.message + + +@pytest.mark.parametrize( + "message, data", + load_json_parameters("responses/uid.json"), +) +def test_uid_response(message, data) -> None: + """Test an UID response.""" + assert UIDResponse(message=message).data == data + assert not UIDResponse(data=data).message diff --git a/tests/helpers/test_data_types.py b/tests/helpers/test_data_types.py index a3a804fb..7336308c 100644 --- a/tests/helpers/test_data_types.py +++ b/tests/helpers/test_data_types.py @@ -1,5 +1,7 @@ """Contains tests for data types.""" +import pytest + from pyplumio.helpers import data_types @@ -15,71 +17,89 @@ def test_type_unpack() -> None: assert data_type.size == 1 +@pytest.mark.parametrize( + "one, another", + [ + ( + data_types.SignedChar.from_bytes(bytearray([0x16])), + data_types.SignedChar.from_bytes(bytearray([0x16])), + ), + ( + data_types.SignedChar.from_bytes(bytearray([0x16])), + 22, + ), + ], +) +def test_type_eq(one, another): + """Test generic type comparison.""" + assert one == another + + def test_undefined0() -> None: - """Test undefined0 data_type.""" + """Test an undefined0 data type.""" data_type = data_types.Undefined0.from_bytes(bytearray([0x0])) assert data_type.value is None assert data_type.size == 0 def test_signed_char() -> None: - """Test signed char data_type.""" + """Test a signed char data type.""" data_type = data_types.SignedChar.from_bytes(bytearray([0x16])) assert data_type.value == 22 assert data_type.size == 1 def test_short() -> None: - """Test short data_type.""" + """Test a short data type.""" data_type = data_types.Short.from_bytes(bytearray([0xEC, 0xFF])) assert data_type.value == -20 assert data_type.size == 2 def test_int() -> None: - """Test integer data_type.""" + """Test an integer data type.""" data_type = data_types.Int.from_bytes(bytearray([0x01, 0x9A, 0xFF, 0xFF])) assert data_type.value == -26111 assert data_type.size == 4 def test_byte() -> None: - """Test byte data_type.""" + """Test a byte data type.""" data_type = data_types.Byte.from_bytes(bytearray([0x3])) assert data_type.value == 3 assert data_type.size == 1 def test_ushort() -> None: - """Test unsigned short data_type.""" + """Test an unsigned short data type.""" data_type = data_types.UnsignedShort.from_bytes(bytearray([0x2A, 0x01])) assert data_type.value == 298 assert data_type.size == 2 def test_uint() -> None: - """Test unsigned integer data_type.""" + """Test an unsigned integer data type.""" data_type = data_types.UnsignedInt.from_bytes(bytearray([0x9A, 0x3F, 0x0, 0x0])) assert data_type.value == 16282 assert data_type.size == 4 def test_float() -> None: - """Test float data_type.""" + """Test a float data type.""" data_type = data_types.Float.from_bytes(bytearray([0x0, 0x0, 0x40, 0x41])) assert data_type.value == 12.0 assert data_type.size == 4 def test_undefined8() -> None: - """Test undefined8 data_type.""" + """Test a undefined8 data type.""" data_type = data_types.Undefined8.from_bytes(bytearray([0x0])) assert data_type.value is None assert data_type.size == 0 def test_double() -> None: - """Test double data_type.""" + """Test a double data type.""" data_type = data_types.Double.from_bytes( bytearray([0x3D, 0x0A, 0xD7, 0xA3, 0x70, 0x3D, 0x28, 0x40]) ) @@ -88,7 +108,7 @@ def test_double() -> None: def test_boolean() -> None: - """Test boolean data_type.""" + """Test a boolean data type.""" data_type = data_types.Boolean.from_bytes(bytearray([0x55])) for index, value in enumerate([1, 0, 1, 0, 1, 0, 1, 0]): next_bit = data_type.next(index) @@ -102,14 +122,14 @@ def test_boolean() -> None: def test_boolean_unpack() -> None: - """Test boolean unpack.""" + """Test a boolean unpack.""" data_type = data_types.Boolean.from_bytes(bytearray([0x55])) assert data_type.value assert data_type.size == 0 def test_int64() -> None: - """Test 64 bit integer data_type.""" + """Test a 64 bit integer data type.""" data_type = data_types.Int64.from_bytes( bytearray([0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xA4, 0x32, 0xEB]) ) @@ -118,7 +138,7 @@ def test_int64() -> None: def test_uint64() -> None: - """Test unsigned 64 bit integer data_type.""" + """Test a unsigned 64 bit integer data type.""" data_type = data_types.UInt64.from_bytes( bytearray([0x45, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55]) ) @@ -127,14 +147,14 @@ def test_uint64() -> None: def test_ipv4() -> None: - """Test IPv4 data_type.""" + """Test an IPv4 data type.""" data_type = data_types.IPv4.from_bytes(bytearray([0x7F, 0x00, 0x00, 0x01])) assert data_type.value == "127.0.0.1" assert data_type.size == 4 def test_ipv6() -> None: - """Test IPv6 data_type.""" + """Test an IPv6 data type.""" data_type = data_types.IPv6.from_bytes( bytearray( [ @@ -162,7 +182,7 @@ def test_ipv6() -> None: def test_string() -> None: - """Test string data_type.""" + """Test a string data type.""" data_type = data_types.String.from_bytes(b"test\x00") assert data_type.value == "test" assert data_type.size == 5 diff --git a/tests/test_devices.py b/tests/test_devices.py index 028b465e..e3e38d42 100644 --- a/tests/test_devices.py +++ b/tests/test_devices.py @@ -55,7 +55,6 @@ ThermostatParametersResponse, ) from pyplumio.helpers.schedule import Schedule -from pyplumio.helpers.typing import EventDataType from pyplumio.structures.ecomax_parameters import ( ATTR_ECOMAX_CONTROL, EcomaxBinaryParameter, @@ -86,6 +85,7 @@ ATTR_THERMOSTAT_COUNT, ATTR_THERMOSTAT_SENSORS, ) +from tests import load_json_parameters, load_json_test_data UNKNOWN_DEVICE: int = 99 UNKNOWN_FRAME: int = 99 @@ -151,13 +151,13 @@ async def test_async_setup_error() -> None: assert mock_request.await_count == len(DATA_FRAME_TYPES) -async def test_frame_versions_update( - ecomax: EcoMAX, messages: dict[FrameType, bytearray] -) -> None: +async def test_frame_versions_update(ecomax: EcoMAX) -> None: """Test requesting updated frames.""" with patch("asyncio.Queue.put_nowait") as mock_put_nowait: ecomax.handle_frame( - SensorDataMessage(message=messages[FrameType.MESSAGE_SENSOR_DATA]) + SensorDataMessage( + message=load_json_test_data("messages/sensor_data.json")[0]["message"] + ) ) await ecomax.wait_until_done() @@ -171,12 +171,12 @@ async def test_frame_versions_update( ) -async def test_ecomax_data_callbacks( - ecomax: EcoMAX, messages: dict[FrameType, bytearray] -) -> None: +async def test_ecomax_data_callbacks(ecomax: EcoMAX) -> None: """Test callbacks that are dispatched on received data frames.""" ecomax.handle_frame( - SensorDataMessage(message=messages[FrameType.MESSAGE_SENSOR_DATA]) + SensorDataMessage( + message=load_json_test_data("messages/sensor_data.json")[0]["message"] + ) ) await ecomax.wait_until_done() heating_target = await ecomax.get("heating_target") @@ -189,12 +189,14 @@ async def test_ecomax_data_callbacks( assert ecomax_control.request.data == {ATTR_VALUE: 0} -async def test_ecomax_parameters_callbacks( - ecomax: EcoMAX, messages: dict[FrameType, bytearray] -) -> None: +async def test_ecomax_parameters_callbacks(ecomax: EcoMAX) -> None: """Test callbacks that are dispatched on received parameter frames.""" ecomax.handle_frame( - EcomaxParametersResponse(message=messages[FrameType.RESPONSE_ECOMAX_PARAMETERS]) + EcomaxParametersResponse( + message=load_json_test_data("responses/ecomax_parameters.json")[0][ + "message" + ] + ) ) await ecomax.wait_until_done() fuzzy_logic = await ecomax.get("fuzzy_logic") @@ -253,44 +255,41 @@ async def test_fuel_consumption_callbacks(mock_time, caplog) -> None: assert "Skipping outdated fuel consumption" in caplog.text -async def test_regdata_callbacks( - ecomax: EcoMAX, messages: dict[FrameType, bytearray] -) -> None: +async def test_regdata_callbacks(ecomax: EcoMAX) -> None: """Test callbacks that are dispatched on received regdata.""" ecomax.handle_frame( - DataSchemaResponse(message=messages[FrameType.RESPONSE_DATA_SCHEMA]) + DataSchemaResponse( + message=load_json_test_data("responses/data_schema.json")[1]["message"] + ) ) ecomax.handle_frame( - RegulatorDataMessage(message=messages[FrameType.MESSAGE_REGULATOR_DATA]) + RegulatorDataMessage( + message=load_json_test_data("messages/regulator_data.json")[1]["message"] + ) ) await ecomax.wait_until_done() regdata = await ecomax.get(ATTR_REGDATA) assert isinstance(regdata, RegulatorData) - assert regdata.data[1792] == 0 - assert round(regdata.data[1024], 1) == 22.4 - assert regdata.data[1280] == 41 - assert regdata.data[183] == "0.0.0.0" - assert regdata.data[184] == "255.255.255.0" -async def test_regdata_callbacks_without_schema( - ecomax: EcoMAX, messages: dict[FrameType, bytearray] -) -> None: +async def test_regdata_callbacks_without_schema(ecomax: EcoMAX) -> None: """Test callbacks that are dispatched on received regdata.""" ecomax.handle_frame( - RegulatorDataMessage(message=messages[FrameType.MESSAGE_REGULATOR_DATA]) + RegulatorDataMessage( + message=load_json_test_data("messages/regulator_data.json")[1]["message"] + ) ) await ecomax.wait_until_done() assert ATTR_FRAME_VERSIONS in ecomax.data assert ATTR_REGDATA not in ecomax.data -async def test_mixer_sensors_callbacks( - ecomax: EcoMAX, messages: dict[FrameType, bytearray] -) -> None: +async def test_mixer_sensors_callbacks(ecomax: EcoMAX) -> None: """Test callbacks that are dispatched on receiving mixer sensors info.""" ecomax.handle_frame( - SensorDataMessage(message=messages[FrameType.MESSAGE_SENSOR_DATA]) + SensorDataMessage( + message=load_json_test_data("messages/sensor_data.json")[0]["message"] + ) ) await ecomax.wait_until_done() mixers = await ecomax.get(ATTR_MIXERS) @@ -314,12 +313,12 @@ async def test_mixer_sensors_callbacks_without_mixers(ecomax: EcoMAX) -> None: assert not await ecomax.get(ATTR_MIXER_SENSORS) -async def test_thermostat_sensors_callbacks( - ecomax: EcoMAX, messages: dict[FrameType, bytearray] -) -> None: +async def test_thermostat_sensors_callbacks(ecomax: EcoMAX) -> None: """Test callbacks that are dispatched on receiving thermostat sensors info.""" ecomax.handle_frame( - SensorDataMessage(message=messages[FrameType.MESSAGE_SENSOR_DATA]) + SensorDataMessage( + message=load_json_test_data("messages/sensor_data.json")[0]["message"] + ) ) await ecomax.wait_until_done() thermostats = await ecomax.get(ATTR_THERMOSTATS) @@ -336,7 +335,7 @@ async def test_thermostat_sensors_callbacks( "thermostat_sensors": True, } thermostat_count = await ecomax.get(ATTR_THERMOSTAT_COUNT) - assert thermostat_count == 1 + assert thermostat_count == 2 async def test_thermostat_sensors_callbacks_without_thermostats(ecomax: EcoMAX) -> None: @@ -349,14 +348,14 @@ async def test_thermostat_sensors_callbacks_without_thermostats(ecomax: EcoMAX) assert not await ecomax.get(ATTR_THERMOSTAT_SENSORS) -async def test_thermostat_parameters_callbacks( - ecomax: EcoMAX, messages: dict[FrameType, bytearray] -) -> None: +async def test_thermostat_parameters_callbacks(ecomax: EcoMAX) -> None: """Test callbacks that are dispatched on receiving thermostat parameters.""" ecomax.handle_frame(Response(data={ATTR_THERMOSTAT_COUNT: 3})) ecomax.handle_frame( ThermostatParametersResponse( - message=messages[FrameType.RESPONSE_THERMOSTAT_PARAMETERS] + message=load_json_test_data("responses/thermostat_parameters.json")[1][ + "message" + ] ) ) await ecomax.wait_until_done() @@ -381,14 +380,16 @@ async def test_thermostat_parameters_callbacks( async def test_thermostat_parameters_callbacks_without_thermostats( - ecomax: EcoMAX, messages: dict[FrameType, bytearray] + ecomax: EcoMAX, ) -> None: """Test callbacks that are dispatched on receiving thermostat parameters without any thermostats.""" ecomax.handle_frame(Response(data={ATTR_THERMOSTAT_COUNT: 0})) ecomax.handle_frame( ThermostatParametersResponse( - message=messages[FrameType.RESPONSE_THERMOSTAT_PARAMETERS] + message=load_json_test_data("responses/thermostat_parameters.json")[0][ + "message" + ] ) ) await ecomax.wait_until_done() @@ -397,14 +398,14 @@ async def test_thermostat_parameters_callbacks_without_thermostats( assert thermostat_profile is None -async def test_thermostat_profile_callbacks( - ecomax: EcoMAX, messages: dict[FrameType, bytearray] -) -> None: +async def test_thermostat_profile_callbacks(ecomax: EcoMAX) -> None: """Test callbacks that are dispatched on receiving thermostat profile.""" ecomax.handle_frame(Response(data={ATTR_THERMOSTAT_COUNT: 3})) ecomax.handle_frame( ThermostatParametersResponse( - message=messages[FrameType.RESPONSE_THERMOSTAT_PARAMETERS] + message=load_json_test_data("responses/thermostat_parameters.json")[1][ + "message" + ] ) ) await ecomax.wait_until_done() @@ -426,12 +427,12 @@ async def test_thermostat_profile_callbacks( assert await ecomax.get(ATTR_THERMOSTAT_PROFILE) is None -async def test_mixer_parameters_callbacks( - ecomax: EcoMAX, messages: dict[FrameType, bytearray] -) -> None: +async def test_mixer_parameters_callbacks(ecomax: EcoMAX) -> None: """Test callbacks that are dispatched on receiving mixer parameters.""" ecomax.handle_frame( - MixerParametersResponse(message=messages[FrameType.RESPONSE_MIXER_PARAMETERS]) + MixerParametersResponse( + message=load_json_test_data("responses/mixer_parameters.json")[1]["message"] + ) ) await ecomax.wait_until_done() mixers = await ecomax.get(ATTR_MIXERS) @@ -469,15 +470,13 @@ async def test_mixer_parameters_callbacks_without_mixers(ecomax: EcoMAX) -> None assert not await ecomax.get(ATTR_MIXER_PARAMETERS) -async def test_schedule_callback( - ecomax: EcoMAX, - messages: dict[FrameType, bytearray], - data: dict[FrameType, EventDataType], -) -> None: +@pytest.mark.parametrize( + "message, data", + load_json_parameters("responses/schedules.json"), +) +async def test_schedule_callback(ecomax: EcoMAX, message, data) -> None: """Test callback that is dispatched on receiving schedule data.""" - ecomax.handle_frame( - SchedulesResponse(message=messages[FrameType.RESPONSE_SCHEDULES]) - ) + ecomax.handle_frame(SchedulesResponse(message=message)) schedules = await ecomax.get(ATTR_SCHEDULES) assert len(schedules) == 1 heating_schedule = schedules["heating"] @@ -500,7 +499,7 @@ async def test_schedule_callback( ATTR_SCHEDULE: ecomax.data[ATTR_SCHEDULES]["heating"], } - schedule_data = data[FrameType.RESPONSE_SCHEDULES][ATTR_SCHEDULES][0][1] + schedule_data = data[ATTR_SCHEDULES][0][1] for index, weekday in enumerate( ("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday") ): @@ -533,19 +532,27 @@ async def test_request_error(ecomax: EcoMAX) -> None: @patch("pyplumio.helpers.parameter.Parameter.is_changed", False) -async def test_set(ecomax: EcoMAX, messages: dict[FrameType, bytearray]) -> None: +async def test_set(ecomax: EcoMAX) -> None: """Test setting parameter value via set helper.""" ecomax.handle_frame( - EcomaxParametersResponse(message=messages[FrameType.RESPONSE_ECOMAX_PARAMETERS]) + EcomaxParametersResponse( + message=load_json_test_data("responses/ecomax_parameters.json")[0][ + "message" + ] + ) ) ecomax.handle_frame(Response(data={ATTR_THERMOSTAT_COUNT: 3})) ecomax.handle_frame( ThermostatParametersResponse( - message=messages[FrameType.RESPONSE_THERMOSTAT_PARAMETERS] + message=load_json_test_data("responses/thermostat_parameters.json")[1][ + "message" + ] ) ) ecomax.handle_frame( - MixerParametersResponse(message=messages[FrameType.RESPONSE_MIXER_PARAMETERS]) + MixerParametersResponse( + message=load_json_test_data("responses/mixer_parameters.json")[1]["message"] + ) ) # Test setting an ecomax parameter. @@ -617,10 +624,12 @@ async def test_turn_off_nowait(mock_create_task, mock_turn_off, ecomax: EcoMAX) mock_turn_off.assert_called_once() -async def test_shutdown(ecomax: EcoMAX, messages: dict[FrameType, bytearray]) -> None: +async def test_shutdown(ecomax: EcoMAX) -> None: """Test device tasks shutdown.""" ecomax.handle_frame( - SensorDataMessage(message=messages[FrameType.MESSAGE_SENSOR_DATA]) + SensorDataMessage( + message=load_json_test_data("messages/sensor_data.json")[0]["message"] + ) ) await ecomax.wait_until_done() diff --git a/tests/testdata/messages/regulator_data.json b/tests/testdata/messages/regulator_data.json new file mode 100644 index 00000000..b6b1422a --- /dev/null +++ b/tests/testdata/messages/regulator_data.json @@ -0,0 +1,97 @@ +[ + { + "id": "unknown_regulator_data_version", + "message": { + "items": [ + "62640002" + ], + "__bytearray__": true + }, + "data": {} + }, + { + "id": "EM350P2_regulator_data", + "message": { + "items": [ + "62640001075500005400006167013D9ED236010064010040000007010050F53142EDD52140C85", + "1E441B3474442847E5E4220BE43C3000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000037000014332400" + ], + "__bytearray__": true + }, + "data": { + "frame_versions": { + "85": 0, + "84": 0, + "97": 359, + "61": 53918, + "54": 1, + "100": 1, + "64": 0 + }, + "regdata": { + "1792": 7, + "1536": true, + "1538": false, + "1542": false, + "1541": false, + "5": false, + "3": false, + "1543": false, + "1544": false, + "1545": false, + "1546": false, + "1547": false, + "1548": false, + "1549": false, + "2": false, + "6": false, + "1024": 44.48956298828125, + "1027": 2.528681993484497, + "1026": 28.539932250976562, + "1030": 49.07001876831055, + "1025": 55.62355041503906, + "29": 0.0, + "1028": 0.0, + "1032": 0.0, + "1031": 0.0, + "1029": 0.0, + "25": 0.0, + "27": 0.0, + "1280": 55, + "1283": 0, + "1282": 0, + "1287": 20, + "1281": 51, + "1288": 36, + "2048": 0 + } + } + }, + { + "id": "incomplete_boolean", + "message": { + "items": [ + "62640001075500005400006167013D9ED23601006401004000000702" + ], + "__bytearray__": true + }, + "data": { + "frame_versions": { + "85": 0, + "84": 0, + "97": 359, + "61": 53918, + "54": 1, + "100": 1, + "64": 0 + }, + "regdata": { + "1536": true, + "1537": true, + "1538": true, + "1792": 2 + } + } + } +] \ No newline at end of file diff --git a/tests/testdata/messages/sensor_data.json b/tests/testdata/messages/sensor_data.json new file mode 100644 index 00000000..01610453 --- /dev/null +++ b/tests/testdata/messages/sensor_data.json @@ -0,0 +1,175 @@ +[ + { + "id": "full_sensor_data", + "message": { + "items": [ + "0755F7B15420BE5698FA3601003802003901003D18310000000000FF0300000900D012B34101F", + "FFFFFFF02FFFFFFFF03FFFFFFFF04FFFFFFFF05FFFFFFFF060000000007FFFFFFFF08FFFFFFFF", + "29002D800020000000000000000000000000000001120B3A4B01FFFFFFFF120A4801022800050", + "20300002E42000048420200000E420000000005FFFFFFFF28000800FFFFFFFF28000800FFFFFF", + "FF28000800FFFFFFFF280008000000A04128000800" + ], + "__bytearray__": true + }, + "data": { + "sensors": { + "frame_versions": { + "85": 45559, + "84": 48672, + "86": 64152, + "54": 1, + "56": 2, + "57": 1, + "61": 12568 + }, + "state": { + "value": 0, + "__module__": "pyplumio.const", + "__class__": "DeviceState" + }, + "fan": false, + "feeder": false, + "heating_pump": false, + "water_heater_pump": false, + "circulation_pump": false, + "lighter": false, + "alarm": false, + "outer_boiler": false, + "fan2_exhaust": false, + "feeder2": false, + "outer_feeder": false, + "solar_pump": false, + "fireplace_pump": false, + "gcz_contact": false, + "blow_fan1": false, + "blow_fan2": false, + "heating_pump_flag": true, + "water_heater_pump_flag": true, + "circulation_pump_flag": true, + "solar_pump_flag": false, + "heating_temp": 22.384185791015625, + "optical_temp": 0.0, + "heating_target": 41, + "heating_status": 0, + "water_heater_target": 45, + "water_heater_status": 128, + "pending_alerts": 0, + "fuel_level": 32, + "transmission": 0, + "fan_power": 0.0, + "load": 0, + "power": 0.0, + "fuel_consumption": 0.0, + "thermostat": 1, + "modules": { + "module_a": "18.11.58.K1", + "module_b": null, + "module_c": null, + "ecolambda": null, + "ecoster": null, + "panel": "18.10.72", + "__module__": "pyplumio.structures.modules", + "__class__": "ConnectedModules" + }, + "lambda_level": 4.0, + "lambda_state": 1, + "lambda_target": 2, + "thermostat_sensors": { + "0": { + "state": 3, + "current_temp": 43.5, + "target_temp": 50.0, + "contacts": true, + "schedule": false + } + }, + "thermostat_count": 2, + "mixer_sensors": { + "4": { + "current_temp": 20.0, + "target_temp": 40, + "pump": false + } + }, + "mixer_count": 5 + } + } + }, + { + "id": "short_sensor_data_without_thermostats", + "message": { + "items": [ + "0755F7B15420BE5698FA3601003802003901003D18310C00000000FF0300000900D012B34101F", + "FFFFFFF02FFFFFFFF03FFFFFFFF04FFFFFFFF05FFFFFFFF060000000007FFFFFFFF08FFFFFFFF", + "29002D8000FF00FFFFFFFFFFFFFFFFFFFFFFFFFF01120B3A4B01FFFFFFFF120A48FFFF05FFFFF", + "FFF28000800FFFFFFFF28000800FFFFFFFF28000800FFFFFFFF280008000000A04128000800" + ], + "__bytearray__": true + }, + "data": { + "sensors": { + "frame_versions": { + "85": 45559, + "84": 48672, + "86": 64152, + "54": 1, + "56": 2, + "57": 1, + "61": 12568 + }, + "state": { + "value": 1, + "__module__": "pyplumio.const", + "__class__": "DeviceState" + }, + "fan": false, + "feeder": false, + "heating_pump": false, + "water_heater_pump": false, + "circulation_pump": false, + "lighter": false, + "alarm": false, + "outer_boiler": false, + "fan2_exhaust": false, + "feeder2": false, + "outer_feeder": false, + "solar_pump": false, + "fireplace_pump": false, + "gcz_contact": false, + "blow_fan1": false, + "blow_fan2": false, + "heating_pump_flag": true, + "water_heater_pump_flag": true, + "circulation_pump_flag": true, + "solar_pump_flag": false, + "heating_temp": 22.384185791015625, + "optical_temp": 0.0, + "heating_target": 41, + "heating_status": 0, + "water_heater_target": 45, + "water_heater_status": 128, + "pending_alerts": 0, + "transmission": 0, + "thermostat": 1, + "modules": { + "module_a": "18.11.58.K1", + "module_b": null, + "module_c": null, + "ecolambda": null, + "ecoster": null, + "panel": "18.10.72", + "__module__": "pyplumio.structures.modules", + "__class__": "ConnectedModules" + }, + "mixer_sensors": { + "4": { + "current_temp": 20.0, + "target_temp": 40, + "pump": false + } + }, + "mixer_count": 5 + } + } + } +] \ No newline at end of file diff --git a/tests/testdata/requests/ecomax_control.json b/tests/testdata/requests/ecomax_control.json new file mode 100644 index 00000000..ea08771b --- /dev/null +++ b/tests/testdata/requests/ecomax_control.json @@ -0,0 +1,26 @@ +[ + { + "id": "ecomax_control_turn_on", + "message": { + "items": [ + "01" + ], + "__bytearray__": true + }, + "data": { + "value": 1 + } + }, + { + "id": "ecomax_control_turn_off", + "message": { + "items": [ + "00" + ], + "__bytearray__": true + }, + "data": { + "value": 0 + } + } +] \ No newline at end of file diff --git a/tests/testdata/requests/ecomax_parameters.json b/tests/testdata/requests/ecomax_parameters.json new file mode 100644 index 00000000..3675b71c --- /dev/null +++ b/tests/testdata/requests/ecomax_parameters.json @@ -0,0 +1,41 @@ +[ + { + "id": "get_ecomax_parameters_from_zero_no_limit", + "message": { + "items": [ + "FF00" + ], + "__bytearray__": true + }, + "data": { + "index": 0, + "count": 255 + } + }, + { + "id": "get_ecomax_parameters_from_0_limit_10", + "message": { + "items": [ + "0A00" + ], + "__bytearray__": true + }, + "data": { + "index": 0, + "count": 10 + } + }, + { + "id": "get_ecomax_parameters_from_50_limit_10", + "message": { + "items": [ + "0A32" + ], + "__bytearray__": true + }, + "data": { + "index": 50, + "count": 10 + } + } +] \ No newline at end of file diff --git a/tests/testdata/requests/set_ecomax_parameter.json b/tests/testdata/requests/set_ecomax_parameter.json new file mode 100644 index 00000000..626f2a8b --- /dev/null +++ b/tests/testdata/requests/set_ecomax_parameter.json @@ -0,0 +1,15 @@ +[ + { + "id": "set_airflow_power_100_to_80", + "message": { + "items": [ + "0050" + ], + "__bytearray__": true + }, + "data": { + "index": 0, + "value": 80 + } + } +] \ No newline at end of file diff --git a/tests/testdata/requests/set_mixer_parameter.json b/tests/testdata/requests/set_mixer_parameter.json new file mode 100644 index 00000000..7fb24deb --- /dev/null +++ b/tests/testdata/requests/set_mixer_parameter.json @@ -0,0 +1,30 @@ +[ + { + "id": "set_mixer_0_target_temp_to_40", + "message": { + "items": [ + "000028" + ], + "__bytearray__": true + }, + "data": { + "index": 0, + "value": 40, + "device_index": 0 + } + }, + { + "id": "set_mixer_1_min_target_temp_to_30", + "message": { + "items": [ + "01001E" + ], + "__bytearray__": true + }, + "data": { + "index": 0, + "value": 30, + "device_index": 1 + } + } +] \ No newline at end of file diff --git a/tests/testdata/requests/set_schedule.json b/tests/testdata/requests/set_schedule.json new file mode 100644 index 00000000..0a53ab6d --- /dev/null +++ b/tests/testdata/requests/set_schedule.json @@ -0,0 +1,369 @@ +[ + { + "id": "set_heating_schedule", + "message": { + "items": [ + "010000050000FFFFFFFE0000FFFFFFFE0000FFFFFFFE0000FFFFFFFE0000FFFFFFFE0000FFFFF", + "FFE0000FFFFFFFE" + ], + "__bytearray__": true + }, + "data": { + "type": "heating", + "switch": 0, + "parameter": 5, + "schedule": [ + [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false + ], + [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false + ], + [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false + ], + [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false + ], + [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false + ], + [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false + ], + [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false + ] + ] + } + } +] \ No newline at end of file diff --git a/tests/testdata/requests/set_thermostat_parameter.json b/tests/testdata/requests/set_thermostat_parameter.json new file mode 100644 index 00000000..3b63f389 --- /dev/null +++ b/tests/testdata/requests/set_thermostat_parameter.json @@ -0,0 +1,32 @@ +[ + { + "id": "set_thermostat_0_correction_to_5", + "message": { + "items": [ + "0305" + ], + "__bytearray__": true + }, + "data": { + "index": 3, + "value": 5, + "offset": 0, + "size": 1 + } + }, + { + "id": "set_thermostat_1_party_target_temp_to_42", + "message": { + "items": [ + "0D2A00" + ], + "__bytearray__": true + }, + "data": { + "index": 1, + "value": 42, + "offset": 12, + "size": 2 + } + } +] \ No newline at end of file diff --git a/tests/testdata/responses/alerts.json b/tests/testdata/responses/alerts.json new file mode 100644 index 00000000..7ca59e73 --- /dev/null +++ b/tests/testdata/responses/alerts.json @@ -0,0 +1,68 @@ +[ + { + "id": "empty_alerts", + "message": { + "items": [ + "000000" + ], + "__bytearray__": true + }, + "data": {} + }, + { + "id": "alerts", + "message": { + "items": [ + "6400021A5493382B9B94382B009C97372B00000000" + ], + "__bytearray__": true + }, + "data": { + "alerts": [ + { + "code": 26, + "from_dt": { + "year": 2022, + "month": 7, + "day": 23, + "hour": 16, + "minute": 27, + "__module__": "datetime", + "__class__": "datetime" + }, + "to_dt": { + "year": 2022, + "month": 7, + "day": 23, + "hour": 16, + "minute": 32, + "second": 27, + "__module__": "datetime", + "__class__": "datetime" + }, + "__module__": "pyplumio.structures.alerts", + "__class__": "Alert" + }, + { + "code": { + "value": 0, + "__module__": "pyplumio.const", + "__class__": "AlertType" + }, + "from_dt": { + "year": 2022, + "month": 7, + "day": 22, + "hour": 22, + "minute": 33, + "__module__": "datetime", + "__class__": "datetime" + }, + "to_dt": null, + "__module__": "pyplumio.structures.alerts", + "__class__": "Alert" + } + ] + } + } +] \ No newline at end of file diff --git a/tests/testdata/responses/data_schema.json b/tests/testdata/responses/data_schema.json new file mode 100644 index 00000000..a8598ae7 --- /dev/null +++ b/tests/testdata/responses/data_schema.json @@ -0,0 +1,481 @@ +[ + { + "id": "empty_data_schema", + "message": { + "items": [ + "0000" + ], + "__bytearray__": true + }, + "data": {} + }, + { + "id": "EM350P2_data_schema", + "message": { + "items": [ + "28000400070A00060A02060A06060A05060A05000A03000A07060A08060A09060A0A060A0B060", + "A0C060A0D060A02000A06000A0600070004070304070204070604070104071D00070404070804", + "070704070504071900071B00071D00071D00071D00071D0004000504030504020504070504010", + "5040805040008" + ], + "__bytearray__": true + }, + "data": { + "schema": [ + { + "items": [ + 1792, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Byte" + } + ], + "__tuple__": true + }, + { + "items": [ + 1536, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 1538, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 1542, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 1541, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 5, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 3, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 1543, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 1544, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 1545, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 1546, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 1547, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 1548, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 1549, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 2, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 6, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 6, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 1024, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Float" + } + ], + "__tuple__": true + }, + { + "items": [ + 1027, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Float" + } + ], + "__tuple__": true + }, + { + "items": [ + 1026, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Float" + } + ], + "__tuple__": true + }, + { + "items": [ + 1030, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Float" + } + ], + "__tuple__": true + }, + { + "items": [ + 1025, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Float" + } + ], + "__tuple__": true + }, + { + "items": [ + 29, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Float" + } + ], + "__tuple__": true + }, + { + "items": [ + 1028, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Float" + } + ], + "__tuple__": true + }, + { + "items": [ + 1032, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Float" + } + ], + "__tuple__": true + }, + { + "items": [ + 1031, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Float" + } + ], + "__tuple__": true + }, + { + "items": [ + 1029, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Float" + } + ], + "__tuple__": true + }, + { + "items": [ + 25, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Float" + } + ], + "__tuple__": true + }, + { + "items": [ + 27, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Float" + } + ], + "__tuple__": true + }, + { + "items": [ + 29, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Float" + } + ], + "__tuple__": true + }, + { + "items": [ + 29, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Float" + } + ], + "__tuple__": true + }, + { + "items": [ + 29, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Float" + } + ], + "__tuple__": true + }, + { + "items": [ + 29, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Float" + } + ], + "__tuple__": true + }, + { + "items": [ + 1280, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Byte" + } + ], + "__tuple__": true + }, + { + "items": [ + 1283, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Byte" + } + ], + "__tuple__": true + }, + { + "items": [ + 1282, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Byte" + } + ], + "__tuple__": true + }, + { + "items": [ + 1287, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Byte" + } + ], + "__tuple__": true + }, + { + "items": [ + 1281, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Byte" + } + ], + "__tuple__": true + }, + { + "items": [ + 1288, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Byte" + } + ], + "__tuple__": true + }, + { + "items": [ + 2048, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Byte" + } + ], + "__tuple__": true + } + ] + } + }, + { + "id": "incomplete_boolean", + "message": { + "items": [ + "04000A02060A00060A0106040007" + ], + "__bytearray__": true + }, + "data": { + "schema": [ + { + "items": [ + 1538, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 1536, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 1537, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Boolean" + } + ], + "__tuple__": true + }, + { + "items": [ + 1792, + { + "__module__": "pyplumio.helpers.data_types", + "__class__": "Byte" + } + ], + "__tuple__": true + } + ] + } + } +] \ No newline at end of file diff --git a/tests/testdata/responses/device_available.json b/tests/testdata/responses/device_available.json new file mode 100644 index 00000000..97db1a06 --- /dev/null +++ b/tests/testdata/responses/device_available.json @@ -0,0 +1,35 @@ +[ + { + "id": "econet_device_available", + "message": { + "items": [ + "01C0A80102FFFFFF00C0A8010101C0A80202FFFFFF00C0A802010101640100000000057465737", + "473" + ], + "__bytearray__": true + }, + "data": { + "network": { + "eth": { + "ip": "192.168.1.2", + "netmask": "255.255.255.0", + "gateway": "192.168.1.1", + "status": true, + "__module__": "pyplumio.structures.network_info", + "__class__": "EthernetParameters" + }, + "wlan": { + "ip": "192.168.2.2", + "netmask": "255.255.255.0", + "gateway": "192.168.2.1", + "status": true, + "ssid": "tests", + "__module__": "pyplumio.structures.network_info", + "__class__": "WirelessParameters" + }, + "__module__": "pyplumio.structures.network_info", + "__class__": "NetworkInfo" + } + } + } +] \ No newline at end of file diff --git a/tests/testdata/responses/ecomax_parameters.json b/tests/testdata/responses/ecomax_parameters.json new file mode 100644 index 00000000..d13f07ea --- /dev/null +++ b/tests/testdata/responses/ecomax_parameters.json @@ -0,0 +1,714 @@ +[ + { + "id": "EM350P2_parameters", + "message": { + "items": [ + "00008B3D3D643C293C28143BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "FFFFFFFFFFFFF1401FA03011E01011E05011E01000100003C3C0064FFFFFFFFFFFF140A64FFFF", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1E146404011E000", + "0640801FA3228550A0A1E1E1432FFFFFFFFFFFF0A0AF0FFFFFFFFFFFF0F0A14FFFFFFFFFFFF32", + "2896FFFFFFFFFFFF02010F03010A28143CFFFFFFFFFFFFFFFFFFFFFFFF3C01FA1E1432FFFFFFF", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7D01FAFFFFFF0201642F01F", + "A0A0A1EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF413250321E50503C5A321E", + "50000063FFFFFFFFFFFF05030F0000010D0128140028FFFFFFFFFFFF00000105001EFFFFFF5A5", + "55F3C285AFFFFFFFFFFFFFFFFFF3328462814374628500200020A011E00000100000210051E0A", + "010F030063FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + ], + "__bytearray__": true + }, + "data": { + "ecomax_parameters": [ + { + "items": [ + 0, + { + "value": 61, + "min_value": 61, + "max_value": 100, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 1, + { + "value": 60, + "min_value": 41, + "max_value": 60, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 2, + { + "value": 40, + "min_value": 20, + "max_value": 59, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 14, + { + "value": 20, + "min_value": 1, + "max_value": 250, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 15, + { + "value": 3, + "min_value": 1, + "max_value": 30, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 16, + { + "value": 1, + "min_value": 1, + "max_value": 30, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 17, + { + "value": 5, + "min_value": 1, + "max_value": 30, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 18, + { + "value": 1, + "min_value": 0, + "max_value": 1, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 19, + { + "value": 0, + "min_value": 0, + "max_value": 60, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 20, + { + "value": 60, + "min_value": 0, + "max_value": 100, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 23, + { + "value": 20, + "min_value": 10, + "max_value": 100, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 35, + { + "value": 30, + "min_value": 20, + "max_value": 100, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 36, + { + "value": 4, + "min_value": 1, + "max_value": 30, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 37, + { + "value": 0, + "min_value": 0, + "max_value": 100, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 38, + { + "value": 8, + "min_value": 1, + "max_value": 250, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 39, + { + "value": 50, + "min_value": 40, + "max_value": 85, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 40, + { + "value": 10, + "min_value": 10, + "max_value": 30, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 41, + { + "value": 30, + "min_value": 20, + "max_value": 50, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 44, + { + "value": 10, + "min_value": 10, + "max_value": 240, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 47, + { + "value": 15, + "min_value": 10, + "max_value": 20, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 50, + { + "value": 50, + "min_value": 40, + "max_value": 150, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 53, + { + "value": 2, + "min_value": 1, + "max_value": 15, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 54, + { + "value": 3, + "min_value": 1, + "max_value": 10, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 55, + { + "value": 40, + "min_value": 20, + "max_value": 60, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 60, + { + "value": 60, + "min_value": 1, + "max_value": 250, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 61, + { + "value": 30, + "min_value": 20, + "max_value": 50, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 85, + { + "value": 125, + "min_value": 1, + "max_value": 250, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 87, + { + "value": 2, + "min_value": 1, + "max_value": 100, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 88, + { + "value": 47, + "min_value": 1, + "max_value": 250, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 89, + { + "value": 10, + "min_value": 10, + "max_value": 30, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 98, + { + "value": 65, + "min_value": 50, + "max_value": 80, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 99, + { + "value": 50, + "min_value": 30, + "max_value": 80, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 100, + { + "value": 80, + "min_value": 60, + "max_value": 90, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 101, + { + "value": 50, + "min_value": 30, + "max_value": 80, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 102, + { + "value": 0, + "min_value": 0, + "max_value": 99, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 105, + { + "value": 5, + "min_value": 3, + "max_value": 15, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 106, + { + "value": 0, + "min_value": 0, + "max_value": 1, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 107, + { + "value": 13, + "min_value": 1, + "max_value": 40, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 108, + { + "value": 20, + "min_value": 0, + "max_value": 40, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 111, + { + "value": 0, + "min_value": 0, + "max_value": 1, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 112, + { + "value": 5, + "min_value": 0, + "max_value": 30, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 114, + { + "value": 90, + "min_value": 85, + "max_value": 95, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 115, + { + "value": 60, + "min_value": 40, + "max_value": 90, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 119, + { + "value": 51, + "min_value": 40, + "max_value": 70, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 120, + { + "value": 40, + "min_value": 20, + "max_value": 55, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 121, + { + "value": 70, + "min_value": 40, + "max_value": 80, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 122, + { + "value": 2, + "min_value": 0, + "max_value": 2, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 123, + { + "value": 10, + "min_value": 1, + "max_value": 30, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 124, + { + "value": 0, + "min_value": 0, + "max_value": 1, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 125, + { + "value": 0, + "min_value": 0, + "max_value": 2, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 126, + { + "value": 16, + "min_value": 5, + "max_value": 30, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 127, + { + "value": 10, + "min_value": 1, + "max_value": 15, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 128, + { + "value": 3, + "min_value": 0, + "max_value": 99, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + } + ] + } + } +] \ No newline at end of file diff --git a/tests/testdata/responses/mixer_parameters.json b/tests/testdata/responses/mixer_parameters.json new file mode 100644 index 00000000..fcf8a5fe --- /dev/null +++ b/tests/testdata/responses/mixer_parameters.json @@ -0,0 +1,107 @@ +[ + { + "id": "no_mixers_detected", + "message": { + "items": [ + "00000201" + ], + "__bytearray__": true + }, + "data": { + "mixer_parameters": {} + } + }, + { + "id": "single_mixer_detected", + "message": { + "items": [ + "00000601281E3C141E2850465A140A1E0100010D0A1E" + ], + "__bytearray__": true + }, + "data": { + "mixer_parameters": { + "0": [ + { + "items": [ + 0, + { + "value": 40, + "min_value": 30, + "max_value": 60, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 1, + { + "value": 20, + "min_value": 30, + "max_value": 40, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 2, + { + "value": 80, + "min_value": 70, + "max_value": 90, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 3, + { + "value": 20, + "min_value": 10, + "max_value": 30, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 4, + { + "value": 1, + "min_value": 0, + "max_value": 1, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 5, + { + "value": 13, + "min_value": 10, + "max_value": 30, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + } + ] + } + } + } +] \ No newline at end of file diff --git a/tests/testdata/responses/password.json b/tests/testdata/responses/password.json new file mode 100644 index 00000000..3df13672 --- /dev/null +++ b/tests/testdata/responses/password.json @@ -0,0 +1,14 @@ +[ + { + "id": "ecomax_service_password", + "message": { + "items": [ + "0430303030" + ], + "__bytearray__": true + }, + "data": { + "password": "0000" + } + } +] \ No newline at end of file diff --git a/tests/testdata/responses/program_version.json b/tests/testdata/responses/program_version.json new file mode 100644 index 00000000..500c0167 --- /dev/null +++ b/tests/testdata/responses/program_version.json @@ -0,0 +1,18 @@ +[ + { + "id": "econet_program_version", + "message": { + "items": [ + "FFFF057A0000000001000000000056" + ], + "__bytearray__": true + }, + "data": { + "version": { + "software": "1.0.0", + "__module__": "pyplumio.structures.program_version", + "__class__": "VersionInfo" + } + } + } +] \ No newline at end of file diff --git a/tests/testdata/responses/schedules.json b/tests/testdata/responses/schedules.json new file mode 100644 index 00000000..ba372087 --- /dev/null +++ b/tests/testdata/responses/schedules.json @@ -0,0 +1,402 @@ +[ + { + "id": "only_heating_schedule", + "message": { + "items": [ + "100101000005001E0000FFFFFFFE0000FFFFFFFE0000FFFFFFFE0000FFFFFFFE0000FFFFFFFE0", + "000FFFFFFFE0000FFFFFFFE" + ], + "__bytearray__": true + }, + "data": { + "schedules": [ + { + "items": [ + 0, + [ + [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false + ], + [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false + ], + [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false + ], + [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false + ], + [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false + ], + [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false + ], + [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false + ] + ] + ], + "__tuple__": true + } + ], + "schedule_parameters": [ + { + "items": [ + 0, + { + "value": 0, + "min_value": 0, + "max_value": 1, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 1, + { + "value": 5, + "min_value": 0, + "max_value": 30, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + } + ] + } + } +] \ No newline at end of file diff --git a/tests/testdata/responses/thermostat_parameters.json b/tests/testdata/responses/thermostat_parameters.json new file mode 100644 index 00000000..22434321 --- /dev/null +++ b/tests/testdata/responses/thermostat_parameters.json @@ -0,0 +1,199 @@ +[ + { + "id": "no_thersmostats_connected", + "message": { + "items": [ + "00000300FFFFFFFFFFFFFFFFFF" + ], + "__bytearray__": true + }, + "data": { + "thermostat_count": 3, + "thermostat_profile": null, + "thermostat_parameters": {} + } + }, + { + "id": "3_thermostats_connected", + "message": { + "items": [ + "000025000005000007DC0064005E01960064005E01643C8C02003C01003C01003C0A003C09003", + "2DE0064005E01D40064005E015A0032002C01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "FFFFFFFFFF" + ], + "__bytearray__": true + }, + "data": { + "thermostat_count": 3, + "thermostat_profile": { + "value": 0, + "min_value": 0, + "max_value": 5, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + }, + "thermostat_parameters": { + "0": [ + { + "items": [ + 0, + { + "value": 0, + "min_value": 0, + "max_value": 7, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 1, + { + "value": 220, + "min_value": 100, + "max_value": 350, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 2, + { + "value": 150, + "min_value": 100, + "max_value": 350, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 3, + { + "value": 100, + "min_value": 60, + "max_value": 140, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 4, + { + "value": 2, + "min_value": 0, + "max_value": 60, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 5, + { + "value": 1, + "min_value": 0, + "max_value": 60, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 6, + { + "value": 1, + "min_value": 0, + "max_value": 60, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 7, + { + "value": 10, + "min_value": 0, + "max_value": 60, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 8, + { + "value": 9, + "min_value": 0, + "max_value": 50, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 9, + { + "value": 222, + "min_value": 100, + "max_value": 350, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 10, + { + "value": 212, + "min_value": 100, + "max_value": 350, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + }, + { + "items": [ + 11, + { + "value": 90, + "min_value": 50, + "max_value": 300, + "__module__": "pyplumio.helpers.parameter", + "__class__": "ParameterValues" + } + ], + "__tuple__": true + } + ] + } + } + } +] \ No newline at end of file diff --git a/tests/testdata/responses/uid.json b/tests/testdata/responses/uid.json new file mode 100644 index 00000000..c59076aa --- /dev/null +++ b/tests/testdata/responses/uid.json @@ -0,0 +1,23 @@ +[ + { + "id": "EM350P2_uid", + "message": { + "items": [ + "005A000B001600110D3833383655395A0000000A454D33353050322D5A46" + ], + "__bytearray__": true + }, + "data": { + "product": { + "type": 0, + "id": 90, + "uid": "D251PAKR3GCPZ1K8G05G0", + "logo": 23040, + "image": 2816, + "model": "EM350P2-ZF", + "__module__": "pyplumio.structures.product_info", + "__class__": "ProductInfo" + } + } + } +] \ No newline at end of file