qolsys-controller 0.0.76__tar.gz → 0.3.2__tar.gz

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.
Files changed (95) hide show
  1. qolsys_controller-0.3.2/.github/dependabot.yml +11 -0
  2. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/PKG-INFO +1 -1
  3. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/pyproject.toml +1 -1
  4. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/adc_service_garagedoor.py +1 -1
  5. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/controller.py +163 -35
  6. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/db.py +8 -8
  7. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_sensor.py +2 -0
  8. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_smartsocket.py +12 -1
  9. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_thermostat.py +1 -0
  10. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_user.py +1 -0
  11. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_zwave_node.py +3 -1
  12. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/enum.py +38 -24
  13. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/enum_zwave.py +87 -23
  14. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/mqtt_command.py +8 -1
  15. qolsys_controller-0.3.2/qolsys_controller/observable_v2.py +14 -0
  16. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/panel.py +49 -20
  17. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/state.py +146 -41
  18. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/zone.py +74 -12
  19. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/zwave_device.py +156 -10
  20. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/zwave_dimmer.py +7 -11
  21. qolsys_controller-0.3.2/qolsys_controller/zwave_energy_clamp.py +19 -0
  22. qolsys_controller-0.3.2/qolsys_controller/zwave_extenal_siren.py +86 -0
  23. qolsys_controller-0.3.2/qolsys_controller/zwave_garagedoor.py +20 -0
  24. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/zwave_generic.py +6 -2
  25. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/zwave_lock.py +15 -12
  26. qolsys_controller-0.3.2/qolsys_controller/zwave_service_meter.py +192 -0
  27. qolsys_controller-0.3.2/qolsys_controller/zwave_service_multilevelsensor.py +128 -0
  28. qolsys_controller-0.3.2/qolsys_controller/zwave_smart_socket.py +20 -0
  29. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/zwave_thermometer.py +6 -2
  30. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/zwave_thermostat.py +70 -22
  31. qolsys_controller-0.3.2/qolsys_controller/zwave_water_valve.py +83 -0
  32. qolsys_controller-0.3.2/requirements.txt +8 -0
  33. qolsys_controller-0.0.76/qolsys_controller/zwave_garagedoor.py +0 -13
  34. qolsys_controller-0.0.76/qolsys_controller/zwave_meter.py +0 -272
  35. qolsys_controller-0.0.76/qolsys_controller/zwave_outlet.py +0 -13
  36. qolsys_controller-0.0.76/requirements.txt +0 -8
  37. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/.github/workflows/build.yml +0 -0
  38. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/.github/workflows/publish.yml +0 -0
  39. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/.gitignore +0 -0
  40. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/Info_mqtt.md +0 -0
  41. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/LICENSE +0 -0
  42. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/README.md +0 -0
  43. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/bin/qolsys.py +0 -0
  44. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/example.py +0 -0
  45. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/info_pairing.md +0 -0
  46. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/info_qolsys.md +0 -0
  47. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/mypy.ini +0 -0
  48. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/__init__.py +0 -0
  49. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/adc_device.py +0 -0
  50. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/adc_service.py +0 -0
  51. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table.py +0 -0
  52. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_alarmedsensor.py +0 -0
  53. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_automation.py +0 -0
  54. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_country_locale.py +0 -0
  55. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_dashboard_msgs.py +0 -0
  56. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_dimmerlight.py +0 -0
  57. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_doorlock.py +0 -0
  58. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_eu_event.py +0 -0
  59. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_heat_map.py +0 -0
  60. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_history.py +0 -0
  61. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_iqremotesettings.py +0 -0
  62. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_iqrouter_network_config.py +0 -0
  63. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_iqrouter_user_device.py +0 -0
  64. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_master_slave.py +0 -0
  65. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_nest_device.py +0 -0
  66. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_output_rules.py +0 -0
  67. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_partition.py +0 -0
  68. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_pgm_outputs.py +0 -0
  69. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_powerg_device.py +0 -0
  70. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_qolsyssettings.py +0 -0
  71. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_scene.py +0 -0
  72. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_sensor_group.py +0 -0
  73. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_shades.py +0 -0
  74. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_state.py +0 -0
  75. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_tcc.py +0 -0
  76. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_trouble_conditions.py +0 -0
  77. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_virtual_device.py +0 -0
  78. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_weather.py +0 -0
  79. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_zigbee_device.py +0 -0
  80. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_zwave_association_group.py +0 -0
  81. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_zwave_history.py +0 -0
  82. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/database/table_zwave_other.py +0 -0
  83. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/enum_adc.py +0 -0
  84. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/errors.py +0 -0
  85. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/mdns.py +0 -0
  86. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/mqtt_command_queue.py +0 -0
  87. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/observable.py +0 -0
  88. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/partition.py +0 -0
  89. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/pki.py +0 -0
  90. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/scene.py +0 -0
  91. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/settings.py +0 -0
  92. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/task_manager.py +0 -0
  93. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/users.py +0 -0
  94. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/utils_mqtt.py +0 -0
  95. {qolsys_controller-0.0.76 → qolsys_controller-0.3.2}/qolsys_controller/weather.py +0 -0
