diff --git a/.gitignore b/.gitignore index 02bbf52..d6b6599 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build/ dist/ *.egg-info +apk/ \ No newline at end of file diff --git a/Pipfile b/Pipfile index 226bc53..e048f66 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,7 @@ websocket-client = "*" [dev-packages] flake8 = "*" nose2 = "*" +black = "*" [requires] python_version = "3.10" diff --git a/Pipfile.lock b/Pipfile.lock index 8099015..15d165f 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "639d3feb7cceff03152bba06e3e00b01dc9a6f54b8573e2d8c0fb200ed6106a8" + "sha256": "936b1e8363863c64fdae113358df0ee3cdce37f00f7b7dbe4aed121279be12b6" }, "pipfile-spec": 6, "requires": { @@ -34,6 +34,38 @@ } }, "develop": { + "black": { + "hashes": [ + "sha256:037e9b4664cafda5f025a1728c50a9e9aedb99a759c89f760bd83730e76ba884", + "sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916", + "sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258", + "sha256:33d40f5b06be80c1bbce17b173cda17994fbad096ce60eb22054da021bf933d1", + "sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce", + "sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d", + "sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982", + "sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7", + "sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173", + "sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9", + "sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb", + "sha256:840015166dbdfbc47992871325799fd2dc0dcf9395e401ada6d88fe11498abad", + "sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc", + "sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0", + "sha256:cfcce6f0a384d0da692119f2d72d79ed07c7159879d0bb1bb32d2e443382bf3a", + "sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe", + "sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace", + "sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69" + ], + "index": "pypi", + "version": "==23.10.1" + }, + "click": { + "hashes": [ + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.7" + }, "flake8": { "hashes": [ "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23", @@ -50,6 +82,14 @@ "markers": "python_version >= '3.6'", "version": "==0.7.0" }, + "mypy-extensions": { + "hashes": [ + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.0" + }, "nose2": { "hashes": [ "sha256:5c28d770a0b9a702862bd6c3755ba2cd2f7994dd518c982e5ce298d7f37466a4", @@ -58,6 +98,30 @@ "index": "pypi", "version": "==0.14.0" }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "pathspec": { + "hashes": [ + "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20", + "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3" + ], + "markers": "python_version >= '3.7'", + "version": "==0.11.2" + }, + "platformdirs": { + "hashes": [ + "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3", + "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e" + ], + "markers": "python_version >= '3.7'", + "version": "==3.11.0" + }, "pycodestyle": { "hashes": [ "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f", @@ -73,6 +137,22 @@ ], "markers": "python_version >= '3.8'", "version": "==3.1.0" + }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "markers": "python_version < '3.11'", + "version": "==2.0.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", + "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef" + ], + "markers": "python_version < '3.11'", + "version": "==4.8.0" } } } diff --git a/daikin_altherma/__init__.py b/daikin_altherma/__init__.py index f9cd503..935a079 100644 --- a/daikin_altherma/__init__.py +++ b/daikin_altherma/__init__.py @@ -1,4 +1,7 @@ import json +from typing import Callable +from dataclasses import dataclass +import enum import logging import time import uuid @@ -9,13 +12,53 @@ Day = Hour = str Temperature = float -Schedule = dict[Day, dict[Hour, Temperature]] +HeatingSchedule = dict[Day, dict[Hour, Temperature]] +TankSchedule = dict[Day, dict[Hour, 'TankStateEnum']] + + +# XXX use StrEnum in some years when distros will have py 3.11 +class TankStateEnum(str, enum.Enum): + OFF = "off" + COMFORT = "comfort" + ECO = "eco" + + def __str__(self): + return str(self.value) + + @staticmethod + def int_to_state(x: int) -> 'TankStateEnum': + x = str(x) + return { + "2": TankStateEnum.OFF, + "1": TankStateEnum.COMFORT, + "0": TankStateEnum.ECO, + }[x] + + +class HeatingOperationMode(str, enum.Enum): + Heating = 'Heating' + Cooling = 'Cooling' + +@dataclass +class _ScheduleState: + OperationMode: HeatingOperationMode + StartTime: int + Day: str ## XXX enum + +@dataclass +class HeatingScheduleState(_ScheduleState): + TargetTemperature: float + +@dataclass +class TankScheduleState(_ScheduleState): + TankState: TankStateEnum class DaikinAltherma: UserAgent = "python-daikin-altherma" - DAYS = ['Mo','Tu','We','Th', 'Fr', 'Sa', 'Su'] - + DAYS = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"] + _heating_value_parser = lambda x: float(x)/10 + DATETIME_FMT = "%Y%m%dT%H%M%SZ" def __init__(self, adapter_ip: str): self.adapter_ip = adapter_ip @@ -33,14 +76,13 @@ def _requestValue(self, item: str, output_path: str, payload=None): } if payload: set_value_params = { - 'ty': 4, - 'op': 1, - 'pc': { - 'm2m:cin': payload, - } + "ty": 4, + "op": 1, + "pc": { + "m2m:cin": payload, + }, } - js_request['m2m:rqp'].update(set_value_params) - + js_request["m2m:rqp"].update(set_value_params) self.ws.send(json.dumps(js_request)) result = json.loads(self.ws.recv()) @@ -51,99 +93,117 @@ def _requestValue(self, item: str, output_path: str, payload=None): try: return dpath.util.get(result, output_path) except: - logging.error('Could not get data. Maybe the unit is rebooting ?') + logging.error("Could not get data. Maybe the unit is starting up?") raise - def _requestValueHP(self, item: str, output_path: str, payload=None): return self._requestValue(f"MNAE/{item}", output_path, payload) @property def _unit_api(self): - return json.loads(self._requestValueHP("1/UnitProfile/la", "/m2m:rsp/pc/m2m:cin/con")) + return json.loads( + self._requestValueHP("1/UnitProfile/la", "/m2m:rsp/pc/m2m:cin/con") + ) @property def adapter_model(self) -> str: - """ Returns the model of the LAN adapter. - Ex: BRP069A61 """ + """Returns the model of the LAN adapter. + Ex: BRP069A61""" # either BRP069A61 or BRP069A62 return self._requestValue("MNCSE-node/deviceInfo", "/m2m:rsp/pc/m2m:dvi/mod") @property def unit_datetime(self) -> datetime.datetime: - """ Returns the current date of the unit. Takes time to refresh """ + """Returns the current date of the unit. Takes time to refresh""" d = self._requestValueHP("0/DateTime/la", "/m2m:rsp/pc/m2m:cin/con") - return datetime.datetime.strptime(d, '%Y%m%dT%H%M%SZ') - + return datetime.datetime.strptime(d, self.DATETIME_FMT) @property def unit_model(self) -> str: - """ Returns the model of the heating unit. - Ex: EAVH16S23DA6V """ - return self._requestValueHP("1/UnitInfo/ModelNumber/la", "/m2m:rsp/pc/m2m:cin/con") + """Returns the model of the heating unit. + Ex: EAVH16S23DA6V""" + return self._requestValueHP( + "1/UnitInfo/ModelNumber/la", "/m2m:rsp/pc/m2m:cin/con" + ) @property def unit_type(self) -> str: - """ Returns the type of unit """ + """Returns the type of unit""" return self._requestValueHP("1/UnitInfo/UnitType/la", "/m2m:rsp/pc/m2m:cin/con") @property def indoor_unit_version(self) -> str: - """ Returns the unit version """ - return self._requestValueHP("1/UnitInfo/Version/IndoorSettings/la", "/m2m:rsp/pc/m2m:cin/con") + """Returns the unit version""" + return self._requestValueHP( + "1/UnitInfo/Version/IndoorSettings/la", "/m2m:rsp/pc/m2m:cin/con" + ) @property def indoor_unit_software_version(self) -> str: - """ Returns the unit software version """ - return self._requestValueHP("1/UnitInfo/Version/IndoorSoftware/la", "/m2m:rsp/pc/m2m:cin/con") + """Returns the unit software version""" + return self._requestValueHP( + "1/UnitInfo/Version/IndoorSoftware/la", "/m2m:rsp/pc/m2m:cin/con" + ) @property def outdoor_unit_software_version(self) -> str: - """ Returns the unit software version """ - return self._requestValueHP("1/UnitInfo/Version/OutdoorSoftware/la", "/m2m:rsp/pc/m2m:cin/con") + """Returns the unit software version""" + return self._requestValueHP( + "1/UnitInfo/Version/OutdoorSoftware/la", "/m2m:rsp/pc/m2m:cin/con" + ) @property def remote_setting_version(self) -> str: - """ Returns the remote console setting version """ - return self._requestValueHP("1/UnitInfo/Version/RemoconSettings/la", "/m2m:rsp/pc/m2m:cin/con") + """Returns the remote console setting version""" + return self._requestValueHP( + "1/UnitInfo/Version/RemoconSettings/la", "/m2m:rsp/pc/m2m:cin/con" + ) @property def remote_software_version(self) -> str: - """ Returns the remote console setting software version """ - return self._requestValueHP("1/UnitInfo/Version/RemoconSoftware/la", "/m2m:rsp/pc/m2m:cin/con") + """Returns the remote console setting software version""" + return self._requestValueHP( + "1/UnitInfo/Version/RemoconSoftware/la", "/m2m:rsp/pc/m2m:cin/con" + ) @property def pin_code(self) -> str: - """ Returns the pin code of the LAN adapter """ + """Returns the pin code of the LAN adapter""" return self._requestValueHP("1/ChildLock/PinCode/la", "/m2m:rsp/pc/m2m:cin/con") # HOT WATER TANK STUFF @property def tank_temperature(self) -> float: - """ Returns the hot water tank temperature, in °C """ + """Returns the hot water tank temperature, in °C""" return self._requestValueHP( "2/Sensor/TankTemperature/la", "/m2m:rsp/pc/m2m:cin/con" ) @property def tank_setpoint_temperature(self) -> float: - """ Returns the hot water tank setpoint (target) temperature, in °C """ + """Returns the hot water tank setpoint (target) temperature, in °C""" return self._requestValueHP( "2/Operation/TargetTemperature/la", "/m2m:rsp/pc/m2m:cin/con" ) @property def is_tank_heating_enabled(self) -> bool: - """ Returns if the tank heating is currently enabled""" - return self._requestValueHP("2/Operation/Power/la", "m2m:rsp/pc/m2m:cin/con") == "on" + """Returns if the tank heating is currently enabled""" + return ( + self._requestValueHP("2/Operation/Power/la", "m2m:rsp/pc/m2m:cin/con") + == "on" + ) @property def is_tank_powerful(self) -> bool: - """ Returns if the tank is in powerful state """ - return self._requestValueHP("2/Operation/Powerful/la", "m2m:rsp/pc/m2m:cin/con") == 1 + """Returns if the tank is in powerful state""" + return ( + self._requestValueHP("2/Operation/Powerful/la", "m2m:rsp/pc/m2m:cin/con") + == 1 + ) def set_tank_heating_enabled(self, powerful_active: bool): - """ Whether to turn the water tank heating on(True) or off(False). + """Whether to turn the water tank heating on(True) or off(False). You can confirm that it works by calling self.is_tank_heating_enabled """ mode_dict = { @@ -152,183 +212,229 @@ def set_tank_heating_enabled(self, powerful_active: bool): } payload = { - 'con': mode_dict[powerful_active], - 'cnf': 'text/plain:0', + "con": mode_dict[powerful_active], + "cnf": "text/plain:0", } self._requestValueHP("2/Operation/Powerful", "/", payload) - # HEATING STUFF @property def indoor_temperature(self) -> float: - """ Returns the indoor temperature, in °C """ + """Returns the indoor temperature, in °C""" return self._requestValueHP( "1/Sensor/IndoorTemperature/la", "/m2m:rsp/pc/m2m:cin/con" ) @property def outdoor_temperature(self) -> float: - """ Returns the outdoor temperature, in °C """ + """Returns the outdoor temperature, in °C""" return self._requestValueHP( "1/Sensor/OutdoorTemperature/la", "/m2m:rsp/pc/m2m:cin/con" ) @property def indoor_setpoint_temperature(self) -> float: - """ Returns the indoor setpoint (target) temperature, in °C """ + """Returns the indoor setpoint (target) temperature, in °C""" return self._requestValueHP( "1/Operation/TargetTemperature/la", "/m2m:rsp/pc/m2m:cin/con" ) @property def leaving_water_temperature(self) -> float: - """ Returns the heating leaving water temperature, in °C """ + """Returns the heating leaving water temperature, in °C""" return self._requestValueHP( "1/Sensor/LeavingWaterTemperatureCurrent/la", "m2m:rsp/pc/m2m:cin/con" ) @property def is_heating_enabled(self) -> bool: - """ Returns if the unit heating is enabled""" - return self._requestValueHP("1/Operation/Power/la", "m2m:rsp/pc/m2m:cin/con") == "on" + """Returns if the unit heating is enabled""" + return ( + self._requestValueHP("1/Operation/Power/la", "m2m:rsp/pc/m2m:cin/con") + == "on" + ) @property def heating_mode(self) -> str: - """ This function name makes no sense, because it + """This function name makes no sense, because it returns whether the heat pump is heating or cooling. """ - return self._requestValueHP("1/Operation/OperationMode/la", "m2m:rsp/pc/m2m:cin/con") + return self._requestValueHP( + "1/Operation/OperationMode/la", "m2m:rsp/pc/m2m:cin/con" + ) @property def power_consumption(self) -> dict: - """ Returns the energy consumption in kWh per [D]ay, [W]eek, [M]onth """ + """Returns the energy consumption in kWh per [D]ay, [W]eek, [M]onth""" return self._requestValueHP("1/Consumption/la", "m2m:rsp/pc/m2m:cin/con") def set_setpoint_temperature(self, setpoint_temperature_c: float): - """ Sets the heating setpoint (target) temperature, in °C""" + """Sets the heating setpoint (target) temperature, in °C""" payload = { - 'con': setpoint_temperature_c, - 'cnf': 'text/plain:0', + "con": setpoint_temperature_c, + "cnf": "text/plain:0", } self._requestValueHP("1/Operation/TargetTemperature", "/", payload) def set_heating_enabled(self, heating_active: bool): - """ Whether to turn the heating on(True) or off(False). + """Whether to turn the heating on(True) or off(False). You can confirm that it works by calling self.is_heating_enabled """ mode_dict = { - True: 'on', - False: 'standby', + True: "on", + False: "standby", } payload = { - 'con': mode_dict[heating_active], - 'cnf': 'text/plain:0', + "con": mode_dict[heating_active], + "cnf": "text/plain:0", } self._requestValueHP("1/Operation/Power", "/", payload) + @property + def heating_schedule(self) -> list[HeatingSchedule]: + """Returns the HeatingSchedule list heating""" + d = self._requestValueHP( + "1/Schedule/List/Heating/la", "/m2m:rsp/pc/m2m:cin/con" + ) + j = json.loads(d) + + out_schedules = [] + for schedule in j["data"]: + out_schedules.append(self._unmarshall_schedule(schedule, DaikinAltherma._heating_value_parser)) + return out_schedules @property - def schedule_list_heating(self) -> list[Schedule]: - """ Returns the Schedule list heating """ - d = self._requestValueHP("1/Schedule/List/Heating/la", "/m2m:rsp/pc/m2m:cin/con") + def tank_schedule(self) -> list[TankSchedule]: + """Returns the TankSchedule list heating""" + d = self._requestValueHP( + "2/Schedule/List/Heating/la", "/m2m:rsp/pc/m2m:cin/con" + ) j = json.loads(d) + def value_parser(x): + return TankStateEnum.int_to_state(x) + out_schedules = [] - for schedule in j['data']: - out_schedules.append(self._unmarshall_schedule(schedule)) + for schedule in j["data"]: + out_schedules.append(self._unmarshall_schedule(schedule, value_parser)) return out_schedules - def set_heating_schedule(self, schedule: Schedule): - ''' Sets the heating schedule for the heating. ''' + def set_heating_schedule(self, schedule: HeatingSchedule): + """Sets the heating schedule for the heating.""" schedule_str = self._marshall_schedule(schedule) - dq = {'data': [schedule_str]} + dq = {"data": [schedule_str]} payload = { - 'con': json.dumps(dq), - 'cnf': 'text/plain:0', + "con": json.dumps(dq), + "cnf": "text/plain:0", } self._requestValueHP("1/Schedule/List/Heating", "/", payload) -# @property -# def is_heating_schedule_enabled(self): -# return self._requestValueHP("1/Schedule/Active/la", "/m2m:rsp/pc/m2m:cin/con") - @property - def schedule_next(self) -> Schedule: - """ What will happen next the temperature """ + def heating_schedule_state(self) -> HeatingScheduleState: + """Returns the actual heating schedule state""" d = self._requestValueHP("1/Schedule/Next/la", "/m2m:rsp/pc/m2m:cin/con") j = json.loads(d) - return self._unmarshall_schedule(j) + dq = j['data'] + + return HeatingScheduleState( + OperationMode=dq['OperationMode'], + StartTime=dq['StartTime'], + TargetTemperature=DaikinAltherma._heating_value_parser(dq['TargetTemperature']), + Day=dq['Day'], + ) + + @property + def tank_schedule_state(self) -> TankScheduleState: + """Returns the actual tank schedule state""" + d = self._requestValueHP("2/Schedule/Next/la", "/m2m:rsp/pc/m2m:cin/con") + j = json.loads(d) + dq = j['data'] + + return TankScheduleState( + OperationMode=dq['OperationMode'], + StartTime=dq['StartTime'], + TankState=TankStateEnum.int_to_state(dq['TargetTemperature']), #Copy paste powa + Day=dq['Day'], + ) def print_all_status(self): - print(f''' + print( + f""" Daikin adapter: {self.adapter_ip} {self.adapter_model} Daikin unit: {self.unit_model} {self.unit_type} Daikin time: {self.unit_datetime} Hot water tank: Current: {self.tank_temperature}°C (target {self.tank_setpoint_temperature}°C) Heating enabled: {self.is_tank_heating_enabled} (Powerful: {self.is_tank_powerful}) + Schedule: {self.tank_schedule[0]} + Schedule state: {self.tank_schedule_state} Heating: Outdoor temp:{self.outdoor_temperature}°C Indoor temp: {self.indoor_temperature}°C Heating target: {self.indoor_setpoint_temperature}°C (is heating enabled: {self.is_heating_enabled}) Leaving water: {self.leaving_water_temperature}°C Heating mode: {self.heating_mode} - Schedule: {self.schedule_list_heating[0]} - ''') - + Schedule: {self.heating_schedule[0]} + Schedule state: {self.heating_schedule_state} + """ + ) @staticmethod - def _unmarshall_schedule(schedule_str: str) -> Schedule: - ''' Converts a schedule string to a schedule dict. + def _unmarshall_schedule(schedule_str: str, value_parser: Callable): + """Converts a schedule string to a schedule dict. The dict keys are the days (DaikinAltherma.DAYS), and the values are a dict of hour (HHMM) -> Setpoint T° - Ex: {'Mo': {'0000': 23.4}}''' + Ex: {'Mo': {'0000': 23.4}}""" - hrs = schedule_str.split('|')[2].split(';') + hrs = schedule_str.split("|")[2].split(";") i = 0 schedule = {} for day in DaikinAltherma.DAYS: schedule_wk = {} - temps = hrs[i:i+6] + temps = hrs[i : i + 6] for c in temps: - ctime, ctemp = c.split(',') - if ctime == '': + ctime, cval = c.split(",") + if ctime == "": + continue + if cval == "": + # For some reason, the time is declared but not the value.. ? continue - ctemp = float(ctemp)/10 - schedule_wk[ctime] = ctemp + val = value_parser(cval) + schedule_wk[ctime] = val i += 6 schedule[day] = schedule_wk return schedule - @staticmethod - def _marshall_schedule(schedule: Schedule) -> str: - '''' Converts a schedule dict to a Daikin schedule string''' + def _marshall_schedule(schedule) -> str: + """' Converts a schedule dict to a Daikin schedule string""" week_schedule = [] for day in DaikinAltherma.DAYS: sday = schedule.get(day, {}) schedule_day = [] - assert(len(sday) <= 6) + assert len(sday) <= 6 for hour in sorted(sday.keys()): - schedule_day.append(f'{hour},{int(sday[hour]*10)}') - padding_schedule = 6-len(schedule_day) - schedule_day += [','] * padding_schedule + schedule_day.append(f"{hour},{int(sday[hour]*10)}") + padding_schedule = 6 - len(schedule_day) + schedule_day += [","] * padding_schedule week_schedule += schedule_day - schedule_str = '$NULL|1|' + ';'.join(week_schedule) + schedule_str = "$NULL|1|" + ";".join(week_schedule) return schedule_str -if __name__ == '__main__': + +if __name__ == "__main__": ad = DaikinAltherma("192.168.11.100") ad.print_all_status() import pprint + pprint.pprint(ad._unit_api) diff --git a/daikin_altherma/tests/test_schedule.py b/daikin_altherma/tests/test_schedule.py index 2f4bb70..d0ed15a 100644 --- a/daikin_altherma/tests/test_schedule.py +++ b/daikin_altherma/tests/test_schedule.py @@ -4,21 +4,39 @@ from daikin_altherma import DaikinAltherma -S1 = '$NULL|1|0000,180;0450,200;2300,180;,;,;,;0000,180;0450,200;2300,180;,;,;,;0000,180;0450,200;2300,180;,;,;,;0000,180;0450,200;2300,180;,;,;,;0000,180;0450,200;2300,180;,;,;,;0000,180;0450,200;2300,180;,;,;,;0000,180;0450,200;2300,180;,;,;,' +S1 = "$NULL|1|0000,180;0450,200;2300,180;,;,;,;0000,180;0450,200;2300,180;,;,;,;0000,180;0450,200;2300,180;,;,;,;0000,180;0450,200;2300,180;,;,;,;0000,180;0450,200;2300,180;,;,;,;0000,180;0450,200;2300,180;,;,;,;0000,180;0450,200;2300,180;,;,;," -class TestSchedule(unittest.TestCase): - +class TestHeatingSchedule(unittest.TestCase): def test_schedule(self): - ''' Tests conversion from str to dict to str''' - s_dict = DaikinAltherma._unmarshall_schedule(S1) + """Tests conversion from str to dict to str""" + s_dict = DaikinAltherma._unmarshall_schedule(S1, DaikinAltherma._heating_value_parser) s_str = DaikinAltherma._marshall_schedule(s_dict) assert S1 == s_str def test_schedule_dict(self): schedule = { - 'Mo': {'0000': 22.0}, - 'Tu': {}, 'We': {}, 'Th': {}, 'Fr': {}, 'Sa': {}, 'Su': {}, + "Mo": {"0000": 22.0}, + "Tu": {}, + "We": {}, + "Th": {}, + "Fr": {}, + "Sa": {}, + "Su": {}, } - back_schedule = DaikinAltherma._unmarshall_schedule(DaikinAltherma._marshall_schedule(schedule)) + back_schedule = DaikinAltherma._unmarshall_schedule( + DaikinAltherma._marshall_schedule(schedule), DaikinAltherma._heating_value_parser + ) assert schedule == back_schedule + + def test_rl(self): + S1 = "$NULL|0|0700,200;0900,180;1700,200;2300,180;,;,;0700,200;0900,180;1700,200;2300,180;,;,;0700,200;0900,180;1700,200;2300,180;,;,;0700,200;0900,180;1700,200;2300,180;,;,;0700,200;0900,180;1700,200;2300,180;,;,;0800,200;2300,180;,;,;,;,;0800,200;2300,180;,;,;,;," + S2 = "$NULL|0|0700,200;0900,180;1200,200;1400,180;1700,200;2300,180;0700,200;0900,180;1200,200;1400,180;1700,200;2300,180;0700,200;0900,180;1200,200;1400,180;1700,200;2300,180;0700,200;0900,180;1200,200;1400,180;1700,200;2300,180;0700,200;0900,180;1200,200;1400,180;1700,200;2300,180;0800,200;2300,180;,;,;,;,;0800,200;2300,180;,;,;,;," + S3 = "$NULL|0|0800,200;2300,180;,;,;,;,;0800,200;2300,180;,;,;,;,;0800,200;2300,180;,;,;,;,;0800,200;2300,180;,;,;,;,;0800,200;2300,180;,;,;,;,;0800,200;2300,180;,;,;,;,;0800,200;2300,180;,;,;,;," + S4 = "JANEDOE|1|0330,200;1800,180;,;,;,;,;0330,200;1800,180;,;,;,;,;0330,200;1800,180;,;,;,;,;0330,200;1800,180;,;,;,;,;0330,200;1800,180;,;,;,;,;0330,200;1800,180;,;,;,;,;0330,200;1800,180;,;,;0000,180;0000,180" + S5 = "$NULL|1|0010,120;0000,180;0000,180;0000,180;0000,180;0000,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;," + S6 = "$NULL|1|,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;,;," + + schedules = [S1, S2, S3, S4, S5, S6] + for s in schedules: + DaikinAltherma._unmarshall_schedule(s, DaikinAltherma._heating_value_parser) \ No newline at end of file diff --git a/example.py b/example.py index ea0d5ca..0b20de1 100644 --- a/example.py +++ b/example.py @@ -1,7 +1,27 @@ +import datetime +import time from daikin_altherma import DaikinAltherma d = DaikinAltherma('192.168.11.100') -d.print_all_status() +now = datetime.datetime.utcnow() - datetime.timedelta(minutes=30) +d.set_unit_datetime(now) + +while True: + dat = d.unit_datetime + now = datetime.datetime.utcnow() + print(f'NOW: {now}\nDAI: {dat}') + print(abs(datetime.datetime.utcnow() - dat)) + time.sleep(1) + +#print(f'BEF: {d.unit_datetime}') +#d.set_unit_datetime(now) +#print(f'AFT: {d.unit_datetime}') +#print(d.tank_schedule_state) +#print(d.heating_schedule_state) +#print(d.tank_schedule) + +#d.print_all_status() +1/0 _present_day_schedule = { '0000': 17,