qolsys-controller 0.0.44__py3-none-any.whl → 0.0.87__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of qolsys-controller might be problematic. Click here for more details.
- qolsys_controller/adc_device.py +202 -0
- qolsys_controller/adc_service.py +139 -0
- qolsys_controller/adc_service_garagedoor.py +35 -0
- qolsys_controller/controller.py +1040 -20
- qolsys_controller/database/db.py +108 -29
- qolsys_controller/database/table.py +90 -60
- qolsys_controller/database/table_alarmedsensor.py +2 -2
- qolsys_controller/database/table_automation.py +0 -1
- qolsys_controller/database/table_country_locale.py +0 -1
- qolsys_controller/database/table_dashboard_msgs.py +1 -2
- qolsys_controller/database/table_dimmerlight.py +0 -1
- qolsys_controller/database/table_doorlock.py +0 -1
- qolsys_controller/database/table_eu_event.py +1 -2
- qolsys_controller/database/table_heat_map.py +0 -2
- qolsys_controller/database/table_history.py +4 -1
- qolsys_controller/database/table_iqremotesettings.py +0 -2
- qolsys_controller/database/table_iqrouter_network_config.py +0 -1
- qolsys_controller/database/table_iqrouter_user_device.py +0 -2
- qolsys_controller/database/table_master_slave.py +0 -1
- qolsys_controller/database/table_nest_device.py +0 -1
- qolsys_controller/database/table_output_rules.py +0 -1
- qolsys_controller/database/table_partition.py +0 -1
- qolsys_controller/database/table_pgm_outputs.py +0 -2
- qolsys_controller/database/table_powerg_device.py +0 -2
- qolsys_controller/database/table_qolsyssettings.py +0 -2
- qolsys_controller/database/table_scene.py +0 -2
- qolsys_controller/database/table_sensor.py +2 -2
- qolsys_controller/database/table_sensor_group.py +23 -0
- qolsys_controller/database/table_shades.py +0 -2
- qolsys_controller/database/table_smartsocket.py +12 -3
- qolsys_controller/database/table_state.py +0 -1
- qolsys_controller/database/table_tcc.py +0 -1
- qolsys_controller/database/table_thermostat.py +3 -1
- qolsys_controller/database/table_trouble_conditions.py +0 -2
- qolsys_controller/database/table_user.py +0 -2
- qolsys_controller/database/table_virtual_device.py +13 -3
- qolsys_controller/database/table_weather.py +0 -2
- qolsys_controller/database/table_zigbee_device.py +0 -1
- qolsys_controller/database/table_zwave_association_group.py +0 -1
- qolsys_controller/database/table_zwave_history.py +0 -1
- qolsys_controller/database/table_zwave_node.py +3 -1
- qolsys_controller/database/table_zwave_other.py +0 -1
- qolsys_controller/enum.py +42 -13
- qolsys_controller/enum_adc.py +28 -0
- qolsys_controller/enum_zwave.py +210 -36
- qolsys_controller/errors.py +14 -12
- qolsys_controller/mdns.py +7 -4
- qolsys_controller/mqtt_command.py +125 -0
- qolsys_controller/mqtt_command_queue.py +5 -4
- qolsys_controller/observable.py +2 -2
- qolsys_controller/panel.py +304 -156
- qolsys_controller/partition.py +149 -127
- qolsys_controller/pki.py +69 -97
- qolsys_controller/scene.py +30 -28
- qolsys_controller/settings.py +96 -50
- qolsys_controller/state.py +221 -34
- qolsys_controller/task_manager.py +11 -14
- qolsys_controller/users.py +25 -0
- qolsys_controller/utils_mqtt.py +8 -16
- qolsys_controller/weather.py +71 -0
- qolsys_controller/zone.py +243 -214
- qolsys_controller/zwave_device.py +234 -93
- qolsys_controller/zwave_dimmer.py +55 -49
- qolsys_controller/zwave_energy_clamp.py +15 -0
- qolsys_controller/zwave_garagedoor.py +3 -1
- qolsys_controller/zwave_generic.py +5 -3
- qolsys_controller/zwave_lock.py +51 -44
- qolsys_controller/zwave_outlet.py +3 -1
- qolsys_controller/zwave_service_meter.py +192 -0
- qolsys_controller/zwave_service_multilevelsensor.py +119 -0
- qolsys_controller/zwave_thermometer.py +21 -0
- qolsys_controller/zwave_thermostat.py +249 -143
- qolsys_controller-0.0.87.dist-info/METADATA +89 -0
- qolsys_controller-0.0.87.dist-info/RECORD +77 -0
- {qolsys_controller-0.0.44.dist-info → qolsys_controller-0.0.87.dist-info}/WHEEL +1 -1
- qolsys_controller/plugin.py +0 -34
- qolsys_controller/plugin_c4.py +0 -17
- qolsys_controller/plugin_remote.py +0 -1298
- qolsys_controller-0.0.44.dist-info/METADATA +0 -93
- qolsys_controller-0.0.44.dist-info/RECORD +0 -68
- {qolsys_controller-0.0.44.dist-info → qolsys_controller-0.0.87.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,48 +1,62 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
from .enum_zwave import
|
|
3
|
+
from .enum_zwave import (
|
|
4
|
+
BITMASK_SUPPORTED_THERMOSTAT_FAN_MODE,
|
|
5
|
+
BITMASK_SUPPORTED_THERMOSTAT_MODE,
|
|
6
|
+
BITMASK_SUPPORTED_THERMOSTAT_SETPOINT_MODE,
|
|
7
|
+
ThermostatFanMode,
|
|
8
|
+
ThermostatMode,
|
|
9
|
+
ThermostatSetpointMode,
|
|
10
|
+
ZWaveMultilevelSensorScale,
|
|
11
|
+
)
|
|
4
12
|
from .zwave_device import QolsysZWaveDevice
|
|
5
13
|
|
|
6
14
|
LOGGER = logging.getLogger(__name__)
|
|
7
15
|
|
|
8
16
|
|
|
9
17
|
class QolsysThermostat(QolsysZWaveDevice):
|
|
10
|
-
|
|
11
|
-
def __init__(self, thermostat_dict: dict, zwave_dict: dict) -> None:
|
|
12
|
-
|
|
18
|
+
def __init__(self, thermostat_dict: dict[str, str], zwave_dict: dict[str, str]) -> None:
|
|
13
19
|
super().__init__(zwave_dict)
|
|
14
20
|
|
|
15
|
-
self._thermostat_id = thermostat_dict.get("_id")
|
|
16
|
-
self._thermostat_version = thermostat_dict.get("version", "")
|
|
17
|
-
self._thermostat_opr = thermostat_dict.get("opr", "")
|
|
18
|
-
self._thermostat_partition_id = thermostat_dict.get("partition_id", "")
|
|
19
|
-
self._thermostat_name = thermostat_dict.get("thermostat_name", "")
|
|
20
|
-
self._thermostat_node_id = thermostat_dict.get("node_id", "")
|
|
21
|
-
self._thermostat_device_temp_unit = thermostat_dict.get("device_temp_unit", "") # 'F'
|
|
22
|
-
self._thermostat_current_temp = thermostat_dict.get("current_temp", "") # "78.0"
|
|
23
|
-
self._thermostat_target_cool_temp = thermostat_dict.get("target_cool_temp", "") # "78"
|
|
24
|
-
self._thermostat_target_heat_temp = thermostat_dict.get("target_heat_temp", "") # "65"
|
|
25
|
-
self._thermostat_target_temp = thermostat_dict.get("target_temp", "")
|
|
26
|
-
self._thermostat_power_usage = thermostat_dict.get("power_usage", "")
|
|
27
|
-
self._thermostat_mode = thermostat_dict.get("thermostat_mode", "") # "2"
|
|
28
|
-
self._thermostat_mode_bitmask = thermostat_dict.get("thermostat_mode_bitmask", "") # "7,24"
|
|
29
|
-
self._thermostat_fan_mode = thermostat_dict.get("fan_mode", "") # "0"
|
|
30
|
-
self._thermostat_fan_mode_bitmask = thermostat_dict.get("fan_mode_bitmask", "") # "67"
|
|
31
|
-
self._thermostat_set_point_mode = thermostat_dict.get("set_point_mode", "")
|
|
32
|
-
self._thermostat_set_point_mode_bitmask = thermostat_dict.get("set_point_mode_bitmask", "") # "-122,1"
|
|
33
|
-
self._thermostat_created_by = thermostat_dict.get("created_by", "")
|
|
34
|
-
self._thermostat_created_date = thermostat_dict.get("created_date", "")
|
|
35
|
-
self._thermostat_updated_by = thermostat_dict.get("updated_by", "")
|
|
36
|
-
self._thermostat_last_updated_date = thermostat_dict.get("last_updated_date", "")
|
|
37
|
-
self._thermostat_mode_updated_time = thermostat_dict.get("thermostat_mode_updated_time", "")
|
|
38
|
-
self._thermostat_fan_mode_updated_time = thermostat_dict.get("fan_mode_updated_time", "")
|
|
39
|
-
self._thermostat_set_point_mode_updated_time = thermostat_dict.get("set_point_mode_updated_time", "")
|
|
40
|
-
self._thermostat_target_cool_temp_updated_time = thermostat_dict.get("target_cool_temp_updated_time", "")
|
|
41
|
-
self._thermostat_target_heat_temp_updated_time = thermostat_dict.get("target_heat_temp_updated_time", "")
|
|
42
|
-
self._thermostat_current_temp_updated_time = thermostat_dict.get("current_temp_updated_time", "")
|
|
43
|
-
self._thermostat_endpoint = thermostat_dict.get("endpoint", "")
|
|
44
|
-
self._thermostat_paired_status = thermostat_dict.get("paired_status", "")
|
|
45
|
-
self._thermostat_configuration_parameter = thermostat_dict.get("configuration_parameter", "")
|
|
21
|
+
self._thermostat_id: str = thermostat_dict.get("_id", "")
|
|
22
|
+
self._thermostat_version: str = thermostat_dict.get("version", "")
|
|
23
|
+
self._thermostat_opr: str = thermostat_dict.get("opr", "")
|
|
24
|
+
self._thermostat_partition_id: str = thermostat_dict.get("partition_id", "")
|
|
25
|
+
self._thermostat_name: str = thermostat_dict.get("thermostat_name", "")
|
|
26
|
+
self._thermostat_node_id: str = thermostat_dict.get("node_id", "")
|
|
27
|
+
self._thermostat_device_temp_unit: str = thermostat_dict.get("device_temp_unit", "") # 'F'
|
|
28
|
+
self._thermostat_current_temp: str = thermostat_dict.get("current_temp", "") # "78.0"
|
|
29
|
+
self._thermostat_target_cool_temp: str = thermostat_dict.get("target_cool_temp", "") # "78"
|
|
30
|
+
self._thermostat_target_heat_temp: str = thermostat_dict.get("target_heat_temp", "") # "65"
|
|
31
|
+
self._thermostat_target_temp: str = thermostat_dict.get("target_temp", "")
|
|
32
|
+
self._thermostat_power_usage: str = thermostat_dict.get("power_usage", "")
|
|
33
|
+
self._thermostat_mode: str = thermostat_dict.get("thermostat_mode", "") # "2"
|
|
34
|
+
self._thermostat_mode_bitmask: str = thermostat_dict.get("thermostat_mode_bitmask", "") # "7,24"
|
|
35
|
+
self._thermostat_fan_mode: str = thermostat_dict.get("fan_mode", "") # "0"
|
|
36
|
+
self._thermostat_fan_mode_bitmask: str = thermostat_dict.get("fan_mode_bitmask", "") # "67"
|
|
37
|
+
self._thermostat_set_point_mode: str = thermostat_dict.get("set_point_mode", "")
|
|
38
|
+
self._thermostat_set_point_mode_bitmask: str = thermostat_dict.get("set_point_mode_bitmask", "") # "-122,1"
|
|
39
|
+
self._thermostat_created_by: str = thermostat_dict.get("created_by", "")
|
|
40
|
+
self._thermostat_created_date: str = thermostat_dict.get("created_date", "")
|
|
41
|
+
self._thermostat_updated_by: str = thermostat_dict.get("updated_by", "")
|
|
42
|
+
self._thermostat_last_updated_date: str = thermostat_dict.get("last_updated_date", "")
|
|
43
|
+
self._thermostat_mode_updated_time: str = thermostat_dict.get("thermostat_mode_updated_time", "")
|
|
44
|
+
self._thermostat_fan_mode_updated_time: str = thermostat_dict.get("fan_mode_updated_time", "")
|
|
45
|
+
self._thermostat_set_point_mode_updated_time: str = thermostat_dict.get("set_point_mode_updated_time", "")
|
|
46
|
+
self._thermostat_target_cool_temp_updated_time: str = thermostat_dict.get("target_cool_temp_updated_time", "")
|
|
47
|
+
self._thermostat_target_heat_temp_updated_time: str = thermostat_dict.get("target_heat_temp_updated_time", "")
|
|
48
|
+
self._thermostat_current_temp_updated_time: str = thermostat_dict.get("current_temp_updated_time", "")
|
|
49
|
+
self._thermostat_endpoint: str = thermostat_dict.get("endpoint", "")
|
|
50
|
+
self._thermostat_paired_status: str = thermostat_dict.get("paired_status", "")
|
|
51
|
+
self._thermostat_configuration_parameter: str = thermostat_dict.get("configuration_parameter", "")
|
|
52
|
+
self._thermostat_operating_state: str = thermostat_dict.get("operating_state", "")
|
|
53
|
+
self._thermostat_setpoint_capabilites = thermostat_dict.get("setpoint_capabilites", "")
|
|
54
|
+
|
|
55
|
+
self._thermostat_current_humidity: float | None = None
|
|
56
|
+
|
|
57
|
+
# -----------------------------
|
|
58
|
+
# properties + setters
|
|
59
|
+
# -----------------------------
|
|
46
60
|
|
|
47
61
|
@property
|
|
48
62
|
def thermostat_node_id(self) -> str:
|
|
@@ -52,44 +66,51 @@ class QolsysThermostat(QolsysZWaveDevice):
|
|
|
52
66
|
def thermostat_name(self) -> str:
|
|
53
67
|
return self._thermostat_name
|
|
54
68
|
|
|
55
|
-
@
|
|
56
|
-
def
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return self._thermostat_current_temp
|
|
62
|
-
|
|
63
|
-
@property
|
|
64
|
-
def thermostat_target_cool_temp(self) -> str:
|
|
65
|
-
return self._thermostat_target_cool_temp
|
|
66
|
-
|
|
67
|
-
@property
|
|
68
|
-
def thermostat_target_heat_temp(self) -> str:
|
|
69
|
-
return self._thermostat_target_heat_temp
|
|
69
|
+
@thermostat_name.setter
|
|
70
|
+
def thermostat_name(self, value: str) -> None:
|
|
71
|
+
if self._thermostat_name != value:
|
|
72
|
+
LOGGER.debug("Thermostat%s (%s) - name: %s", self.thermostat_node_id, self.thermostat_name, value)
|
|
73
|
+
self._thermostat_name = value
|
|
74
|
+
self.notify()
|
|
70
75
|
|
|
71
76
|
@property
|
|
72
|
-
def
|
|
73
|
-
return self.
|
|
77
|
+
def thermostat_operating_state(self) -> str:
|
|
78
|
+
return self._thermostat_operating_state
|
|
79
|
+
|
|
80
|
+
@thermostat_operating_state.setter
|
|
81
|
+
def thermostat_operating_state(self, value: str) -> None:
|
|
82
|
+
if self._thermostat_operating_state != value:
|
|
83
|
+
LOGGER.debug("Thermostat%s (%s) - operating_state: %s", self.thermostat_node_id, self.thermostat_name, value)
|
|
84
|
+
self._thermostat_operating_state = value
|
|
85
|
+
self.notify()
|
|
74
86
|
|
|
75
87
|
@property
|
|
76
|
-
def
|
|
77
|
-
return self.
|
|
88
|
+
def thermostat_configuration_parameter(self) -> str:
|
|
89
|
+
return self._thermostat_configuration_parameter
|
|
90
|
+
|
|
91
|
+
@thermostat_configuration_parameter.setter
|
|
92
|
+
def thermostat_configuration_parameter(self, value: str) -> None:
|
|
93
|
+
if self._thermostat_configuration_parameter != value:
|
|
94
|
+
LOGGER.debug(
|
|
95
|
+
"Thermostat%s (%s) - configuration_parameter: %s", self.thermostat_node_id, self.thermostat_name, value
|
|
96
|
+
)
|
|
97
|
+
self._thermostat_configuration_parameter = value
|
|
98
|
+
self.notify()
|
|
78
99
|
|
|
79
100
|
@property
|
|
80
|
-
def
|
|
81
|
-
return self.
|
|
101
|
+
def thermostat_setpoint_capabilites(self) -> str:
|
|
102
|
+
return self._thermostat_setpoint_capabilites
|
|
103
|
+
|
|
104
|
+
@thermostat_setpoint_capabilites.setter
|
|
105
|
+
def thermostat_setpoint_capabilites(self, value: str) -> None:
|
|
106
|
+
if self._thermostat_setpoint_capabilites != value:
|
|
107
|
+
LOGGER.debug("Thermostat%s (%s) - setpoint_capabilites: %s", self.thermostat_node_id, self.thermostat_name, value)
|
|
108
|
+
self._thermostat_setpoint_capabilites = value
|
|
109
|
+
self.notify()
|
|
82
110
|
|
|
83
111
|
@property
|
|
84
|
-
def
|
|
85
|
-
return self.
|
|
86
|
-
|
|
87
|
-
@thermostat_name.setter
|
|
88
|
-
def thermostat_name(self, value: str) -> None:
|
|
89
|
-
if self._thermostat_name != value:
|
|
90
|
-
LOGGER.debug("Thermostat%s (%s) - name: %s", self.thermostat_node_id, self.thermostat_name, value)
|
|
91
|
-
self._thermostat_name = value
|
|
92
|
-
self.notify()
|
|
112
|
+
def thermostat_device_temp_unit(self) -> str:
|
|
113
|
+
return self._thermostat_device_temp_unit
|
|
93
114
|
|
|
94
115
|
@thermostat_device_temp_unit.setter
|
|
95
116
|
def thermostat_device_temp_unit(self, value: str) -> None:
|
|
@@ -98,6 +119,10 @@ class QolsysThermostat(QolsysZWaveDevice):
|
|
|
98
119
|
self._thermostat_device_temp_unit = value
|
|
99
120
|
self.notify()
|
|
100
121
|
|
|
122
|
+
@property
|
|
123
|
+
def thermostat_current_temp(self) -> str:
|
|
124
|
+
return self._thermostat_current_temp
|
|
125
|
+
|
|
101
126
|
@thermostat_current_temp.setter
|
|
102
127
|
def thermostat_current_temp(self, value: str) -> None:
|
|
103
128
|
if self._thermostat_current_temp != value:
|
|
@@ -105,6 +130,10 @@ class QolsysThermostat(QolsysZWaveDevice):
|
|
|
105
130
|
self._thermostat_current_temp = value
|
|
106
131
|
self.notify()
|
|
107
132
|
|
|
133
|
+
@property
|
|
134
|
+
def thermostat_target_cool_temp(self) -> str:
|
|
135
|
+
return self._thermostat_target_cool_temp
|
|
136
|
+
|
|
108
137
|
@thermostat_target_cool_temp.setter
|
|
109
138
|
def thermostat_target_cool_temp(self, value: str) -> None:
|
|
110
139
|
if self._thermostat_target_cool_temp != value:
|
|
@@ -112,6 +141,10 @@ class QolsysThermostat(QolsysZWaveDevice):
|
|
|
112
141
|
self._thermostat_target_cool_temp = value
|
|
113
142
|
self.notify()
|
|
114
143
|
|
|
144
|
+
@property
|
|
145
|
+
def thermostat_target_heat_temp(self) -> str:
|
|
146
|
+
return self._thermostat_target_heat_temp
|
|
147
|
+
|
|
115
148
|
@thermostat_target_heat_temp.setter
|
|
116
149
|
def thermostat_target_heat_temp(self, value: str) -> None:
|
|
117
150
|
if self._thermostat_target_heat_temp != value:
|
|
@@ -119,6 +152,10 @@ class QolsysThermostat(QolsysZWaveDevice):
|
|
|
119
152
|
self._thermostat_target_heat_temp = value
|
|
120
153
|
self.notify()
|
|
121
154
|
|
|
155
|
+
@property
|
|
156
|
+
def thermostat_target_temp(self) -> str:
|
|
157
|
+
return self._thermostat_target_temp
|
|
158
|
+
|
|
122
159
|
@thermostat_target_temp.setter
|
|
123
160
|
def thermostat_target_temp(self, value: str) -> None:
|
|
124
161
|
if self._thermostat_target_temp != value:
|
|
@@ -126,20 +163,82 @@ class QolsysThermostat(QolsysZWaveDevice):
|
|
|
126
163
|
self._thermostat_target_temp = value
|
|
127
164
|
self.notify()
|
|
128
165
|
|
|
166
|
+
@property
|
|
167
|
+
def thermostat_mode(self) -> ThermostatMode | None:
|
|
168
|
+
value = self._thermostat_mode.strip("[]").split(",")
|
|
169
|
+
|
|
170
|
+
if len(value) > 1:
|
|
171
|
+
LOGGER.error(
|
|
172
|
+
"Thermostat%s (%s) - thermostat_mode has multiple values: %s",
|
|
173
|
+
self.thermostat_node_id,
|
|
174
|
+
self.thermostat_name,
|
|
175
|
+
value,
|
|
176
|
+
)
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
int_value = int(value[0])
|
|
181
|
+
for mode in ThermostatMode:
|
|
182
|
+
if int_value == mode.value:
|
|
183
|
+
return mode
|
|
184
|
+
return None
|
|
185
|
+
except ValueError:
|
|
186
|
+
LOGGER.error(
|
|
187
|
+
"Thermostat%s (%s) - thermostat_mode value is not an integer: %s",
|
|
188
|
+
self.thermostat_node_id,
|
|
189
|
+
self.thermostat_name,
|
|
190
|
+
value,
|
|
191
|
+
)
|
|
192
|
+
return None
|
|
193
|
+
|
|
129
194
|
@thermostat_mode.setter
|
|
130
195
|
def thermostat_mode(self, value: str) -> None:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
self.
|
|
196
|
+
new_value = value.strip("[]")
|
|
197
|
+
if self._thermostat_mode != new_value:
|
|
198
|
+
LOGGER.debug("Thermostat%s (%s) - mode: %s", self.thermostat_node_id, self.thermostat_name, new_value)
|
|
199
|
+
self._thermostat_mode = new_value
|
|
134
200
|
self.notify()
|
|
135
201
|
|
|
202
|
+
@property
|
|
203
|
+
def thermostat_fan_mode(self) -> ThermostatFanMode | None:
|
|
204
|
+
value = self._thermostat_fan_mode.strip("[]").split(",")
|
|
205
|
+
|
|
206
|
+
if len(value) > 1:
|
|
207
|
+
LOGGER.error(
|
|
208
|
+
"Thermostat%s (%s) - thermostat_fan_mode has multiple values: %s",
|
|
209
|
+
self.thermostat_node_id,
|
|
210
|
+
self.thermostat_name,
|
|
211
|
+
value,
|
|
212
|
+
)
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
int_value = int(value[0])
|
|
217
|
+
for mode in ThermostatFanMode:
|
|
218
|
+
if int_value == mode.value:
|
|
219
|
+
return mode
|
|
220
|
+
return None
|
|
221
|
+
except ValueError:
|
|
222
|
+
LOGGER.error(
|
|
223
|
+
"Thermostat%s (%s) - thermostat_fan_mode value is not an integer: %s",
|
|
224
|
+
self.thermostat_node_id,
|
|
225
|
+
self.thermostat_name,
|
|
226
|
+
value,
|
|
227
|
+
)
|
|
228
|
+
return None
|
|
229
|
+
|
|
136
230
|
@thermostat_fan_mode.setter
|
|
137
231
|
def thermostat_fan_mode(self, value: str) -> None:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
self.
|
|
232
|
+
new_value = value.strip("[]")
|
|
233
|
+
if self._thermostat_fan_mode != new_value:
|
|
234
|
+
LOGGER.debug("Thermostat%s (%s) - fan_mode: %s", self.thermostat_node_id, self.thermostat_name, new_value)
|
|
235
|
+
self._thermostat_fan_mode = new_value
|
|
141
236
|
self.notify()
|
|
142
237
|
|
|
238
|
+
@property
|
|
239
|
+
def thermostat_set_point_mode(self) -> str:
|
|
240
|
+
return self._thermostat_set_point_mode.strip("[]")
|
|
241
|
+
|
|
143
242
|
@thermostat_set_point_mode.setter
|
|
144
243
|
def thermostat_set_point_mode(self, value: str) -> None:
|
|
145
244
|
if self._thermostat_set_point_mode != value:
|
|
@@ -147,93 +246,116 @@ class QolsysThermostat(QolsysZWaveDevice):
|
|
|
147
246
|
self._thermostat_set_point_mode = value
|
|
148
247
|
self.notify()
|
|
149
248
|
|
|
150
|
-
|
|
249
|
+
@property
|
|
250
|
+
def thermostat_current_humidity(self) -> float | None:
|
|
251
|
+
sensor = self.multilevelsensor_value(ZWaveMultilevelSensorScale.RELATIVE_HUMIDITY)
|
|
252
|
+
|
|
253
|
+
if not sensor:
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
if len(sensor) == 1:
|
|
257
|
+
return sensor[0].value
|
|
258
|
+
|
|
259
|
+
LOGGER.error("Multiple humidity sensor present")
|
|
260
|
+
return sensor[0].value
|
|
261
|
+
|
|
262
|
+
def update_raw(self, payload: bytes) -> None:
|
|
263
|
+
pass
|
|
264
|
+
|
|
265
|
+
def update_thermostat(self, data: dict[str, str]) -> None: # noqa: C901, PLR0912, PLR0915
|
|
151
266
|
# Check if we are updating same none_id
|
|
152
267
|
node_id_update = data.get("node_id", "")
|
|
153
|
-
if node_id_update != self.
|
|
268
|
+
if node_id_update != self.thermostat_node_id:
|
|
154
269
|
LOGGER.error(
|
|
155
270
|
"Updating Thermostat '%s' (%s) with Thermostat '%s' (different id)",
|
|
156
|
-
self.thermostat_node_id,
|
|
271
|
+
self.thermostat_node_id,
|
|
272
|
+
self.thermostat_name,
|
|
273
|
+
node_id_update,
|
|
157
274
|
)
|
|
158
275
|
return
|
|
159
276
|
|
|
160
277
|
self.start_batch_update()
|
|
161
278
|
|
|
162
279
|
if "version" in data:
|
|
163
|
-
self._thermostat_version = data.get("version")
|
|
280
|
+
self._thermostat_version = data.get("version", "")
|
|
164
281
|
if "opr" in data:
|
|
165
|
-
self._thermostat_opr = data.get("opr")
|
|
282
|
+
self._thermostat_opr = data.get("opr", "")
|
|
166
283
|
if "partition_id" in data:
|
|
167
|
-
self._thermostat_partition_id = data.get("partition_id")
|
|
284
|
+
self._thermostat_partition_id = data.get("partition_id", "")
|
|
168
285
|
if "thermostat_name" in data:
|
|
169
|
-
self.thermostat_name = data.get("thermostat_name")
|
|
286
|
+
self.thermostat_name = data.get("thermostat_name", "")
|
|
170
287
|
if "device_temp_unit" in data:
|
|
171
|
-
self.thermostat_device_temp_unit = data.get("device_temp_unit")
|
|
288
|
+
self.thermostat_device_temp_unit = data.get("device_temp_unit", "")
|
|
172
289
|
if "current_temp" in data:
|
|
173
|
-
self.thermostat_current_temp = data.get("current_temp")
|
|
290
|
+
self.thermostat_current_temp = data.get("current_temp", "")
|
|
174
291
|
if "target_cool_temp" in data:
|
|
175
|
-
self.thermostat_target_cool_temp = data.get("target_cool_temp")
|
|
292
|
+
self.thermostat_target_cool_temp = data.get("target_cool_temp", "")
|
|
176
293
|
if "target_heat_temp" in data:
|
|
177
|
-
self.thermostat_target_heat_temp = data.get("target_heat_temp")
|
|
294
|
+
self.thermostat_target_heat_temp = data.get("target_heat_temp", "")
|
|
178
295
|
if "target_temp" in data:
|
|
179
|
-
self.thermostat_target_temp = data.get("target_temp")
|
|
296
|
+
self.thermostat_target_temp = data.get("target_temp", "")
|
|
180
297
|
if "power_usage" in data:
|
|
181
|
-
self._thermostat_power_usage = data.get("power_usage")
|
|
298
|
+
self._thermostat_power_usage = data.get("power_usage", "")
|
|
182
299
|
if "thermostat_mode" in data:
|
|
183
|
-
self.thermostat_mode = data.get("thermostat_mode")
|
|
300
|
+
self.thermostat_mode = data.get("thermostat_mode", "")
|
|
184
301
|
if "thermostat_mode_bitmask" in data:
|
|
185
|
-
self._thermostat_mode_bitmask = data.get("thermostat_mode_bitmask")
|
|
302
|
+
self._thermostat_mode_bitmask = data.get("thermostat_mode_bitmask", "")
|
|
186
303
|
if "fan_mode" in data:
|
|
187
|
-
self.thermostat_fan_mode = data.get("fan_mode")
|
|
304
|
+
self.thermostat_fan_mode = data.get("fan_mode", "")
|
|
188
305
|
if "fan_mode_bitmask" in data:
|
|
189
|
-
self._thermostat_fan_mode_bitmask = data.get("fan_mode_bitmask")
|
|
306
|
+
self._thermostat_fan_mode_bitmask = data.get("fan_mode_bitmask", "")
|
|
190
307
|
if "set_point_mode" in data:
|
|
191
|
-
self.thermostat_set_point_mode = data.get("set_point_mode")
|
|
308
|
+
self.thermostat_set_point_mode = data.get("set_point_mode", "")
|
|
192
309
|
if "set_point_mode_bitmask" in data:
|
|
193
|
-
self._thermostat_set_point_mode_bitmask = data.get("set_point_mode_bitmask")
|
|
310
|
+
self._thermostat_set_point_mode_bitmask = data.get("set_point_mode_bitmask", "")
|
|
194
311
|
if "created_by" in data:
|
|
195
|
-
self._thermostat_created_by = data.get("created_by")
|
|
312
|
+
self._thermostat_created_by = data.get("created_by", "")
|
|
196
313
|
if "updated_by" in data:
|
|
197
|
-
self._thermostat_updated_by = data.get("updated_by")
|
|
314
|
+
self._thermostat_updated_by = data.get("updated_by", "")
|
|
198
315
|
if "last_updated_date" in data:
|
|
199
|
-
self._thermostat_last_updated_date = data.get("last_updated_date")
|
|
316
|
+
self._thermostat_last_updated_date = data.get("last_updated_date", "")
|
|
200
317
|
if "thermostat_mode_updated_time" in data:
|
|
201
|
-
self._thermostat_mode_updated_time = data.get("thermostat_mode_updated_time")
|
|
318
|
+
self._thermostat_mode_updated_time = data.get("thermostat_mode_updated_time", "")
|
|
202
319
|
if "fan_mode_updated_time" in data:
|
|
203
|
-
self._thermostat_fan_mode_updated_time = data.get("fan_mode_updated_time")
|
|
320
|
+
self._thermostat_fan_mode_updated_time = data.get("fan_mode_updated_time", "")
|
|
204
321
|
if "set_point_mode_updated_time" in data:
|
|
205
|
-
self._thermostat_set_point_mode_updated_time = data.get("set_point_mode_updated_time")
|
|
322
|
+
self._thermostat_set_point_mode_updated_time = data.get("set_point_mode_updated_time", "")
|
|
206
323
|
if "target_cool_temp_updated_time" in data:
|
|
207
|
-
self._thermostat_target_cool_temp_updated_time = data.get("target_cool_temp_updated_time")
|
|
324
|
+
self._thermostat_target_cool_temp_updated_time = data.get("target_cool_temp_updated_time", "")
|
|
208
325
|
if "target_heat_temp_updated_time" in data:
|
|
209
|
-
self._thermostat_target_heat_temp_updated_time = data.get("target_heat_temp_updated_time")
|
|
326
|
+
self._thermostat_target_heat_temp_updated_time = data.get("target_heat_temp_updated_time", "")
|
|
210
327
|
if "current_temp_updated_time" in data:
|
|
211
|
-
self._thermostat_current_temp_updated_time = data.get("current_temp_updated_time")
|
|
328
|
+
self._thermostat_current_temp_updated_time = data.get("current_temp_updated_time", "")
|
|
212
329
|
if "paired_status" in data:
|
|
213
|
-
self._thermostat_paired_status = data.get("paired_status")
|
|
330
|
+
self._thermostat_paired_status = data.get("paired_status", "")
|
|
214
331
|
if "endpoint" in data:
|
|
215
|
-
self._thermostat_endpoint = data.get("endpoint")
|
|
332
|
+
self._thermostat_endpoint = data.get("endpoint", "")
|
|
216
333
|
if "configuration_parameter" in data:
|
|
217
|
-
self._thermostat_configuration_parameter = data.get("configuration_parameter")
|
|
334
|
+
self._thermostat_configuration_parameter = data.get("configuration_parameter", "")
|
|
335
|
+
if "operating_state" in data:
|
|
336
|
+
self.thermostat_operating_state = data.get("operating_state", "")
|
|
337
|
+
if "setpoint_capabilites" in data:
|
|
338
|
+
self.thermostat_setpoint_capabilites = data.get("setpoint_capabilites", "")
|
|
218
339
|
|
|
219
340
|
self.end_batch_update()
|
|
220
341
|
|
|
221
|
-
def to_dict_thermostat(self) -> dict:
|
|
342
|
+
def to_dict_thermostat(self) -> dict[str, str]:
|
|
222
343
|
return {
|
|
223
344
|
"_id": self._thermostat_id,
|
|
224
345
|
"version": self._thermostat_version,
|
|
225
346
|
"opr": self._thermostat_opr,
|
|
226
347
|
"partition_id": self._thermostat_partition_id,
|
|
227
348
|
"thermostat_name": self.thermostat_name,
|
|
349
|
+
"node_id": self.thermostat_node_id,
|
|
228
350
|
"device_temp_unit": self.thermostat_device_temp_unit,
|
|
229
351
|
"current_temp": self.thermostat_current_temp,
|
|
230
352
|
"target_cool_temp": self.thermostat_target_cool_temp,
|
|
231
353
|
"target_heat_temp": self.thermostat_target_heat_temp,
|
|
232
354
|
"target_temp": self.thermostat_target_temp,
|
|
233
355
|
"power_usage": self._thermostat_power_usage,
|
|
234
|
-
"thermostat_mode": self.
|
|
356
|
+
"thermostat_mode": self._thermostat_mode,
|
|
235
357
|
"thermostat_mode_bitmask": self._thermostat_mode_bitmask,
|
|
236
|
-
"fan_mode": self.
|
|
358
|
+
"fan_mode": self._thermostat_fan_mode,
|
|
237
359
|
"fan_mode_bitmask": self._thermostat_fan_mode_bitmask,
|
|
238
360
|
"set_point_mode": self.thermostat_set_point_mode,
|
|
239
361
|
"set_point_mode_bitmask": self._thermostat_set_point_mode_bitmask,
|
|
@@ -248,59 +370,43 @@ class QolsysThermostat(QolsysZWaveDevice):
|
|
|
248
370
|
"current_temp_updated_time": self._thermostat_current_temp_updated_time,
|
|
249
371
|
"paired_status": self._thermostat_paired_status,
|
|
250
372
|
"endpoint": self._thermostat_endpoint,
|
|
251
|
-
"configuration_parameter": self.
|
|
373
|
+
"configuration_parameter": self.thermostat_configuration_parameter,
|
|
374
|
+
"operating_state": self.thermostat_operating_state,
|
|
375
|
+
"setpoint_capabilites": self._thermostat_setpoint_capabilites,
|
|
252
376
|
}
|
|
253
377
|
|
|
254
|
-
def thermostat_mode(self) -> ThermostatMode:
|
|
255
|
-
thermostat_mode = int(self._thermostat_mode)
|
|
256
|
-
for mode in ThermostatMode:
|
|
257
|
-
if thermostat_mode == mode:
|
|
258
|
-
return mode
|
|
259
|
-
return None
|
|
260
|
-
|
|
261
378
|
def available_thermostat_mode(self) -> list[ThermostatMode]:
|
|
262
|
-
|
|
263
|
-
int_list = [int(x) for x in self._thermostat_mode_bitmask.split(",")]
|
|
379
|
+
int_list = [int(x) for x in self._thermostat_mode_bitmask.strip("[]").split(",")]
|
|
264
380
|
byte_array = bytes(int_list)
|
|
265
381
|
bitmask = int.from_bytes(byte_array, byteorder="little")
|
|
266
382
|
|
|
267
|
-
|
|
268
|
-
for mode in
|
|
269
|
-
if
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return mode_array
|
|
383
|
+
supported = []
|
|
384
|
+
for bit, mode in BITMASK_SUPPORTED_THERMOSTAT_MODE.items():
|
|
385
|
+
if bitmask & (1 << bit):
|
|
386
|
+
supported.append(mode)
|
|
273
387
|
|
|
274
|
-
|
|
275
|
-
thermostat_fan_mode = int(self._thermostat_fan_mode)
|
|
276
|
-
for mode in ThermostatMode:
|
|
277
|
-
if thermostat_fan_mode == mode:
|
|
278
|
-
return mode
|
|
279
|
-
return None
|
|
388
|
+
return supported
|
|
280
389
|
|
|
281
390
|
def available_thermostat_fan_mode(self) -> list[ThermostatFanMode]:
|
|
282
|
-
|
|
283
|
-
int_list = [int(x) for x in self._thermostat_fan_mode_bitmask.split(",")]
|
|
391
|
+
int_list = [int(x) for x in self._thermostat_fan_mode_bitmask.strip("[]").split(",")]
|
|
284
392
|
byte_array = bytes(int_list)
|
|
285
393
|
bitmask = int.from_bytes(byte_array, byteorder="little")
|
|
286
394
|
|
|
287
|
-
|
|
288
|
-
for mode in
|
|
289
|
-
if
|
|
290
|
-
|
|
395
|
+
supported = []
|
|
396
|
+
for bit, mode in BITMASK_SUPPORTED_THERMOSTAT_FAN_MODE.items():
|
|
397
|
+
if bitmask & (1 << bit):
|
|
398
|
+
supported.append(mode)
|
|
291
399
|
|
|
292
|
-
return
|
|
400
|
+
return supported
|
|
293
401
|
|
|
294
|
-
def available_thermostat_set_point_mode(self) -> list[
|
|
295
|
-
|
|
296
|
-
int_list = [int(x) for x in self._thermostat_set_point_mode_bitmask.split(",")]
|
|
402
|
+
def available_thermostat_set_point_mode(self) -> list[ThermostatSetpointMode]:
|
|
403
|
+
int_list = [int(x) for x in self._thermostat_set_point_mode_bitmask.strip("[]").split(",")]
|
|
297
404
|
byte_array = bytes(int_list)
|
|
298
405
|
bitmask = int.from_bytes(byte_array, byteorder="little")
|
|
299
406
|
|
|
300
|
-
|
|
301
|
-
for mode in
|
|
302
|
-
if
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return set_point_mode_array
|
|
407
|
+
supported = []
|
|
408
|
+
for bit, mode in BITMASK_SUPPORTED_THERMOSTAT_SETPOINT_MODE.items():
|
|
409
|
+
if bitmask & (1 << bit):
|
|
410
|
+
supported.append(mode)
|
|
306
411
|
|
|
412
|
+
return supported
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qolsys-controller
|
|
3
|
+
Version: 0.0.87
|
|
4
|
+
Summary: A Python module that emulates a virtual IQ Remote device, enabling full local control of a Qolsys IQ Panel
|
|
5
|
+
Project-URL: Homepage, https://github.com/EHylands/QolsysController
|
|
6
|
+
Project-URL: Issues, https://github.com/EHylands/QolsysController/issues
|
|
7
|
+
Project-URL: Repository, https://github.com/EHylands/QolsysController.git
|
|
8
|
+
Author: Eric Hylands
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Topic :: Home Automation
|
|
14
|
+
Requires-Python: >=3.12
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Qolsys Controller - qolsys-controller
|
|
18
|
+
|
|
19
|
+
[](https://github.com/EHylands/QolsysController/actions/workflows/build.yml)
|
|
20
|
+
|
|
21
|
+
A Python module that emulates a virtual IQ Remote device, enabling full **local control** of a Qolsys IQ Panel over MQTT — no cloud access required.
|
|
22
|
+
|
|
23
|
+
## QolsysController
|
|
24
|
+
- ✅ Connects directly to the **Qolsys Panel's local MQTT server as an IQ Remote**
|
|
25
|
+
- 🔐 Pairs by only using **Installer Code** (same procedure as standard IQ Remote pairing)
|
|
26
|
+
- 🔢 Supports **4-digit user codes**
|
|
27
|
+
- ⚠️ Uses a **custom local usercode database** — panel's internal user code verification process is not yet supported
|
|
28
|
+
|
|
29
|
+
## ✨ Functionality Highlights
|
|
30
|
+
|
|
31
|
+
| Category | Feature | Status |
|
|
32
|
+
|------------------------|--------------------------------------|--------|
|
|
33
|
+
| **Panel** | Diagnostic Sensors | ✅ |
|
|
34
|
+
| | Panel Scenes | ✅ |
|
|
35
|
+
| | Weather Forecast | ✅ |
|
|
36
|
+
| | (Alarm.com Weather to Panel) | |
|
|
37
|
+
| **Partition** | Arming Status | ✅ |
|
|
38
|
+
| | Alarm State | ✅ |
|
|
39
|
+
| | Home Instant Arming | ✅ |
|
|
40
|
+
| | Home Silent Disarming (Firmware 4.6.1)| ✅ |
|
|
41
|
+
| | Set Exit Sounds | ✅ |
|
|
42
|
+
| | Set Entry Delay | ✅ |
|
|
43
|
+
| | TTS | 🛠️ |
|
|
44
|
+
| **Zones** | Sensor Status | ✅ |
|
|
45
|
+
| | Tamper State | ✅ |
|
|
46
|
+
| | Battery Level | ✅ |
|
|
47
|
+
| | Temperature (supported PowerG device)| ✅ |
|
|
48
|
+
| | Light (supported PowerG device) | ✅ |
|
|
49
|
+
| | Average dBm | ✅ |
|
|
50
|
+
| | Latest dBm | ✅ |
|
|
51
|
+
| **Z-Wave Devices** | Battery Level | ✅ |
|
|
52
|
+
| | Node Status | ✅ |
|
|
53
|
+
| | Control Generic Devices | TBD |
|
|
54
|
+
| **Z-Wave Dimmers** | Binary Switch | ✅ |
|
|
55
|
+
| | Multi Level Dimmer | ✅ |
|
|
56
|
+
| **Z-Wave Door Locks** | Lock, Unlock | ✅ |
|
|
57
|
+
| **Z-Wave Thermostats** | Read device status | ✅ |
|
|
58
|
+
| | Write device status | ✅ |
|
|
59
|
+
| **Z-Wave Garage Doors**| | 🛠️ |
|
|
60
|
+
| **Z-Wave Outlets** | | 🛠️ |
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## ⚠️ Certificate Warning
|
|
64
|
+
|
|
65
|
+
During pairing, the main panel issues **only one signed client certificate** per virtual IQ Remote. If any key files are lost or deleted, re-pairing may become impossible.
|
|
66
|
+
|
|
67
|
+
A new PKI, including a new private key, can be recreated under specific circumstances, though the precise conditions remain unknown at this time.
|
|
68
|
+
|
|
69
|
+
**Important:**
|
|
70
|
+
Immediately back up the following files from the `pki/` directory after initial pairing:
|
|
71
|
+
|
|
72
|
+
- `.key` (private key)
|
|
73
|
+
- `.cer` (certificate)
|
|
74
|
+
- `.csr` (certificate signing request)
|
|
75
|
+
- `.secure` (signed client certificate)
|
|
76
|
+
- `.qolsys` (Qolsys Panel public certificate)
|
|
77
|
+
|
|
78
|
+
Store these files securely.
|
|
79
|
+
|
|
80
|
+
## 📦 Installation
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
git clone https://github.com/EHylands/QolsysController.git
|
|
84
|
+
cd qolsys_controller
|
|
85
|
+
pip3.12 install -r requirements.txt
|
|
86
|
+
|
|
87
|
+
# Change panel_ip and plugin_in in main.py file
|
|
88
|
+
python3.12 example.py
|
|
89
|
+
```
|