@@ -0,0 +1,11 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "pip" # See documentation for possible values
9
+ directory: "/" # Location of package manifests
10
+ schedule:
11
+ interval: "weekly"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qolsys-controller
3
- Version: 0.0.76
3
+ Version: 0.3.2
4
4
  Summary: A Python module that emulates a virtual IQ Remote device, enabling full local control of a Qolsys IQ Panel
5
5
  Project-URL: Homepage, https://github.com/EHylands/QolsysController
6
6
  Project-URL: Issues, https://github.com/EHylands/QolsysController/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "qolsys-controller"
3
- version = "0.0.76"
3
+ version = "0.3.2"
4
4
  authors = [
5
5
  { name="Eric Hylands", email="" },
6
6
  ]
@@ -32,4 +32,4 @@ class QolsysAdcGarageDoorService(QolsysAdcService):
32
32
  )
33
33
 
34
34
  def is_open(self) -> bool:
35
- return self.func_state == vdFuncState.ON
35
+ return self.func_state == 0
@@ -18,9 +18,10 @@ from qolsys_controller.mqtt_command import (
18
18
  MQTTCommand_Panel,
19
19
  MQTTCommand_ZWave,
20
20
  )
21
+ from qolsys_controller.zwave_thermostat import QolsysThermostat
21
22
 
22
23
  from .enum import PartitionAlarmState, PartitionArmingType, PartitionSystemStatus
23
- from .enum_zwave import ThermostatFanMode, ThermostatMode, ZwaveCommandClass, ZwaveDeviceClass
24
+ from .enum_zwave import ThermostatFanMode, ThermostatMode, ThermostatSetpointMode, ZwaveCommandClass
24
25
  from .errors import QolsysMqttError, QolsysSslError, QolsysUserCodeError
25
26
  from .mdns import QolsysMDNS
26
27
  from .mqtt_command_queue import QolsysMqttCommandQueue
@@ -60,6 +61,7 @@ class QolsysController:
60
61
  self._mqtt_task_listen_label: str = "mqtt_task_listen"
61
62
  self._mqtt_task_connect_label: str = "mqtt_task_connect"
62
63
  self._mqtt_task_ping_label: str = "mqtt_task_ping"
64
+ self._mqtt_task_zwave_meter_update_label: str = "mqtt_task_zwave_meter_update"
63
65
 
64
66
  @property
65
67
  def state(self) -> QolsysState:
@@ -151,32 +153,43 @@ class QolsysController:
151
153
  self._task_manager.cancel(self._mqtt_task_listen_label)
152
154
  self._task_manager.cancel(self._mqtt_task_ping_label)
153
155
  self._task_manager.cancel(self._mqtt_task_config_label)
156
+ self._task_manager.cancel(self._mqtt_task_zwave_meter_update_label)
154
157
 
155
158
  self.connected = False
156
159
  self.connected_observer.notify()
157
160
 
158
161
  async def mqtt_connect_task(self, reconnect: bool, run_forever: bool) -> None:
159
- # Configure TLS parameters for MQTT connection
160
- tls_params = aiomqtt.TLSParameters(
161
- ca_certs=str(self._pki.qolsys_cer_file_path),
162
- certfile=str(self._pki.secure_file_path),
163
- keyfile=str(self._pki.key_file_path),
164
- cert_reqs=ssl.CERT_REQUIRED,
165
- tls_version=ssl.PROTOCOL_TLSv1_2,
166
- ciphers="ALL:@SECLEVEL=0",
167
- )
162
+ # Configure TLS context for MQTT connection
163
+ def create_tls_context(self: QolsysController) -> ssl.SSLContext:
164
+ ctx = ssl.create_default_context(
165
+ purpose=ssl.Purpose.SERVER_AUTH,
166
+ cafile=str(self._pki.qolsys_cer_file_path),
167
+ )
168
+ ctx.set_ciphers("DEFAULT:@SECLEVEL=0")
169
+ ctx.minimum_version = ssl.TLSVersion.TLSv1_2
170
+ ctx.check_hostname = False
171
+ ctx.verify_mode = ssl.CERT_NONE
172
+ ctx.load_cert_chain(
173
+ certfile=str(self._pki.secure_file_path),
174
+ keyfile=str(self._pki.key_file_path),
175
+ )
176
+ return ctx
177
+
178
+ loop = asyncio.get_running_loop()
179
+ ctx = await loop.run_in_executor(None, create_tls_context, self)
168
180
 
