From 4ca76210135013c77690df1a5678f42f4ef6eed9 Mon Sep 17 00:00:00 2001 From: Krisjanis Lejejs Date: Tue, 28 Jun 2022 00:07:55 +0300 Subject: [PATCH] Add debug function, update Readme about it --- .gitignore | 5 +- README.md | 23 ++++-- ThermiaOnlineAPI/api/ThermiaAPI.py | 46 +++++++++--- ThermiaOnlineAPI/model/HeatPump.py | 115 +++++++++++++++++++++++------ ThermiaOnlineAPI/utils/utils.py | 19 +++++ example.py | 48 ++++++------ 6 files changed, 191 insertions(+), 65 deletions(-) diff --git a/.gitignore b/.gitignore index 11204ef..3f06b0a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,7 @@ __pycache__/ # PyPi MANIFEST build/ -dist/ \ No newline at end of file +dist/ + +# Debug file +debug.txt diff --git a/README.md b/README.md index dea1e0a..143fa57 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,12 @@ ## Confirmed Thermia models that API supports: * Danfoss DHP-AQ 9 +## Regarding unsupported models +I am willing to do my best to support them, but as there turns out to be many different Thermia models and configurations, it is hard for me to implement all functionalities and test them thoroughly. +Thus, I have created a `debug()` function that runs when `example.py` is executed and creates a `debug.txt` file which has data about your heat pump and all its supported features. If you want to submit a bug or feature request, please include the debug file as it will make my development much easier. + +**Note:** I have done my best to remove the sensitive parts from debugging, but I do not guarantee that no sensitive data is printed to the debug file. I have no intention of using it maliciously, but if you post the file publicly on GitHub, please make sure you remove anything you feel might be suspicious of sharing. + ## Supported APIs: * `classic`, default, online access url is https://online.thermia.se * `genesis`, online access url is https://online-genesis.thermia.se @@ -18,8 +24,8 @@ See [example.py](https://github.com/klejejs/python-thermia-online-api/blob/main/ ## Available functions in Thermia class: | Function | Description | | --- | --- | -| `fetch_heat_pumps` | Fetches all heat pumps from Thermia Online API and their data | -| `update_data` | Updates all heat pump data | +| `fetch_heat_pumps()` | Fetches all heat pumps from Thermia Online API and their data | +| `update_data()` | Updates all heat pump data | ## Available properties within ThermiaHeatPump class: | Property | Description | @@ -80,12 +86,15 @@ See [example.py](https://github.com/klejejs/python-thermia-online-api/blob/main/ ## Available functions within ThermiaHeatPump class: | Function | Description | | --- | --- | -| `update_data` | Refetch all data from Thermia for Heat Pump | +| `update_data()` | Refetch all data from Thermia for Heat Pump | | --- | --- | | Change heat pump state | | -| `set_temperature` | Set the target temperature for the Heat Pump | -| `set_operation_mode` | Set the operation mode for the Heat Pump | -| `set_hot_water_switch_state` | Set the hot water switch state to 0 (off) or 1 (on) for the Heat Pump | +| `set_temperature()` | Set the target temperature for the Heat Pump | +| `set_operation_mode()` | Set the operation mode for the Heat Pump | +| `set_hot_water_switch_state()` | Set the hot water switch state to 0 (off) or 1 (on) for the Heat Pump | | --- | --- | | Fetch historical data | | -| `get_historical_data_for_register` | Fetch historical data by using register name from `historical_data_registers` together with start_time and end_time of the data in Python datatime format. Returns list of dictionaries which contains data in format `{ "time": datetime, "value": int }` | +| `get_historical_data_for_register()` | Fetch historical data by using register name from `historical_data_registers` together with start_time and end_time of the data in Python datatime format. Returns list of dictionaries which contains data in format `{ "time": datetime, "value": int }` | +| --- | --- | +| Fetch debug data | | +| `debug()` | Fetch debug data from Thermia API and save it to `debug.txt` file | diff --git a/ThermiaOnlineAPI/api/ThermiaAPI.py b/ThermiaOnlineAPI/api/ThermiaAPI.py index 8d341fe..459d25f 100644 --- a/ThermiaOnlineAPI/api/ThermiaAPI.py +++ b/ThermiaOnlineAPI/api/ThermiaAPI.py @@ -45,7 +45,8 @@ def __init__(self, email, password, api_type): def get_devices(self): self.__check_token_validity() - url = self.configuration["apiBaseUrl"] + "/api/v1/InstallationsInfo/own" + url = self.configuration["apiBaseUrl"] + \ + "/api/v1/InstallationsInfo/own" request = requests.get(url, headers=self.__default_request_headers) status = request.status_code @@ -71,7 +72,8 @@ def get_device_by_id(self, device_id: str): def get_device_info(self, device_id: str): self.__check_token_validity() - url = self.configuration["apiBaseUrl"] + "/api/v1/installations/" + device_id + url = self.configuration["apiBaseUrl"] + \ + "/api/v1/installations/" + device_id request = requests.get(url, headers=self.__default_request_headers) status = request.status_code @@ -155,12 +157,29 @@ def get_historical_data( if status != 200: _LOGGER.error( - "Error in historical data for specific register. " + str(status) + "Error in historical data for specific register. " + + str(status) ) return None return request.json() + def get_all_available_groups(self, installation_profile_id: int): + self.__check_token_validity() + + url = self.configuration["apiBaseUrl"] + \ + "/api/v1/installationprofiles/" + \ + str(installation_profile_id) + "/groups" + + request = requests.get(url, headers=self.__default_request_headers) + status = request.status_code + + if status != 200: + _LOGGER.error("Error in getting available groups. " + str(status)) + return None + + return request.json() + def get__group_temperatures(self, device_id: str): return self.__get_register_group(device_id, REG_GROUP_TEMPERATURES) @@ -221,7 +240,8 @@ def get_group_operational_operation(self, device: ThermiaHeatPump): device.id, REG_GROUP_OPERATIONAL_OPERATION ) - data = [d for d in register_data if d["registerName"] == "REG_OPERATIONMODE"] + data = [d for d in register_data if d["registerName"] + == "REG_OPERATIONMODE"] if len(data) != 1: # Operation mode not supported @@ -264,9 +284,11 @@ def get_group_operational_operation(self, device: ThermiaHeatPump): return None def get_group_hot_water(self, device: ThermiaHeatPump): - register_data = self.__get_register_group(device.id, REG_GROUP_HOT_WATER) + register_data = self.__get_register_group( + device.id, REG_GROUP_HOT_WATER) - data = [d for d in register_data if d["registerName"] == "REG_HOT_WATER_STATUS"] + data = [d for d in register_data if d["registerName"] + == "REG_HOT_WATER_STATUS"] if len(data) == 0: # Hot water switch not supported @@ -285,7 +307,8 @@ def get_group_hot_water(self, device: ThermiaHeatPump): return None def set_temperature(self, device: ThermiaHeatPump, temperature): - device_temperature_register_index = device.get_register_indexes()["temperature"] + device_temperature_register_index = device.get_register_indexes()[ + "temperature"] if device_temperature_register_index is None: _LOGGER.error( "Error setting device's temperature. No temperature register index." @@ -344,6 +367,9 @@ def set_hot_water_switch_state( device, device_hot_water_switch_state_register_index, state ) + def get_register_group_json(self, device_id: str, register_group): + return self.__get_register_group(device_id, register_group) + def __get_register_group(self, device_id: str, register_group: REGISTER_GROUPS): self.__check_token_validity() @@ -385,7 +411,8 @@ def __set_register_value( "clientUuid": "api-client-uuid", } - request = requests.post(url, headers=self.__default_request_headers, json=body) + request = requests.post( + url, headers=self.__default_request_headers, json=body) status = request.status_code if status != 200: @@ -429,7 +456,8 @@ def __authenticate(self): auth_data = request_auth.json() token_valid_to = auth_data.get("tokenValidToUtc").split(".")[0] - datetime_object = datetime.strptime(token_valid_to, "%Y-%m-%dT%H:%M:%S") + datetime_object = datetime.strptime( + token_valid_to, "%Y-%m-%dT%H:%M:%S") token_valid_to = datetime_object.timestamp() self.__token = auth_data.get("token") diff --git a/ThermiaOnlineAPI/model/HeatPump.py b/ThermiaOnlineAPI/model/HeatPump.py index 10dd6a8..293350c 100644 --- a/ThermiaOnlineAPI/model/HeatPump.py +++ b/ThermiaOnlineAPI/model/HeatPump.py @@ -1,6 +1,8 @@ from datetime import datetime import json import logging +import sys +from ..utils.utils import pretty_print_except from typing import TYPE_CHECKING @@ -65,8 +67,10 @@ def __init__(self, device_data: json, api_interface: "ThermiaAPI"): def update_data(self): self.__info = self.__api_interface.get_device_info(self.__device_id) - self.__status = self.__api_interface.get_device_status(self.__device_id) - self.__device_data = self.__api_interface.get_device_by_id(self.__device_id) + self.__status = self.__api_interface.get_device_status( + self.__device_id) + self.__device_data = self.__api_interface.get_device_by_id( + self.__device_id) self.__register_indexes["temperature"] = self.__status.get( "heatingEffectRegisters", [None, None] @@ -76,7 +80,8 @@ def update_data(self): self.__device_id ) self.__group_operational_status = ( - self.__api_interface.get__group_operational_status(self.__device_id) + self.__api_interface.get__group_operational_status( + self.__device_id) ) self.__group_operational_time = ( self.__api_interface.get__group_operational_time(self.__device_id) @@ -126,7 +131,8 @@ def set_hot_water_switch_state(self, state: int): self.update_data() def __get_heat_temperature_data(self): - device_temperature_register_index = self.get_register_indexes()["temperature"] + device_temperature_register_index = self.get_register_indexes()[ + "temperature"] if device_temperature_register_index is None: return None @@ -186,12 +192,14 @@ def __get_data_from_group_by_register_name(self, group, register_name: str): def __get_active_alarms(self): active_alarms = filter( - lambda alarm: alarm.get("isActiveAlarm", False) is True, self.__alarms + lambda alarm: alarm.get( + "isActiveAlarm", False) is True, self.__alarms ) return list(active_alarms) def __set_historical_data_registers(self): - data = self.__api_interface.get_historical_data_registers(self.__device_id) + data = self.__api_interface.get_historical_data_registers( + self.__device_id) data_map = {} @@ -279,9 +287,11 @@ def heat_temperature_step(self): @property def supply_line_temperature(self): return get_dict_value_safe( - self.__get_temperature_data_by_register_name(REG_SUPPLY_LINE), "value" + self.__get_temperature_data_by_register_name( + REG_SUPPLY_LINE), "value" ) or get_dict_value_safe( - self.__get_temperature_data_by_register_name(REG_OPER_DATA_SUPPLY_MA_SA), + self.__get_temperature_data_by_register_name( + REG_OPER_DATA_SUPPLY_MA_SA), "value", ) @@ -289,7 +299,8 @@ def supply_line_temperature(self): def desired_supply_line_temperature(self): return ( get_dict_value_safe( - self.__get_temperature_data_by_register_name(REG_DESIRED_SUPPLY_LINE), + self.__get_temperature_data_by_register_name( + REG_DESIRED_SUPPLY_LINE), "value", ) or get_dict_value_safe( @@ -309,15 +320,18 @@ def desired_supply_line_temperature(self): @property def return_line_temperature(self): return get_dict_value_safe( - self.__get_temperature_data_by_register_name(REG_RETURN_LINE), "value" + self.__get_temperature_data_by_register_name( + REG_RETURN_LINE), "value" ) or get_dict_value_safe( - self.__get_temperature_data_by_register_name(REG_OPER_DATA_RETURN), "value" + self.__get_temperature_data_by_register_name( + REG_OPER_DATA_RETURN), "value" ) @property def brine_out_temperature(self): return get_dict_value_safe( - self.__get_temperature_data_by_register_name(REG_BRINE_OUT), "value" + self.__get_temperature_data_by_register_name( + REG_BRINE_OUT), "value" ) @property @@ -329,13 +343,15 @@ def brine_in_temperature(self): @property def cooling_tank_temperature(self): return get_dict_value_safe( - self.__get_temperature_data_by_register_name(REG_COOL_SENSOR_TANK), "value" + self.__get_temperature_data_by_register_name( + REG_COOL_SENSOR_TANK), "value" ) @property def cooling_supply_line_temperature(self): return get_dict_value_safe( - self.__get_temperature_data_by_register_name(REG_COOL_SENSOR_SUPPLY), + self.__get_temperature_data_by_register_name( + REG_COOL_SENSOR_SUPPLY), "value", ) @@ -366,35 +382,40 @@ def available_operational_statuses_map(self): @property def compressor_operational_time(self): return get_dict_value_safe( - self.__get_operational_time_data_by_register_name(REG_OPER_TIME_COMPRESSOR), + self.__get_operational_time_data_by_register_name( + REG_OPER_TIME_COMPRESSOR), "value", ) @property def hot_water_operational_time(self): return get_dict_value_safe( - self.__get_operational_time_data_by_register_name(REG_OPER_TIME_HOT_WATER), + self.__get_operational_time_data_by_register_name( + REG_OPER_TIME_HOT_WATER), "value", ) @property def auxiliary_heater_1_operational_time(self): return get_dict_value_safe( - self.__get_operational_time_data_by_register_name(REG_OPER_TIME_IMM1), + self.__get_operational_time_data_by_register_name( + REG_OPER_TIME_IMM1), "value", ) @property def auxiliary_heater_2_operational_time(self): return get_dict_value_safe( - self.__get_operational_time_data_by_register_name(REG_OPER_TIME_IMM2), + self.__get_operational_time_data_by_register_name( + REG_OPER_TIME_IMM2), "value", ) @property def auxiliary_heater_3_operational_time(self): return get_dict_value_safe( - self.__get_operational_time_data_by_register_name(REG_OPER_TIME_IMM3), + self.__get_operational_time_data_by_register_name( + REG_OPER_TIME_IMM3), "value", ) @@ -446,7 +467,8 @@ def active_alarm_count(self): @property def active_alarms(self): active_alarms = self.__get_active_alarms() - active_alarm_texts = map(lambda alarm: alarm.get("eventTitle"), active_alarms) + active_alarm_texts = map( + lambda alarm: alarm.get("eventTitle"), active_alarms) return list(active_alarm_texts) ########################################################################### @@ -469,7 +491,8 @@ def get_historical_data_for_register( register_id = self.__historical_data_registers_map.get(register_name) if register_id is None: - self._LOGGER.error("Register name is not supported: " + str(register_name)) + self._LOGGER.error( + "Register name is not supported: " + str(register_name)) return None historical_data = self.__api_interface.get_historical_data( @@ -493,3 +516,53 @@ def get_historical_data_for_register( historical_data["data"], ) ) + + ########################################################################### + # Print debug data + ########################################################################### + + def debug(self): + print("Creating debug file") + + original_stdout = sys.stdout + f = open("debug.txt", "w") + sys.stdout = f + + print("########## DEBUG START ##########") + + print("self.__info:") + pretty_print_except(self.__info, ["address", "macAddress", "ownerId", + "retailerAccess", "retailerId", "timeZoneId", "id", "hasUserAccount"]) + + print("self.__status:") + pretty_print_except(self.__status) + + print("self.__device_data:") + pretty_print_except(self.__device_data, [ + "macAddress", "owner", "retailerAccess", "retailerId", "id", "status"]) + + print("self.__group_temperatures:") + pretty_print_except(self.__group_temperatures) + + installation_profile_id = self.__info.get("installationProfileId") + + if installation_profile_id is not None: + all_available_groups = self.__api_interface.get_all_available_groups( + installation_profile_id) + if all_available_groups is not None: + print("All available groups:") + pretty_print_except(all_available_groups) + + for group in all_available_groups: + group_name = group.get("name") + if group_name is not None: + print("Group " + group_name + ":") + group_data = self.__api_interface.get_register_group_json( + self.__device_id, group_name) + pretty_print_except(group_data) + + print("########## DEBUG END ##########") + + sys.stdout = original_stdout + f.close() + print("Debug file created") diff --git a/ThermiaOnlineAPI/utils/utils.py b/ThermiaOnlineAPI/utils/utils.py index ed89b84..1fa3397 100644 --- a/ThermiaOnlineAPI/utils/utils.py +++ b/ThermiaOnlineAPI/utils/utils.py @@ -1,4 +1,23 @@ +import json + + def get_dict_value_safe(dictionary, key, default=None): if dictionary is None or key not in dictionary: return default return dictionary[key] + + +def pretty_print(json_object): + print(json.dumps(json_object, indent=4, sort_keys=True)) + print("\n") + + +def pretty_print_except(json_object, except_keys=[]): + if json_object is None: + return + + json_object_copy = json_object.copy() + for key in except_keys: + if key in json_object_copy: + del json_object_copy[key] + pretty_print(json_object_copy) diff --git a/example.py b/example.py index b15e47f..fe62226 100644 --- a/example.py +++ b/example.py @@ -10,38 +10,25 @@ heat_pump = thermia.fetch_heat_pumps()[0] -print("Name: " + heat_pump.name) -print("Id: " + str(heat_pump.id)) -print("Is Online: " + str(heat_pump.is_online)) -print("Last Online: " + str(heat_pump.last_online)) -print("Model: " + str(heat_pump.model)) -print("Has Indoor Temp Sensor: " + str(heat_pump.has_indoor_temp_sensor)) -print("Indoor Temperature: " + str(heat_pump.indoor_temperature)) -print( - "Is Outdoor Temp Sensor Functioning: " - + str(heat_pump.is_outdoor_temp_sensor_functioning) -) -print("Outdoor Temperature: " + str(heat_pump.outdoor_temperature)) -print("Is Hot Water Active: " + str(heat_pump.is_hot_water_active)) -print("Hot Water Temperature: " + str(heat_pump.hot_water_temperature)) -print("Heat Temperature: " + str(heat_pump.heat_temperature)) -print("Heat Min Temperature Value: " + str(heat_pump.heat_min_temperature_value)) -print("Heat Max Temperature Value: " + str(heat_pump.heat_max_temperature_value)) -print("Heat Temperature Step: " + str(heat_pump.heat_temperature_step)) +heat_pump.debug() + +print("\n") print("\n") print("Other temperatures") print("Supply Line Temperature: " + str(heat_pump.supply_line_temperature)) print( - "Desired Supply Line Temperature: " + str(heat_pump.desired_supply_line_temperature) + "Desired Supply Line Temperature: " + + str(heat_pump.desired_supply_line_temperature) ) print("Return Line Temperature: " + str(heat_pump.return_line_temperature)) print("Brine Out Temperature: " + str(heat_pump.brine_out_temperature)) print("Brine In Temperature: " + str(heat_pump.brine_in_temperature)) print("Cooling Tank Temperature: " + str(heat_pump.cooling_tank_temperature)) print( - "Cooling Supply Line Temperature: " + str(heat_pump.cooling_supply_line_temperature) + "Cooling Supply Line Temperature: " + + str(heat_pump.cooling_supply_line_temperature) ) print("\n") @@ -49,7 +36,8 @@ print("Operational status") print("Operational status: " + str(heat_pump.operational_status)) print( - "Available operational statuses: " + str(heat_pump.available_operational_statuses) + "Available operational statuses: " + + str(heat_pump.available_operational_statuses) ) print( "Available operational statuses map: " @@ -59,8 +47,10 @@ print("\n") print("Operational Times") -print("Compressor Operational Time: " + str(heat_pump.compressor_operational_time)) -print("Hot Water Operational Time: " + str(heat_pump.hot_water_operational_time)) +print("Compressor Operational Time: " + + str(heat_pump.compressor_operational_time)) +print("Hot Water Operational Time: " + + str(heat_pump.hot_water_operational_time)) print( "Auxiliary Heater 1 Operational Time: " + str(heat_pump.auxiliary_heater_1_operational_time) @@ -86,20 +76,24 @@ print("Operation Mode data") print("Operation Mode: " + str(heat_pump.operation_mode)) print("Available Operation Modes: " + str(heat_pump.available_operation_modes)) -print("Available Operation Modes Map: " + str(heat_pump.available_operation_mode_map)) -print("Is Operation Mode Read Only: " + str(heat_pump.is_operation_mode_read_only)) +print("Available Operation Modes Map: " + + str(heat_pump.available_operation_mode_map)) +print("Is Operation Mode Read Only: " + + str(heat_pump.is_operation_mode_read_only)) print("\n") print("Hot Water data") -print("Is Hot Water Switch Available: " + str(heat_pump.is_hot_water_switch_available)) +print("Is Hot Water Switch Available: " + + str(heat_pump.is_hot_water_switch_available)) if heat_pump.is_hot_water_switch_available: print("Hot Water Switch State: " + str(heat_pump.hot_water_switch_state)) print("\n") print( - "Available historical data registers: " + str(heat_pump.historical_data_registers) + "Available historical data registers: " + + str(heat_pump.historical_data_registers) ) print( "Historical data for outdoor temperature during past 24h: "