├── .gitignore ├── LICENSE.txt ├── README.md ├── draytonwiser ├── __init__.py ├── cloud.py ├── device.py ├── device_measurement.py ├── exceptions.py ├── heating_channel.py ├── hotwater.py ├── manager.py ├── room.py ├── schedule.py ├── system.py └── wiserapi.py ├── examples ├── all_info.py ├── boost_room.py ├── config.json.template └── list_rooms.py ├── requirements.txt ├── requirements_dev.txt ├── setup.cfg ├── setup.py └── tests ├── BaseTest.py ├── context.py ├── data ├── all-no-itrv.json ├── all-with-2itrv.json ├── all-with-itrv-and-hotwater.json ├── all-with-itrv-and-room-override.json ├── all-with-itrv-awaymode.json ├── all-with-itrv.json ├── cancel-boost-result.json ├── hotwater-states.json └── set-boost-result.json ├── run_tests ├── test_cloud.py ├── test_device.py ├── test_device_measurement.py ├── test_heating_channel.py ├── test_hotwater.py ├── test_manager.py ├── test_room.py ├── test_schedule.py └── test_system.py /.gitignore: -------------------------------------------------------------------------------- 1 | secret.txt 2 | .idea 3 | config.json 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # celery beat schedule file 89 | celerybeat-schedule 90 | 91 | # SageMath parsed files 92 | *.sage.py 93 | 94 | # Environments 95 | .env 96 | .venv 97 | env/ 98 | venv/ 99 | ENV/ 100 | env.bak/ 101 | venv.bak/ 102 | 103 | # Spyder project settings 104 | .spyderproject 105 | .spyproject 106 | 107 | # Rope project settings 108 | .ropeproject 109 | 110 | # mkdocs documentation 111 | /site 112 | 113 | # mypy 114 | .mypy_cache/ 115 | .dmypy.json 116 | dmypy.json 117 | 118 | # Pyre type checker 119 | .pyre/ 120 | 121 | .DS_Store -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 2019 David Connor 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Drayton Wiser Smart Thermostat Local Python API 2 | 3 | This API interacts with the local API for the [Drayton Wiser Smart Thermostat](https://wiser.draytoncontrols.co.uk/). 4 | 5 | The Drayton Wiser system provides a great option for a smart thermostat that includes a form 6 | of local API control. 7 | 8 | The API connects through the local Wiser HeatHub which in turn connects to the ZigBee devices (thermostats and iTRVs). 9 | 10 | ## Table of Contents 11 | 12 | - [Setup](#setup) 13 | - [Features](#features) 14 | - [Caching](#Caching) 15 | - [Examples](#examples) 16 | - [Testing](#testing) 17 | - [Links](#links) 18 | 19 | ## Features 20 | 21 | TODO: Add information about the features of the library highlighting: 22 | 23 | - How WiserBaseAPI abstracts most Restful functions. 24 | - Overview of top level objects (System, Cloud Room, Device, DeviceMeasurements, Schedule, HeatingChannel, HotWater) 25 | - The manager class for retrieving data 26 | - How sending data back to server works on an object by object basis but with the WiserBaseAPI doing the work 27 | - Exceptions and error reporting 28 | - Caching 29 | 30 | ### Library Design and Caching 31 | 32 | The client library takes care of formatting all the remote RESTful calls 33 | from the objects. 34 | 35 | TODO: Explain the caching concept in the backend. 36 | 37 | ## Setup 38 | 39 | ### Get your Wiser Hub secret token 40 | 41 | Referenced from: https://community.openhab.org/t/drayton-wiser-thermostat-binding/35640/24 42 | 43 | - Press the Setup button on the hub. This will start a WiserHeatXXX access point as in the regular setup instructions. 44 | - Connect to this Wifi point with a computer (Win/Linux/Mac). You should get an IP in the 192.168.8.0/24 range. 45 | - Perform a GET request on the secret endpoint: 46 | - Windows - In powershell `Invoke-RestMethod -Method Get -UseBasicParsing -Uri http://192.168.8.1/secret/` 47 | - Linux/Mac - In terminal `curl http://192.168.8.1/secret` 48 | - The return you get back is your secret token. Keep it secret, keep it safe. 49 | - Press the setup button on the hub and it should stop flashing and return to normal 50 | 51 | ### Get your Wiser Hub IP address 52 | 53 | You'll need your Wiser Hub's IP address to connect to your system. You can 54 | do this by logging into your router or some IP search tool. 55 | 56 | TODO: Add mDNS discovery as apparently the Wiser Heat Hub supports it as per the OpenHAB discussion. 57 | 58 | ### Configure 59 | 60 | TODO: Add PyPi instructions after submitting 61 | 62 | ``` 63 | pip3 install python-draytonwiser-api 64 | ``` 65 | 66 | ``` 67 | import draytonwiser 68 | manager = draytonwiser.Manager(wiser_hub_ip=HUB_IP_ADDRESS, api_secret=API_SECRET) 69 | ``` 70 | 71 | ## Examples 72 | 73 | The library is designed to make it easy to discover what commands and variables 74 | are available in the Drayton Wiser system. If you open the various modules 75 | in the 'draytonwiser' directory you'll see 76 | 77 | Most of the main data retrieval actions with the API are dealt with via the Manager 78 | class. 79 | 80 | ### Get basic system info 81 | 82 | ``` 83 | import draytonwiser 84 | manager = draytonwiser.Manager(wiser_hub_ip=HUB_IP_ADDRESS, api_secret=API_SECRET) 85 | 86 | system_info = manager.get_system() 87 | print("System Version:" + system_info.active_system_version) 88 | ``` 89 | 90 | ### Show information about Devices 91 | 92 | This example shows how in the backend the client library will make interconnections 93 | between related objects. Devices, Rooms, RoomStats and iTRVs are all 94 | related and connected but come as separate endpoints in the API. These are separated 95 | to make sending data back to them easier but when retrieving data the client 96 | library will make the interconnections between objects for you. 97 | 98 | ``` 99 | import draytonwiser 100 | manager = draytonwiser.Manager(wiser_hub_ip=HUB_IP_ADDRESS, api_secret=API_SECRET) 101 | 102 | devices = manager.get_all_devices() 103 | for device in devices: 104 | 105 | print("Type: " + str(type(device))) 106 | print("Room ID: " + str(device.get_room_id())) 107 | print(device.product_type + " ID:[" + str(device.id) + "]") 108 | print("Battery: " + str(device.get_battery_percentage())) 109 | 110 | # A measurement object is a related RoomStat or SmartValve. To make it 111 | # easier to iterate the Device class abstracts some of this away for you 112 | # so you don't have to always care if it's a RoomStat or an iTRV or a SmartPlug 113 | 114 | if device.has_measurement(): 115 | print(" Temperature: " + str(device.measurement.temperature())) 116 | 117 | if device.product_type == "RoomStat": 118 | print(" Humidity: " + str(device.measurement.measured_humidity)) 119 | ``` 120 | 121 | ### Boost temperature manually in a Room 122 | 123 | ``` 124 | import draytonwiser 125 | manager = draytonwiser.Manager(wiser_hub_ip=HUB_IP_ADDRESS, api_secret=API_SECRET) 126 | 127 | room = manager.get_room(3) 128 | room.set_boost(30, 18) # Parameters are: duration in minutes, temperature in celcius 129 | ``` 130 | 131 | ### Setup examples 132 | 133 | Copy the config.json.template file to config.json. Fill in the IP and Secret as 134 | you retrieved them from the [Setup](#setup) stage. 135 | 136 | ### Listing all rooms 137 | 138 | `python3 list_rooms.py` 139 | 140 | ### Listing all devices 141 | 142 | 143 | ## Testing 144 | 145 | The tests use the responses library to mock returns from the Drayton Wiser hub. 146 | 147 | In the tests/data directory are a series of sample returns from a Drayton Wiser Controller 148 | which are then used to run tests. 149 | 150 | These allow for easy development without needing to hammer your Drayton Wiser controller or without 151 | needing to be on the same network. 152 | 153 | Use pytest to run the test library. Use a virtual environment for testing with either venv or virtualenv: 154 | 155 | $ python3 -m venv /tmp/digitalocean_env 156 | $ source /tmp/digitalocean_env/bin/activate 157 | $ pip install -r requirements_dev.txt 158 | 159 | To run all the tests: 160 | 161 | $ python3 -m pytest 162 | 163 | ## Links 164 | 165 | A lot of the work in figuring out the Wiser Hub local API was done for the OpenHAB project. 166 | The discussion on the OpenHAB forums provided a lot of insight into the endpoints and overall 167 | architecture for the library. The Pyecobee library also provided some nice insight into a simple 168 | but robust client library architecture. 169 | 170 | * OpenHAB Discussion - https://community.openhab.org/t/drayton-wiser-thermostat-binding/35640 171 | * Pyecobee - https://github.com/nkgilley/python-ecobee-api - Useful for seeing different API design 172 | 173 | Thanks to these great projects! 174 | 175 | -------------------------------------------------------------------------------- /draytonwiser/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Drayton Wiser API Client Library 3 | 4 | API library to manage interactions with your Drayton Wiser Home hub. 5 | 6 | """ 7 | 8 | __version__ = "1.0.0" 9 | __author__ = "David Connor" 10 | __author_email__ = "dconnor@gmail.com" 11 | __license__ = "MIT" 12 | 13 | from .wiserapi import WiserBaseAPI 14 | from .exceptions import * 15 | 16 | from .manager import Manager 17 | 18 | from .system import System 19 | from .cloud import Cloud 20 | 21 | from .heating_channel import HeatingChannel 22 | 23 | from .room import Room 24 | from .hotwater import HotWater 25 | from .schedule import Schedule 26 | 27 | from .device import Controller, RoomStat, iTRV 28 | from .device_measurement import SmartValveMeasurement, RoomStatMeasurement 29 | 30 | import logging 31 | from logging import NullHandler 32 | 33 | logging.getLogger(__name__).addHandler(NullHandler()) 34 | -------------------------------------------------------------------------------- /draytonwiser/cloud.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .wiserapi import WiserBaseAPI 4 | 5 | class Cloud(WiserBaseAPI): 6 | """Represnts the /Cloud object in the Restful API""" 7 | 8 | 9 | def __init__(self, *args, **kwargs): 10 | self.environment = None # "Prod", 11 | self.detailed_publishing = None # false, 12 | self.wiser_api_host = None # "api-nl.wiserair.com", 13 | self.boot_strap_api_host = None # "bootstrap.gl.struxurewarecloud.com" 14 | 15 | super(Cloud, self).__init__(*args, **kwargs) -------------------------------------------------------------------------------- /draytonwiser/device.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .wiserapi import WiserBaseAPI, _convert_case 4 | from .device_measurement import DeviceMeasurement 5 | 6 | FULL_BATTERY_CAPACITY = 32 7 | 8 | class Device(WiserBaseAPI): 9 | """Base class to represent objects in /Device 10 | 11 | The /Device endpoint supports different types of devices (iTRV, RoomStat, Controller, SmartPlug) that 12 | have common properties but specific properties as well. These properties listed can change depending on 13 | state of the device. 14 | 15 | This setup is being implemente with a base Device class that the others will inherit from to make it easier 16 | to handle the cases where objects have properties in common. It will also allow custom PATCH methods if these 17 | turn out to be different per device setting. 18 | 19 | Functions are created on the base object to make it easier to infer the state or capabilities of an object. 20 | 21 | When iterating through lots of objects the same function calls can be used and depending on if the device supports 22 | that function you'll get a correct response. See the all_info.py example in the /examples folder for the use 23 | of device.measurement() on any type of device. 24 | 25 | """ 26 | def __init__(self, *args, **kwargs): 27 | 28 | # Base Properties 29 | self.id = None 30 | self.node_id = 0 31 | 32 | self.product_type = None 33 | self.product_identifier = None 34 | self.active_firmware_version = None 35 | self.model_identifier = None 36 | 37 | self.battery_voltage = None # 31, 38 | self.battery_level = None # "Normal", 39 | 40 | self.device_lock_enabled = None 41 | self.displayed_signal_strength = None # "Good", 42 | self.reception_of_controller = {"Rssi": None, "Lqi": None} 43 | 44 | self.measurement = DeviceMeasurement() 45 | self.measurement_object_name = None 46 | 47 | self.room_id = None 48 | 49 | super(Device, self).__init__(*args, **kwargs) 50 | 51 | @classmethod 52 | def create(cls, *args, **kwargs): 53 | """Create factory method for instantiating devices. Takes the source JSON object 54 | and figures out which type of class it should make and then calls the constructor. 55 | """ 56 | 57 | allowed_devices = ["Controller", "RoomStat", "iTRV"] 58 | classname = kwargs['ProductType'] 59 | if classname not in allowed_devices: 60 | raise Exception("Invalid Device") 61 | 62 | cls = globals()[classname] 63 | 64 | return cls(*args, **kwargs) 65 | 66 | def has_measurement(self): 67 | """Determines if Device supports measurement""" 68 | if self.measurement_object_name is not None: 69 | return True 70 | return False 71 | 72 | def get_room_id(self): 73 | """Retrieves the room id""" 74 | 75 | # TODO: Better error checking 76 | return self.room_id 77 | 78 | def get_battery_percentage(self): 79 | """Returns the percentage battery remmaining for a device, if supported""" 80 | if self.battery_voltage is None: 81 | return None 82 | return (self.battery_voltage/FULL_BATTERY_CAPACITY)*100 83 | 84 | class Controller(Device): 85 | """Implements a Controller type from /Device 86 | 87 | Inherits from the base Device object 88 | """ 89 | 90 | 91 | def __init__(self, *args, **kwargs): 92 | super(Controller, self).__init__(*args, **kwargs) 93 | 94 | class RoomStat(Device): 95 | """Implements a RoomStat type from /Device 96 | 97 | Inherits from the base Device object 98 | """ 99 | 100 | 101 | def __init__(self, *args, **kwargs): 102 | 103 | self.parent_node_id = None # 0, 104 | 105 | self.hardware_version = None # "1", 106 | self.serial_number = None # "D0CF5EFFFE36D105", 107 | self.product_model = None # "Thermostat", 108 | 109 | self.reception_of_device = {"Rssi": None, "Lqi": None} 110 | 111 | self.ota_image_query_count = 0 # 1, 112 | self.last_ota_image_query_count = 0 # 1, 113 | self.ota_last_image_sent_bytes = None # 313162 114 | 115 | super(RoomStat, self).__init__(*args, **kwargs) 116 | 117 | self.measurement_object_name = "RoomStat" 118 | 119 | class iTRV(Device): 120 | """Implements a iTRV type from /Device 121 | 122 | Inherits from the base Device object 123 | """ 124 | 125 | 126 | def __init__(self, *args, **kwargs): 127 | self.parent_node_id = None # 65534, 128 | 129 | self.hardware_version = None # "0", 130 | self.serial_number = None # "90FD9FFFFEC39AA8", 131 | self.product_model = None # "ProductModel": "iTRV", 132 | 133 | self.reception_of_device = {"Rssi": None, "Lqi": None} 134 | 135 | self.ota_image_query_count = 0 # 1, 136 | self.last_ota_image_query_count = 0 # 1, 137 | self.ota_last_image_sent_bytes = None # 313162 138 | 139 | self.pending_zigbee_message_mask = None # 0 140 | 141 | super(iTRV, self).__init__(*args, **kwargs) 142 | 143 | self.measurement_object_name = "SmartValve" 144 | -------------------------------------------------------------------------------- /draytonwiser/device_measurement.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Device Measurement 3 | 4 | This is an abstraction of the RoomStat and SmartValve objects that get attached to Devices. Because the core 5 | device contains settings and core state properties, these related objects have what are effectively the measurements 6 | of what the device does. The stateful properties on the core device. 7 | 8 | """ 9 | 10 | from .wiserapi import WiserBaseAPI 11 | 12 | class DeviceMeasurement(WiserBaseAPI): 13 | def __init__(self, *args, **kwargs): 14 | 15 | self.id = None # 34190 16 | self.measured_temperature = None # 224, 17 | 18 | super(DeviceMeasurement, self).__init__(*args, **kwargs) 19 | 20 | def temperature(self): 21 | temperature = self.measured_temperature 22 | if temperature is None: 23 | temperature = 0 24 | return temperature/10 25 | 26 | class SmartValveMeasurement(DeviceMeasurement): 27 | def __init__(self, *args, **kwargs): 28 | 29 | # Defining default values 30 | 31 | self.mounting_orientation = None # Vertical 32 | self.percentage_demand = None # 0 33 | self.window_state = None # "Closed" 34 | 35 | super(SmartValveMeasurement, self).__init__(*args, **kwargs) 36 | 37 | class RoomStatMeasurement(DeviceMeasurement): 38 | def __init__(self, *args, **kwargs): 39 | 40 | # Defining default values 41 | 42 | self.set_point = None # 200 43 | self.measured_humidity = None # 38 44 | 45 | super(RoomStatMeasurement, self).__init__(*args, **kwargs) -------------------------------------------------------------------------------- /draytonwiser/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | class APIException(Exception): 3 | """Base exception class for this module""" 4 | pass 5 | 6 | class ObjectNotFoundException(Exception): 7 | """Base exception class for this module""" 8 | pass 9 | 10 | class HotWaterNotSupportedException(Exception): 11 | """Base exception class for this module""" 12 | pass -------------------------------------------------------------------------------- /draytonwiser/heating_channel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .wiserapi import WiserBaseAPI, _convert_case 4 | 5 | class HeatingChannel(WiserBaseAPI):# 6 | """Represnts the /HeatingChannel object in the Restful API""" 7 | 8 | 9 | def __init__(self, *args, **kwargs): 10 | 11 | # Defining default values 12 | 13 | self.id = None # 1 14 | self.name = None # "Channel-1", 15 | self.room_ids = None # "RoomIds": [ 16 | # 2, 17 | # 3, 18 | # 4 19 | # } 20 | self.percentage_demand = None # 0 21 | self.demand_on_off_output = None # "Off", 22 | self.heating_relay_state = None # "Off", 23 | self.is_smart_valve_preventing_demand = None # true 24 | 25 | super(HeatingChannel, self).__init__(*args, **kwargs) 26 | -------------------------------------------------------------------------------- /draytonwiser/hotwater.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .wiserapi import WiserBaseAPI, _convert_case 4 | 5 | class HotWater(WiserBaseAPI): 6 | def __init__(self, *args, **kwargs): 7 | """Represnts the /HotWater object in the Restful API""" 8 | 9 | # Defining default values 10 | 11 | self.id = None # 2 12 | 13 | self.override_type = None # "None", 14 | self.schedule_id = None # 1000, 15 | self.mode = None # "Auto", 16 | self.water_heating_state = None # "Off", 17 | self.hot_water_relay_state = None # "Off" 18 | 19 | super(HotWater, self).__init__(*args, **kwargs) -------------------------------------------------------------------------------- /draytonwiser/manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Manager object for retrieving objects from Heat Hub. 3 | 4 | The Heat hub client library is generally meant to be used via the Manager class for retrieving data and setting 5 | up classes for post backs. 6 | 7 | i.e. 8 | import draytonwiser 9 | manager = draytonwiser.Manager(wiser_hub_ip=HUB_IP_ADDRESS, api_secret=API_SECRET) 10 | devices = manager.get_all_devices() 11 | 12 | TODO: More about this architecture. 13 | 14 | """ 15 | 16 | from .wiserapi import WiserBaseAPI 17 | 18 | from .system import System 19 | from .cloud import Cloud 20 | 21 | from .heating_channel import HeatingChannel 22 | 23 | from .room import Room 24 | from .hotwater import HotWater 25 | from .schedule import Schedule 26 | 27 | from .device import Device, Controller, RoomStat, iTRV 28 | from .device_measurement import SmartValveMeasurement, RoomStatMeasurement 29 | 30 | from .exceptions import * 31 | 32 | class Manager(WiserBaseAPI): 33 | 34 | def __init__(self, *args, **kwargs): 35 | super(Manager, self).__init__(*args, **kwargs) 36 | 37 | def has_hot_water(self): 38 | try: 39 | hotwaters = self.get_all_hotwater() 40 | 41 | if len(hotwaters) > 0: 42 | return True 43 | except HotWaterNotSupportedException: 44 | # No worries 45 | pass 46 | 47 | return False 48 | 49 | # Get hot water status (On/Off) 50 | 51 | def get_heating_relay_state(self): 52 | 53 | status = "Off" 54 | 55 | # There could be multiple heating channels, 56 | heating_channels = self.get_all_heating_channels() 57 | 58 | for heating_channel in heating_channels: 59 | if heating_channel.heating_relay_state == "On": 60 | status = "On" 61 | 62 | return status 63 | 64 | # Get hot water status (On/Off) 65 | 66 | def get_hotwater_relay_state(self): 67 | 68 | status = "Off" 69 | 70 | # There could be multiple heating channels, 71 | try: 72 | hotwaters = self.get_all_hotwater() 73 | 74 | for hotwater in hotwaters: 75 | if hotwater.hot_water_relay_state == "On": 76 | status = "On" 77 | except HotWaterNotSupportedException: 78 | status = "Off" 79 | 80 | return status 81 | 82 | def get_all_heating_channels(self): 83 | 84 | params = dict() 85 | data = self.get_data(item="HeatingChannel/", params=params) 86 | 87 | heating_channels = list() 88 | 89 | for jsoned in data: 90 | 91 | heating_channel = HeatingChannel(**jsoned) 92 | heating_channel.wiser_hub_ip = self.wiser_hub_ip 93 | heating_channel.api_secret = self.api_secret 94 | 95 | # Load related Room properties and parameters 96 | 97 | heating_channels.append(heating_channel) 98 | 99 | return heating_channels 100 | 101 | def get_heating_channel(self, id): 102 | 103 | url = "HeatingChannel/{}".format(id) 104 | heating_channel_data = self.get_data(url) 105 | 106 | heating_channel = HeatingChannel(**heating_channel_data) 107 | heating_channel.wiser_hub_ip = self.wiser_hub_ip 108 | heating_channel.api_secret = self.api_secret 109 | 110 | return heating_channel 111 | 112 | def get_all_rooms(self): 113 | 114 | params = dict() 115 | data = self.get_data(item="Room/", params=params) 116 | 117 | rooms = list() 118 | 119 | for jsoned in data: 120 | room = self.get_room(jsoned['id']) 121 | rooms.append(room) 122 | 123 | return rooms 124 | 125 | def get_room(self, item_id): 126 | 127 | url = "Room/{}".format(item_id) 128 | 129 | room_data = self.get_data(url) 130 | room_data['wiser_hub_ip'] = self.wiser_hub_ip 131 | room_data['api_secret'] = self.api_secret 132 | 133 | room = Room(**room_data) 134 | 135 | # TODO: Need link these in a more clever way 136 | 137 | # Room Stats 138 | try: 139 | room.room_stat = self.get_room_stat(room.room_stat_id) 140 | except ValueError: 141 | room.room_stat = None 142 | 143 | return room 144 | 145 | def get_all_devices(self): 146 | 147 | params = dict() 148 | data = self.get_data(item="Device/", params=params) 149 | 150 | devices = list() 151 | 152 | for jsoned in data: 153 | device = self.get_device(jsoned['id']) 154 | devices.append(device) 155 | 156 | return devices 157 | 158 | def get_device(self, item_id): 159 | 160 | url = "Device/{}".format(item_id) 161 | 162 | device_data = self.get_data(url) 163 | device_data['wiser_hub_ip'] = self.wiser_hub_ip 164 | device_data['api_secret'] = self.api_secret 165 | 166 | device = Device.create(**device_data) 167 | 168 | # TODO: Need to link these in a more clever way. 169 | if device.measurement_object_name == "SmartValve": 170 | device.measurement = self.get_smart_valve(device.id) 171 | elif device.measurement_object_name == "RoomStat": 172 | device.measurement = self.get_room_stat(device.id) 173 | 174 | # TODO: Find matching room 175 | rooms = self.get_all_rooms() 176 | for room in rooms: 177 | if room.has_device(device.id): 178 | device.room_id = room.id 179 | break 180 | 181 | return device 182 | 183 | def get_all_schedules(self): 184 | 185 | params = dict() 186 | data = self.get_data(item="Schedule/", params=params) 187 | 188 | schedules = list() 189 | 190 | for jsoned in data: 191 | 192 | schedule = Schedule(**jsoned) 193 | schedule.wiser_hub_ip = self.wiser_hub_ip 194 | schedule.api_secret = self.api_secret 195 | 196 | schedules.append(schedule) 197 | 198 | return schedules 199 | 200 | def get_schedule(self, id): 201 | 202 | url = "Schedule/{}".format(id) 203 | schedule_data = self.get_data(url) 204 | 205 | 206 | schedule = Schedule(**schedule_data) 207 | schedule.wiser_hub_ip = self.wiser_hub_ip 208 | schedule.api_secret = self.api_secret 209 | 210 | return schedule 211 | 212 | def get_all_room_stats(self): 213 | 214 | params = dict() 215 | data = self.get_data(item="RoomStat/", params=params) 216 | 217 | room_stats = list() 218 | 219 | for jsoned in data: 220 | room_stat = RoomStatMeasurement(**jsoned) 221 | room_stat.wiser_hub_ip = self.wiser_hub_ip 222 | room_stat.api_secret = self.api_secret 223 | 224 | room_stats.append(room_stat) 225 | 226 | return room_stats 227 | 228 | def get_room_stat(self, item_id): 229 | 230 | url = "RoomStat/{}".format(item_id) 231 | room_stat_data = self.get_data(url) 232 | room_stat_data['wiser_hub_ip'] = self.wiser_hub_ip 233 | room_stat_data['api_secret'] = self.api_secret 234 | 235 | room_stat = RoomStatMeasurement(**room_stat_data) 236 | 237 | return room_stat 238 | 239 | def get_all_smart_valves(self): 240 | 241 | params = dict() 242 | data = self.get_data(item="SmartValve/", params=params) 243 | 244 | smart_valves = list() 245 | 246 | for jsoned in data: 247 | 248 | smart_valve = SmartValveMeasurement(**jsoned) 249 | smart_valve.wiser_hub_ip = self.wiser_hub_ip 250 | smart_valve.api_secret = self.api_secret 251 | 252 | smart_valves.append(smart_valve) 253 | 254 | return smart_valves 255 | 256 | def get_smart_valve(self, id): 257 | 258 | url = "SmartValve/{}".format(id) 259 | smart_valve_data = self.get_data(url) 260 | 261 | smart_valve = SmartValveMeasurement(**smart_valve_data) 262 | smart_valve.wiser_hub_ip = self.wiser_hub_ip 263 | smart_valve.api_secret = self.api_secret 264 | 265 | return smart_valve 266 | 267 | def get_system(self): 268 | 269 | params = dict() 270 | data = self.get_data(item="System/", params=params) 271 | 272 | system = System(**data) 273 | system.wiser_hub_ip = self.wiser_hub_ip 274 | system.api_secret = self.api_secret 275 | 276 | return system 277 | 278 | def get_cloud(self): 279 | 280 | params = dict() 281 | data = self.get_data(item="Cloud/", params=params) 282 | 283 | cloud = Cloud(**data) 284 | cloud.wiser_hub_ip = self.wiser_hub_ip 285 | cloud.api_secret = self.api_secret 286 | 287 | return cloud 288 | 289 | def get_all_hotwater(self): 290 | 291 | params = dict() 292 | try: 293 | data = self.get_data(item="HotWater/", params=params) 294 | except KeyError: 295 | raise HotWaterNotSupportedException 296 | 297 | hotwaters = list() 298 | 299 | for jsoned in data: 300 | hotwater = HotWater(**jsoned) 301 | hotwater.wiser_hub_ip = self.wiser_hub_ip 302 | hotwater.api_secret = self.api_secret 303 | 304 | # Load related Room properties and parameters 305 | 306 | hotwaters.append(hotwater) 307 | 308 | return hotwaters -------------------------------------------------------------------------------- /draytonwiser/room.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .wiserapi import WiserBaseAPI, _convert_case 4 | 5 | class Room(WiserBaseAPI): 6 | """Represnts the /Room object in the Restful API""" 7 | 8 | def __init__(self, *args, **kwargs): 9 | 10 | # Defining default values 11 | 12 | self.id = None 13 | self.schedule_id = None # 3, 14 | self.heating_rate = None # 1200, 15 | self.smart_valve_ids = [] 16 | self.ufh_relay_ids = [] # [], 17 | self.name = None # "Bedroom", 18 | self.mode = None # "Auto", 19 | self.window_detection_active = None # false, 20 | self.control_sequence_of_operation = None # "HeatingOnly", 21 | self.heating_type = None # "HydronicRadiator", 22 | self.current_set_point = None # 210, 23 | self.set_point_origin = None # "FromSchedule","FromManualOverride", 24 | self.displayed_set_point = None # 210, 25 | self.scheduled_set_point = None # 210, 26 | self.invalid = None # "NothingAssigned" 27 | 28 | self.window_state = None # "Closed", 29 | 30 | # Properties added once a thermostat is attached 31 | self.manual_set_point = None # 210, 32 | self.override_type = None # Manual", 33 | self.override_setpoint = None # 200, 34 | 35 | self.room_stat_id = None # 34190, 36 | 37 | self.demand_type = None # "Modulating", 38 | self.calculated_temperature = None # 199, 39 | self.percentage_demand = None # 20, 40 | self.control_output_state = None # "Off", 41 | self.away_mode_suppressed = None # false, 42 | self.rounded_alexa_temperature = None # 200 43 | 44 | self.room_stat = None 45 | self.smart_valves = [] 46 | 47 | super(Room, self).__init__(*args, **kwargs) 48 | 49 | def has_room_stat(self): 50 | if self.room_stat_id is not None and self.room_stat_id >= 0: 51 | return True 52 | return False 53 | 54 | def has_smart_valve(self): 55 | if len(self.smart_valves) > 0: 56 | return True 57 | return False 58 | 59 | def get_current_temperature(self): 60 | if self.has_room_stat(): 61 | return self.room_stat.temperature() 62 | 63 | return None 64 | # if self.has_smart_valve(): 65 | # for smart_valve in room.smart_valve: 66 | # print(smart_valve) 67 | 68 | def get_current_set_point(self): 69 | if self.current_set_point is None: 70 | return 0 71 | 72 | return self.current_set_point/10 73 | 74 | 75 | def has_device(self, device_id): 76 | 77 | if self.has_room_stat(): 78 | if self.room_stat.id == device_id: 79 | return True 80 | 81 | for smart_valve_id in self.smart_valve_ids: 82 | if device_id == smart_valve_id: 83 | return True 84 | 85 | return False 86 | 87 | 88 | 89 | def set_boost(self, duration, temperature): 90 | if self.id is None: 91 | # TODO: Exception 92 | return 93 | 94 | calculated_temperature = int(temperature * 10) 95 | params = { 96 | "RequestOverride": { 97 | "Type": "Manual", 98 | "Originator": "App", 99 | "DurationMinutes": str(duration), 100 | "SetPoint": str(calculated_temperature) 101 | } 102 | } 103 | 104 | self.patch_data("Room/{}".format(self.id), params) 105 | 106 | def cancel_boost(self): 107 | if self.id is None: 108 | # TODO: Exception 109 | return 110 | 111 | params = { 112 | "RequestOverride": { 113 | "Type": "None", 114 | "Originator": "App", 115 | "DurationMinutes": 0, 116 | "SetPoint": 0 117 | } 118 | } 119 | 120 | self.patch_data("Room/{}".format(self.id), params) -------------------------------------------------------------------------------- /draytonwiser/schedule.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .wiserapi import WiserBaseAPI, _convert_case 4 | 5 | class SetPoint: 6 | def __init__(self): 7 | self.time = None 8 | self.temperature = None 9 | 10 | class Schedule(WiserBaseAPI): 11 | """Represnts the /Schedule object in the Restful API""" 12 | 13 | def __init__(self, *args, **kwargs): 14 | 15 | # Defining default values 16 | 17 | self.id = None 18 | self.type = None # "Heating" 19 | 20 | self.monday = None 21 | self.tuesday = None 22 | self.wednesday = None 23 | self.thursday = None 24 | self.friday = None 25 | self.saturday = None 26 | 27 | # "Sunday": { 28 | # "SetPoints": [ 29 | # { 30 | # "Time": 700, 31 | # "DegreesC": 200 32 | # }, 33 | # { 34 | # "Time": 900, 35 | # "DegreesC": 180 36 | # }, 37 | # { 38 | # "Time": 1600, 39 | # "DegreesC": 210 40 | # }, 41 | # { 42 | # "Time": 2300, 43 | # "DegreesC": -200 44 | # } 45 | # ] 46 | # }, 47 | 48 | super(Schedule, self).__init__(*args, **kwargs) 49 | 50 | # def _load_attributes(self, *args, **kwargs): 51 | # pass 52 | # # Set attributes of object from CamelCase 53 | # #print("LOAD custom schedule data!!") 54 | # #for attr in kwargs.keys(): 55 | # #setattr(self, _convert_case(attr), kwargs[attr]) 56 | # -------------------------------------------------------------------------------- /draytonwiser/system.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .wiserapi import WiserBaseAPI 4 | 5 | class System(WiserBaseAPI): 6 | """Represnts the /System object in the Restful API""" 7 | 8 | def __init__(self, *args, **kwargs): 9 | # Defining default values 10 | 11 | self.pairing_status = None # ": "Paired", 12 | self.override_type = None # "Away" 13 | self.time_zone_offset = None # ": 0, 14 | self.automatic_daylight_saving = None # ": true, 15 | self.system_mode = None # ": "Heat", 16 | self.version = None # ": 6, 17 | self.fota_enabled = None # ": true, 18 | self.valve_protection_enabled = None # ": false, 19 | self.away_mode_affects_hot_water = None # ": true, 20 | self.away_mode_set_point_limit = None # ": 150, 21 | self.boiler_settings = None 22 | # # ": { 23 | # "ControlType": "HeatSourceType_RelayControlled", 24 | # "FuelType": "Gas", 25 | # "CycleRate": "CPH_6", 26 | # "OnOffHysteresis": 5 27 | # }, 28 | self.zigbee_settings = {"SuppressApsAcks": None} # ": {"SuppressApsAcks": true} 29 | self.cooling_mode_default_setpoint = None # ": 210, 30 | self.cooling_away_mode_setpoint_limit = None # ": 240, 31 | self.comfort_mode_enabled = None # ": false, 32 | self.preheat_time_limit = None # ": 10800, 33 | self.degraded_mode_setpoint_threshold = None # ": 180, 34 | self.unix_time = None # ": 1546640700, 35 | self.active_system_version = None # ": "2.26.16-6340f5b", 36 | self.cloud_connection_status = None # ": "Connected", 37 | self.zigbee_module_version = None # ": "R311 B030517", 38 | self.zigbee_eui = None # ": "90FD9FFFFEAA6AA7", 39 | self.local_date_and_time = None 40 | 41 | # # ": { 42 | # "Year": 2019, 43 | # "Month": "January", 44 | # "Date": 4, 45 | # "Day": "Friday", 46 | # "Time": 2225 47 | # }, 48 | self.heating_button_override_state = None # ": "Off", 49 | self.hot_water_button_override_state = None # ": "Off" 50 | 51 | super(System, self).__init__(*args, **kwargs) 52 | -------------------------------------------------------------------------------- /draytonwiser/wiserapi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Base object for interacting with Wiser Heat Hub API 3 | 4 | The WiseBaseAPI is the root object for all classes that represent a /data/domain/[item] endpoint like 5 | Room or Device. These inherit from WiserBaseAPI as this allow the base class to handle all the dirty work 6 | translating queries into HTTP requests. 7 | 8 | The endpoints and manager class can then use simpler notation to get back JSON return objects. 9 | I.e. 10 | url = "System/{}".format(id) 11 | heating_channel_data_json = self.get_data(url) 12 | 13 | heating_channel = HeatingChannel(**heating_channel_data) 14 | 15 | With those calls the HeatingChannel class object is istantiated with the correct values from the JSON object 16 | via the get_data request. 17 | 18 | Each call to get_data will either send an HTTP request or the cache will return the data for it. 19 | """ 20 | 21 | import logging 22 | 23 | import requests 24 | import re 25 | import time 26 | 27 | from .exceptions import APIException,ObjectNotFoundException 28 | 29 | from requests.exceptions import RequestException 30 | 31 | logger = logging.getLogger(__name__) 32 | 33 | WISER_HUB_URL = "http://{}/data/domain/" 34 | 35 | # TODO: Move to helper 36 | # https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case 37 | def _convert_case(name): 38 | """Converts SomeSpecialVariable type names to some_special_variable 39 | for JSON to python object translation 40 | """ 41 | step1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) 42 | return re.sub('([a-z0-9])([A-Z])', r'\1_\2', step1).lower() 43 | 44 | class WiserBaseAPI(object): 45 | 46 | # TODO: Implement cache of local response data as Wiser hub often goes unresponsive 47 | # Static variables for tracking whether or not request should refresh source data 48 | # Implement internal throttling to the API client library to play nice with the Wiser API. 49 | # I.e. provide a mechanism so that an unintended script doesn't hammer the controller 50 | 51 | _cache = None 52 | _cache_refreshed = None 53 | _data_query_count = 0 54 | _cache_query_count = 0 55 | 56 | def __init__(self, *args, **kwargs): 57 | 58 | self.api_secret = "" 59 | self.wiser_hub_ip = "" 60 | 61 | # TODO: Load from config 62 | self.use_cache = True 63 | self.refresh_interval = 5 64 | 65 | # Temp 66 | try: 67 | self.api_secret = kwargs["api_secret"] # api_secret 68 | self.wiser_hub_ip = kwargs["wiser_hub_ip"] # wiser_hub_ip 69 | except: 70 | #logger.warning("No hub IP or secret information provided") 71 | pass 72 | 73 | self._load_attributes(*args, **kwargs) 74 | 75 | def update_cache(self): 76 | self.get_data("") 77 | 78 | def get_data(self, item, params=None): 79 | """GET requests to Wiser Home hub 80 | 81 | Has a simple form of data caching added to handle cases when the Heat Hub is unresponsive. 82 | TODO: Make this easier to configure and specify how it works. 83 | 84 | """ 85 | WiserBaseAPI._data_query_count += 1 86 | 87 | # Check for data in cache 88 | if self.use_cache: 89 | if WiserBaseAPI._cache_refreshed == None: 90 | WiserBaseAPI._cache_refreshed = time.time() 91 | else: 92 | last_update_diff = time.time() - WiserBaseAPI._cache_refreshed 93 | if last_update_diff <= self.refresh_interval: 94 | data = WiserBaseAPI._cache 95 | WiserBaseAPI._cache_query_count += 1 96 | 97 | # TODO: See note below about temporary parsing 98 | # Parse out query for now 99 | return self._parse_item_data(item, data) 100 | 101 | # TODO: See note below about temporary parsing 102 | # Parse out query for now 103 | 104 | # url = "http://{}/data/domain/{}".format(self.wiser_hub_ip, item) 105 | url = "http://{}/data/domain/".format(self.wiser_hub_ip) 106 | 107 | logger.info("get_data - " + url) 108 | logger.info("get_data - item: " + item) 109 | 110 | header = {'Content-Type': 'application/json;charset=UTF-8', 111 | 'Secret': self.api_secret} 112 | 113 | try: 114 | logger.info("Querying Wiser Heat Hub") 115 | request = requests.get(url, headers=header) 116 | except RequestException: 117 | raise APIException("Error connecting to Wiser. Possible connectivity outage.") 118 | 119 | if request.status_code != requests.codes.ok: 120 | raise APIException("Error connecting to Wiser while attempting to get thermostat data.") 121 | 122 | data = request.json() 123 | 124 | if self.use_cache: 125 | WiserBaseAPI._cache = data 126 | 127 | logger.info("get_data - query: " + str(WiserBaseAPI._data_query_count)) 128 | logger.info("get_data - cache: " + str(WiserBaseAPI._cache_query_count)) 129 | 130 | # TODO: See note below above temporary parsing 131 | # Parse out query for now 132 | 133 | return self._parse_item_data(item, data) 134 | 135 | def patch_data(self, item, params=None): 136 | """PATCH requests to Wiser Home hub""" 137 | 138 | url = "http://{}/data/domain/{}".format(self.wiser_hub_ip, item) 139 | 140 | #print(url) 141 | header = {'Content-Type': 'application/json;charset=UTF-8', 142 | 'Secret': self.api_secret} 143 | 144 | try: 145 | request = requests.patch(url, json=params, headers=header) 146 | except RequestException: 147 | logger.warn("Error connecting to Wiser. Possible connectivity outage.") 148 | return None 149 | 150 | if request.status_code != requests.codes.ok: 151 | logger.info("Error connecting to Wiser while attempting to get " 152 | "thermostat data.") 153 | 154 | data = request.json() 155 | 156 | def _load_attributes(self, *args, **kwargs): 157 | # Set attributes of object from CamelCase 158 | for attr in kwargs.keys(): 159 | setattr(self, _convert_case(attr), kwargs[attr]) 160 | 161 | # TODO: For now all get_data requests are just taking the rooot /data/domain/ 162 | # level query and then parsing out the data as required. In the future 163 | # this library may attempt to spread out the get queries as required 164 | # but the slight unstability of the wiser hub means trying to reduce the number 165 | # of calls for the moment. 166 | # This parsing routine will make sure the correct query returns the correct subset 167 | # of data like it had been real direct API call. 168 | 169 | def _parse_item_data(self, item, data): 170 | """Parses data from /data/domain/ call to serve it as if /data/domain/[Item]/[ID] was called 171 | 172 | This has been added to provide compatibility for the library supporting direct restful 173 | calls to specific endpoints. At the moment each call gets the root /data/domain/ for caching 174 | but may be split out into separate calls when caching is made more robust. 175 | """ 176 | 177 | if item.startswith('HeatingChannel'): 178 | data = self._get_item_if_exists(item, data['HeatingChannel']) 179 | elif item.startswith('HotWater'): 180 | data = self._get_item_if_exists(item, data['HotWater']) 181 | elif item.startswith('Device'): 182 | data = self._get_item_if_exists(item, data['Device']) 183 | elif item.startswith('RoomStat'): 184 | data = self._get_item_if_exists(item, data['RoomStat']) 185 | elif item.startswith('Room'): 186 | data = self._get_item_if_exists(item, data['Room']) 187 | elif item.startswith('Cloud'): 188 | data = data['Cloud'] 189 | elif item.startswith('System'): 190 | data = data['System'] 191 | elif item.startswith('SmartValve'): 192 | data = self._get_item_if_exists(item, data['SmartValve']) 193 | elif item.startswith('Schedule'): 194 | data = self._get_item_if_exists(item, data['Schedule']) 195 | 196 | return data 197 | 198 | def _get_item_if_exists(self, item, data): 199 | """Retrieves the JSON object for a specific ID of a /data/domain/[item]/[id] call""" 200 | 201 | item_id = item[item.index('/') + 1:] 202 | if len(item_id) > 0 and item_id is not None: 203 | # we have an id 204 | item_id = int(item_id) 205 | found = False 206 | if item_id >= 0: 207 | for temp_item in data: 208 | if temp_item['id'] == item_id: 209 | data = temp_item 210 | found = True 211 | break 212 | if not found: 213 | raise ObjectNotFoundException("Item ID not found") 214 | 215 | return data 216 | -------------------------------------------------------------------------------- /examples/all_info.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../') 3 | from time import sleep 4 | 5 | import logging 6 | #logging.basicConfig(level=logging.INFO) 7 | 8 | import json 9 | import draytonwiser 10 | 11 | # Simple loading of config data 12 | with open("config.json") as f: 13 | json_data = json.loads(f.read()) 14 | 15 | manager = draytonwiser.Manager(wiser_hub_ip=json_data['wiser_hub_ip'], api_secret=json_data['api_secret']) 16 | 17 | 18 | print("Wiser Home Heat Hub") 19 | print("===================") 20 | system_info = manager.get_system() 21 | 22 | print("System Version:" + system_info.active_system_version) 23 | print("Away Mode Set Point Limit:" + str(system_info.away_mode_set_point_limit)) 24 | print("Heating Relay State: " + str(manager.get_heating_relay_state())) 25 | print("Hot Water Relay State: " + str(manager.get_hotwater_relay_state())) 26 | print("Hot Water: " + str(manager.has_hot_water())) 27 | 28 | print("") 29 | 30 | print("Heating Channels") 31 | print("****************") 32 | 33 | heating_channels = manager.get_all_heating_channels() 34 | for heating_channel in heating_channels: 35 | print(heating_channel.name + " [" + str(heating_channel.id) + "]") 36 | 37 | print("") 38 | 39 | print("Rooms") 40 | print("*****") 41 | 42 | rooms = manager.get_all_rooms() 43 | for room in rooms: 44 | print(room.name + " ID:[" + str(room.id) + "]") 45 | 46 | print(" SetPoint: " + str(room.get_current_set_point()) + " " + room.set_point_origin) 47 | print(" Temp: " + str(room.get_current_temperature())) 48 | if room.has_room_stat(): 49 | print(" Thermostat: " + str(room.room_stat.temperature())) 50 | 51 | if room.has_smart_valve(): 52 | for smart_valve in room.smart_valve: 53 | print(smart_valve) 54 | 55 | # if device.has_measurement(): 56 | # print(" Temperature: " + str(device.measurement.temperature())) 57 | 58 | print("") 59 | 60 | print("Devices") 61 | print("*******") 62 | 63 | devices = manager.get_all_devices() 64 | for device in devices: 65 | 66 | print("Type: " + str(type(device))) 67 | print("Room ID: " + str(device.get_room_id())) 68 | print(device.product_type + " ID:[" + str(device.id) + "]") 69 | print("Battery: " + str(device.get_battery_percentage())) 70 | 71 | if device.has_measurement(): 72 | print(" Temperature: " + str(device.measurement.temperature())) 73 | 74 | if device.product_type == "RoomStat": 75 | print(" Humidity: " + str(device.measurement.measured_humidity)) 76 | 77 | 78 | 79 | try: 80 | hotwaters = manager.get_all_hotwater() 81 | except draytonwiser.exceptions.HotWaterNotSupportedException: 82 | pass 83 | 84 | # 85 | # 86 | # rooms = manager.get_all_rooms() 87 | # for room in rooms: 88 | # 89 | # if room.has_room_stat(): 90 | # print(room.name + " - " + str(room.room_stat.temperature()) + "C") 91 | # else: 92 | # print(room.name) 93 | -------------------------------------------------------------------------------- /examples/boost_room.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../') 3 | from time import sleep 4 | 5 | import json 6 | import draytonwiser 7 | 8 | # Simple loading of config data 9 | with open("config.json") as f: 10 | json_data = json.loads(f.read()) 11 | 12 | manager = draytonwiser.Manager(wiser_hub_ip=json_data['wiser_hub_ip'], api_secret=json_data['api_secret']) 13 | 14 | room = manager.get_room(3) 15 | 16 | room.set_boost(30, 18) 17 | 18 | # room.cancel_boost() - You can call this to cancel the boost. -------------------------------------------------------------------------------- /examples/config.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "api_secret": "secret", 3 | "wiser_hub_ip": "192.168.1.100" 4 | } -------------------------------------------------------------------------------- /examples/list_rooms.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../') 3 | from time import sleep 4 | 5 | import logging 6 | logging.basicConfig(level=logging.INFO) 7 | 8 | import json 9 | import draytonwiser 10 | 11 | # Simple loading of config data 12 | with open("config.json") as f: 13 | json_data = json.loads(f.read()) 14 | 15 | manager = draytonwiser.Manager(wiser_hub_ip=json_data['wiser_hub_ip'], api_secret=json_data['api_secret']) 16 | 17 | system_info = manager.get_system() 18 | print("System Version:" + system_info.active_system_version) 19 | 20 | rooms = manager.get_all_rooms() 21 | for room in rooms: 22 | 23 | if room.has_room_stat(): 24 | print(room.name + " - " + str(room.room_stat.temperature()) + "C") 25 | else: 26 | print(room.name) 27 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2,<3 2 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | requests>=2,<3 2 | 3 | # Testing requirements 4 | responses>=0.10.5 5 | pytest 6 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | 6 | try: 7 | from setuptools import setup 8 | except ImportError: 9 | from distutils.core import setup 10 | 11 | if sys.argv[-1] == 'publish': 12 | os.system('python setup.py sdist upload') 13 | sys.exit() 14 | 15 | version = '1.0.0' 16 | 17 | setup(name='python-draytonwiser-api', 18 | version=version, 19 | description='Python API and command line tool for talking to Drayton Wiser Thermostat', 20 | url='', 21 | author='', 22 | author_email='', 23 | license='MIT', 24 | install_requires=['requests>=2.0'], 25 | packages=['draytonwiser'], 26 | zip_safe=True) -------------------------------------------------------------------------------- /tests/BaseTest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | import logging 5 | #logging.basicConfig(level=logging.INFO) 6 | 7 | class BaseTest(unittest.TestCase): 8 | 9 | def setUp(self): 10 | 11 | self.wiser_hub_ip = '192.168.1.171' 12 | self.base_url = url = "http://{}/data/domain/".format(self.wiser_hub_ip) 13 | self.token = "afaketokenthatwillworksincewemockthings" 14 | self.source_data_file = "all-with-itrv.json" 15 | #self.source_data_file = "all-with-itrv-and-hotwater.json" 16 | 17 | 18 | def load_from_file(self, json_file): 19 | filename = os.path.dirname(__file__) 20 | with open(os.path.join(filename, 'data/%s' % json_file), 'r') as f: 21 | return f.read() -------------------------------------------------------------------------------- /tests/context.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 4 | 5 | import draytonwiser -------------------------------------------------------------------------------- /tests/data/all-no-itrv.json: -------------------------------------------------------------------------------- 1 | { 2 | "System": { 3 | "PairingStatus": "Paired", 4 | "TimeZoneOffset": 0, 5 | "AutomaticDaylightSaving": true, 6 | "SystemMode": "Heat", 7 | "Version": 6, 8 | "FotaEnabled": true, 9 | "ValveProtectionEnabled": false, 10 | "AwayModeAffectsHotWater": true, 11 | "AwayModeSetPointLimit": 150, 12 | "BoilerSettings": { 13 | "ControlType": "HeatSourceType_RelayControlled", 14 | "FuelType": "Gas", 15 | "CycleRate": "CPH_6", 16 | "OnOffHysteresis": 5 17 | }, 18 | "ZigbeeSettings": { 19 | "SuppressApsAcks": true 20 | }, 21 | "CoolingModeDefaultSetpoint": 210, 22 | "CoolingAwayModeSetpointLimit": 240, 23 | "ComfortModeEnabled": false, 24 | "PreheatTimeLimit": 10800, 25 | "DegradedModeSetpointThreshold": 180, 26 | "UnixTime": 1546640700, 27 | "ActiveSystemVersion": "2.26.16-6340f5b", 28 | "CloudConnectionStatus": "Connected", 29 | "ZigbeeModuleVersion": "R311 B030517", 30 | "ZigbeeEui": "90FD9FFFFEAA6AA7", 31 | "LocalDateAndTime": { 32 | "Year": 2019, 33 | "Month": "January", 34 | "Date": 4, 35 | "Day": "Friday", 36 | "Time": 2225 37 | }, 38 | "HeatingButtonOverrideState": "Off", 39 | "HotWaterButtonOverrideState": "Off" 40 | }, 41 | "Cloud": { 42 | "Environment": "Prod", 43 | "DetailedPublishing": false, 44 | "WiserApiHost": "api-nl.wiserair.com", 45 | "BootStrapApiHost": "bootstrap.gl.struxurewarecloud.com" 46 | }, 47 | "HeatingChannel": [ 48 | { 49 | "id": 1, 50 | "Name": "Channel-1", 51 | "RoomIds": [ 52 | 2, 53 | 3 54 | ], 55 | "PercentageDemand": 20, 56 | "DemandOnOffOutput": "Off", 57 | "HeatingRelayState": "Off", 58 | "IsSmartValvePreventingDemand": false 59 | } 60 | ], 61 | "Room": [ 62 | { 63 | "id": 2, 64 | "ScheduleId": 2, 65 | "HeatingRate": 1200, 66 | "UfhRelayIds": [], 67 | "Name": "Bedroom", 68 | "Mode": "Auto", 69 | "WindowDetectionActive": false, 70 | "ControlSequenceOfOperation": "HeatingOnly", 71 | "HeatingType": "HydronicRadiator", 72 | "CurrentSetPoint": 210, 73 | "SetPointOrigin": "FromSchedule", 74 | "DisplayedSetPoint": 210, 75 | "ScheduledSetPoint": 210, 76 | "Invalid": "NothingAssigned" 77 | }, 78 | { 79 | "id": 3, 80 | "ManualSetPoint": 210, 81 | "OverrideType": "Manual", 82 | "OverrideSetpoint": 200, 83 | "ScheduleId": 3, 84 | "HeatingRate": 1200, 85 | "RoomStatId": 34190, 86 | "UfhRelayIds": [], 87 | "Name": "Living Room", 88 | "Mode": "Auto", 89 | "DemandType": "Modulating", 90 | "WindowDetectionActive": false, 91 | "ControlSequenceOfOperation": "HeatingOnly", 92 | "HeatingType": "HydronicRadiator", 93 | "CalculatedTemperature": 199, 94 | "CurrentSetPoint": 200, 95 | "PercentageDemand": 20, 96 | "ControlOutputState": "Off", 97 | "SetPointOrigin": "FromManualOverride", 98 | "DisplayedSetPoint": 200, 99 | "ScheduledSetPoint": 200, 100 | "AwayModeSuppressed": false, 101 | "RoundedAlexaTemperature": 200 102 | } 103 | ], 104 | "Device": [ 105 | { 106 | "id": 0, 107 | "NodeId": 0, 108 | "ProductType": "Controller", 109 | "ProductIdentifier": "Controller", 110 | "ActiveFirmwareVersion": "2.26.16", 111 | "ModelIdentifier": "WT714R1S0902", 112 | "DeviceLockEnabled": false, 113 | "DisplayedSignalStrength": "VeryGood", 114 | "ReceptionOfController": { 115 | "Rssi": -65 116 | } 117 | }, 118 | { 119 | "id": 34190, 120 | "NodeId": 34190, 121 | "ProductType": "RoomStat", 122 | "ProductIdentifier": "RoomStat", 123 | "ActiveFirmwareVersion": "04E1000900010012", 124 | "ModelIdentifier": "Thermostat", 125 | "HardwareVersion": "1", 126 | "SerialNumber": "D0CF5EFFFE36D105", 127 | "ProductModel": "Thermostat", 128 | "OtaImageQueryCount": 1, 129 | "LastOtaImageQueryCount": 1, 130 | "ParentNodeId": 0, 131 | "DeviceLockEnabled": false, 132 | "DisplayedSignalStrength": "Good", 133 | "BatteryVoltage": 32, 134 | "BatteryLevel": "Normal", 135 | "ReceptionOfController": { 136 | "Rssi": -67, 137 | "Lqi": 132 138 | }, 139 | "ReceptionOfDevice": { 140 | "Rssi": -68, 141 | "Lqi": 128 142 | }, 143 | "OtaLastImageSentBytes": 313162 144 | } 145 | ], 146 | "UpgradeInfo": [ 147 | { 148 | "id": 8, 149 | "FirmwareFilename": "0A00000000010012FOTA.BIN" 150 | }, 151 | { 152 | "id": 7, 153 | "FirmwareFilename": "05E1000900000021FOTA.BIN" 154 | }, 155 | { 156 | "id": 6, 157 | "FirmwareFilename": "04E1000900010012FOTA.BIN" 158 | }, 159 | { 160 | "id": 5, 161 | "FirmwareFilename": "0441010100010005FOTA.BIN" 162 | }, 163 | { 164 | "id": 4, 165 | "FirmwareFilename": "0441000000010005FOTA.BIN" 166 | }, 167 | { 168 | "id": 3, 169 | "FirmwareFilename": "0401010100010005FOTA.BIN" 170 | }, 171 | { 172 | "id": 2, 173 | "FirmwareFilename": "0401000000010005FOTA.BIN" 174 | }, 175 | { 176 | "id": 1, 177 | "FirmwareFilename": "0201000000010012FOTA.BIN" 178 | } 179 | ], 180 | "RoomStat": [ 181 | { 182 | "id": 34190, 183 | "SetPoint": 200, 184 | "MeasuredTemperature": 199, 185 | "MeasuredHumidity": 44 186 | } 187 | ], 188 | "DeviceCapabilityMatrix": { 189 | "Roomstat": true, 190 | "ITRV": true, 191 | "SmartPlug": true, 192 | "UFH": false, 193 | "UFHFloorTempSensor": false, 194 | "UFHDewSensor": false, 195 | "HACT": false, 196 | "LACT": false 197 | }, 198 | "Schedule": [ 199 | { 200 | "id": 2, 201 | "Monday": { 202 | "SetPoints": [ 203 | { 204 | "Time": 630, 205 | "DegreesC": 200 206 | }, 207 | { 208 | "Time": 830, 209 | "DegreesC": 160 210 | }, 211 | { 212 | "Time": 1630, 213 | "DegreesC": 210 214 | }, 215 | { 216 | "Time": 2230, 217 | "DegreesC": -200 218 | } 219 | ] 220 | }, 221 | "Tuesday": { 222 | "SetPoints": [ 223 | { 224 | "Time": 630, 225 | "DegreesC": 200 226 | }, 227 | { 228 | "Time": 830, 229 | "DegreesC": 160 230 | }, 231 | { 232 | "Time": 1630, 233 | "DegreesC": 210 234 | }, 235 | { 236 | "Time": 2230, 237 | "DegreesC": -200 238 | } 239 | ] 240 | }, 241 | "Wednesday": { 242 | "SetPoints": [ 243 | { 244 | "Time": 630, 245 | "DegreesC": 200 246 | }, 247 | { 248 | "Time": 830, 249 | "DegreesC": 160 250 | }, 251 | { 252 | "Time": 1630, 253 | "DegreesC": 210 254 | }, 255 | { 256 | "Time": 2230, 257 | "DegreesC": -200 258 | } 259 | ] 260 | }, 261 | "Thursday": { 262 | "SetPoints": [ 263 | { 264 | "Time": 630, 265 | "DegreesC": 200 266 | }, 267 | { 268 | "Time": 830, 269 | "DegreesC": 160 270 | }, 271 | { 272 | "Time": 1630, 273 | "DegreesC": 210 274 | }, 275 | { 276 | "Time": 2230, 277 | "DegreesC": -200 278 | } 279 | ] 280 | }, 281 | "Friday": { 282 | "SetPoints": [ 283 | { 284 | "Time": 630, 285 | "DegreesC": 200 286 | }, 287 | { 288 | "Time": 830, 289 | "DegreesC": 160 290 | }, 291 | { 292 | "Time": 1630, 293 | "DegreesC": 210 294 | }, 295 | { 296 | "Time": 2230, 297 | "DegreesC": -200 298 | } 299 | ] 300 | }, 301 | "Saturday": { 302 | "SetPoints": [ 303 | { 304 | "Time": 700, 305 | "DegreesC": 200 306 | }, 307 | { 308 | "Time": 900, 309 | "DegreesC": 180 310 | }, 311 | { 312 | "Time": 1600, 313 | "DegreesC": 210 314 | }, 315 | { 316 | "Time": 2300, 317 | "DegreesC": -200 318 | } 319 | ] 320 | }, 321 | "Sunday": { 322 | "SetPoints": [ 323 | { 324 | "Time": 700, 325 | "DegreesC": 200 326 | }, 327 | { 328 | "Time": 900, 329 | "DegreesC": 180 330 | }, 331 | { 332 | "Time": 1600, 333 | "DegreesC": 210 334 | }, 335 | { 336 | "Time": 2300, 337 | "DegreesC": -200 338 | } 339 | ] 340 | }, 341 | "Type": "Heating" 342 | }, 343 | { 344 | "id": 3, 345 | "Monday": { 346 | "SetPoints": [ 347 | { 348 | "Time": 630, 349 | "DegreesC": 200 350 | }, 351 | { 352 | "Time": 830, 353 | "DegreesC": 160 354 | }, 355 | { 356 | "Time": 1630, 357 | "DegreesC": 210 358 | }, 359 | { 360 | "Time": 2230, 361 | "DegreesC": -200 362 | } 363 | ] 364 | }, 365 | "Tuesday": { 366 | "SetPoints": [ 367 | { 368 | "Time": 630, 369 | "DegreesC": 200 370 | }, 371 | { 372 | "Time": 830, 373 | "DegreesC": 160 374 | }, 375 | { 376 | "Time": 1630, 377 | "DegreesC": 210 378 | }, 379 | { 380 | "Time": 2230, 381 | "DegreesC": -200 382 | } 383 | ] 384 | }, 385 | "Wednesday": { 386 | "SetPoints": [ 387 | { 388 | "Time": 630, 389 | "DegreesC": 200 390 | }, 391 | { 392 | "Time": 830, 393 | "DegreesC": 160 394 | }, 395 | { 396 | "Time": 1630, 397 | "DegreesC": 210 398 | }, 399 | { 400 | "Time": 2230, 401 | "DegreesC": -200 402 | } 403 | ] 404 | }, 405 | "Thursday": { 406 | "SetPoints": [ 407 | { 408 | "Time": 630, 409 | "DegreesC": 200 410 | }, 411 | { 412 | "Time": 830, 413 | "DegreesC": 160 414 | }, 415 | { 416 | "Time": 1630, 417 | "DegreesC": 210 418 | }, 419 | { 420 | "Time": 2230, 421 | "DegreesC": -200 422 | } 423 | ] 424 | }, 425 | "Friday": { 426 | "SetPoints": [ 427 | { 428 | "Time": 630, 429 | "DegreesC": 210 430 | }, 431 | { 432 | "Time": 830, 433 | "DegreesC": 140 434 | }, 435 | { 436 | "Time": 1630, 437 | "DegreesC": 200 438 | }, 439 | { 440 | "Time": 2230, 441 | "DegreesC": -200 442 | } 443 | ] 444 | }, 445 | "Saturday": { 446 | "SetPoints": [ 447 | { 448 | "Time": 700, 449 | "DegreesC": 200 450 | }, 451 | { 452 | "Time": 900, 453 | "DegreesC": 180 454 | }, 455 | { 456 | "Time": 1600, 457 | "DegreesC": 210 458 | }, 459 | { 460 | "Time": 2300, 461 | "DegreesC": -200 462 | } 463 | ] 464 | }, 465 | "Sunday": { 466 | "SetPoints": [ 467 | { 468 | "Time": 700, 469 | "DegreesC": 200 470 | }, 471 | { 472 | "Time": 900, 473 | "DegreesC": 180 474 | }, 475 | { 476 | "Time": 1600, 477 | "DegreesC": 210 478 | }, 479 | { 480 | "Time": 2300, 481 | "DegreesC": -200 482 | } 483 | ] 484 | }, 485 | "Type": "Heating" 486 | } 487 | ] 488 | } -------------------------------------------------------------------------------- /tests/data/all-with-2itrv.json: -------------------------------------------------------------------------------- 1 | { 2 | "System": { 3 | "PairingStatus": "NotPaired", 4 | "TimeZoneOffset": 0, 5 | "AutomaticDaylightSaving": true, 6 | "Version": 6, 7 | "FotaEnabled": true, 8 | "ValveProtectionEnabled": false, 9 | "EcoModeEnabled": false, 10 | "BoilerSettings": { 11 | "ControlType": "HeatSourceType_RelayControlled", 12 | "FuelType": "Gas", 13 | "CycleRate": "CPH_6" 14 | }, 15 | "UnixTime": 1517247034, 16 | "CloudConnectionStatus": "Connected", 17 | "ZigbeeModuleVersion": "R311 B030517", 18 | "ZigbeeEui": "000B57FFFEAA1F2C", 19 | "LocalDateAndTime": { 20 | "Year": 2018, 21 | "Month": "January", 22 | "Date": 29, 23 | "Day": "Monday", 24 | "Time": 1730 25 | }, 26 | "HeatingButtonOverrideState": "Off", 27 | "HotWaterButtonOverrideState": "Off" 28 | }, 29 | "Cloud": { 30 | "Environment": "Prod", 31 | "WiserApiHost": "api-nl.wiserair.com 5", 32 | "BootStrapApiHost": "bootstrap.gl.struxurewarecloud.com 2", 33 | "PublishRequests": { 34 | "RoomTimeSeries": false, 35 | "EcoMode": false, 36 | "BoilerOnOffEvent": false, 37 | "PercentageDemand": false, 38 | "ZigbeeDebug": false, 39 | "FotaProgress": false, 40 | "SupportPackage": false, 41 | "PairingToken": false, 42 | "Notification": false 43 | }, 44 | "ScheduleIdsToPublish": [] 45 | }, 46 | "HeatingChannel": [ 47 | { 48 | "id": 1, 49 | "Name": "Channel-1", 50 | "RoomIds": [ 51 | 1, 52 | 2 53 | ], 54 | "PercentageDemand": 0, 55 | "DemandOnOffOutput": "Off", 56 | "HeatingRelayState": "Off", 57 | "IsSmartValvePreventingDemand": true 58 | } 59 | ], 60 | "Room": [ 61 | { 62 | "id": 1, 63 | "RoomStatId": 19911, 64 | "SmartValveIds": [ 65 | 13143, 66 | 31688 67 | ], 68 | "ScheduleId": 1, 69 | "Name": "Living", 70 | "Mode": "Auto", 71 | "DemandType": "Modulating", 72 | "CalculatedTemperature": 188, 73 | "CurrentSetPoint": 180, 74 | "PercentageDemand": 0, 75 | "ControlOutputState": "Off", 76 | "WindowState": "Closed", 77 | "DisplayedSetPoint": 180 78 | }, 79 | { 80 | "id": 2, 81 | "SmartValveIds": [], 82 | "ScheduleId": 2, 83 | "Name": "Lroom", 84 | "Mode": "Auto", 85 | "CalculatedTemperature": -32768, 86 | "CurrentSetPoint": 210, 87 | "DisplayedSetPoint": 210 88 | } 89 | ], 90 | "Schedule": [ 91 | { 92 | "id": 1, 93 | "Monday": { 94 | "SetPoints": [ 95 | { 96 | "Time": 700, 97 | "DegreesC": 200 98 | }, 99 | { 100 | "Time": 900, 101 | "DegreesC": 180 102 | }, 103 | { 104 | "Time": 1800, 105 | "DegreesC": 200 106 | }, 107 | { 108 | "Time": 2300, 109 | "DegreesC": -200 110 | } 111 | ] 112 | }, 113 | "Tuesday": { 114 | "SetPoints": [ 115 | { 116 | "Time": 700, 117 | "DegreesC": 200 118 | }, 119 | { 120 | "Time": 900, 121 | "DegreesC": 180 122 | }, 123 | { 124 | "Time": 1800, 125 | "DegreesC": 200 126 | }, 127 | { 128 | "Time": 2300, 129 | "DegreesC": -200 130 | } 131 | ] 132 | }, 133 | "Wednesday": { 134 | "SetPoints": [ 135 | { 136 | "Time": 700, 137 | "DegreesC": 200 138 | }, 139 | { 140 | "Time": 900, 141 | "DegreesC": 180 142 | }, 143 | { 144 | "Time": 1800, 145 | "DegreesC": 200 146 | }, 147 | { 148 | "Time": 2300, 149 | "DegreesC": -200 150 | } 151 | ] 152 | }, 153 | "Thursday": { 154 | "SetPoints": [ 155 | { 156 | "Time": 700, 157 | "DegreesC": 200 158 | }, 159 | { 160 | "Time": 900, 161 | "DegreesC": 180 162 | }, 163 | { 164 | "Time": 1800, 165 | "DegreesC": 200 166 | }, 167 | { 168 | "Time": 2300, 169 | "DegreesC": -200 170 | } 171 | ] 172 | }, 173 | "Friday": { 174 | "SetPoints": [ 175 | { 176 | "Time": 700, 177 | "DegreesC": 200 178 | }, 179 | { 180 | "Time": 900, 181 | "DegreesC": 180 182 | }, 183 | { 184 | "Time": 1800, 185 | "DegreesC": 200 186 | }, 187 | { 188 | "Time": 2300, 189 | "DegreesC": -200 190 | } 191 | ] 192 | }, 193 | "Saturday": { 194 | "SetPoints": [ 195 | { 196 | "Time": 700, 197 | "DegreesC": 200 198 | }, 199 | { 200 | "Time": 900, 201 | "DegreesC": 180 202 | }, 203 | { 204 | "Time": 1800, 205 | "DegreesC": 200 206 | }, 207 | { 208 | "Time": 2300, 209 | "DegreesC": -200 210 | } 211 | ] 212 | }, 213 | "Sunday": { 214 | "SetPoints": [ 215 | { 216 | "Time": 700, 217 | "DegreesC": 200 218 | }, 219 | { 220 | "Time": 900, 221 | "DegreesC": 180 222 | }, 223 | { 224 | "Time": 1800, 225 | "DegreesC": 200 226 | }, 227 | { 228 | "Time": 2300, 229 | "DegreesC": -200 230 | } 231 | ] 232 | }, 233 | "Type": "Heating", 234 | "CurrentSetpoint": 180, 235 | "NextEventTime": 1080, 236 | "NextEventSetpoint": 200 237 | }, 238 | { 239 | "id": 2, 240 | "Monday": { 241 | "SetPoints": [ 242 | { 243 | "Time": 630, 244 | "DegreesC": 200 245 | }, 246 | { 247 | "Time": 830, 248 | "DegreesC": 160 249 | }, 250 | { 251 | "Time": 1630, 252 | "DegreesC": 210 253 | }, 254 | { 255 | "Time": 2230, 256 | "DegreesC": -200 257 | } 258 | ] 259 | }, 260 | "Tuesday": { 261 | "SetPoints": [ 262 | { 263 | "Time": 630, 264 | "DegreesC": 200 265 | }, 266 | { 267 | "Time": 830, 268 | "DegreesC": 160 269 | }, 270 | { 271 | "Time": 1630, 272 | "DegreesC": 210 273 | }, 274 | { 275 | "Time": 2230, 276 | "DegreesC": -200 277 | } 278 | ] 279 | }, 280 | "Wednesday": { 281 | "SetPoints": [ 282 | { 283 | "Time": 630, 284 | "DegreesC": 200 285 | }, 286 | { 287 | "Time": 830, 288 | "DegreesC": 160 289 | }, 290 | { 291 | "Time": 1630, 292 | "DegreesC": 210 293 | }, 294 | { 295 | "Time": 2230, 296 | "DegreesC": -200 297 | } 298 | ] 299 | }, 300 | "Thursday": { 301 | "SetPoints": [ 302 | { 303 | "Time": 630, 304 | "DegreesC": 200 305 | }, 306 | { 307 | "Time": 830, 308 | "DegreesC": 160 309 | }, 310 | { 311 | "Time": 1630, 312 | "DegreesC": 210 313 | }, 314 | { 315 | "Time": 2230, 316 | "DegreesC": -200 317 | } 318 | ] 319 | }, 320 | "Friday": { 321 | "SetPoints": [ 322 | { 323 | "Time": 630, 324 | "DegreesC": 200 325 | }, 326 | { 327 | "Time": 830, 328 | "DegreesC": 160 329 | }, 330 | { 331 | "Time": 1630, 332 | "DegreesC": 210 333 | }, 334 | { 335 | "Time": 2230, 336 | "DegreesC": -200 337 | } 338 | ] 339 | }, 340 | "Saturday": { 341 | "SetPoints": [ 342 | { 343 | "Time": 700, 344 | "DegreesC": 200 345 | }, 346 | { 347 | "Time": 900, 348 | "DegreesC": 180 349 | }, 350 | { 351 | "Time": 1600, 352 | "DegreesC": 210 353 | }, 354 | { 355 | "Time": 2300, 356 | "DegreesC": -200 357 | } 358 | ] 359 | }, 360 | "Sunday": { 361 | "SetPoints": [ 362 | { 363 | "Time": 700, 364 | "DegreesC": 200 365 | }, 366 | { 367 | "Time": 900, 368 | "DegreesC": 180 369 | }, 370 | { 371 | "Time": 1600, 372 | "DegreesC": 210 373 | }, 374 | { 375 | "Time": 2300, 376 | "DegreesC": -200 377 | } 378 | ] 379 | }, 380 | "Type": "Heating", 381 | "CurrentSetpoint": 210, 382 | "NextEventTime": 1350, 383 | "NextEventSetpoint": -200 384 | } 385 | ], 386 | "Device": [ 387 | { 388 | "id": 0, 389 | "ProductType": "Controller", 390 | "ProductIdentifier": "Controller", 391 | "ActiveFirmwareVersion": "2.18.100", 392 | "Manufacturer": "Schneider", 393 | "ModelIdentifier": "WT714R1S0902", 394 | "DisplayedSignalStrength": "Good" 395 | }, 396 | { 397 | "id": 19911, 398 | "ProductIdentifier": "RoomStat", 399 | "ActiveFirmwareVersion": "0401000000002371", 400 | "Manufacturer": "Schneider", 401 | "ModelIdentifier": "Thermostat", 402 | "HardwareVersion": "0", 403 | "SerialNumber": "000XXXXXXXXXXX", 404 | "ProductRange": "Wiser Heat", 405 | "ProductModel": "Thermostat", 406 | "ProductFamily": "Wiser Heat", 407 | "OtaImageQueryCount": 0, 408 | "LastOtaImageQueryCount": 1, 409 | "DisplayedSignalStrength": "VeryGood", 410 | "BatteryVoltage": 32, 411 | "BatteryLevel": "Normal", 412 | "Rssi": -58, 413 | "Lqi": 168 414 | }, 415 | { 416 | "id": 13143, 417 | "ProductIdentifier": "iTRV", 418 | "ActiveFirmwareVersion": "0201000000002371", 419 | "Manufacturer": "Schneider", 420 | "ModelIdentifier": "iTRV", 421 | "HardwareVersion": "0", 422 | "SerialNumber": "000XXXXXXXXXXXX", 423 | "ProductRange": "Wiser Heat", 424 | "ProductModel": "iTRV", 425 | "ProductFamily": "Wiser Heat", 426 | "OtaImageQueryCount": 0, 427 | "LastOtaImageQueryCount": 1, 428 | "DisplayedSignalStrength": "Good", 429 | "BatteryVoltage": 31, 430 | "BatteryLevel": "Normal", 431 | "Rssi": -70, 432 | "Lqi": 120 433 | }, 434 | { 435 | "id": 31688, 436 | "ProductIdentifier": "iTRV", 437 | "ActiveFirmwareVersion": "0201000000002371", 438 | "Manufacturer": "Schneider", 439 | "ModelIdentifier": "iTRV", 440 | "HardwareVersion": "0", 441 | "SerialNumber": "XXXXXXXXXXXXX", 442 | "ProductRange": "Wiser Heat", 443 | "ProductModel": "iTRV", 444 | "ProductFamily": "Wiser Heat", 445 | "OtaImageQueryCount": 0, 446 | "LastOtaImageQueryCount": 1, 447 | "DisplayedSignalStrength": "VeryGood", 448 | "BatteryVoltage": 31, 449 | "BatteryLevel": "Normal", 450 | "Rssi": -55, 451 | "Lqi": 180 452 | } 453 | ], 454 | "UpgradeInfo": [ 455 | { 456 | "id": 9, 457 | "FirmwareFilename": "0541010100000018FOTA.BIN" 458 | }, 459 | { 460 | "id": 8, 461 | "FirmwareFilename": "0541000000000018FOTA.BIN" 462 | }, 463 | { 464 | "id": 7, 465 | "FirmwareFilename": "0501010100000018FOTA.BIN" 466 | }, 467 | { 468 | "id": 6, 469 | "FirmwareFilename": "0501000000000018FOTA.BIN" 470 | }, 471 | { 472 | "id": 5, 473 | "FirmwareFilename": "0441010100002371FOTA.BIN" 474 | }, 475 | { 476 | "id": 4, 477 | "FirmwareFilename": "0441000000002371FOTA.BIN" 478 | }, 479 | { 480 | "id": 3, 481 | "FirmwareFilename": "0401010100002371FOTA.BIN" 482 | }, 483 | { 484 | "id": 2, 485 | "FirmwareFilename": "0401000000002371FOTA.BIN" 486 | }, 487 | { 488 | "id": 1, 489 | "FirmwareFilename": "0201000000002371FOTA.BIN" 490 | } 491 | ], 492 | "SmartValve": [ 493 | { 494 | "id": 13143, 495 | "SetPoint": 180, 496 | "MeasuredTemperature": 178, 497 | "PercentageDemand": 0, 498 | "WindowState": "Closed", 499 | "ExternalRoomStatTemperature": 188 500 | }, 501 | { 502 | "id": 31688, 503 | "SetPoint": 180, 504 | "MeasuredTemperature": 177, 505 | "PercentageDemand": 0, 506 | "WindowState": "Closed", 507 | "ExternalRoomStatTemperature": 188 508 | } 509 | ], 510 | "RoomStat": [ 511 | { 512 | "id": 19911, 513 | "SetPoint": 180, 514 | "MeasuredTemperature": 188, 515 | "MeasuredHumidity": 68 516 | } 517 | ] 518 | } -------------------------------------------------------------------------------- /tests/data/all-with-itrv-and-hotwater.json: -------------------------------------------------------------------------------- 1 | { 2 | "System": { 3 | "PairingStatus": "Paired", 4 | "TimeZoneOffset": 0, 5 | "AutomaticDaylightSaving": true, 6 | "Version": 6, 7 | "FotaEnabled": true, 8 | "ValveProtectionEnabled": false, 9 | "EcoModeEnabled": false, 10 | "BoilerSettings": { 11 | "ControlType": "HeatSourceType_RelayControlled", 12 | "FuelType": "Gas", 13 | "CycleRate": "CPH_6" 14 | }, 15 | "UnixTime": 1517775517, 16 | "CloudConnectionStatus": "Connected", 17 | "ZigbeeModuleVersion": "R311 B030517", 18 | "ZigbeeEui": "000B57FFFEAA7FC6", 19 | "LocalDateAndTime": { 20 | "Year": 2018, 21 | "Month": "February", 22 | "Date": 4, 23 | "Day": "Sunday", 24 | "Time": 2018 25 | }, 26 | "HeatingButtonOverrideState": "Off", 27 | "HotWaterButtonOverrideState": "Off" 28 | }, 29 | "Cloud": { 30 | "Environment": "Prod", 31 | "WiserApiHost": "api-nl.wiserair.com", 32 | "BootStrapApiHost": "bootstrap.gl.struxurewarecloud.com", 33 | "PublishRequests": { 34 | "RoomTimeSeries": false, 35 | "EcoMode": false, 36 | "BoilerOnOffEvent": false, 37 | "PercentageDemand": false, 38 | "ZigbeeDebug": false, 39 | "FotaProgress": false, 40 | "SupportPackage": false, 41 | "PairingToken": false, 42 | "Notification": false 43 | }, 44 | "ScheduleIdsToPublish": [] 45 | }, 46 | "HeatingChannel": [ 47 | { 48 | "id": 1, 49 | "Name": "Channel-1", 50 | "RoomIds": [ 51 | 1, 52 | 2, 53 | 3, 54 | 4, 55 | 5, 56 | 6, 57 | 7 58 | ], 59 | "PercentageDemand": 100, 60 | "DemandOnOffOutput": "Off", 61 | "HeatingRelayState": "Off", 62 | "IsSmartValvePreventingDemand": true 63 | } 64 | ], 65 | "HotWater": [ 66 | { 67 | "id": 2, 68 | "OverrideType": "None", 69 | "ScheduleId": 1000, 70 | "Mode": "Auto", 71 | "WaterHeatingState": "Off", 72 | "HotWaterRelayState": "Off" 73 | } 74 | ], 75 | "Room": [ 76 | { 77 | "id": 1, 78 | "OverrideType": "None", 79 | "RoomStatId": 63270, 80 | "SmartValveIds": [ 81 | 12483, 82 | 3323 83 | ], 84 | "ScheduleId": 1001, 85 | "Name": "Living Room", 86 | "Mode": "Auto", 87 | "DemandType": "Modulating", 88 | "WindowDetectionActive": false, 89 | "CalculatedTemperature": 207, 90 | "CurrentSetPoint": 205, 91 | "PercentageDemand": 0, 92 | "ControlOutputState": "Off", 93 | "WindowState": "Closed", 94 | "DisplayedSetPoint": 205 95 | }, 96 | { 97 | "id": 2, 98 | "OverrideType": "None", 99 | "SmartValveIds": [ 100 | 25436 101 | ], 102 | "ScheduleId": 1002, 103 | "Name": "Kitchen", 104 | "Mode": "Auto", 105 | "DemandType": "Modulating", 106 | "WindowDetectionActive": false, 107 | "CalculatedTemperature": 180, 108 | "CurrentSetPoint": 175, 109 | "PercentageDemand": 0, 110 | "ControlOutputState": "Off", 111 | "WindowState": "Closed", 112 | "DisplayedSetPoint": 175 113 | }, 114 | { 115 | "id": 3, 116 | "OverrideType": "None", 117 | "SmartValveIds": [ 118 | 62258 119 | ], 120 | "ScheduleId": 1003, 121 | "Name": "Hallway", 122 | "Mode": "Auto", 123 | "DemandType": "Modulating", 124 | "WindowDetectionActive": false, 125 | "CalculatedTemperature": 171, 126 | "CurrentSetPoint": 175, 127 | "PercentageDemand": 100, 128 | "ControlOutputState": "On", 129 | "WindowState": "Closed", 130 | "DisplayedSetPoint": 175 131 | }, 132 | { 133 | "id": 4, 134 | "OverrideType": "None", 135 | "SmartValveIds": [ 136 | 12711 137 | ], 138 | "ScheduleId": 1004, 139 | "Name": "Bathroom", 140 | "Mode": "Auto", 141 | "DemandType": "Modulating", 142 | "WindowDetectionActive": false, 143 | "CalculatedTemperature": 175, 144 | "CurrentSetPoint": 175, 145 | "PercentageDemand": 0, 146 | "ControlOutputState": "Off", 147 | "WindowState": "Closed", 148 | "DisplayedSetPoint": 175 149 | }, 150 | { 151 | "id": 5, 152 | "OverrideType": "None", 153 | "SmartValveIds": [ 154 | 52952 155 | ], 156 | "ScheduleId": 1005, 157 | "Name": "Front Bedroom", 158 | "Mode": "Auto", 159 | "DemandType": "Modulating", 160 | "WindowDetectionActive": false, 161 | "CalculatedTemperature": 179, 162 | "CurrentSetPoint": 175, 163 | "PercentageDemand": 0, 164 | "ControlOutputState": "Off", 165 | "WindowState": "Closed", 166 | "DisplayedSetPoint": 175 167 | }, 168 | { 169 | "id": 6, 170 | "OverrideType": "None", 171 | "SmartValveIds": [ 172 | 40332 173 | ], 174 | "ScheduleId": 1006, 175 | "Name": "Back Bedroom", 176 | "Mode": "Auto", 177 | "DemandType": "Modulating", 178 | "WindowDetectionActive": false, 179 | "CalculatedTemperature": 179, 180 | "CurrentSetPoint": 175, 181 | "PercentageDemand": 0, 182 | "ControlOutputState": "Off", 183 | "WindowState": "Closed", 184 | "DisplayedSetPoint": 175 185 | }, 186 | { 187 | "id": 7, 188 | "OverrideType": "None", 189 | "SmartValveIds": [ 190 | 8288 191 | ], 192 | "ScheduleId": 1007, 193 | "Name": "Top Bedroom", 194 | "Mode": "Auto", 195 | "DemandType": "Modulating", 196 | "WindowDetectionActive": false, 197 | "CalculatedTemperature": 141, 198 | "CurrentSetPoint": 140, 199 | "PercentageDemand": 0, 200 | "ControlOutputState": "Off", 201 | "WindowState": "Closed", 202 | "DisplayedSetPoint": 140 203 | } 204 | ], 205 | "Schedule": [ 206 | { 207 | "id": 1000, 208 | "Monday": { 209 | "SetPoints": [ 210 | { 211 | "Time": 630, 212 | "DegreesC": 1100 213 | }, 214 | { 215 | "Time": 1000, 216 | "DegreesC": -200 217 | }, 218 | { 219 | "Time": 1700, 220 | "DegreesC": 1100 221 | }, 222 | { 223 | "Time": 2000, 224 | "DegreesC": -200 225 | } 226 | ] 227 | }, 228 | "Tuesday": { 229 | "SetPoints": [ 230 | { 231 | "Time": 630, 232 | "DegreesC": 1100 233 | }, 234 | { 235 | "Time": 1000, 236 | "DegreesC": -200 237 | }, 238 | { 239 | "Time": 1700, 240 | "DegreesC": 1100 241 | }, 242 | { 243 | "Time": 2000, 244 | "DegreesC": -200 245 | } 246 | ] 247 | }, 248 | "Wednesday": { 249 | "SetPoints": [ 250 | { 251 | "Time": 630, 252 | "DegreesC": 1100 253 | }, 254 | { 255 | "Time": 1000, 256 | "DegreesC": -200 257 | }, 258 | { 259 | "Time": 1700, 260 | "DegreesC": 1100 261 | }, 262 | { 263 | "Time": 2000, 264 | "DegreesC": -200 265 | } 266 | ] 267 | }, 268 | "Thursday": { 269 | "SetPoints": [ 270 | { 271 | "Time": 630, 272 | "DegreesC": 1100 273 | }, 274 | { 275 | "Time": 1000, 276 | "DegreesC": -200 277 | }, 278 | { 279 | "Time": 1700, 280 | "DegreesC": 1100 281 | }, 282 | { 283 | "Time": 2000, 284 | "DegreesC": -200 285 | } 286 | ] 287 | }, 288 | "Friday": { 289 | "SetPoints": [ 290 | { 291 | "Time": 630, 292 | "DegreesC": 1100 293 | }, 294 | { 295 | "Time": 1000, 296 | "DegreesC": -200 297 | }, 298 | { 299 | "Time": 1700, 300 | "DegreesC": 1100 301 | }, 302 | { 303 | "Time": 2000, 304 | "DegreesC": -200 305 | } 306 | ] 307 | }, 308 | "Saturday": { 309 | "SetPoints": [ 310 | { 311 | "Time": 630, 312 | "DegreesC": 1100 313 | }, 314 | { 315 | "Time": 1000, 316 | "DegreesC": -200 317 | }, 318 | { 319 | "Time": 1700, 320 | "DegreesC": 1100 321 | }, 322 | { 323 | "Time": 2000, 324 | "DegreesC": -200 325 | } 326 | ] 327 | }, 328 | "Sunday": { 329 | "SetPoints": [ 330 | { 331 | "Time": 630, 332 | "DegreesC": 1100 333 | }, 334 | { 335 | "Time": 1000, 336 | "DegreesC": -200 337 | }, 338 | { 339 | "Time": 1700, 340 | "DegreesC": 1100 341 | }, 342 | { 343 | "Time": 2000, 344 | "DegreesC": -200 345 | } 346 | ] 347 | }, 348 | "Type": "HotWater", 349 | "CurrentSetpoint": -200, 350 | "NextEventTime": 390, 351 | "NextEventSetpoint": 1100 352 | }, 353 | { 354 | "id": 1001, 355 | "Monday": { 356 | "SetPoints": [ 357 | { 358 | "Time": 630, 359 | "DegreesC": 205 360 | }, 361 | { 362 | "Time": 1800, 363 | "DegreesC": 205 364 | }, 365 | { 366 | "Time": 2300, 367 | "DegreesC": -200 368 | } 369 | ] 370 | }, 371 | "Tuesday": { 372 | "SetPoints": [ 373 | { 374 | "Time": 630, 375 | "DegreesC": 205 376 | }, 377 | { 378 | "Time": 1800, 379 | "DegreesC": 205 380 | }, 381 | { 382 | "Time": 2300, 383 | "DegreesC": -200 384 | } 385 | ] 386 | }, 387 | "Wednesday": { 388 | "SetPoints": [ 389 | { 390 | "Time": 630, 391 | "DegreesC": 205 392 | }, 393 | { 394 | "Time": 1800, 395 | "DegreesC": 205 396 | }, 397 | { 398 | "Time": 2300, 399 | "DegreesC": -200 400 | } 401 | ] 402 | }, 403 | "Thursday": { 404 | "SetPoints": [ 405 | { 406 | "Time": 630, 407 | "DegreesC": 205 408 | }, 409 | { 410 | "Time": 1800, 411 | "DegreesC": 205 412 | }, 413 | { 414 | "Time": 2300, 415 | "DegreesC": -200 416 | } 417 | ] 418 | }, 419 | "Friday": { 420 | "SetPoints": [ 421 | { 422 | "Time": 630, 423 | "DegreesC": 205 424 | }, 425 | { 426 | "Time": 1800, 427 | "DegreesC": 205 428 | }, 429 | { 430 | "Time": 2300, 431 | "DegreesC": -200 432 | } 433 | ] 434 | }, 435 | "Saturday": { 436 | "SetPoints": [ 437 | { 438 | "Time": 630, 439 | "DegreesC": 205 440 | }, 441 | { 442 | "Time": 1800, 443 | "DegreesC": 205 444 | }, 445 | { 446 | "Time": 2300, 447 | "DegreesC": -200 448 | } 449 | ] 450 | }, 451 | "Sunday": { 452 | "SetPoints": [ 453 | { 454 | "Time": 630, 455 | "DegreesC": 205 456 | }, 457 | { 458 | "Time": 1800, 459 | "DegreesC": 205 460 | }, 461 | { 462 | "Time": 2300, 463 | "DegreesC": -200 464 | } 465 | ] 466 | }, 467 | "Type": "Heating", 468 | "CurrentSetpoint": 205, 469 | "NextEventTime": 1380, 470 | "NextEventSetpoint": -200 471 | }, 472 | { 473 | "id": 1002, 474 | "Monday": { 475 | "SetPoints": [ 476 | { 477 | "Time": 630, 478 | "DegreesC": 175 479 | }, 480 | { 481 | "Time": 2300, 482 | "DegreesC": -200 483 | } 484 | ] 485 | }, 486 | "Tuesday": { 487 | "SetPoints": [ 488 | { 489 | "Time": 630, 490 | "DegreesC": 175 491 | }, 492 | { 493 | "Time": 2300, 494 | "DegreesC": -200 495 | } 496 | ] 497 | }, 498 | "Wednesday": { 499 | "SetPoints": [ 500 | { 501 | "Time": 630, 502 | "DegreesC": 175 503 | }, 504 | { 505 | "Time": 2300, 506 | "DegreesC": -200 507 | } 508 | ] 509 | }, 510 | "Thursday": { 511 | "SetPoints": [ 512 | { 513 | "Time": 630, 514 | "DegreesC": 175 515 | }, 516 | { 517 | "Time": 2300, 518 | "DegreesC": -200 519 | } 520 | ] 521 | }, 522 | "Friday": { 523 | "SetPoints": [ 524 | { 525 | "Time": 630, 526 | "DegreesC": 175 527 | }, 528 | { 529 | "Time": 2300, 530 | "DegreesC": -200 531 | } 532 | ] 533 | }, 534 | "Saturday": { 535 | "SetPoints": [ 536 | { 537 | "Time": 630, 538 | "DegreesC": 175 539 | }, 540 | { 541 | "Time": 2300, 542 | "DegreesC": -200 543 | } 544 | ] 545 | }, 546 | "Sunday": { 547 | "SetPoints": [ 548 | { 549 | "Time": 630, 550 | "DegreesC": 175 551 | }, 552 | { 553 | "Time": 2300, 554 | "DegreesC": -200 555 | } 556 | ] 557 | }, 558 | "Type": "Heating", 559 | "CurrentSetpoint": 175, 560 | "NextEventTime": 1380, 561 | "NextEventSetpoint": -200 562 | }, 563 | { 564 | "id": 1003, 565 | "Monday": { 566 | "SetPoints": [ 567 | { 568 | "Time": 630, 569 | "DegreesC": 175 570 | }, 571 | { 572 | "Time": 2300, 573 | "DegreesC": -200 574 | } 575 | ] 576 | }, 577 | "Tuesday": { 578 | "SetPoints": [ 579 | { 580 | "Time": 630, 581 | "DegreesC": 175 582 | }, 583 | { 584 | "Time": 2300, 585 | "DegreesC": -200 586 | } 587 | ] 588 | }, 589 | "Wednesday": { 590 | "SetPoints": [ 591 | { 592 | "Time": 630, 593 | "DegreesC": 175 594 | }, 595 | { 596 | "Time": 2300, 597 | "DegreesC": -200 598 | } 599 | ] 600 | }, 601 | "Thursday": { 602 | "SetPoints": [ 603 | { 604 | "Time": 630, 605 | "DegreesC": 175 606 | }, 607 | { 608 | "Time": 2300, 609 | "DegreesC": -200 610 | } 611 | ] 612 | }, 613 | "Friday": { 614 | "SetPoints": [ 615 | { 616 | "Time": 630, 617 | "DegreesC": 175 618 | }, 619 | { 620 | "Time": 2300, 621 | "DegreesC": -200 622 | } 623 | ] 624 | }, 625 | "Saturday": { 626 | "SetPoints": [ 627 | { 628 | "Time": 630, 629 | "DegreesC": 175 630 | }, 631 | { 632 | "Time": 2300, 633 | "DegreesC": -200 634 | } 635 | ] 636 | }, 637 | "Sunday": { 638 | "SetPoints": [ 639 | { 640 | "Time": 630, 641 | "DegreesC": 175 642 | }, 643 | { 644 | "Time": 2300, 645 | "DegreesC": -200 646 | } 647 | ] 648 | }, 649 | "Type": "Heating", 650 | "CurrentSetpoint": 175, 651 | "NextEventTime": 1380, 652 | "NextEventSetpoint": -200 653 | }, 654 | { 655 | "id": 1004, 656 | "Monday": { 657 | "SetPoints": [ 658 | { 659 | "Time": 630, 660 | "DegreesC": 175 661 | }, 662 | { 663 | "Time": 2300, 664 | "DegreesC": -200 665 | } 666 | ] 667 | }, 668 | "Tuesday": { 669 | "SetPoints": [ 670 | { 671 | "Time": 630, 672 | "DegreesC": 175 673 | }, 674 | { 675 | "Time": 2300, 676 | "DegreesC": -200 677 | } 678 | ] 679 | }, 680 | "Wednesday": { 681 | "SetPoints": [ 682 | { 683 | "Time": 630, 684 | "DegreesC": 175 685 | }, 686 | { 687 | "Time": 2300, 688 | "DegreesC": -200 689 | } 690 | ] 691 | }, 692 | "Thursday": { 693 | "SetPoints": [ 694 | { 695 | "Time": 630, 696 | "DegreesC": 175 697 | }, 698 | { 699 | "Time": 2300, 700 | "DegreesC": -200 701 | } 702 | ] 703 | }, 704 | "Friday": { 705 | "SetPoints": [ 706 | { 707 | "Time": 630, 708 | "DegreesC": 175 709 | }, 710 | { 711 | "Time": 2300, 712 | "DegreesC": -200 713 | } 714 | ] 715 | }, 716 | "Saturday": { 717 | "SetPoints": [ 718 | { 719 | "Time": 630, 720 | "DegreesC": 175 721 | }, 722 | { 723 | "Time": 2300, 724 | "DegreesC": -200 725 | } 726 | ] 727 | }, 728 | "Sunday": { 729 | "SetPoints": [ 730 | { 731 | "Time": 630, 732 | "DegreesC": 175 733 | }, 734 | { 735 | "Time": 2300, 736 | "DegreesC": -200 737 | } 738 | ] 739 | }, 740 | "Type": "Heating", 741 | "CurrentSetpoint": 175, 742 | "NextEventTime": 1380, 743 | "NextEventSetpoint": -200 744 | }, 745 | { 746 | "id": 1005, 747 | "Monday": { 748 | "SetPoints": [ 749 | { 750 | "Time": 630, 751 | "DegreesC": 175 752 | }, 753 | { 754 | "Time": 2300, 755 | "DegreesC": -200 756 | } 757 | ] 758 | }, 759 | "Tuesday": { 760 | "SetPoints": [ 761 | { 762 | "Time": 630, 763 | "DegreesC": 175 764 | }, 765 | { 766 | "Time": 2300, 767 | "DegreesC": -200 768 | } 769 | ] 770 | }, 771 | "Wednesday": { 772 | "SetPoints": [ 773 | { 774 | "Time": 630, 775 | "DegreesC": 175 776 | }, 777 | { 778 | "Time": 2300, 779 | "DegreesC": -200 780 | } 781 | ] 782 | }, 783 | "Thursday": { 784 | "SetPoints": [ 785 | { 786 | "Time": 630, 787 | "DegreesC": 175 788 | }, 789 | { 790 | "Time": 2300, 791 | "DegreesC": -200 792 | } 793 | ] 794 | }, 795 | "Friday": { 796 | "SetPoints": [ 797 | { 798 | "Time": 630, 799 | "DegreesC": 175 800 | }, 801 | { 802 | "Time": 2300, 803 | "DegreesC": -200 804 | } 805 | ] 806 | }, 807 | "Saturday": { 808 | "SetPoints": [ 809 | { 810 | "Time": 630, 811 | "DegreesC": 175 812 | }, 813 | { 814 | "Time": 2300, 815 | "DegreesC": -200 816 | } 817 | ] 818 | }, 819 | "Sunday": { 820 | "SetPoints": [ 821 | { 822 | "Time": 630, 823 | "DegreesC": 175 824 | }, 825 | { 826 | "Time": 2300, 827 | "DegreesC": -200 828 | } 829 | ] 830 | }, 831 | "Type": "Heating", 832 | "CurrentSetpoint": 175, 833 | "NextEventTime": 1380, 834 | "NextEventSetpoint": -200 835 | }, 836 | { 837 | "id": 1006, 838 | "Monday": { 839 | "SetPoints": [ 840 | { 841 | "Time": 630, 842 | "DegreesC": 175 843 | }, 844 | { 845 | "Time": 2300, 846 | "DegreesC": -200 847 | } 848 | ] 849 | }, 850 | "Tuesday": { 851 | "SetPoints": [ 852 | { 853 | "Time": 630, 854 | "DegreesC": 175 855 | }, 856 | { 857 | "Time": 2300, 858 | "DegreesC": -200 859 | } 860 | ] 861 | }, 862 | "Wednesday": { 863 | "SetPoints": [ 864 | { 865 | "Time": 630, 866 | "DegreesC": 175 867 | }, 868 | { 869 | "Time": 2300, 870 | "DegreesC": -200 871 | } 872 | ] 873 | }, 874 | "Thursday": { 875 | "SetPoints": [ 876 | { 877 | "Time": 630, 878 | "DegreesC": 175 879 | }, 880 | { 881 | "Time": 2300, 882 | "DegreesC": -200 883 | } 884 | ] 885 | }, 886 | "Friday": { 887 | "SetPoints": [ 888 | { 889 | "Time": 630, 890 | "DegreesC": 175 891 | }, 892 | { 893 | "Time": 2300, 894 | "DegreesC": -200 895 | } 896 | ] 897 | }, 898 | "Saturday": { 899 | "SetPoints": [ 900 | { 901 | "Time": 630, 902 | "DegreesC": 175 903 | }, 904 | { 905 | "Time": 2300, 906 | "DegreesC": -200 907 | } 908 | ] 909 | }, 910 | "Sunday": { 911 | "SetPoints": [ 912 | { 913 | "Time": 630, 914 | "DegreesC": 175 915 | }, 916 | { 917 | "Time": 2300, 918 | "DegreesC": -200 919 | } 920 | ] 921 | }, 922 | "Type": "Heating", 923 | "CurrentSetpoint": 175, 924 | "NextEventTime": 1380, 925 | "NextEventSetpoint": -200 926 | }, 927 | { 928 | "id": 1007, 929 | "Monday": { 930 | "SetPoints": [ 931 | { 932 | "Time": 1800, 933 | "DegreesC": 140 934 | }, 935 | { 936 | "Time": 2300, 937 | "DegreesC": -200 938 | } 939 | ] 940 | }, 941 | "Tuesday": { 942 | "SetPoints": [ 943 | { 944 | "Time": 1800, 945 | "DegreesC": 140 946 | }, 947 | { 948 | "Time": 2300, 949 | "DegreesC": -200 950 | } 951 | ] 952 | }, 953 | "Wednesday": { 954 | "SetPoints": [ 955 | { 956 | "Time": 1800, 957 | "DegreesC": 140 958 | }, 959 | { 960 | "Time": 2300, 961 | "DegreesC": -200 962 | } 963 | ] 964 | }, 965 | "Thursday": { 966 | "SetPoints": [ 967 | { 968 | "Time": 1800, 969 | "DegreesC": 140 970 | }, 971 | { 972 | "Time": 2300, 973 | "DegreesC": -200 974 | } 975 | ] 976 | }, 977 | "Friday": { 978 | "SetPoints": [ 979 | { 980 | "Time": 1800, 981 | "DegreesC": 140 982 | }, 983 | { 984 | "Time": 2300, 985 | "DegreesC": -200 986 | } 987 | ] 988 | }, 989 | "Saturday": { 990 | "SetPoints": [ 991 | { 992 | "Time": 1800, 993 | "DegreesC": 140 994 | }, 995 | { 996 | "Time": 2300, 997 | "DegreesC": -200 998 | } 999 | ] 1000 | }, 1001 | "Sunday": { 1002 | "SetPoints": [ 1003 | { 1004 | "Time": 1800, 1005 | "DegreesC": 140 1006 | }, 1007 | { 1008 | "Time": 2300, 1009 | "DegreesC": -200 1010 | } 1011 | ] 1012 | }, 1013 | "Type": "Heating", 1014 | "CurrentSetpoint": 140, 1015 | "NextEventTime": 1380, 1016 | "NextEventSetpoint": -200 1017 | } 1018 | ], 1019 | "Device": [ 1020 | { 1021 | "id": 0, 1022 | "ProductType": "Controller", 1023 | "ProductIdentifier": "Controller", 1024 | "ActiveFirmwareVersion": "2.18.100", 1025 | "Manufacturer": "Schneider", 1026 | "ModelIdentifier": "WT724R1S0902", 1027 | "DisplayedSignalStrength": "VeryGood", 1028 | "Rssi": -40 1029 | }, 1030 | { 1031 | "id": 63270, 1032 | "ProductIdentifier": "RoomStat", 1033 | "ActiveFirmwareVersion": "0401000000002371", 1034 | "Manufacturer": "Schneider", 1035 | "ModelIdentifier": "Thermostat", 1036 | "HardwareVersion": "0", 1037 | "SerialNumber": "000B57FFFE5516AA", 1038 | "ProductRange": "Wiser Heat", 1039 | "ProductModel": "Thermostat", 1040 | "ProductFamily": "Wiser Heat", 1041 | "OtaImageQueryCount": 0, 1042 | "LastOtaImageQueryCount": 0, 1043 | "DisplayedSignalStrength": "VeryGood", 1044 | "BatteryVoltage": 29, 1045 | "BatteryLevel": "Normal", 1046 | "Rssi": -59, 1047 | "Lqi": 164 1048 | }, 1049 | { 1050 | "id": 12483, 1051 | "ProductIdentifier": "iTRV", 1052 | "ActiveFirmwareVersion": "0201000000002371", 1053 | "Manufacturer": "Schneider", 1054 | "ModelIdentifier": "iTRV", 1055 | "HardwareVersion": "0", 1056 | "SerialNumber": "90FD9FFFFE62D27C", 1057 | "ProductRange": "Wiser Heat", 1058 | "ProductModel": "iTRV", 1059 | "ProductFamily": "Wiser Heat", 1060 | "OtaImageQueryCount": 0, 1061 | "LastOtaImageQueryCount": 0, 1062 | "DisplayedSignalStrength": "VeryGood", 1063 | "BatteryVoltage": 29, 1064 | "BatteryLevel": "Normal", 1065 | "Rssi": -55, 1066 | "Lqi": 180, 1067 | "PendingZigbeeMessageMask": 0 1068 | }, 1069 | { 1070 | "id": 3323, 1071 | "ProductIdentifier": "iTRV", 1072 | "ActiveFirmwareVersion": "0201000000002371", 1073 | "Manufacturer": "Schneider", 1074 | "ModelIdentifier": "iTRV", 1075 | "HardwareVersion": "0", 1076 | "SerialNumber": "90FD9FFFFE62D30A", 1077 | "ProductRange": "Wiser Heat", 1078 | "ProductModel": "iTRV", 1079 | "ProductFamily": "Wiser Heat", 1080 | "OtaImageQueryCount": 0, 1081 | "LastOtaImageQueryCount": 0, 1082 | "DisplayedSignalStrength": "Good", 1083 | "BatteryVoltage": 29, 1084 | "BatteryLevel": "Normal", 1085 | "Rssi": -64, 1086 | "Lqi": 144, 1087 | "PendingZigbeeMessageMask": 0 1088 | }, 1089 | { 1090 | "id": 25436, 1091 | "ProductIdentifier": "iTRV", 1092 | "ActiveFirmwareVersion": "0201000000002371", 1093 | "Manufacturer": "Schneider", 1094 | "ModelIdentifier": "iTRV", 1095 | "HardwareVersion": "0", 1096 | "SerialNumber": "90FD9FFFFE5191E0", 1097 | "ProductRange": "Wiser Heat", 1098 | "ProductModel": "iTRV", 1099 | "ProductFamily": "Wiser Heat", 1100 | "OtaImageQueryCount": 0, 1101 | "LastOtaImageQueryCount": 0, 1102 | "DisplayedSignalStrength": "Good", 1103 | "BatteryVoltage": 29, 1104 | "BatteryLevel": "Normal", 1105 | "Rssi": -70, 1106 | "Lqi": 120, 1107 | "PendingZigbeeMessageMask": 0 1108 | }, 1109 | { 1110 | "id": 62258, 1111 | "ProductIdentifier": "iTRV", 1112 | "ActiveFirmwareVersion": "0201000000002371", 1113 | "Manufacturer": "Schneider", 1114 | "ModelIdentifier": "iTRV", 1115 | "HardwareVersion": "0", 1116 | "SerialNumber": "90FD9FFFFE519351", 1117 | "ProductRange": "Wiser Heat", 1118 | "ProductModel": "iTRV", 1119 | "ProductFamily": "Wiser Heat", 1120 | "OtaImageQueryCount": 0, 1121 | "LastOtaImageQueryCount": 1, 1122 | "DisplayedSignalStrength": "Good", 1123 | "BatteryVoltage": 29, 1124 | "BatteryLevel": "Normal", 1125 | "Rssi": -64, 1126 | "Lqi": 144, 1127 | "PendingZigbeeMessageMask": 0 1128 | }, 1129 | { 1130 | "id": 12711, 1131 | "ProductIdentifier": "iTRV", 1132 | "ActiveFirmwareVersion": "0201000000002371", 1133 | "Manufacturer": "Schneider", 1134 | "ModelIdentifier": "iTRV", 1135 | "HardwareVersion": "0", 1136 | "SerialNumber": "90FD9FFFFE518FDD", 1137 | "ProductRange": "Wiser Heat", 1138 | "ProductModel": "iTRV", 1139 | "ProductFamily": "Wiser Heat", 1140 | "OtaImageQueryCount": 0, 1141 | "LastOtaImageQueryCount": 1, 1142 | "DisplayedSignalStrength": "Medium", 1143 | "BatteryVoltage": 29, 1144 | "BatteryLevel": "Normal", 1145 | "Rssi": -79, 1146 | "Lqi": 84, 1147 | "PendingZigbeeMessageMask": 0 1148 | }, 1149 | { 1150 | "id": 52952, 1151 | "ProductIdentifier": "iTRV", 1152 | "ActiveFirmwareVersion": "0201000000002371", 1153 | "Manufacturer": "Schneider", 1154 | "ModelIdentifier": "iTRV", 1155 | "HardwareVersion": "0", 1156 | "SerialNumber": "90FD9FFFFE62D2F7", 1157 | "ProductRange": "Wiser Heat", 1158 | "ProductModel": "iTRV", 1159 | "ProductFamily": "Wiser Heat", 1160 | "OtaImageQueryCount": 0, 1161 | "LastOtaImageQueryCount": 1, 1162 | "DisplayedSignalStrength": "Good", 1163 | "BatteryVoltage": 29, 1164 | "BatteryLevel": "Normal", 1165 | "Rssi": -63, 1166 | "Lqi": 148, 1167 | "PendingZigbeeMessageMask": 0 1168 | }, 1169 | { 1170 | "id": 40332, 1171 | "ProductIdentifier": "iTRV", 1172 | "ActiveFirmwareVersion": "0201000000002371", 1173 | "Manufacturer": "Schneider", 1174 | "ModelIdentifier": "iTRV", 1175 | "HardwareVersion": "0", 1176 | "SerialNumber": "90FD9FFFFE62D3CD", 1177 | "ProductRange": "Wiser Heat", 1178 | "ProductModel": "iTRV", 1179 | "ProductFamily": "Wiser Heat", 1180 | "OtaImageQueryCount": 0, 1181 | "LastOtaImageQueryCount": 1, 1182 | "DisplayedSignalStrength": "VeryGood", 1183 | "BatteryVoltage": 29, 1184 | "BatteryLevel": "Normal", 1185 | "Rssi": -49, 1186 | "Lqi": 204, 1187 | "PendingZigbeeMessageMask": 0 1188 | }, 1189 | { 1190 | "id": 8288, 1191 | "ProductIdentifier": "iTRV", 1192 | "ActiveFirmwareVersion": "0201000000002371", 1193 | "Manufacturer": "Schneider", 1194 | "ModelIdentifier": "iTRV", 1195 | "HardwareVersion": "0", 1196 | "SerialNumber": "90FD9FFFFE518342", 1197 | "ProductRange": "Wiser Heat", 1198 | "ProductModel": "iTRV", 1199 | "ProductFamily": "Wiser Heat", 1200 | "OtaImageQueryCount": 0, 1201 | "LastOtaImageQueryCount": 1, 1202 | "DisplayedSignalStrength": "Good", 1203 | "BatteryVoltage": 30, 1204 | "BatteryLevel": "Normal", 1205 | "Rssi": -70, 1206 | "Lqi": 120, 1207 | "PendingZigbeeMessageMask": 0 1208 | } 1209 | ], 1210 | "UpgradeInfo": [ 1211 | { 1212 | "id": 9, 1213 | "FirmwareFilename": "0541010100000018FOTA.BIN" 1214 | }, 1215 | { 1216 | "id": 8, 1217 | "FirmwareFilename": "0541000000000018FOTA.BIN" 1218 | }, 1219 | { 1220 | "id": 7, 1221 | "FirmwareFilename": "0501010100000018FOTA.BIN" 1222 | }, 1223 | { 1224 | "id": 6, 1225 | "FirmwareFilename": "0501000000000018FOTA.BIN" 1226 | }, 1227 | { 1228 | "id": 5, 1229 | "FirmwareFilename": "0441010100002371FOTA.BIN" 1230 | }, 1231 | { 1232 | "id": 4, 1233 | "FirmwareFilename": "0441000000002371FOTA.BIN" 1234 | }, 1235 | { 1236 | "id": 3, 1237 | "FirmwareFilename": "0401010100002371FOTA.BIN" 1238 | }, 1239 | { 1240 | "id": 2, 1241 | "FirmwareFilename": "0401000000002371FOTA.BIN" 1242 | }, 1243 | { 1244 | "id": 1, 1245 | "FirmwareFilename": "0201000000002371FOTA.BIN" 1246 | } 1247 | ], 1248 | "SmartValve": [ 1249 | { 1250 | "id": 12483, 1251 | "MountingOrientation": "Vertical", 1252 | "SetPoint": 205, 1253 | "MeasuredTemperature": 176, 1254 | "PercentageDemand": 0, 1255 | "WindowState": "Closed", 1256 | "ExternalRoomStatTemperature": 207 1257 | }, 1258 | { 1259 | "id": 25436, 1260 | "MountingOrientation": "Vertical", 1261 | "SetPoint": 175, 1262 | "MeasuredTemperature": 180, 1263 | "PercentageDemand": 0, 1264 | "WindowState": "Closed" 1265 | }, 1266 | { 1267 | "id": 62258, 1268 | "MountingOrientation": "Vertical", 1269 | "SetPoint": 175, 1270 | "MeasuredTemperature": 171, 1271 | "PercentageDemand": 0, 1272 | "WindowState": "Closed" 1273 | }, 1274 | { 1275 | "id": 12711, 1276 | "MountingOrientation": "Vertical", 1277 | "SetPoint": 175, 1278 | "MeasuredTemperature": 175, 1279 | "PercentageDemand": 0, 1280 | "WindowState": "Closed" 1281 | }, 1282 | { 1283 | "id": 52952, 1284 | "MountingOrientation": "Vertical", 1285 | "SetPoint": 175, 1286 | "MeasuredTemperature": 179, 1287 | "PercentageDemand": 0, 1288 | "WindowState": "Closed" 1289 | }, 1290 | { 1291 | "id": 40332, 1292 | "MountingOrientation": "Vertical", 1293 | "SetPoint": 175, 1294 | "MeasuredTemperature": 179, 1295 | "PercentageDemand": 0, 1296 | "WindowState": "Closed" 1297 | }, 1298 | { 1299 | "id": 8288, 1300 | "MountingOrientation": "Vertical", 1301 | "SetPoint": 140, 1302 | "MeasuredTemperature": 141, 1303 | "PercentageDemand": 0, 1304 | "WindowState": "Closed" 1305 | }, 1306 | { 1307 | "id": 3323, 1308 | "MountingOrientation": "Vertical", 1309 | "SetPoint": 205, 1310 | "MeasuredTemperature": 170, 1311 | "PercentageDemand": 0, 1312 | "WindowState": "Closed", 1313 | "ExternalRoomStatTemperature": 207 1314 | } 1315 | ], 1316 | "RoomStat": [ 1317 | { 1318 | "id": 63270, 1319 | "SetPoint": 205, 1320 | "MeasuredTemperature": 207, 1321 | "MeasuredHumidity": 44 1322 | } 1323 | ] 1324 | } -------------------------------------------------------------------------------- /tests/data/all-with-itrv-and-room-override.json: -------------------------------------------------------------------------------- 1 | { 2 | "System": { 3 | "PairingStatus": "Paired", 4 | "TimeZoneOffset": 0, 5 | "AutomaticDaylightSaving": true, 6 | "SystemMode": "Heat", 7 | "Version": 6, 8 | "FotaEnabled": true, 9 | "ValveProtectionEnabled": false, 10 | "AwayModeAffectsHotWater": true, 11 | "AwayModeSetPointLimit": 150, 12 | "BoilerSettings": { 13 | "ControlType": "HeatSourceType_RelayControlled", 14 | "FuelType": "Gas", 15 | "CycleRate": "CPH_6", 16 | "OnOffHysteresis": 5 17 | }, 18 | "ZigbeeSettings": { 19 | "SuppressApsAcks": true 20 | }, 21 | "CoolingModeDefaultSetpoint": 210, 22 | "CoolingAwayModeSetpointLimit": 240, 23 | "ComfortModeEnabled": false, 24 | "PreheatTimeLimit": 10800, 25 | "DegradedModeSetpointThreshold": 180, 26 | "UnixTime": 1548102360, 27 | "ActiveSystemVersion": "2.26.16-6340f5b", 28 | "ZigbeePermitJoinActive": false, 29 | "CloudConnectionStatus": "Connected", 30 | "ZigbeeModuleVersion": "R311 B030517", 31 | "ZigbeeEui": "90FD9FFFFEAA6AA7", 32 | "LocalDateAndTime": { 33 | "Year": 2019, 34 | "Month": "January", 35 | "Date": 21, 36 | "Day": "Monday", 37 | "Time": 2026 38 | }, 39 | "HeatingButtonOverrideState": "Off", 40 | "HotWaterButtonOverrideState": "Off" 41 | }, 42 | "Cloud": { 43 | "Environment": "Prod", 44 | "DetailedPublishing": false, 45 | "WiserApiHost": "api-nl.wiserair.com", 46 | "BootStrapApiHost": "bootstrap.gl.struxurewarecloud.com" 47 | }, 48 | "HeatingChannel": [ 49 | { 50 | "id": 1, 51 | "Name": "Channel-1", 52 | "RoomIds": [ 53 | 2, 54 | 3, 55 | 4 56 | ], 57 | "PercentageDemand": 0, 58 | "DemandOnOffOutput": "Off", 59 | "HeatingRelayState": "Off", 60 | "IsSmartValvePreventingDemand": true 61 | } 62 | ], 63 | "Room": [ 64 | { 65 | "id": 4, 66 | "ScheduleId": 4, 67 | "HeatingRate": 1200, 68 | "SmartValveIds": [ 69 | 34191 70 | ], 71 | "UfhRelayIds": [], 72 | "Name": "Office", 73 | "Mode": "Auto", 74 | "DemandType": "Modulating", 75 | "WindowDetectionActive": false, 76 | "ControlSequenceOfOperation": "HeatingOnly", 77 | "HeatingType": "HydronicRadiator", 78 | "CalculatedTemperature": -32768, 79 | "CurrentSetPoint": 210, 80 | "PercentageDemand": 0, 81 | "ControlOutputState": "Off", 82 | "WindowState": "Closed", 83 | "SetPointOrigin": "FromSchedule", 84 | "DisplayedSetPoint": 210, 85 | "ScheduledSetPoint": 210, 86 | "RoundedAlexaTemperature": 32766 87 | }, 88 | { 89 | "id": 2, 90 | "ScheduleId": 2, 91 | "HeatingRate": 1200, 92 | "UfhRelayIds": [], 93 | "Name": "Bedroom", 94 | "Mode": "Auto", 95 | "WindowDetectionActive": false, 96 | "ControlSequenceOfOperation": "HeatingOnly", 97 | "HeatingType": "HydronicRadiator", 98 | "CurrentSetPoint": 210, 99 | "SetPointOrigin": "FromSchedule", 100 | "DisplayedSetPoint": 210, 101 | "ScheduledSetPoint": 210, 102 | "Invalid": "NothingAssigned" 103 | }, 104 | { 105 | "id": 3, 106 | "ManualSetPoint": 210, 107 | "OverrideType": "Manual", 108 | "OverrideTimeoutUnixTime": 1548104160, 109 | "OverrideSetpoint": 180, 110 | "ScheduleId": 3, 111 | "HeatingRate": 1200, 112 | "RoomStatId": 34190, 113 | "UfhRelayIds": [], 114 | "Name": "Living Room", 115 | "Mode": "Auto", 116 | "DemandType": "Modulating", 117 | "WindowDetectionActive": false, 118 | "ControlSequenceOfOperation": "HeatingOnly", 119 | "HeatingType": "HydronicRadiator", 120 | "CalculatedTemperature": 201, 121 | "CurrentSetPoint": 180, 122 | "PercentageDemand": 0, 123 | "ControlOutputState": "Off", 124 | "SetPointOrigin": "FromBoost", 125 | "DisplayedSetPoint": 180, 126 | "ScheduledSetPoint": 200, 127 | "AwayModeSuppressed": false, 128 | "RoundedAlexaTemperature": 200 129 | } 130 | ], 131 | "Device": [ 132 | { 133 | "id": 0, 134 | "NodeId": 0, 135 | "ProductType": "Controller", 136 | "ProductIdentifier": "Controller", 137 | "ActiveFirmwareVersion": "2.26.16", 138 | "ModelIdentifier": "WT714R1S0902", 139 | "DeviceLockEnabled": false, 140 | "DisplayedSignalStrength": "VeryGood", 141 | "ReceptionOfController": { 142 | "Rssi": -64 143 | } 144 | }, 145 | { 146 | "id": 34190, 147 | "NodeId": 34190, 148 | "ProductType": "RoomStat", 149 | "ProductIdentifier": "RoomStat", 150 | "ActiveFirmwareVersion": "04E1000900010012", 151 | "ModelIdentifier": "Thermostat", 152 | "HardwareVersion": "1", 153 | "SerialNumber": "D0CF5EFFFE36D105", 154 | "ProductModel": "Thermostat", 155 | "OtaImageQueryCount": 1, 156 | "LastOtaImageQueryCount": 1, 157 | "ParentNodeId": 0, 158 | "DeviceLockEnabled": false, 159 | "DisplayedSignalStrength": "Good", 160 | "BatteryVoltage": 31, 161 | "BatteryLevel": "Normal", 162 | "ReceptionOfController": { 163 | "Rssi": -61, 164 | "Lqi": 156 165 | }, 166 | "ReceptionOfDevice": { 167 | "Rssi": -61, 168 | "Lqi": 156 169 | }, 170 | "OtaLastImageSentBytes": 313162 171 | }, 172 | { 173 | "id": 34191, 174 | "NodeId": 3122, 175 | "ProductType": "iTRV", 176 | "ProductIdentifier": "iTRV", 177 | "ActiveFirmwareVersion": "0201000000010012", 178 | "ModelIdentifier": "iTRV", 179 | "HardwareVersion": "0", 180 | "SerialNumber": "90FD9FFFFEC39AA8", 181 | "ProductModel": "iTRV", 182 | "OtaImageQueryCount": 0, 183 | "LastOtaImageQueryCount": 0, 184 | "ParentNodeId": 65534, 185 | "DeviceLockEnabled": false, 186 | "DisplayedSignalStrength": "NoSignal", 187 | "ReceptionOfController": {}, 188 | "ReceptionOfDevice": {}, 189 | "PendingZigbeeMessageMask": 2 190 | } 191 | ], 192 | "UpgradeInfo": [ 193 | { 194 | "id": 8, 195 | "FirmwareFilename": "0A00000000010012FOTA.BIN" 196 | }, 197 | { 198 | "id": 7, 199 | "FirmwareFilename": "05E1000900000021FOTA.BIN" 200 | }, 201 | { 202 | "id": 6, 203 | "FirmwareFilename": "04E1000900010012FOTA.BIN" 204 | }, 205 | { 206 | "id": 5, 207 | "FirmwareFilename": "0441010100010005FOTA.BIN" 208 | }, 209 | { 210 | "id": 4, 211 | "FirmwareFilename": "0441000000010005FOTA.BIN" 212 | }, 213 | { 214 | "id": 3, 215 | "FirmwareFilename": "0401010100010005FOTA.BIN" 216 | }, 217 | { 218 | "id": 2, 219 | "FirmwareFilename": "0401000000010005FOTA.BIN" 220 | }, 221 | { 222 | "id": 1, 223 | "FirmwareFilename": "0201000000010012FOTA.BIN" 224 | } 225 | ], 226 | "SmartValve": [ 227 | { 228 | "id": 34191, 229 | "MountingOrientation": "Vertical", 230 | "SetPoint": 210, 231 | "MeasuredTemperature": -32768, 232 | "WindowState": "Closed" 233 | } 234 | ], 235 | "RoomStat": [ 236 | { 237 | "id": 34190, 238 | "SetPoint": 180, 239 | "MeasuredTemperature": 201, 240 | "MeasuredHumidity": 41 241 | } 242 | ], 243 | "DeviceCapabilityMatrix": { 244 | "Roomstat": true, 245 | "ITRV": true, 246 | "SmartPlug": true, 247 | "UFH": false, 248 | "UFHFloorTempSensor": false, 249 | "UFHDewSensor": false, 250 | "HACT": false, 251 | "LACT": false 252 | }, 253 | "Schedule": [ 254 | { 255 | "id": 4, 256 | "Monday": { 257 | "SetPoints": [ 258 | { 259 | "Time": 630, 260 | "DegreesC": 200 261 | }, 262 | { 263 | "Time": 830, 264 | "DegreesC": 160 265 | }, 266 | { 267 | "Time": 1630, 268 | "DegreesC": 210 269 | }, 270 | { 271 | "Time": 2230, 272 | "DegreesC": -200 273 | } 274 | ] 275 | }, 276 | "Tuesday": { 277 | "SetPoints": [ 278 | { 279 | "Time": 630, 280 | "DegreesC": 200 281 | }, 282 | { 283 | "Time": 830, 284 | "DegreesC": 160 285 | }, 286 | { 287 | "Time": 1630, 288 | "DegreesC": 210 289 | }, 290 | { 291 | "Time": 2230, 292 | "DegreesC": -200 293 | } 294 | ] 295 | }, 296 | "Wednesday": { 297 | "SetPoints": [ 298 | { 299 | "Time": 630, 300 | "DegreesC": 200 301 | }, 302 | { 303 | "Time": 830, 304 | "DegreesC": 160 305 | }, 306 | { 307 | "Time": 1630, 308 | "DegreesC": 210 309 | }, 310 | { 311 | "Time": 2230, 312 | "DegreesC": -200 313 | } 314 | ] 315 | }, 316 | "Thursday": { 317 | "SetPoints": [ 318 | { 319 | "Time": 630, 320 | "DegreesC": 200 321 | }, 322 | { 323 | "Time": 830, 324 | "DegreesC": 160 325 | }, 326 | { 327 | "Time": 1630, 328 | "DegreesC": 210 329 | }, 330 | { 331 | "Time": 2230, 332 | "DegreesC": -200 333 | } 334 | ] 335 | }, 336 | "Friday": { 337 | "SetPoints": [ 338 | { 339 | "Time": 630, 340 | "DegreesC": 200 341 | }, 342 | { 343 | "Time": 830, 344 | "DegreesC": 160 345 | }, 346 | { 347 | "Time": 1630, 348 | "DegreesC": 210 349 | }, 350 | { 351 | "Time": 2230, 352 | "DegreesC": -200 353 | } 354 | ] 355 | }, 356 | "Saturday": { 357 | "SetPoints": [ 358 | { 359 | "Time": 700, 360 | "DegreesC": 200 361 | }, 362 | { 363 | "Time": 900, 364 | "DegreesC": 180 365 | }, 366 | { 367 | "Time": 1600, 368 | "DegreesC": 210 369 | }, 370 | { 371 | "Time": 2300, 372 | "DegreesC": -200 373 | } 374 | ] 375 | }, 376 | "Sunday": { 377 | "SetPoints": [ 378 | { 379 | "Time": 700, 380 | "DegreesC": 200 381 | }, 382 | { 383 | "Time": 900, 384 | "DegreesC": 180 385 | }, 386 | { 387 | "Time": 1600, 388 | "DegreesC": 210 389 | }, 390 | { 391 | "Time": 2300, 392 | "DegreesC": -200 393 | } 394 | ] 395 | }, 396 | "Type": "Heating" 397 | }, 398 | { 399 | "id": 2, 400 | "Monday": { 401 | "SetPoints": [ 402 | { 403 | "Time": 630, 404 | "DegreesC": 200 405 | }, 406 | { 407 | "Time": 830, 408 | "DegreesC": 160 409 | }, 410 | { 411 | "Time": 1630, 412 | "DegreesC": 210 413 | }, 414 | { 415 | "Time": 2230, 416 | "DegreesC": -200 417 | } 418 | ] 419 | }, 420 | "Tuesday": { 421 | "SetPoints": [ 422 | { 423 | "Time": 630, 424 | "DegreesC": 200 425 | }, 426 | { 427 | "Time": 830, 428 | "DegreesC": 160 429 | }, 430 | { 431 | "Time": 1630, 432 | "DegreesC": 210 433 | }, 434 | { 435 | "Time": 2230, 436 | "DegreesC": -200 437 | } 438 | ] 439 | }, 440 | "Wednesday": { 441 | "SetPoints": [ 442 | { 443 | "Time": 630, 444 | "DegreesC": 200 445 | }, 446 | { 447 | "Time": 830, 448 | "DegreesC": 160 449 | }, 450 | { 451 | "Time": 1630, 452 | "DegreesC": 210 453 | }, 454 | { 455 | "Time": 2230, 456 | "DegreesC": -200 457 | } 458 | ] 459 | }, 460 | "Thursday": { 461 | "SetPoints": [ 462 | { 463 | "Time": 630, 464 | "DegreesC": 200 465 | }, 466 | { 467 | "Time": 830, 468 | "DegreesC": 160 469 | }, 470 | { 471 | "Time": 1630, 472 | "DegreesC": 210 473 | }, 474 | { 475 | "Time": 2230, 476 | "DegreesC": -200 477 | } 478 | ] 479 | }, 480 | "Friday": { 481 | "SetPoints": [ 482 | { 483 | "Time": 630, 484 | "DegreesC": 200 485 | }, 486 | { 487 | "Time": 830, 488 | "DegreesC": 160 489 | }, 490 | { 491 | "Time": 1630, 492 | "DegreesC": 210 493 | }, 494 | { 495 | "Time": 2230, 496 | "DegreesC": -200 497 | } 498 | ] 499 | }, 500 | "Saturday": { 501 | "SetPoints": [ 502 | { 503 | "Time": 700, 504 | "DegreesC": 200 505 | }, 506 | { 507 | "Time": 900, 508 | "DegreesC": 180 509 | }, 510 | { 511 | "Time": 1600, 512 | "DegreesC": 210 513 | }, 514 | { 515 | "Time": 2300, 516 | "DegreesC": -200 517 | } 518 | ] 519 | }, 520 | "Sunday": { 521 | "SetPoints": [ 522 | { 523 | "Time": 700, 524 | "DegreesC": 200 525 | }, 526 | { 527 | "Time": 900, 528 | "DegreesC": 180 529 | }, 530 | { 531 | "Time": 1600, 532 | "DegreesC": 210 533 | }, 534 | { 535 | "Time": 2300, 536 | "DegreesC": -200 537 | } 538 | ] 539 | }, 540 | "Type": "Heating" 541 | }, 542 | { 543 | "id": 3, 544 | "Monday": { 545 | "SetPoints": [ 546 | { 547 | "Time": 630, 548 | "DegreesC": 200 549 | }, 550 | { 551 | "Time": 830, 552 | "DegreesC": -200 553 | }, 554 | { 555 | "Time": 1430, 556 | "DegreesC": 140 557 | }, 558 | { 559 | "Time": 1700, 560 | "DegreesC": 200 561 | }, 562 | { 563 | "Time": 2230, 564 | "DegreesC": -200 565 | } 566 | ] 567 | }, 568 | "Tuesday": { 569 | "SetPoints": [ 570 | { 571 | "Time": 630, 572 | "DegreesC": 200 573 | }, 574 | { 575 | "Time": 830, 576 | "DegreesC": -200 577 | }, 578 | { 579 | "Time": 1430, 580 | "DegreesC": 140 581 | }, 582 | { 583 | "Time": 1700, 584 | "DegreesC": 200 585 | }, 586 | { 587 | "Time": 2230, 588 | "DegreesC": -200 589 | } 590 | ] 591 | }, 592 | "Wednesday": { 593 | "SetPoints": [ 594 | { 595 | "Time": 630, 596 | "DegreesC": 200 597 | }, 598 | { 599 | "Time": 830, 600 | "DegreesC": -200 601 | }, 602 | { 603 | "Time": 1430, 604 | "DegreesC": 140 605 | }, 606 | { 607 | "Time": 1700, 608 | "DegreesC": 200 609 | }, 610 | { 611 | "Time": 2230, 612 | "DegreesC": -200 613 | } 614 | ] 615 | }, 616 | "Thursday": { 617 | "SetPoints": [ 618 | { 619 | "Time": 630, 620 | "DegreesC": 200 621 | }, 622 | { 623 | "Time": 830, 624 | "DegreesC": -200 625 | }, 626 | { 627 | "Time": 1430, 628 | "DegreesC": 140 629 | }, 630 | { 631 | "Time": 1700, 632 | "DegreesC": 200 633 | }, 634 | { 635 | "Time": 2230, 636 | "DegreesC": -200 637 | } 638 | ] 639 | }, 640 | "Friday": { 641 | "SetPoints": [ 642 | { 643 | "Time": 630, 644 | "DegreesC": 200 645 | }, 646 | { 647 | "Time": 830, 648 | "DegreesC": -200 649 | }, 650 | { 651 | "Time": 1430, 652 | "DegreesC": 140 653 | }, 654 | { 655 | "Time": 1700, 656 | "DegreesC": 200 657 | }, 658 | { 659 | "Time": 2230, 660 | "DegreesC": -200 661 | } 662 | ] 663 | }, 664 | "Saturday": { 665 | "SetPoints": [ 666 | { 667 | "Time": 800, 668 | "DegreesC": 200 669 | }, 670 | { 671 | "Time": 1030, 672 | "DegreesC": 170 673 | }, 674 | { 675 | "Time": 1600, 676 | "DegreesC": 200 677 | }, 678 | { 679 | "Time": 2300, 680 | "DegreesC": -200 681 | } 682 | ] 683 | }, 684 | "Sunday": { 685 | "SetPoints": [ 686 | { 687 | "Time": 800, 688 | "DegreesC": 200 689 | }, 690 | { 691 | "Time": 1030, 692 | "DegreesC": 170 693 | }, 694 | { 695 | "Time": 1600, 696 | "DegreesC": 200 697 | }, 698 | { 699 | "Time": 2300, 700 | "DegreesC": -200 701 | } 702 | ] 703 | }, 704 | "Type": "Heating" 705 | } 706 | ] 707 | } -------------------------------------------------------------------------------- /tests/data/all-with-itrv-awaymode.json: -------------------------------------------------------------------------------- 1 | { 2 | "System": { 3 | "PairingStatus": "Paired", 4 | "OverrideType": "Away", 5 | "TimeZoneOffset": 0, 6 | "AutomaticDaylightSaving": true, 7 | "SystemMode": "Heat", 8 | "Version": 6, 9 | "FotaEnabled": true, 10 | "ValveProtectionEnabled": false, 11 | "AwayModeAffectsHotWater": true, 12 | "AwayModeSetPointLimit": 150, 13 | "BoilerSettings": { 14 | "ControlType": "HeatSourceType_RelayControlled", 15 | "FuelType": "Gas", 16 | "CycleRate": "CPH_6", 17 | "OnOffHysteresis": 5 18 | }, 19 | "ZigbeeSettings": { 20 | "SuppressApsAcks": true 21 | }, 22 | "CoolingModeDefaultSetpoint": 210, 23 | "CoolingAwayModeSetpointLimit": 240, 24 | "ComfortModeEnabled": false, 25 | "PreheatTimeLimit": 10800, 26 | "DegradedModeSetpointThreshold": 180, 27 | "UnixTime": 1548193980, 28 | "ActiveSystemVersion": "2.26.16-6340f5b", 29 | "ZigbeePermitJoinActive": false, 30 | "CloudConnectionStatus": "Connected", 31 | "ZigbeeModuleVersion": "R311 B030517", 32 | "ZigbeeEui": "90FD9FFFFEAA6AA7", 33 | "LocalDateAndTime": { 34 | "Year": 2019, 35 | "Month": "January", 36 | "Date": 22, 37 | "Day": "Tuesday", 38 | "Time": 2153 39 | }, 40 | "HeatingButtonOverrideState": "Off", 41 | "HotWaterButtonOverrideState": "Off" 42 | }, 43 | "Cloud": { 44 | "Environment": "Prod", 45 | "DetailedPublishing": false, 46 | "WiserApiHost": "api-nl.wiserair.com", 47 | "BootStrapApiHost": "bootstrap.gl.struxurewarecloud.com" 48 | }, 49 | "HeatingChannel": [ 50 | { 51 | "id": 1, 52 | "Name": "Channel-1", 53 | "RoomIds": [ 54 | 2, 55 | 3, 56 | 4 57 | ], 58 | "PercentageDemand": 0, 59 | "DemandOnOffOutput": "Off", 60 | "HeatingRelayState": "Off", 61 | "IsSmartValvePreventingDemand": true 62 | } 63 | ], 64 | "Room": [ 65 | { 66 | "id": 4, 67 | "ScheduleId": 4, 68 | "HeatingRate": 1200, 69 | "SmartValveIds": [ 70 | 34191 71 | ], 72 | "UfhRelayIds": [], 73 | "Name": "Office", 74 | "Mode": "Auto", 75 | "DemandType": "Modulating", 76 | "WindowDetectionActive": false, 77 | "ControlSequenceOfOperation": "HeatingOnly", 78 | "HeatingType": "HydronicRadiator", 79 | "CalculatedTemperature": -32768, 80 | "CurrentSetPoint": 150, 81 | "PercentageDemand": 0, 82 | "ControlOutputState": "Off", 83 | "WindowState": "Closed", 84 | "SetPointOrigin": "FromAwayMode", 85 | "DisplayedSetPoint": 150, 86 | "ScheduledSetPoint": 210, 87 | "RoundedAlexaTemperature": 32766 88 | }, 89 | { 90 | "id": 2, 91 | "ScheduleId": 2, 92 | "HeatingRate": 1200, 93 | "UfhRelayIds": [], 94 | "Name": "Bedroom", 95 | "Mode": "Auto", 96 | "WindowDetectionActive": false, 97 | "ControlSequenceOfOperation": "HeatingOnly", 98 | "HeatingType": "HydronicRadiator", 99 | "CurrentSetPoint": 150, 100 | "SetPointOrigin": "FromAwayMode", 101 | "DisplayedSetPoint": 150, 102 | "ScheduledSetPoint": 210, 103 | "Invalid": "NothingAssigned" 104 | }, 105 | { 106 | "id": 3, 107 | "ManualSetPoint": 210, 108 | "ScheduleId": 3, 109 | "HeatingRate": 1200, 110 | "RoomStatId": 34190, 111 | "UfhRelayIds": [], 112 | "Name": "Living Room", 113 | "Mode": "Auto", 114 | "DemandType": "Modulating", 115 | "WindowDetectionActive": false, 116 | "ControlSequenceOfOperation": "HeatingOnly", 117 | "HeatingType": "HydronicRadiator", 118 | "CalculatedTemperature": 199, 119 | "CurrentSetPoint": 150, 120 | "PercentageDemand": 0, 121 | "ControlOutputState": "Off", 122 | "SetPointOrigin": "FromAwayMode", 123 | "DisplayedSetPoint": 150, 124 | "ScheduledSetPoint": 200, 125 | "AwayModeSuppressed": false, 126 | "RoundedAlexaTemperature": 200 127 | } 128 | ], 129 | "Device": [ 130 | { 131 | "id": 0, 132 | "NodeId": 0, 133 | "ProductType": "Controller", 134 | "ProductIdentifier": "Controller", 135 | "ActiveFirmwareVersion": "2.26.16", 136 | "ModelIdentifier": "WT714R1S0902", 137 | "DeviceLockEnabled": false, 138 | "DisplayedSignalStrength": "VeryGood", 139 | "ReceptionOfController": { 140 | "Rssi": -62 141 | } 142 | }, 143 | { 144 | "id": 34190, 145 | "NodeId": 34190, 146 | "ProductType": "RoomStat", 147 | "ProductIdentifier": "RoomStat", 148 | "ActiveFirmwareVersion": "04E1000900010012", 149 | "ModelIdentifier": "Thermostat", 150 | "HardwareVersion": "1", 151 | "SerialNumber": "D0CF5EFFFE36D105", 152 | "ProductModel": "Thermostat", 153 | "OtaImageQueryCount": 1, 154 | "LastOtaImageQueryCount": 1, 155 | "ParentNodeId": 0, 156 | "DeviceLockEnabled": false, 157 | "DisplayedSignalStrength": "Good", 158 | "BatteryVoltage": 31, 159 | "BatteryLevel": "Normal", 160 | "ReceptionOfController": { 161 | "Rssi": -61, 162 | "Lqi": 156 163 | }, 164 | "ReceptionOfDevice": { 165 | "Rssi": -61, 166 | "Lqi": 156 167 | }, 168 | "OtaLastImageSentBytes": 313162 169 | }, 170 | { 171 | "id": 34191, 172 | "NodeId": 3122, 173 | "ProductType": "iTRV", 174 | "ProductIdentifier": "iTRV", 175 | "ActiveFirmwareVersion": "0201000000010012", 176 | "ModelIdentifier": "iTRV", 177 | "HardwareVersion": "0", 178 | "SerialNumber": "90FD9FFFFEC39AA8", 179 | "ProductModel": "iTRV", 180 | "OtaImageQueryCount": 0, 181 | "LastOtaImageQueryCount": 0, 182 | "ParentNodeId": 65534, 183 | "DeviceLockEnabled": false, 184 | "DisplayedSignalStrength": "NoSignal", 185 | "ReceptionOfController": {}, 186 | "ReceptionOfDevice": {}, 187 | "PendingZigbeeMessageMask": 2 188 | } 189 | ], 190 | "UpgradeInfo": [ 191 | { 192 | "id": 8, 193 | "FirmwareFilename": "0A00000000010012FOTA.BIN" 194 | }, 195 | { 196 | "id": 7, 197 | "FirmwareFilename": "05E1000900000021FOTA.BIN" 198 | }, 199 | { 200 | "id": 6, 201 | "FirmwareFilename": "04E1000900010012FOTA.BIN" 202 | }, 203 | { 204 | "id": 5, 205 | "FirmwareFilename": "0441010100010005FOTA.BIN" 206 | }, 207 | { 208 | "id": 4, 209 | "FirmwareFilename": "0441000000010005FOTA.BIN" 210 | }, 211 | { 212 | "id": 3, 213 | "FirmwareFilename": "0401010100010005FOTA.BIN" 214 | }, 215 | { 216 | "id": 2, 217 | "FirmwareFilename": "0401000000010005FOTA.BIN" 218 | }, 219 | { 220 | "id": 1, 221 | "FirmwareFilename": "0201000000010012FOTA.BIN" 222 | } 223 | ], 224 | "SmartValve": [ 225 | { 226 | "id": 34191, 227 | "MountingOrientation": "Vertical", 228 | "SetPoint": 150, 229 | "MeasuredTemperature": -32768, 230 | "WindowState": "Closed" 231 | } 232 | ], 233 | "RoomStat": [ 234 | { 235 | "id": 34190, 236 | "SetPoint": 150, 237 | "MeasuredTemperature": 199, 238 | "MeasuredHumidity": 40 239 | } 240 | ], 241 | "DeviceCapabilityMatrix": { 242 | "Roomstat": true, 243 | "ITRV": true, 244 | "SmartPlug": true, 245 | "UFH": false, 246 | "UFHFloorTempSensor": false, 247 | "UFHDewSensor": false, 248 | "HACT": false, 249 | "LACT": false 250 | }, 251 | "Schedule": [ 252 | { 253 | "id": 4, 254 | "Monday": { 255 | "SetPoints": [ 256 | { 257 | "Time": 630, 258 | "DegreesC": 200 259 | }, 260 | { 261 | "Time": 830, 262 | "DegreesC": 160 263 | }, 264 | { 265 | "Time": 1630, 266 | "DegreesC": 210 267 | }, 268 | { 269 | "Time": 2230, 270 | "DegreesC": -200 271 | } 272 | ] 273 | }, 274 | "Tuesday": { 275 | "SetPoints": [ 276 | { 277 | "Time": 630, 278 | "DegreesC": 200 279 | }, 280 | { 281 | "Time": 830, 282 | "DegreesC": 160 283 | }, 284 | { 285 | "Time": 1630, 286 | "DegreesC": 210 287 | }, 288 | { 289 | "Time": 2230, 290 | "DegreesC": -200 291 | } 292 | ] 293 | }, 294 | "Wednesday": { 295 | "SetPoints": [ 296 | { 297 | "Time": 630, 298 | "DegreesC": 200 299 | }, 300 | { 301 | "Time": 830, 302 | "DegreesC": 160 303 | }, 304 | { 305 | "Time": 1630, 306 | "DegreesC": 210 307 | }, 308 | { 309 | "Time": 2230, 310 | "DegreesC": -200 311 | } 312 | ] 313 | }, 314 | "Thursday": { 315 | "SetPoints": [ 316 | { 317 | "Time": 630, 318 | "DegreesC": 200 319 | }, 320 | { 321 | "Time": 830, 322 | "DegreesC": 160 323 | }, 324 | { 325 | "Time": 1630, 326 | "DegreesC": 210 327 | }, 328 | { 329 | "Time": 2230, 330 | "DegreesC": -200 331 | } 332 | ] 333 | }, 334 | "Friday": { 335 | "SetPoints": [ 336 | { 337 | "Time": 630, 338 | "DegreesC": 200 339 | }, 340 | { 341 | "Time": 830, 342 | "DegreesC": 160 343 | }, 344 | { 345 | "Time": 1630, 346 | "DegreesC": 210 347 | }, 348 | { 349 | "Time": 2230, 350 | "DegreesC": -200 351 | } 352 | ] 353 | }, 354 | "Saturday": { 355 | "SetPoints": [ 356 | { 357 | "Time": 700, 358 | "DegreesC": 200 359 | }, 360 | { 361 | "Time": 900, 362 | "DegreesC": 180 363 | }, 364 | { 365 | "Time": 1600, 366 | "DegreesC": 210 367 | }, 368 | { 369 | "Time": 2300, 370 | "DegreesC": -200 371 | } 372 | ] 373 | }, 374 | "Sunday": { 375 | "SetPoints": [ 376 | { 377 | "Time": 700, 378 | "DegreesC": 200 379 | }, 380 | { 381 | "Time": 900, 382 | "DegreesC": 180 383 | }, 384 | { 385 | "Time": 1600, 386 | "DegreesC": 210 387 | }, 388 | { 389 | "Time": 2300, 390 | "DegreesC": -200 391 | } 392 | ] 393 | }, 394 | "Type": "Heating" 395 | }, 396 | { 397 | "id": 2, 398 | "Monday": { 399 | "SetPoints": [ 400 | { 401 | "Time": 630, 402 | "DegreesC": 200 403 | }, 404 | { 405 | "Time": 830, 406 | "DegreesC": 160 407 | }, 408 | { 409 | "Time": 1630, 410 | "DegreesC": 210 411 | }, 412 | { 413 | "Time": 2230, 414 | "DegreesC": -200 415 | } 416 | ] 417 | }, 418 | "Tuesday": { 419 | "SetPoints": [ 420 | { 421 | "Time": 630, 422 | "DegreesC": 200 423 | }, 424 | { 425 | "Time": 830, 426 | "DegreesC": 160 427 | }, 428 | { 429 | "Time": 1630, 430 | "DegreesC": 210 431 | }, 432 | { 433 | "Time": 2230, 434 | "DegreesC": -200 435 | } 436 | ] 437 | }, 438 | "Wednesday": { 439 | "SetPoints": [ 440 | { 441 | "Time": 630, 442 | "DegreesC": 200 443 | }, 444 | { 445 | "Time": 830, 446 | "DegreesC": 160 447 | }, 448 | { 449 | "Time": 1630, 450 | "DegreesC": 210 451 | }, 452 | { 453 | "Time": 2230, 454 | "DegreesC": -200 455 | } 456 | ] 457 | }, 458 | "Thursday": { 459 | "SetPoints": [ 460 | { 461 | "Time": 630, 462 | "DegreesC": 200 463 | }, 464 | { 465 | "Time": 830, 466 | "DegreesC": 160 467 | }, 468 | { 469 | "Time": 1630, 470 | "DegreesC": 210 471 | }, 472 | { 473 | "Time": 2230, 474 | "DegreesC": -200 475 | } 476 | ] 477 | }, 478 | "Friday": { 479 | "SetPoints": [ 480 | { 481 | "Time": 630, 482 | "DegreesC": 200 483 | }, 484 | { 485 | "Time": 830, 486 | "DegreesC": 160 487 | }, 488 | { 489 | "Time": 1630, 490 | "DegreesC": 210 491 | }, 492 | { 493 | "Time": 2230, 494 | "DegreesC": -200 495 | } 496 | ] 497 | }, 498 | "Saturday": { 499 | "SetPoints": [ 500 | { 501 | "Time": 700, 502 | "DegreesC": 200 503 | }, 504 | { 505 | "Time": 900, 506 | "DegreesC": 180 507 | }, 508 | { 509 | "Time": 1600, 510 | "DegreesC": 210 511 | }, 512 | { 513 | "Time": 2300, 514 | "DegreesC": -200 515 | } 516 | ] 517 | }, 518 | "Sunday": { 519 | "SetPoints": [ 520 | { 521 | "Time": 700, 522 | "DegreesC": 200 523 | }, 524 | { 525 | "Time": 900, 526 | "DegreesC": 180 527 | }, 528 | { 529 | "Time": 1600, 530 | "DegreesC": 210 531 | }, 532 | { 533 | "Time": 2300, 534 | "DegreesC": -200 535 | } 536 | ] 537 | }, 538 | "Type": "Heating" 539 | }, 540 | { 541 | "id": 3, 542 | "Monday": { 543 | "SetPoints": [ 544 | { 545 | "Time": 630, 546 | "DegreesC": 200 547 | }, 548 | { 549 | "Time": 830, 550 | "DegreesC": -200 551 | }, 552 | { 553 | "Time": 1430, 554 | "DegreesC": 140 555 | }, 556 | { 557 | "Time": 1700, 558 | "DegreesC": 200 559 | }, 560 | { 561 | "Time": 2230, 562 | "DegreesC": -200 563 | } 564 | ] 565 | }, 566 | "Tuesday": { 567 | "SetPoints": [ 568 | { 569 | "Time": 630, 570 | "DegreesC": 200 571 | }, 572 | { 573 | "Time": 830, 574 | "DegreesC": -200 575 | }, 576 | { 577 | "Time": 1430, 578 | "DegreesC": 140 579 | }, 580 | { 581 | "Time": 1700, 582 | "DegreesC": 200 583 | }, 584 | { 585 | "Time": 2230, 586 | "DegreesC": -200 587 | } 588 | ] 589 | }, 590 | "Wednesday": { 591 | "SetPoints": [ 592 | { 593 | "Time": 630, 594 | "DegreesC": 200 595 | }, 596 | { 597 | "Time": 830, 598 | "DegreesC": -200 599 | }, 600 | { 601 | "Time": 1430, 602 | "DegreesC": 140 603 | }, 604 | { 605 | "Time": 1700, 606 | "DegreesC": 200 607 | }, 608 | { 609 | "Time": 2230, 610 | "DegreesC": -200 611 | } 612 | ] 613 | }, 614 | "Thursday": { 615 | "SetPoints": [ 616 | { 617 | "Time": 630, 618 | "DegreesC": 200 619 | }, 620 | { 621 | "Time": 830, 622 | "DegreesC": -200 623 | }, 624 | { 625 | "Time": 1430, 626 | "DegreesC": 140 627 | }, 628 | { 629 | "Time": 1700, 630 | "DegreesC": 200 631 | }, 632 | { 633 | "Time": 2230, 634 | "DegreesC": -200 635 | } 636 | ] 637 | }, 638 | "Friday": { 639 | "SetPoints": [ 640 | { 641 | "Time": 630, 642 | "DegreesC": 200 643 | }, 644 | { 645 | "Time": 830, 646 | "DegreesC": -200 647 | }, 648 | { 649 | "Time": 1430, 650 | "DegreesC": 140 651 | }, 652 | { 653 | "Time": 1700, 654 | "DegreesC": 200 655 | }, 656 | { 657 | "Time": 2230, 658 | "DegreesC": -200 659 | } 660 | ] 661 | }, 662 | "Saturday": { 663 | "SetPoints": [ 664 | { 665 | "Time": 800, 666 | "DegreesC": 200 667 | }, 668 | { 669 | "Time": 1030, 670 | "DegreesC": 170 671 | }, 672 | { 673 | "Time": 1600, 674 | "DegreesC": 200 675 | }, 676 | { 677 | "Time": 2300, 678 | "DegreesC": -200 679 | } 680 | ] 681 | }, 682 | "Sunday": { 683 | "SetPoints": [ 684 | { 685 | "Time": 800, 686 | "DegreesC": 200 687 | }, 688 | { 689 | "Time": 1030, 690 | "DegreesC": 170 691 | }, 692 | { 693 | "Time": 1600, 694 | "DegreesC": 200 695 | }, 696 | { 697 | "Time": 2300, 698 | "DegreesC": -200 699 | } 700 | ] 701 | }, 702 | "Type": "Heating" 703 | } 704 | ] 705 | } -------------------------------------------------------------------------------- /tests/data/all-with-itrv.json: -------------------------------------------------------------------------------- 1 | { 2 | "System": { 3 | "PairingStatus": "Paired", 4 | "TimeZoneOffset": 0, 5 | "AutomaticDaylightSaving": true, 6 | "SystemMode": "Heat", 7 | "Version": 6, 8 | "FotaEnabled": true, 9 | "ValveProtectionEnabled": false, 10 | "AwayModeAffectsHotWater": true, 11 | "AwayModeSetPointLimit": 150, 12 | "BoilerSettings": { 13 | "ControlType": "HeatSourceType_RelayControlled", 14 | "FuelType": "Gas", 15 | "CycleRate": "CPH_6", 16 | "OnOffHysteresis": 5 17 | }, 18 | "ZigbeeSettings": { 19 | "SuppressApsAcks": true 20 | }, 21 | "CoolingModeDefaultSetpoint": 210, 22 | "CoolingAwayModeSetpointLimit": 240, 23 | "ComfortModeEnabled": false, 24 | "PreheatTimeLimit": 10800, 25 | "DegradedModeSetpointThreshold": 180, 26 | "UnixTime": 1548002580, 27 | "ActiveSystemVersion": "2.26.16-6340f5b", 28 | "ZigbeePermitJoinActive": false, 29 | "CloudConnectionStatus": "Connected", 30 | "ZigbeeModuleVersion": "R311 B030517", 31 | "ZigbeeEui": "90FD9FFFFEAA6AA7", 32 | "LocalDateAndTime": { 33 | "Year": 2019, 34 | "Month": "January", 35 | "Date": 20, 36 | "Day": "Sunday", 37 | "Time": 1643 38 | }, 39 | "HeatingButtonOverrideState": "Off", 40 | "HotWaterButtonOverrideState": "Off" 41 | }, 42 | "Cloud": { 43 | "Environment": "Prod", 44 | "DetailedPublishing": false, 45 | "WiserApiHost": "api-nl.wiserair.com", 46 | "BootStrapApiHost": "bootstrap.gl.struxurewarecloud.com" 47 | }, 48 | "HeatingChannel": [ 49 | { 50 | "id": 1, 51 | "Name": "Channel-1", 52 | "RoomIds": [ 53 | 2, 54 | 3, 55 | 4 56 | ], 57 | "PercentageDemand": 0, 58 | "DemandOnOffOutput": "Off", 59 | "HeatingRelayState": "Off", 60 | "IsSmartValvePreventingDemand": true 61 | } 62 | ], 63 | "Room": [ 64 | { 65 | "id": 4, 66 | "ScheduleId": 4, 67 | "HeatingRate": 1200, 68 | "SmartValveIds": [ 69 | 34191 70 | ], 71 | "UfhRelayIds": [], 72 | "Name": "Office", 73 | "Mode": "Auto", 74 | "DemandType": "Modulating", 75 | "WindowDetectionActive": false, 76 | "ControlSequenceOfOperation": "HeatingOnly", 77 | "HeatingType": "HydronicRadiator", 78 | "CalculatedTemperature": 224, 79 | "CurrentSetPoint": 210, 80 | "PercentageDemand": 0, 81 | "ControlOutputState": "Off", 82 | "WindowState": "Closed", 83 | "SetPointOrigin": "FromSchedule", 84 | "DisplayedSetPoint": 210, 85 | "ScheduledSetPoint": 210, 86 | "RoundedAlexaTemperature": 225 87 | }, 88 | { 89 | "id": 2, 90 | "ScheduleId": 2, 91 | "HeatingRate": 1200, 92 | "UfhRelayIds": [], 93 | "Name": "Bedroom", 94 | "Mode": "Auto", 95 | "WindowDetectionActive": false, 96 | "ControlSequenceOfOperation": "HeatingOnly", 97 | "HeatingType": "HydronicRadiator", 98 | "CurrentSetPoint": 210, 99 | "SetPointOrigin": "FromSchedule", 100 | "DisplayedSetPoint": 210, 101 | "ScheduledSetPoint": 210, 102 | "Invalid": "NothingAssigned" 103 | }, 104 | { 105 | "id": 3, 106 | "ManualSetPoint": 210, 107 | "ScheduleId": 3, 108 | "HeatingRate": 1200, 109 | "RoomStatId": 34190, 110 | "UfhRelayIds": [], 111 | "Name": "Living Room", 112 | "Mode": "Auto", 113 | "DemandType": "Modulating", 114 | "WindowDetectionActive": false, 115 | "ControlSequenceOfOperation": "HeatingOnly", 116 | "HeatingType": "HydronicRadiator", 117 | "CalculatedTemperature": 205, 118 | "CurrentSetPoint": 200, 119 | "PercentageDemand": 0, 120 | "ControlOutputState": "Off", 121 | "SetPointOrigin": "FromSchedule", 122 | "DisplayedSetPoint": 200, 123 | "ScheduledSetPoint": 200, 124 | "AwayModeSuppressed": false, 125 | "RoundedAlexaTemperature": 205 126 | } 127 | ], 128 | "Device": [ 129 | { 130 | "id": 0, 131 | "NodeId": 0, 132 | "ProductType": "Controller", 133 | "ProductIdentifier": "Controller", 134 | "ActiveFirmwareVersion": "2.26.16", 135 | "ModelIdentifier": "WT714R1S0902", 136 | "DeviceLockEnabled": false, 137 | "DisplayedSignalStrength": "VeryGood", 138 | "ReceptionOfController": { 139 | "Rssi": -65 140 | } 141 | }, 142 | { 143 | "id": 34190, 144 | "NodeId": 34190, 145 | "ProductType": "RoomStat", 146 | "ProductIdentifier": "RoomStat", 147 | "ActiveFirmwareVersion": "04E1000900010012", 148 | "ModelIdentifier": "Thermostat", 149 | "HardwareVersion": "1", 150 | "SerialNumber": "D0CF5EFFFE36D105", 151 | "ProductModel": "Thermostat", 152 | "OtaImageQueryCount": 1, 153 | "LastOtaImageQueryCount": 1, 154 | "ParentNodeId": 0, 155 | "DeviceLockEnabled": false, 156 | "DisplayedSignalStrength": "Good", 157 | "BatteryVoltage": 31, 158 | "BatteryLevel": "Normal", 159 | "ReceptionOfController": { 160 | "Rssi": -63, 161 | "Lqi": 148 162 | }, 163 | "ReceptionOfDevice": { 164 | "Rssi": -62, 165 | "Lqi": 152 166 | }, 167 | "OtaLastImageSentBytes": 313162 168 | }, 169 | { 170 | "id": 34191, 171 | "NodeId": 3122, 172 | "ProductType": "iTRV", 173 | "ProductIdentifier": "iTRV", 174 | "ActiveFirmwareVersion": "0201000000010012", 175 | "ModelIdentifier": "iTRV", 176 | "HardwareVersion": "0", 177 | "SerialNumber": "90FD9FFFFEC39AA8", 178 | "ProductModel": "iTRV", 179 | "OtaImageQueryCount": 0, 180 | "LastOtaImageQueryCount": 0, 181 | "ParentNodeId": 65534, 182 | "DeviceLockEnabled": false, 183 | "DisplayedSignalStrength": "Medium", 184 | "BatteryVoltage": 32, 185 | "BatteryLevel": "Normal", 186 | "ReceptionOfController": { 187 | "Rssi": -71, 188 | "Lqi": 116 189 | }, 190 | "ReceptionOfDevice": { 191 | "Rssi": -66, 192 | "Lqi": 136 193 | }, 194 | "PendingZigbeeMessageMask": 0 195 | } 196 | ], 197 | "UpgradeInfo": [ 198 | { 199 | "id": 8, 200 | "FirmwareFilename": "0A00000000010012FOTA.BIN" 201 | }, 202 | { 203 | "id": 7, 204 | "FirmwareFilename": "05E1000900000021FOTA.BIN" 205 | }, 206 | { 207 | "id": 6, 208 | "FirmwareFilename": "04E1000900010012FOTA.BIN" 209 | }, 210 | { 211 | "id": 5, 212 | "FirmwareFilename": "0441010100010005FOTA.BIN" 213 | }, 214 | { 215 | "id": 4, 216 | "FirmwareFilename": "0441000000010005FOTA.BIN" 217 | }, 218 | { 219 | "id": 3, 220 | "FirmwareFilename": "0401010100010005FOTA.BIN" 221 | }, 222 | { 223 | "id": 2, 224 | "FirmwareFilename": "0401000000010005FOTA.BIN" 225 | }, 226 | { 227 | "id": 1, 228 | "FirmwareFilename": "0201000000010012FOTA.BIN" 229 | } 230 | ], 231 | "SmartValve": [ 232 | { 233 | "id": 34191, 234 | "MountingOrientation": "Vertical", 235 | "MeasuredTemperature": 224, 236 | "PercentageDemand": 0, 237 | "WindowState": "Closed" 238 | } 239 | ], 240 | "RoomStat": [ 241 | { 242 | "id": 34190, 243 | "SetPoint": 200, 244 | "MeasuredTemperature": 205, 245 | "MeasuredHumidity": 38 246 | } 247 | ], 248 | "DeviceCapabilityMatrix": { 249 | "Roomstat": true, 250 | "ITRV": true, 251 | "SmartPlug": true, 252 | "UFH": false, 253 | "UFHFloorTempSensor": false, 254 | "UFHDewSensor": false, 255 | "HACT": false, 256 | "LACT": false 257 | }, 258 | "Schedule": [ 259 | { 260 | "id": 4, 261 | "Monday": { 262 | "SetPoints": [ 263 | { 264 | "Time": 630, 265 | "DegreesC": 200 266 | }, 267 | { 268 | "Time": 830, 269 | "DegreesC": 160 270 | }, 271 | { 272 | "Time": 1630, 273 | "DegreesC": 210 274 | }, 275 | { 276 | "Time": 2230, 277 | "DegreesC": -200 278 | } 279 | ] 280 | }, 281 | "Tuesday": { 282 | "SetPoints": [ 283 | { 284 | "Time": 630, 285 | "DegreesC": 200 286 | }, 287 | { 288 | "Time": 830, 289 | "DegreesC": 160 290 | }, 291 | { 292 | "Time": 1630, 293 | "DegreesC": 210 294 | }, 295 | { 296 | "Time": 2230, 297 | "DegreesC": -200 298 | } 299 | ] 300 | }, 301 | "Wednesday": { 302 | "SetPoints": [ 303 | { 304 | "Time": 630, 305 | "DegreesC": 200 306 | }, 307 | { 308 | "Time": 830, 309 | "DegreesC": 160 310 | }, 311 | { 312 | "Time": 1630, 313 | "DegreesC": 210 314 | }, 315 | { 316 | "Time": 2230, 317 | "DegreesC": -200 318 | } 319 | ] 320 | }, 321 | "Thursday": { 322 | "SetPoints": [ 323 | { 324 | "Time": 630, 325 | "DegreesC": 200 326 | }, 327 | { 328 | "Time": 830, 329 | "DegreesC": 160 330 | }, 331 | { 332 | "Time": 1630, 333 | "DegreesC": 210 334 | }, 335 | { 336 | "Time": 2230, 337 | "DegreesC": -200 338 | } 339 | ] 340 | }, 341 | "Friday": { 342 | "SetPoints": [ 343 | { 344 | "Time": 630, 345 | "DegreesC": 200 346 | }, 347 | { 348 | "Time": 830, 349 | "DegreesC": 160 350 | }, 351 | { 352 | "Time": 1630, 353 | "DegreesC": 210 354 | }, 355 | { 356 | "Time": 2230, 357 | "DegreesC": -200 358 | } 359 | ] 360 | }, 361 | "Saturday": { 362 | "SetPoints": [ 363 | { 364 | "Time": 700, 365 | "DegreesC": 200 366 | }, 367 | { 368 | "Time": 900, 369 | "DegreesC": 180 370 | }, 371 | { 372 | "Time": 1600, 373 | "DegreesC": 210 374 | }, 375 | { 376 | "Time": 2300, 377 | "DegreesC": -200 378 | } 379 | ] 380 | }, 381 | "Sunday": { 382 | "SetPoints": [ 383 | { 384 | "Time": 700, 385 | "DegreesC": 200 386 | }, 387 | { 388 | "Time": 900, 389 | "DegreesC": 180 390 | }, 391 | { 392 | "Time": 1600, 393 | "DegreesC": 210 394 | }, 395 | { 396 | "Time": 2300, 397 | "DegreesC": -200 398 | } 399 | ] 400 | }, 401 | "Type": "Heating" 402 | }, 403 | { 404 | "id": 2, 405 | "Monday": { 406 | "SetPoints": [ 407 | { 408 | "Time": 630, 409 | "DegreesC": 200 410 | }, 411 | { 412 | "Time": 830, 413 | "DegreesC": 160 414 | }, 415 | { 416 | "Time": 1630, 417 | "DegreesC": 210 418 | }, 419 | { 420 | "Time": 2230, 421 | "DegreesC": -200 422 | } 423 | ] 424 | }, 425 | "Tuesday": { 426 | "SetPoints": [ 427 | { 428 | "Time": 630, 429 | "DegreesC": 200 430 | }, 431 | { 432 | "Time": 830, 433 | "DegreesC": 160 434 | }, 435 | { 436 | "Time": 1630, 437 | "DegreesC": 210 438 | }, 439 | { 440 | "Time": 2230, 441 | "DegreesC": -200 442 | } 443 | ] 444 | }, 445 | "Wednesday": { 446 | "SetPoints": [ 447 | { 448 | "Time": 630, 449 | "DegreesC": 200 450 | }, 451 | { 452 | "Time": 830, 453 | "DegreesC": 160 454 | }, 455 | { 456 | "Time": 1630, 457 | "DegreesC": 210 458 | }, 459 | { 460 | "Time": 2230, 461 | "DegreesC": -200 462 | } 463 | ] 464 | }, 465 | "Thursday": { 466 | "SetPoints": [ 467 | { 468 | "Time": 630, 469 | "DegreesC": 200 470 | }, 471 | { 472 | "Time": 830, 473 | "DegreesC": 160 474 | }, 475 | { 476 | "Time": 1630, 477 | "DegreesC": 210 478 | }, 479 | { 480 | "Time": 2230, 481 | "DegreesC": -200 482 | } 483 | ] 484 | }, 485 | "Friday": { 486 | "SetPoints": [ 487 | { 488 | "Time": 630, 489 | "DegreesC": 200 490 | }, 491 | { 492 | "Time": 830, 493 | "DegreesC": 160 494 | }, 495 | { 496 | "Time": 1630, 497 | "DegreesC": 210 498 | }, 499 | { 500 | "Time": 2230, 501 | "DegreesC": -200 502 | } 503 | ] 504 | }, 505 | "Saturday": { 506 | "SetPoints": [ 507 | { 508 | "Time": 700, 509 | "DegreesC": 200 510 | }, 511 | { 512 | "Time": 900, 513 | "DegreesC": 180 514 | }, 515 | { 516 | "Time": 1600, 517 | "DegreesC": 210 518 | }, 519 | { 520 | "Time": 2300, 521 | "DegreesC": -200 522 | } 523 | ] 524 | }, 525 | "Sunday": { 526 | "SetPoints": [ 527 | { 528 | "Time": 700, 529 | "DegreesC": 200 530 | }, 531 | { 532 | "Time": 900, 533 | "DegreesC": 180 534 | }, 535 | { 536 | "Time": 1600, 537 | "DegreesC": 210 538 | }, 539 | { 540 | "Time": 2300, 541 | "DegreesC": -200 542 | } 543 | ] 544 | }, 545 | "Type": "Heating" 546 | }, 547 | { 548 | "id": 3, 549 | "Monday": { 550 | "SetPoints": [ 551 | { 552 | "Time": 630, 553 | "DegreesC": 200 554 | }, 555 | { 556 | "Time": 830, 557 | "DegreesC": -200 558 | }, 559 | { 560 | "Time": 1430, 561 | "DegreesC": 140 562 | }, 563 | { 564 | "Time": 1700, 565 | "DegreesC": 200 566 | }, 567 | { 568 | "Time": 2230, 569 | "DegreesC": -200 570 | } 571 | ] 572 | }, 573 | "Tuesday": { 574 | "SetPoints": [ 575 | { 576 | "Time": 630, 577 | "DegreesC": 200 578 | }, 579 | { 580 | "Time": 830, 581 | "DegreesC": -200 582 | }, 583 | { 584 | "Time": 1430, 585 | "DegreesC": 140 586 | }, 587 | { 588 | "Time": 1700, 589 | "DegreesC": 200 590 | }, 591 | { 592 | "Time": 2230, 593 | "DegreesC": -200 594 | } 595 | ] 596 | }, 597 | "Wednesday": { 598 | "SetPoints": [ 599 | { 600 | "Time": 630, 601 | "DegreesC": 200 602 | }, 603 | { 604 | "Time": 830, 605 | "DegreesC": -200 606 | }, 607 | { 608 | "Time": 1430, 609 | "DegreesC": 140 610 | }, 611 | { 612 | "Time": 1700, 613 | "DegreesC": 200 614 | }, 615 | { 616 | "Time": 2230, 617 | "DegreesC": -200 618 | } 619 | ] 620 | }, 621 | "Thursday": { 622 | "SetPoints": [ 623 | { 624 | "Time": 630, 625 | "DegreesC": 200 626 | }, 627 | { 628 | "Time": 830, 629 | "DegreesC": -200 630 | }, 631 | { 632 | "Time": 1430, 633 | "DegreesC": 140 634 | }, 635 | { 636 | "Time": 1700, 637 | "DegreesC": 200 638 | }, 639 | { 640 | "Time": 2230, 641 | "DegreesC": -200 642 | } 643 | ] 644 | }, 645 | "Friday": { 646 | "SetPoints": [ 647 | { 648 | "Time": 630, 649 | "DegreesC": 200 650 | }, 651 | { 652 | "Time": 830, 653 | "DegreesC": -200 654 | }, 655 | { 656 | "Time": 1430, 657 | "DegreesC": 140 658 | }, 659 | { 660 | "Time": 1700, 661 | "DegreesC": 200 662 | }, 663 | { 664 | "Time": 2230, 665 | "DegreesC": -200 666 | } 667 | ] 668 | }, 669 | "Saturday": { 670 | "SetPoints": [ 671 | { 672 | "Time": 800, 673 | "DegreesC": 200 674 | }, 675 | { 676 | "Time": 1030, 677 | "DegreesC": 170 678 | }, 679 | { 680 | "Time": 1600, 681 | "DegreesC": 200 682 | }, 683 | { 684 | "Time": 2300, 685 | "DegreesC": -200 686 | } 687 | ] 688 | }, 689 | "Sunday": { 690 | "SetPoints": [ 691 | { 692 | "Time": 800, 693 | "DegreesC": 200 694 | }, 695 | { 696 | "Time": 1030, 697 | "DegreesC": 170 698 | }, 699 | { 700 | "Time": 1600, 701 | "DegreesC": 200 702 | }, 703 | { 704 | "Time": 2300, 705 | "DegreesC": -200 706 | } 707 | ] 708 | }, 709 | "Type": "Heating" 710 | } 711 | ] 712 | } -------------------------------------------------------------------------------- /tests/data/cancel-boost-result.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/connor9/python-draytonwiser-api/db90222eda0c5e8b4920396961da8edc815fd920/tests/data/cancel-boost-result.json -------------------------------------------------------------------------------- /tests/data/hotwater-states.json: -------------------------------------------------------------------------------- 1 | "HotWater":[ 2 | { 3 | "id": 2, 4 | "OverrideType": "None", 5 | "ScheduleId": 1000, 6 | "Mode": "Auto", 7 | "WaterHeatingState": "Off", 8 | "HotWaterRelayState": "Off" 9 | } 10 | ], 11 | 12 | "HotWater":[ 13 | { 14 | "id": 2, 15 | "ScheduleId": 1000, 16 | "Mode": "Manual", 17 | "WaterHeatingState": "Off", 18 | "HotWaterRelayState": "Off" 19 | } 20 | ], 21 | 22 | HotWater":[ 23 | { 24 | "id": 2, 25 | "OverrideType": "Manual", 26 | "OverrideWaterHeatingState": "On", 27 | "ScheduleId": 1000, 28 | "Mode": "Auto", 29 | "WaterHeatingState": "On", 30 | "HotWaterRelayState": "On" 31 | } 32 | ], 33 | 34 | "HotWater":[ 35 | { 36 | "id": 2, 37 | "OverrideType": "Manual", 38 | "OverrideTimeoutUnixTime": 1517780154, 39 | "OverrideWaterHeatingState": "On", 40 | "ScheduleId": 1000, 41 | "Mode": "Auto", 42 | "WaterHeatingState": "On", 43 | "HotWaterRelayState": "On" 44 | } 45 | ], -------------------------------------------------------------------------------- /tests/data/set-boost-result.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/connor9/python-draytonwiser-api/db90222eda0c5e8b4920396961da8edc815fd920/tests/data/set-boost-result.json -------------------------------------------------------------------------------- /tests/run_tests: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python3 -m unittest discover . -v -------------------------------------------------------------------------------- /tests/test_cloud.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import responses 3 | 4 | from context import draytonwiser 5 | from BaseTest import BaseTest 6 | 7 | class TestManager(BaseTest): 8 | 9 | def setUp(self): 10 | super(TestManager, self).setUp() 11 | 12 | self.manager = draytonwiser.Manager(wiser_hub_ip=self.wiser_hub_ip, api_secret=self.token) 13 | 14 | @responses.activate 15 | def test_cloud_status(self): 16 | 17 | data = self.load_from_file(self.source_data_file) 18 | url = self.base_url #+ 'Cloud/' 19 | 20 | responses.add(responses.GET, url, 21 | body=data, 22 | status=200, 23 | content_type='application/json') 24 | 25 | cloud_info = self.manager.get_cloud() 26 | 27 | # How to handle when request isn't made due to cache? 28 | #self.assertEqual(responses.calls[0].request.url, url) 29 | self.assertEqual(cloud_info.wiser_api_host, "api-nl.wiserair.com") 30 | 31 | if __name__ == '__main__': 32 | unittest.main() -------------------------------------------------------------------------------- /tests/test_device.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import responses 3 | 4 | from context import draytonwiser 5 | from draytonwiser import APIException, ObjectNotFoundException 6 | 7 | from BaseTest import BaseTest 8 | 9 | class TestAction(BaseTest): 10 | 11 | def setUp(self): 12 | super(TestAction, self).setUp() 13 | 14 | self.manager = draytonwiser.Manager(wiser_hub_ip=self.wiser_hub_ip, api_secret=self.token) 15 | 16 | @responses.activate 17 | def test_device_load_single(self): 18 | data = self.load_from_file(self.source_data_file) 19 | url = self.base_url # + 'Device/3' 20 | 21 | responses.add(responses.GET, url, 22 | body=data, 23 | status=200, 24 | content_type='application/json') 25 | 26 | device = self.manager.get_device(34190) 27 | 28 | self.assertEqual(device.id, 34190) 29 | 30 | @responses.activate 31 | def test_device_load_single_2(self): 32 | data = self.load_from_file(self.source_data_file) 33 | url = self.base_url # + 'Device/3' 34 | 35 | responses.add(responses.GET, url, 36 | body=data, 37 | status=200, 38 | content_type='application/json') 39 | 40 | device = self.manager.get_device(34191) 41 | 42 | self.assertEqual(device.id, 34191) 43 | 44 | @responses.activate 45 | def test_device_load_single_missing(self): 46 | data = self.load_from_file(self.source_data_file) 47 | url = self.base_url # + 'Device/3' 48 | 49 | responses.add(responses.GET, url, 50 | body=data, 51 | status=200, 52 | content_type='application/json') 53 | 54 | self.assertRaises(draytonwiser.exceptions.ObjectNotFoundException, self.manager.get_device, 3) 55 | 56 | if __name__ == '__main__': 57 | unittest.main() -------------------------------------------------------------------------------- /tests/test_device_measurement.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import responses 3 | 4 | from context import draytonwiser 5 | from BaseTest import BaseTest 6 | 7 | from draytonwiser import APIException, ObjectNotFoundException 8 | 9 | class TestAction(BaseTest): 10 | 11 | def setUp(self): 12 | super(TestAction, self).setUp() 13 | 14 | self.manager = draytonwiser.Manager(wiser_hub_ip=self.wiser_hub_ip, api_secret=self.token) 15 | 16 | @responses.activate 17 | def test_room_stat_load_single(self): 18 | data = self.load_from_file(self.source_data_file) 19 | url = self.base_url # + 'RoomStat/3' 20 | 21 | responses.add(responses.GET, url, 22 | body=data, 23 | status=200, 24 | content_type='application/json') 25 | 26 | room_stat = self.manager.get_room_stat(34190) 27 | 28 | self.assertEqual(room_stat.id, 34190) 29 | 30 | @responses.activate 31 | def test_room_stat_load_single_missing(self): 32 | data = self.load_from_file(self.source_data_file) 33 | url = self.base_url # + 'RoomStat/3' 34 | 35 | responses.add(responses.GET, url, 36 | body=data, 37 | status=200, 38 | content_type='application/json') 39 | 40 | self.assertRaises(ObjectNotFoundException, self.manager.get_room_stat, -33) 41 | 42 | @responses.activate 43 | def test_smart_valve_load_single(self): 44 | data = self.load_from_file(self.source_data_file) 45 | url = self.base_url # + 'SmartValve/3' 46 | 47 | responses.add(responses.GET, url, 48 | body=data, 49 | status=200, 50 | content_type='application/json') 51 | 52 | smart_valve = self.manager.get_smart_valve(34191) 53 | 54 | self.assertEqual(smart_valve.id, 34191) 55 | 56 | @responses.activate 57 | def test_smart_valve_load_single_missing(self): 58 | data = self.load_from_file(self.source_data_file) 59 | url = self.base_url # + 'SmartValve/3' 60 | 61 | responses.add(responses.GET, url, 62 | body=data, 63 | status=200, 64 | content_type='application/json') 65 | 66 | self.assertRaises(ObjectNotFoundException, self.manager.get_smart_valve, -33) 67 | 68 | if __name__ == '__main__': 69 | unittest.main() -------------------------------------------------------------------------------- /tests/test_heating_channel.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import responses 3 | import json 4 | 5 | from context import draytonwiser 6 | from BaseTest import BaseTest 7 | 8 | from draytonwiser import APIException, ObjectNotFoundException 9 | 10 | class TestAction(BaseTest): 11 | 12 | def setUp(self): 13 | super(TestAction, self).setUp() 14 | 15 | self.manager = draytonwiser.Manager(wiser_hub_ip=self.wiser_hub_ip, api_secret=self.token) 16 | 17 | @responses.activate 18 | def test_heating_channel_load_single(self): 19 | data = self.load_from_file(self.source_data_file) 20 | url = self.base_url # + 'HeatingChannel/1' 21 | 22 | responses.add(responses.GET, url, 23 | body=data, 24 | status=200, 25 | content_type='application/json') 26 | 27 | heating_channel = self.manager.get_heating_channel(1) 28 | 29 | self.assertEqual(heating_channel.name, "Channel-1") 30 | 31 | @responses.activate 32 | def test_heating_channel_load_single_missing(self): 33 | data = self.load_from_file(self.source_data_file) 34 | url = self.base_url # + 'HeatingChannel/1' 35 | 36 | responses.add(responses.GET, url, 37 | body=data, 38 | status=200, 39 | content_type='application/json') 40 | 41 | self.assertRaises(ObjectNotFoundException, self.manager.get_heating_channel, -33) 42 | 43 | if __name__ == '__main__': 44 | unittest.main() 45 | 46 | 47 | -------------------------------------------------------------------------------- /tests/test_hotwater.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import responses 3 | 4 | from context import draytonwiser 5 | from draytonwiser import APIException, ObjectNotFoundException 6 | 7 | from BaseTest import BaseTest 8 | 9 | class TestManager(BaseTest): 10 | 11 | def setUp(self): 12 | super(TestManager, self).setUp() 13 | 14 | self.source_data_file = 'all-with-itrv-and-hotwater.json' 15 | 16 | self.manager = draytonwiser.Manager(wiser_hub_ip=self.wiser_hub_ip, api_secret=self.token) 17 | self.manager.use_cache = False 18 | 19 | @responses.activate 20 | def test_hotwater_load_all(self): 21 | 22 | data = self.load_from_file(self.source_data_file) 23 | url = self.base_url #+ 'HotWater/' 24 | 25 | responses.add(responses.GET, url, 26 | body=data, 27 | status=200, 28 | content_type='application/json') 29 | 30 | hotwaters = self.manager.get_all_hotwater() 31 | 32 | # How to handle when request isn't made due to cache? 33 | #self.assertEqual(responses.calls[0].request.url, url) 34 | self.assertEqual(len(hotwaters), 1) 35 | 36 | if __name__ == '__main__': 37 | unittest.main() 38 | 39 | 40 | -------------------------------------------------------------------------------- /tests/test_manager.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import responses 3 | 4 | from context import draytonwiser 5 | from BaseTest import BaseTest 6 | 7 | class TestManager(BaseTest): 8 | 9 | def setUp(self): 10 | super(TestManager, self).setUp() 11 | 12 | self.manager = draytonwiser.Manager(wiser_hub_ip=self.wiser_hub_ip, api_secret=self.token) 13 | 14 | @responses.activate 15 | def test_get_all_heating_channels(self): 16 | 17 | data = self.load_from_file(self.source_data_file) 18 | url = self.base_url #+ 'HeatingChannel/' 19 | 20 | responses.add(responses.GET, url, 21 | body=data, 22 | status=200, 23 | content_type='application/json') 24 | 25 | heating_channels = self.manager.get_all_heating_channels() 26 | 27 | # How to handle when request isn't made due to cache? 28 | #self.assertEqual(responses.calls[0].request.url, url) 29 | self.assertEqual(len(heating_channels), 1) 30 | self.assertEqual(heating_channels[0].name, "Channel-1") 31 | 32 | @responses.activate 33 | def test_get_all_rooms(self): 34 | 35 | data = self.load_from_file(self.source_data_file) 36 | url = self.base_url #+ 'Room/' 37 | 38 | responses.add(responses.GET, url, 39 | body=data, 40 | status=200, 41 | content_type='application/json') 42 | 43 | rooms = self.manager.get_all_rooms() 44 | 45 | # How to handle when request isn't made due to cache? 46 | #self.assertEqual(responses.calls[0].request.url, url) 47 | self.assertEqual(len(rooms), 3) 48 | self.assertEqual(rooms[0].name, "Office") 49 | 50 | @responses.activate 51 | def test_get_all_devices(self): 52 | 53 | data = self.load_from_file(self.source_data_file) 54 | url = self.base_url #+ 'Device/' 55 | 56 | responses.add(responses.GET, url, 57 | body=data, 58 | status=200, 59 | content_type='application/json') 60 | 61 | devices = self.manager.get_all_devices() 62 | 63 | #self.assertEqual(responses.calls[0].request.url, url) 64 | self.assertEqual(len(devices), 3) 65 | 66 | @responses.activate 67 | def test_get_all_schedules(self): 68 | 69 | data = self.load_from_file(self.source_data_file) 70 | url = self.base_url #+ 'Device/' 71 | 72 | responses.add(responses.GET, url, 73 | body=data, 74 | status=200, 75 | content_type='application/json') 76 | 77 | schedules = self.manager.get_all_schedules() 78 | 79 | #self.assertEqual(responses.calls[0].request.url, url) 80 | self.assertEqual(len(schedules), 3) 81 | 82 | @responses.activate 83 | def test_get_all_room_stats(self): 84 | 85 | data = self.load_from_file(self.source_data_file) 86 | url = self.base_url #+ 'RoomStat/' 87 | 88 | responses.add(responses.GET, url, 89 | body=data, 90 | status=200, 91 | content_type='application/json') 92 | 93 | room_stats = self.manager.get_all_room_stats() 94 | 95 | #self.assertEqual(responses.calls[0].request.url, url) 96 | self.assertEqual(len(room_stats), 1) 97 | 98 | @responses.activate 99 | def test_get_all_smart_valves(self): 100 | 101 | data = self.load_from_file(self.source_data_file) 102 | url = self.base_url #+ 'Device/' 103 | 104 | responses.add(responses.GET, url, 105 | body=data, 106 | status=200, 107 | content_type='application/json') 108 | 109 | smart_valves = self.manager.get_all_smart_valves() 110 | 111 | #self.assertEqual(responses.calls[0].request.url, url) 112 | self.assertEqual(len(smart_valves), 1) 113 | 114 | @responses.activate 115 | def test_get_system(self): 116 | 117 | data = self.load_from_file(self.source_data_file) 118 | url = self.base_url #+ 'Device/' 119 | 120 | responses.add(responses.GET, url, 121 | body=data, 122 | status=200, 123 | content_type='application/json') 124 | 125 | system = self.manager.get_system() 126 | 127 | #self.assertEqual(responses.calls[0].request.url, url) 128 | self.assertEqual(system.active_system_version, "2.26.16-6340f5b") 129 | 130 | @responses.activate 131 | def test_get_cloud(self): 132 | data = self.load_from_file(self.source_data_file) 133 | url = self.base_url # + 'Device/' 134 | 135 | responses.add(responses.GET, url, 136 | body=data, 137 | status=200, 138 | content_type='application/json') 139 | 140 | cloud = self.manager.get_cloud() 141 | 142 | # self.assertEqual(responses.calls[0].request.url, url) 143 | self.assertEqual(cloud.wiser_api_host, "api-nl.wiserair.com") 144 | 145 | if __name__ == '__main__': 146 | unittest.main() -------------------------------------------------------------------------------- /tests/test_room.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import responses 3 | 4 | from context import draytonwiser 5 | from BaseTest import BaseTest 6 | 7 | from draytonwiser import APIException, ObjectNotFoundException 8 | 9 | class TestAction(BaseTest): 10 | 11 | def setUp(self): 12 | super(TestAction, self).setUp() 13 | 14 | self.manager = draytonwiser.Manager(wiser_hub_ip=self.wiser_hub_ip, api_secret=self.token) 15 | 16 | @responses.activate 17 | def test_room_load_single(self): 18 | data = self.load_from_file(self.source_data_file) 19 | url = self.base_url # + 'Room/3' 20 | 21 | responses.add(responses.GET, url, 22 | body=data, 23 | status=200, 24 | content_type='application/json') 25 | 26 | room = self.manager.get_room(3) 27 | 28 | self.assertEqual(room.name, "Living Room") 29 | 30 | @responses.activate 31 | def test_room_load_single_missing(self): 32 | data = self.load_from_file(self.source_data_file) 33 | url = self.base_url # + 'Room/3' 34 | 35 | responses.add(responses.GET, url, 36 | body=data, 37 | status=200, 38 | content_type='application/json') 39 | 40 | self.assertRaises(ObjectNotFoundException, self.manager.get_room, -33) 41 | 42 | @responses.activate 43 | def test_room_set_boost(self): 44 | 45 | data = self.load_from_file(self.source_data_file) 46 | url = self.base_url # + 'Room/3' 47 | 48 | update_url = self.base_url + 'Room/3' 49 | update_data = self.load_from_file(self.source_data_file) 50 | 51 | responses.add(responses.GET, url, 52 | body=data, 53 | status=200, 54 | content_type='application/json') 55 | 56 | responses.add(responses.PATCH, update_url, 57 | body=update_data, 58 | status=200, 59 | content_type='application/json') 60 | 61 | room = self.manager.get_room(3) 62 | room.set_boost(30, 21) 63 | 64 | self.assertEqual(1, 1) 65 | 66 | @responses.activate 67 | def test_room_cancel_boost(self): 68 | 69 | data = self.load_from_file(self.source_data_file) 70 | url = self.base_url # + 'Room/3' 71 | 72 | update_url = self.base_url + 'Room/3' 73 | update_data = self.load_from_file(self.source_data_file) 74 | 75 | responses.add(responses.GET, url, 76 | body=data, 77 | status=200, 78 | content_type='application/json') 79 | 80 | responses.add(responses.PATCH, update_url, 81 | body=update_data, 82 | status=200, 83 | content_type='application/json') 84 | 85 | room = self.manager.get_room(3) 86 | room.cancel_boost() 87 | 88 | self.assertEqual(1, 1) 89 | 90 | if __name__ == '__main__': 91 | unittest.main() -------------------------------------------------------------------------------- /tests/test_schedule.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import responses 3 | 4 | from context import draytonwiser 5 | from draytonwiser import APIException, ObjectNotFoundException 6 | 7 | from BaseTest import BaseTest 8 | 9 | class TestManager(BaseTest): 10 | 11 | def setUp(self): 12 | super(TestManager, self).setUp() 13 | 14 | self.manager = draytonwiser.Manager(wiser_hub_ip=self.wiser_hub_ip, api_secret=self.token) 15 | 16 | @responses.activate 17 | def test_schedule_get_all(self): 18 | 19 | data = self.load_from_file(self.source_data_file) 20 | url = self.base_url #+ 'Schedule/' 21 | 22 | responses.add(responses.GET, url, 23 | body=data, 24 | status=200, 25 | content_type='application/json') 26 | 27 | schedules = self.manager.get_all_schedules() 28 | 29 | # How to handle when request isn't made due to cache? 30 | #self.assertEqual(responses.calls[0].request.url, url) 31 | self.assertEqual(len(schedules), 3) 32 | 33 | @responses.activate 34 | def test_schedule_load_single(self): 35 | 36 | data = self.load_from_file(self.source_data_file) 37 | url = self.base_url #+ 'Schedule/3' 38 | 39 | responses.add(responses.GET, url, 40 | body=data, 41 | status=200, 42 | content_type='application/json') 43 | 44 | schedule = self.manager.get_schedule(3) 45 | 46 | # How to handle when request isn't made due to cache? 47 | #self.assertEqual(responses.calls[0].request.url, url) 48 | self.assertEqual(schedule.id, 3) 49 | 50 | @responses.activate 51 | def test_schedule_load_single_missing(self): 52 | 53 | data = self.load_from_file(self.source_data_file) 54 | url = self.base_url #+ 'Schedule/3' 55 | 56 | responses.add(responses.GET, url, 57 | body=data, 58 | status=200, 59 | content_type='application/json') 60 | 61 | # How to handle when request isn't made due to cache? 62 | #self.assertEqual(responses.calls[0].request.url, url) 63 | self.assertRaises(draytonwiser.exceptions.ObjectNotFoundException, self.manager.get_schedule, -33) 64 | 65 | 66 | if __name__ == '__main__': 67 | unittest.main() -------------------------------------------------------------------------------- /tests/test_system.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import responses 3 | import json 4 | 5 | from context import draytonwiser 6 | from BaseTest import BaseTest 7 | 8 | class TestManager(BaseTest): 9 | 10 | def setUp(self): 11 | super(TestManager, self).setUp() 12 | 13 | self.manager = draytonwiser.Manager(wiser_hub_ip=self.wiser_hub_ip, api_secret=self.token) 14 | 15 | @responses.activate 16 | def test_system_status(self): 17 | 18 | data = self.load_from_file(self.source_data_file) 19 | url = self.base_url #+ 'System/' 20 | 21 | responses.add(responses.GET, url, 22 | body=data, 23 | status=200, 24 | content_type='application/json') 25 | 26 | system_info = self.manager.get_system() 27 | 28 | # How to handle when request isn't made due to cache? 29 | #self.assertEqual(responses.calls[0].request.url, url) 30 | self.assertEqual(system_info.active_system_version, "2.26.16-6340f5b") 31 | self.assertEqual(system_info.cloud_connection_status, "Connected") 32 | 33 | if __name__ == '__main__': 34 | unittest.main() --------------------------------------------------------------------------------