169
181
  LOGGER.debug("MQTT: Connecting ...")
170
182
 
171
183
  self._task_manager.cancel(self._mqtt_task_listen_label)
172
184
  self._task_manager.cancel(self._mqtt_task_ping_label)
185
+ self._task_manager.cancel(self._mqtt_task_zwave_meter_update_label)
173
186
 
174
187
  while True:
175
188
  try:
176
189
  self.aiomqtt = aiomqtt.Client(
177
190
  hostname=self.settings.panel_ip,
178
191
  port=8883,
179
- tls_params=tls_params,
192
+ tls_context=ctx,
180
193
  tls_insecure=True,
181
194
  clean_session=True,
182
195
  timeout=self.settings.mqtt_timeout,
@@ -213,11 +226,14 @@ class QolsysController:
213
226
 
214
227
  response_database = await self.command_sync_database()
215
228
  LOGGER.debug("MQTT: Updating State from syncdatabase")
216
- self.panel.load_database(response_database.get("fulldbdata")) # type: ignore[arg-type]
229
+ await self.panel.load_database(response_database.get("fulldbdata"))
217
230
  self.panel.dump()
218
231
  self.state.dump()
219
232
 
220
233
  self.connected = True
234
+
235
+ self._task_manager.run(self.mqtt_zwave_meter_update(), self._mqtt_task_zwave_meter_update_label)
236
+
221
237
  self.connected_observer.notify()
222
238
 
223
239
  if not run_forever:
@@ -258,6 +274,19 @@ class QolsysController:
258
274
 
259
275
  await asyncio.sleep(self.settings.mqtt_ping)
260
276
 
277
+ async def mqtt_zwave_meter_update(self) -> None:
278
+ while True:
279
+ if self.aiomqtt is not None and self.connected:
280
+ LOGGER.debug("Updating Z-Wave Energy Clamps")
281
+ with contextlib.suppress(aiomqtt.MqttError):
282
+ for energy_clamp in self.state.zwave_meters:
283
+ for meter in energy_clamp.meter_endpoints:
284
+ zwave_command = MQTTCommand_ZWave(
285
+ self, energy_clamp.node_id, meter.endpoint, [ZwaveCommandClass.Meter, 0x01]
286
+ )
287
+ await zwave_command.send_command()
288
+ await asyncio.sleep(60)
289
+
261
290
  async def mqtt_listen_task(self) -> None:
262
291
  try:
263
292
  async for message in self.aiomqtt.messages: # type: ignore[union-attr]
@@ -747,6 +776,70 @@ class QolsysController:
747
776
  LOGGER.debug("MQTT: Receiving execute_scene command")
748
777
  return response
749
778
 
779
+ async def command_panel_virtual_device_action(self, device_id: str, state: int) -> dict[str, Any] | None:
780
+ LOGGER.debug("MQTT: Sending virtual_device command")
781
+
782
+ garage_door = self.state.adc_device(device_id)
783
+ if not garage_door:
784
+ LOGGER.error("Invalid Virtual Garage Door Id: %s", device_id)
785
+
786
+ device_list = {
787
+ "virtualDeviceList": [
788
+ {
789
+ "virtualDeviceId": int(device_id),
790
+ "virtualDeviceFunctionList": [
791
+ {
792
+ "vdFuncId": 1,
793
+ "vdFuncState": state,
794
+ "vdFuncBackendTimestamp": int(time.time() * 1000),
795
+ "vdFuncType": 1,
796
+ }
797
+ ],
798
+ }
799
+ ]
800
+ }
801
+
802
+ virtual_command = {
803
+ "operation_name": "send_virtual_device_description",
804
+ "virtual_device_operation": 4,
805
+ "virtual_device_description": json.dumps(device_list),
806
+ "operation_source": 0,
807
+ }
808
+
809
+ virtual_command2 = {
810
+ "operation_name": "send_virtual_device_description",
811
+ "virtual_device_operation": 5,
812
+ "virtual_device_description": json.dumps(device_list),
813
+ "operation_source": 0,
814
+ }
815
+
816
+ ipc_request = [
817
+ {
818
+ "dataType": "string",
819
+ "dataValue": json.dumps(virtual_command),
820
+ }
821
+ ]
822
+
823
+ ipc_request2 = [
824
+ {
825
+ "dataType": "string",
826
+ "dataValue": json.dumps(virtual_command2),
827
+ }
828
+ ]
829
+
830
+ LOGGER.debug("virtual command: %s", virtual_command)
831
+ command = MQTTCommand_Panel(self)
832
+ command.append_ipc_request(ipc_request)
833
+ response = await command.send_command()
834
+ LOGGER.debug("MQTT: Receiving virtual_device command: %s", response)
835
+
836
+ LOGGER.debug("virtual command: %s", virtual_command2)
837
+ command2 = MQTTCommand_Panel(self)
838
+ command2.append_ipc_request(ipc_request2)
839
+ response2 = await command2.send_command()
840
+ LOGGER.debug("MQTT: Receiving virtual_device command: %s", response2)
841
+ return response
842
+
750
843
  async def command_panel_trigger_police(self, partition_id: str, silent: bool) -> dict[str, Any] | None:
751
844
  LOGGER.debug("MQTT: Sending panel_trigger_police command")
752
845
 
@@ -837,28 +930,28 @@ class QolsysController:
837
930
  LOGGER.debug("MQTT: Receiving panel_trigger_fire command")
838
931
  return response
839
932
 
840
- async def command_zwave_switch_binary_set(self, node_id: str, status: bool) -> dict[str, Any] | None:
841
- LOGGER.debug("MQTT: Sending set_zwave_switch_binary command - Node(%s) - Status(%s)", node_id, status)
933
+ async def command_zwave_switch_binary_set(self, node_id: str, endpoint: str, status: bool) -> dict[str, Any] | None:
934
+ LOGGER.debug("MQTT: Sending set_zwave_switch_binar#y command - Node(%s) - Status(%s)", node_id, status)
842
935
  zwave_node = self.state.zwave_device(node_id)
843
936
 
844
937
  if not zwave_node:
845
938
  LOGGER.error("switch_binary_set - Invalid node_id %s", node_id)
846
939
  return None
847
940
 
848
- if zwave_node.generic_device_type not in (ZwaveDeviceClass.SwitchBinary, ZwaveDeviceClass.RemoteSwitchBinary):
849
- LOGGER.error("switch_binary_set used on invalid %s", zwave_node.generic_device_type)
941
+ if ZwaveCommandClass.SwitchBinary not in zwave_node.command_class_list:
942
+ LOGGER.error("Z-Wave node does not support switch_binary_set")
850
943
  return None
851
944
 
852
945
  level = 0
853
946
  if status:
854
947
  level = 255
855
948
 
856
- command = MQTTCommand_ZWave(self, node_id, [ZwaveCommandClass.SwitchBinary, 1, level])
949
+ command = MQTTCommand_ZWave(self, node_id, endpoint, [ZwaveCommandClass.SwitchBinary, 1, level])
857
950
  response = await command.send_command()
858
951
  LOGGER.debug("MQTT: Receiving set_zwave_switch_binary command")
859
952
  return response
860
953
 
861
- async def command_zwave_switch_multilevel_set(self, node_id: str, level: int) -> dict[str, Any] | None:
954
+ async def command_zwave_switch_multilevel_set(self, node_id: str, endpoint: str, level: int) -> dict[str, Any] | None:
862
955
  LOGGER.debug("MQTT: Sending switch_multilevel_set command - Node(%s) - Level(%s)", node_id, level)
863
956
 
864
957
  zwave_node = self.state.zwave_device(node_id)
@@ -866,16 +959,16 @@ class QolsysController:
866
959
  LOGGER.error("switch_multilevel_set - Invalid node_id %s", node_id)
867
960
  return None
868
961
 
869
- if zwave_node.generic_device_type not in (ZwaveDeviceClass.SwitchMultilevel, ZwaveDeviceClass.RemoteSwitchMultilevel):
870
- LOGGER.error("switch_multilevel_set used on invalid %s", zwave_node.generic_device_type)
962
+ if ZwaveCommandClass.SwitchMultilevel not in zwave_node.command_class_list:
963
+ LOGGER.error("Z-Wave node does not support switch_multilevel_set")
871
964
  return None
872
965
 
873
- command = MQTTCommand_ZWave(self, node_id, [ZwaveCommandClass.SwitchMultilevel, 1, level])
966
+ command = MQTTCommand_ZWave(self, node_id, endpoint, [ZwaveCommandClass.SwitchMultilevel, 1, level])
874
967
  response = await command.send_command()
875
968
  LOGGER.debug("MQTT: Receiving set_zwave_multilevel_switch command")
876
969
  return response
877
970
 
878
- async def command_zwave_doorlock_set(self, node_id: str, locked: bool) -> dict[str, Any] | None:
971
+ async def command_zwave_doorlock_set(self, node_id: str, endpoint: str, locked: bool) -> dict[str, Any] | None:
879
972
  LOGGER.debug("MQTT: Sending zwave_doorlock_set command - Node(%s) - Locked(%s)", node_id, locked)
880
973
 
881
974
  zwave_node = self.state.zwave_device(node_id)
@@ -883,34 +976,68 @@ class QolsysController:
883
976
  LOGGER.error("doorlock_set - Invalid node_id %s", node_id)
884
977
  return None
885
978
 
979
+ if ZwaveCommandClass.DoorLock not in zwave_node.command_class_list:
980
+ LOGGER.error("Z-Wave node does not support zwave_doorlock_set")
981
+ return None
982
+
886
983
  # 0 unlocked, 255 locked
887
984
  lock_mode = 0
888
985
  if locked:
889
986
  lock_mode = 255
890
987
 
891
- command = MQTTCommand_ZWave(self, node_id, [ZwaveCommandClass.DoorLock, 1, lock_mode])
988
+ command = MQTTCommand_ZWave(self, node_id, endpoint, [ZwaveCommandClass.DoorLock, 1, lock_mode])
892
989
  response = await command.send_command()
893
990
  LOGGER.debug("MQTT: Receiving zwave_doorlock_set command")
894
991
  return response
895
992
 
896
993
  async def command_zwave_thermostat_setpoint_set(
897
- self, node_id: str, mode: ThermostatMode, setpoint: float
994
+ self, node_id: str, endpoint: str, mode: ThermostatSetpointMode, setpoint: int
898
995
  ) -> dict[str, Any] | None:
899
- LOGGER.debug(
900
- "MQTT: Sending zwave_thermostat_setpoint_set - Node(%s) - Mode(%s) - Setpoint(%s)", node_id, mode, setpoint
901
- )
902
-
903
996
  zwave_node = self.state.zwave_device(node_id)
904
997
  if not zwave_node:
905
998
  LOGGER.error("thermostat_setpoint_set - Invalid node_id %s", node_id)
906
999
  return None
907
1000
 
908
- command = MQTTCommand_ZWave(self, node_id, [ZwaveCommandClass.ThermostatSetPoint, 1, mode, int(setpoint)])
1001
+ if not isinstance(zwave_node, QolsysThermostat):
1002
+ LOGGER.error("thermostat_setpoint_set - Z-Wave node is not a thermostat %s", node_id)
1003
+ return None
1004
+
1005
+ scale: int = 0
1006
+ if zwave_node.thermostat_device_temp_unit == "F":
1007
+ scale = 1
1008
+
1009
+ precision: int = 1
1010
+ size: int = 2
1011
+ pss = (precision << 5) | (scale << 3) | size
1012
+ temp_int = int(round(setpoint * (10**precision)))
1013
+ temp_bytes = temp_int.to_bytes(size, byteorder="big", signed=True)
1014
+
1015
+ setpointmode = ThermostatSetpointMode.HEATING
1016
+ if mode == ThermostatSetpointMode.COOLING:
1017
+ setpointmode = mode
1018
+
1019
+ zwave_bytes: list[int] = [
1020
+ 0x43, # Thermostat Setpoint
1021
+ 0x01, # SET
1022
+ setpointmode.value,
1023
+ pss,
1024
+ ] + list(temp_bytes)
1025
+
1026
+ LOGGER.debug(
1027
+ "MQTT: Sending zwave_thermostat_setpoint_set - Node(%s) - Mode(%s) - Setpoint(%s): %s",
1028
+ node_id,
1029
+ mode.value,
1030
+ setpoint,
1031
+ zwave_bytes,
1032
+ )
1033
+ command = MQTTCommand_ZWave(self, node_id, endpoint, zwave_bytes)
909
1034
  response = await command.send_command()
910
- LOGGER.debug("MQTT: Receiving zwave_thermostat_mode_set command")
1035
+ LOGGER.debug("MQTT: Receiving zwave_thermostat_mode_set command:%s", response)
911
1036
  return response
912
1037
 
913
- async def command_zwave_thermostat_mode_set(self, node_id: str, mode: ThermostatMode) -> dict[str, Any] | None:
1038
+ async def command_zwave_thermostat_mode_set(
1039
+ self, node_id: str, endpoint: str, mode: ThermostatMode
1040
+ ) -> dict[str, Any] | None:
914
1041
  LOGGER.debug("MQTT: Sending zwave_thermostat_mode_set command - Node(%s) - Mode(%s)", node_id, mode)
915
1042
 
916
1043
  thermostat = self.state.zwave_thermostat(node_id)
@@ -918,16 +1045,17 @@ class QolsysController:
918
1045
  LOGGER.error("zwave_thermostat_mode_set - Invalid node_id %s", node_id)
919
1046
  return None
920
1047
 
921
- LOGGER.debug("thermostat_mode: %s", int(mode))
922
1048
  if mode not in thermostat.available_thermostat_mode():
923
1049
  LOGGER.error("thermostat_mode_set - Invalid mode %s", mode)
924
1050
 
925
- command = MQTTCommand_ZWave(self, node_id, [ZwaveCommandClass.ThermostatMode, 1, int(mode)])
1051
+ command = MQTTCommand_ZWave(self, node_id, endpoint, [ZwaveCommandClass.ThermostatMode, 1, int(mode)])
926
1052
  response = await command.send_command()
927
1053
  LOGGER.debug("MQTT: Receiving zwave_thermostat_mode_set command")
928
1054
  return response
929
1055
 
930
- async def command_zwave_thermostat_fan_mode_set(self, node_id: str, fan_mode: ThermostatFanMode) -> dict[str, Any] | None:
1056
+ async def command_zwave_thermostat_fan_mode_set(
1057
+ self, node_id: str, endpoint: str, fan_mode: ThermostatFanMode
1058
+ ) -> dict[str, Any] | None:
931
1059
  LOGGER.debug("MQTT: Sending zwave_thermostat_fan_mode_set command - Node(%s) - FanMode(%s)", node_id, fan_mode)
932
1060
 
933
1061
  zwave_node = self.state.zwave_device(node_id)
@@ -935,7 +1063,7 @@ class QolsysController:
935
1063
  LOGGER.error("thermostat_fan_mode_set - Invalid node_id %s", node_id)
936
1064
  return None
937
1065
 
938
- command = MQTTCommand_ZWave(self, node_id, [ZwaveCommandClass.ThermostatFanMode, 1, fan_mode])
1066
+ command = MQTTCommand_ZWave(self, node_id, endpoint, [ZwaveCommandClass.ThermostatFanMode, 1, fan_mode])
939
1067
  response = await command.send_command()
940
1068
  LOGGER.debug("MQTT: Receiving zwave_thermostat_fan_mode_set command")
941
1069
  return response
@@ -216,7 +216,7 @@ class QolsysDB:
216
216
  return partitions
217
217
 
218
218
  def get_adc_devices(self) -> list[dict[str, str]]:
219
- self.cursor.execute(f"SELECT * FROM {self.table_virtual_device.table} ORDER BY device_id")
219
+ self.cursor.execute(f"SELECT * FROM {self.table_virtual_device.table} ORDER BY CAST(device_id AS INTEGER)")
220
220
  self.db.commit()
221
221
 
222
222
  devices = []
@@ -228,7 +228,7 @@ class QolsysDB:
228
228
  return devices
229
229
 
230
230
  def get_zwave_devices(self) -> list[dict[str, str]]:
231
- self.cursor.execute(f"SELECT * FROM {self.table_zwave_node.table} ORDER BY node_id")
231
+ self.cursor.execute(f"SELECT * FROM {self.table_zwave_node.table} ORDER BY CAST(node_id AS INTEGER)")
232
232
  self.db.commit()
233
233
 
234
234
  devices = []
@@ -240,7 +240,7 @@ class QolsysDB:
240
240
  return devices
241
241
 
242
242
  def get_zwave_other_devices(self) -> list[dict[str, str]]:
243
- self.cursor.execute(f"SELECT * FROM {self.table_zwave_other.table} ORDER BY node_id")
243
+ self.cursor.execute(f"SELECT * FROM {self.table_zwave_other.table} ORDER BY CAST(node_id AS INTEGER)")
244
244
  self.db.commit()
245
245
 
246
246
  devices = []
@@ -252,7 +252,7 @@ class QolsysDB:
252
252
  return devices
253
253
 
254
254
  def get_locks(self) -> list[dict[str, str]]:
255
- self.cursor.execute(f"SELECT * FROM {self.table_doorlock.table} ORDER BY node_id")
255
+ self.cursor.execute(f"SELECT * FROM {self.table_doorlock.table} ORDER BY CAST(node_id AS INTEGER)")
256
256
  self.db.commit()
257
257
 
258
258
  locks = []
@@ -264,7 +264,7 @@ class QolsysDB:
264
264
  return locks
265
265
 
266
266
  def get_thermostats(self) -> list[dict[str, str]]:
267
- self.cursor.execute(f"SELECT * FROM {self.table_thermostat.table} ORDER BY node_id")
267
+ self.cursor.execute(f"SELECT * FROM {self.table_thermostat.table} ORDER BY CAST(node_id AS INTEGER)")
268
268
  self.db.commit()
269
269
 
270
270
  thermostats = []
@@ -276,7 +276,7 @@ class QolsysDB:
276
276
  return thermostats
277
277
 
278
278
  def get_dimmers(self) -> list[dict[str, str]]:
279
- self.cursor.execute(f"SELECT * FROM {self.table_dimmer.table} ORDER BY node_id")
279
+ self.cursor.execute(f"SELECT * FROM {self.table_dimmer.table} ORDER BY CAST(node_id AS INTEGER)")
280
280
  self.db.commit()
281
281
 
282
282
  dimmers = []
@@ -288,7 +288,7 @@ class QolsysDB:
288
288
  return dimmers
289
289
 
290
290
  def get_zones(self) -> list[dict[str, str]]:
291
- self.cursor.execute(f"SELECT * FROM {self.table_sensor.table} ORDER BY zoneid")
291
+ self.cursor.execute(f"SELECT * FROM {self.table_sensor.table} ORDER BY CAST(zoneid AS INTEGER)")
292
292
  self.db.commit()
293
293
 
294
294
  zones = []
@@ -390,7 +390,7 @@ class QolsysDB:
390
390
 
391
391
  return None
392
392
 
393
- def load_db(self, database: list[dict[str, Any]]) -> None:
393
+ def load_db(self, database: Any | None) -> None:
394
394
  self.clear_db()
395
395
 
396
396
  if not database:
@@ -65,6 +65,8 @@ class QolsysTableSensor(QolsysTable):
65
65
  "extras",
66
66
  "allowspeaker",
67
67
  "firmware_version",
68
+ "radio_version",
69
+ "radio_id",
68
70
  ]
69
71
 
70
72
  self._create_table()
@@ -12,10 +12,21 @@ class QolsysTableSmartSocket(QolsysTable):
12
12
  self._uri = "content://com.qolsys.qolsysprovider.SmartSocketsContentProvider/smartsocket"
13
13
  self._table = "smartsocket"
14
14
  self._abort_on_error = False
15
- self._implemented = False
15
+ self._implemented = True
16
16
 
17
17
  self._columns = [
18
18
  "_id",
19
+ "created_by",
20
+ "created_date",
21
+ "last_updated_date",
22
+ "node_id",
23
+ "paired_status",
24
+ "power_usage",
25
+ "status",
26
+ "updated_by",
27
+ "socket_id",
28
+ "socket_name",
29
+ "current_usagevoltage_usage",
19
30
  ]
20
31
 
21
32
  self._create_table()
@@ -49,6 +49,7 @@ class QolsysTableThermostat(QolsysTable):
49
49
  "paired_status",
50
50
  "configuration_parameter",
51
51
  "operating_state",
52
+ "fan_state",
52
53
  ]
53
54
 
54
55
  self._create_table()
@@ -39,6 +39,7 @@ class QolsysTableUser(QolsysTable):
39
39
  "tag_flag",
40
40
  "check_in_time",
41
41
  "user_feature1",
42
+ "user_feature2",
42
43
  ]
43
44
 
44
45
  self._create_table()
@@ -51,12 +51,12 @@ class QolsysTableZwaveNode(QolsysTable):
51
51
  "node_battery_level_updated_time",
52
52
  "basic_report_updated_time",
53
53
  "switch_multilevel_report_updated_time",
54
- "multi_channel_details",
55
54
  "rediscover_status",
56
55
  "last_rediscover_time",
57
56
  "neighbour_info",
58
57
  "last_node_test_time",
59
58
  "notification_capabilities",
59
+ "multi_channel_details",
60
60
  "endpoint",
61
61
  "endpoint_details",
62
62
  "device_wakeup_time",
@@ -80,6 +80,8 @@ class QolsysTableZwaveNode(QolsysTable):
80
80
  "long_range_nodeid",
81
81
  "hide_device_info",
82
82
  "meter_capabilities",
83
+ "multisensor_capabilities",
84
+ "central_scene_supported",
83
85
  ]
84
86
 
85
87
  self._create_table()
@@ -1,6 +1,16 @@
1
1
  from enum import StrEnum
2
2
 
3
3
 
4
+ class QolsysEvent(StrEnum):
5
+ EVENT_PANEL_PARTITION_ADD = "EVENT_PANEL_PARTITION_ADD"
6
+ EVENT_PANEL_ZONE_ADD = "EVENT_PANEL_ZONE_ADD"
7
+ EVENT_PANEL_DOORBELL = "EVENT_PANEL_DOORBELL"
8
+ EVENT_PANEL_CHIME = "EVENT_PANEL_CHIME"
9
+ EVENT_ZWAVE_DEVICE_ADD = "EVENT_ZWAVE_MULTILEVELSENSOR_ADD"
10
+ EVENT_ZWAVE_MULTILEVELSENSOR_ADD = "EVENT_ZWAVE_MULTILEVELSENSOR_ADD"
11
+ EVENT_ZWAVE_METER_ADD = "EVENT_ZWAVE_METER_ADD"
12
+
13
+
4
14
  class PartitionSystemStatus(StrEnum):
5
15
  ARM_STAY = "ARM-STAY"
6
16
  ARM_AWAY = "ARM-AWAY"
@@ -48,22 +58,24 @@ class PartitionAlarmType(StrEnum):
48
58
 
49
59
 
50
60
  class ZoneStatus(StrEnum):
61
+ ACTIVE = "Active"
62
+ ACTIVATED = "Activated"
51
63
  ALARMED = "Alarmed"
52
- OPEN = "Open"
64
+ ARM_AWAY = "Arm-Away"
65
+ ARM_STAY = "Arm-Stay"
53
66
  CLOSED = "Closed"
54
- ACTIVE = "Active"
67
+ CONNECTED = "connected"
68
+ DISARM = "Disarm"
69
+ OPEN = "Open"
55
70
  INACTIVE = "Inactive"
56
- ACTIVATED = "Activated"
57
71
  IDLE = "Idle"
58
72
  NORMAL = "Normal"
59
73
  UNREACHABLE = "Unreachable"
60
74
  TAMPERED = "Tampered"
61
75
  SYNCHRONIZING = "Synchronizing"
62
- CONNECTED = "connected"
63
76
  DISCONNECTED = "disconnected"
64
77
  FAILURE = "Failure"
65
78
  NOT_NETWORKED = "Not Networked"
66
- DISARM = "Disarm"
67
79
 
68
80
 
69
81
  class DeviceCapability(StrEnum):
@@ -71,37 +83,39 @@ class DeviceCapability(StrEnum):
71
83
  WIFI = "WiFi"
72
84
  POWERG = "POWERG"
73
85
  ZWAVE = "Z-Wave"
86
+ S_LINE = "S-Line"
74
87
 
75
88
 
76
89
  class ZoneSensorType(StrEnum):
77
- DOOR_WINDOW = "Door_Window"
90
+ AUXILIARY_PENDANT = "Auxiliary Pendant"
91
+ BLUETOOTH = "Bluetooth"
92
+ CO_DETECTOR = "CODetector"
78
93
  DOORBELL = "Doorbell"
79
- MOTION = "Motion"
94
+ DOOR_WINDOW = "Door_Window"
95
+ DOOR_WINDOW_M = "Door_Window_M"
96
+ FREEZE = "Freeze"
80
97
  GLASS_BREAK = "GlassBreak"
98
+ # HARDWIRE_TRANSLATOR = "" # TBD
99
+ HEAT = "Heat"
100
+ # HIGH_TEMPERATURE = "" # TBD
81
101
  KEY_FOB = "KeyFob"
82
102
  KEYPAD = "Keypad"
83
- AUXILIARY_PENDANT = "Auxiliary Pendant"
84
- SMOKE_DETECTOR = "SmokeDetector"
85
- CO_DETECTOR = "CODetector"
86
- # HARDWIRE_TRANSLATOR = "" # TBD
103
+ MOTION = "Motion"
104
+ # OCCUPANCY = "" #TBD
105
+ PANEL_GLASS_BREAK = "Panel Glass Break"
106
+ PANEL_MOTION = "Panel Motion"
87
107
  # WIRELESS_TRANSLATOR = "" #TBD
88
- TEMPERATURE = "Temperature"
89
- HEAT = "Heat"
90
- WATER = "Water"
108
+ SIREN = "Siren"
91
109
  SHOCK = "Shock"
92
- FREEZE = "Freeze"
93
- TILT = "Tilt"
110
+ SMOKE_DETECTOR = "SmokeDetector"
94
111
  SMOKE_M = "Smoke_M"
95
- # DOOR_WINDOW_M = "" #TBD
96
- # OCCUPANCY = "" #TBD
97
- SIREN = "Siren"
98
- # HIGH_TEMPERATURE = "" # TBD
99
- PANEL_MOTION = "Panel Motion"
100
- PANEL_GLASS_BREAK = "Panel Glass Break"
101
- BLUETOOTH = "Bluetooth"
102
112
  TAKEOVER_MODULE = "TakeoverModule"
103
- TRANSLATOR = "Translator"
104
113
  TAMPER = "Tamper Sensor"
114
+ TEMPERATURE = "Temperature"
115
+ TILT = "Tilt"
116
+ TRANSLATOR = "Translator"
117
+ UNKNOWN = "Unknown"
118
+ WATER = "Water"
105
119
 
106
120
 
107
121
  class ZoneSensorGroup(StrEnum):