qolsys-controller 0.0.81__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.81 → qolsys_controller-0.3.2}/PKG-INFO +1 -1
  3. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/pyproject.toml +1 -1
  4. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/adc_service_garagedoor.py +1 -1
  5. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/controller.py +137 -93
  6. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/db.py +8 -8
  7. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_sensor.py +2 -0
  8. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_smartsocket.py +12 -1
  9. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_user.py +1 -0
  10. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/enum.py +38 -25
  11. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/enum_zwave.py +65 -23
  12. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/mqtt_command.py +2 -1
  13. qolsys_controller-0.3.2/qolsys_controller/observable_v2.py +14 -0
  14. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/panel.py +47 -18
  15. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/state.py +105 -24
  16. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/zone.py +74 -12
  17. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/zwave_device.py +82 -8
  18. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/zwave_dimmer.py +6 -10
  19. qolsys_controller-0.3.2/qolsys_controller/zwave_energy_clamp.py +19 -0
  20. qolsys_controller-0.3.2/qolsys_controller/zwave_extenal_siren.py +86 -0
  21. qolsys_controller-0.3.2/qolsys_controller/zwave_garagedoor.py +20 -0
  22. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/zwave_generic.py +6 -2
  23. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/zwave_lock.py +15 -12
  24. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/zwave_service_meter.py +1 -1
  25. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/zwave_service_multilevelsensor.py +9 -0
  26. qolsys_controller-0.3.2/qolsys_controller/zwave_smart_socket.py +20 -0
  27. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/zwave_thermometer.py +6 -2
  28. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/zwave_thermostat.py +63 -9
  29. qolsys_controller-0.3.2/qolsys_controller/zwave_water_valve.py +83 -0
  30. qolsys_controller-0.3.2/requirements.txt +8 -0
  31. qolsys_controller-0.0.81/qolsys_controller/zwave_energy_clamp.py +0 -15
  32. qolsys_controller-0.0.81/qolsys_controller/zwave_garagedoor.py +0 -13
  33. qolsys_controller-0.0.81/qolsys_controller/zwave_outlet.py +0 -13
  34. qolsys_controller-0.0.81/requirements.txt +0 -8
  35. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/.github/workflows/build.yml +0 -0
  36. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/.github/workflows/publish.yml +0 -0
  37. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/.gitignore +0 -0
  38. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/Info_mqtt.md +0 -0
  39. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/LICENSE +0 -0
  40. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/README.md +0 -0
  41. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/bin/qolsys.py +0 -0
  42. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/example.py +0 -0
  43. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/info_pairing.md +0 -0
  44. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/info_qolsys.md +0 -0
  45. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/mypy.ini +0 -0
  46. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/__init__.py +0 -0
  47. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/adc_device.py +0 -0
  48. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/adc_service.py +0 -0
  49. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table.py +0 -0
  50. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_alarmedsensor.py +0 -0
  51. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_automation.py +0 -0
  52. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_country_locale.py +0 -0
  53. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_dashboard_msgs.py +0 -0
  54. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_dimmerlight.py +0 -0
  55. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_doorlock.py +0 -0
  56. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_eu_event.py +0 -0
  57. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_heat_map.py +0 -0
  58. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_history.py +0 -0
  59. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_iqremotesettings.py +0 -0
  60. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_iqrouter_network_config.py +0 -0
  61. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_iqrouter_user_device.py +0 -0
  62. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_master_slave.py +0 -0
  63. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_nest_device.py +0 -0
  64. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_output_rules.py +0 -0
  65. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_partition.py +0 -0
  66. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_pgm_outputs.py +0 -0
  67. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_powerg_device.py +0 -0
  68. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_qolsyssettings.py +0 -0
  69. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_scene.py +0 -0
  70. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_sensor_group.py +0 -0
  71. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_shades.py +0 -0
  72. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_state.py +0 -0
  73. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_tcc.py +0 -0
  74. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_thermostat.py +0 -0
  75. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_trouble_conditions.py +0 -0
  76. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_virtual_device.py +0 -0
  77. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_weather.py +0 -0
  78. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_zigbee_device.py +0 -0
  79. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_zwave_association_group.py +0 -0
  80. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_zwave_history.py +0 -0
  81. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_zwave_node.py +1 -1
  82. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/database/table_zwave_other.py +0 -0
  83. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/enum_adc.py +0 -0
  84. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/errors.py +0 -0
  85. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/mdns.py +0 -0
  86. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/mqtt_command_queue.py +0 -0
  87. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/observable.py +0 -0
  88. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/partition.py +0 -0
  89. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/pki.py +0 -0
  90. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/scene.py +0 -0
  91. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/settings.py +0 -0
  92. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/task_manager.py +0 -0
  93. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/users.py +0 -0
  94. {qolsys_controller-0.0.81 → qolsys_controller-0.3.2}/qolsys_controller/utils_mqtt.py +0 -0
  95. {qolsys_controller-0.0.81 → 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.81
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.81"
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
@@ -21,7 +21,7 @@ from qolsys_controller.mqtt_command import (
21
21
  from qolsys_controller.zwave_thermostat import QolsysThermostat
22
22
 
23
23
  from .enum import PartitionAlarmState, PartitionArmingType, PartitionSystemStatus
24
- from .enum_zwave import ThermostatFanMode, ThermostatMode, ZwaveCommandClass, ZwaveDeviceClass
24
+ from .enum_zwave import ThermostatFanMode, ThermostatMode, ThermostatSetpointMode, ZwaveCommandClass
25
25
  from .errors import QolsysMqttError, QolsysSslError, QolsysUserCodeError
26
26
  from .mdns import QolsysMDNS
27
27
  from .mqtt_command_queue import QolsysMqttCommandQueue
@@ -61,6 +61,7 @@ class QolsysController:
61
61
  self._mqtt_task_listen_label: str = "mqtt_task_listen"
62
62
  self._mqtt_task_connect_label: str = "mqtt_task_connect"
63
63
  self._mqtt_task_ping_label: str = "mqtt_task_ping"
64
+ self._mqtt_task_zwave_meter_update_label: str = "mqtt_task_zwave_meter_update"
64
65
 
65
66
  @property
66
67
  def state(self) -> QolsysState:
@@ -152,32 +153,43 @@ class QolsysController:
152
153
  self._task_manager.cancel(self._mqtt_task_listen_label)
153
154
  self._task_manager.cancel(self._mqtt_task_ping_label)
154
155
  self._task_manager.cancel(self._mqtt_task_config_label)
156
+ self._task_manager.cancel(self._mqtt_task_zwave_meter_update_label)
155
157
 
156
158
  self.connected = False
157
159
  self.connected_observer.notify()
158
160
 
159
161
  async def mqtt_connect_task(self, reconnect: bool, run_forever: bool) -> None:
160
- # Configure TLS parameters for MQTT connection
161
- tls_params = aiomqtt.TLSParameters(
162
- ca_certs=str(self._pki.qolsys_cer_file_path),
163
- certfile=str(self._pki.secure_file_path),
164
- keyfile=str(self._pki.key_file_path),
165
- cert_reqs=ssl.CERT_REQUIRED,
166
- tls_version=ssl.PROTOCOL_TLSv1_2,
167
- ciphers="ALL:@SECLEVEL=0",
168
- )
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)
169
180
 
170
181
  LOGGER.debug("MQTT: Connecting ...")
171
182
 
172
183
  self._task_manager.cancel(self._mqtt_task_listen_label)
173
184
  self._task_manager.cancel(self._mqtt_task_ping_label)
185
+ self._task_manager.cancel(self._mqtt_task_zwave_meter_update_label)
174
186
 
175
187
  while True:
176
188
  try:
177
189
  self.aiomqtt = aiomqtt.Client(
178
190
  hostname=self.settings.panel_ip,
179
191
  port=8883,
180
- tls_params=tls_params,
192
+ tls_context=ctx,
181
193
  tls_insecure=True,
182
194
  clean_session=True,
183
195
  timeout=self.settings.mqtt_timeout,
@@ -214,11 +226,14 @@ class QolsysController:
214
226
 
215
227
  response_database = await self.command_sync_database()
216
228
  LOGGER.debug("MQTT: Updating State from syncdatabase")
217
- self.panel.load_database(response_database.get("fulldbdata")) # type: ignore[arg-type]
229
+ await self.panel.load_database(response_database.get("fulldbdata"))
218
230
  self.panel.dump()
219
231
  self.state.dump()
220
232
 
221
233
  self.connected = True
234
+
235
+ self._task_manager.run(self.mqtt_zwave_meter_update(), self._mqtt_task_zwave_meter_update_label)
236
+
222
237
  self.connected_observer.notify()
223
238
 
224
239
  if not run_forever:
@@ -259,6 +274,19 @@ class QolsysController:
259
274
 
260
275
  await asyncio.sleep(self.settings.mqtt_ping)
261
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
+
262
290
  async def mqtt_listen_task(self) -> None:
263
291
  try:
264
292
  async for message in self.aiomqtt.messages: # type: ignore[union-attr]
@@ -748,6 +776,70 @@ class QolsysController:
748
776
  LOGGER.debug("MQTT: Receiving execute_scene command")
749
777
  return response
750
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
+
751
843
  async def command_panel_trigger_police(self, partition_id: str, silent: bool) -> dict[str, Any] | None:
752
844
  LOGGER.debug("MQTT: Sending panel_trigger_police command")
753
845
 
@@ -838,28 +930,28 @@ class QolsysController:
838
930
  LOGGER.debug("MQTT: Receiving panel_trigger_fire command")
839
931
  return response
840
932
 
841
- async def command_zwave_switch_binary_set(self, node_id: str, status: bool) -> dict[str, Any] | None:
842
- 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)
843
935
  zwave_node = self.state.zwave_device(node_id)
844
936
 
845
937
  if not zwave_node:
846
938
  LOGGER.error("switch_binary_set - Invalid node_id %s", node_id)
847
939
  return None
848
940
 
849
- if zwave_node.generic_device_type not in (ZwaveDeviceClass.SwitchBinary, ZwaveDeviceClass.RemoteSwitchBinary):
850
- 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")
851
943
  return None
852
944
 
853
945
  level = 0
854
946
  if status:
855
947
  level = 255
856
948
 
857
- command = MQTTCommand_ZWave(self, node_id, [ZwaveCommandClass.SwitchBinary, 1, level])
949
+ command = MQTTCommand_ZWave(self, node_id, endpoint, [ZwaveCommandClass.SwitchBinary, 1, level])
858
950
  response = await command.send_command()
859
951
  LOGGER.debug("MQTT: Receiving set_zwave_switch_binary command")
860
952
  return response
861
953
 
862
- 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:
863
955
  LOGGER.debug("MQTT: Sending switch_multilevel_set command - Node(%s) - Level(%s)", node_id, level)
864
956
 
865
957
  zwave_node = self.state.zwave_device(node_id)
@@ -867,16 +959,16 @@ class QolsysController:
867
959
  LOGGER.error("switch_multilevel_set - Invalid node_id %s", node_id)
868
960
  return None
869
961
 
870
- if zwave_node.generic_device_type not in (ZwaveDeviceClass.SwitchMultilevel, ZwaveDeviceClass.RemoteSwitchMultilevel):
871
- 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")
872
964
  return None
873
965
 
874
- command = MQTTCommand_ZWave(self, node_id, [ZwaveCommandClass.SwitchMultilevel, 1, level])
966
+ command = MQTTCommand_ZWave(self, node_id, endpoint, [ZwaveCommandClass.SwitchMultilevel, 1, level])
875
967
  response = await command.send_command()
876
968
  LOGGER.debug("MQTT: Receiving set_zwave_multilevel_switch command")
877
969
  return response
878
970
 
879
- 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:
880
972
  LOGGER.debug("MQTT: Sending zwave_doorlock_set command - Node(%s) - Locked(%s)", node_id, locked)
881
973
 
882
974
  zwave_node = self.state.zwave_device(node_id)
@@ -884,18 +976,22 @@ class QolsysController:
884
976
  LOGGER.error("doorlock_set - Invalid node_id %s", node_id)
885
977
  return None
886
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
+
887
983
  # 0 unlocked, 255 locked
888
984
  lock_mode = 0
889
985
  if locked:
890
986
  lock_mode = 255
891
987
 
892
- command = MQTTCommand_ZWave(self, node_id, [ZwaveCommandClass.DoorLock, 1, lock_mode])
988
+ command = MQTTCommand_ZWave(self, node_id, endpoint, [ZwaveCommandClass.DoorLock, 1, lock_mode])
893
989
  response = await command.send_command()
894
990
  LOGGER.debug("MQTT: Receiving zwave_doorlock_set command")
895
991
  return response
896
992
 
897
993
  async def command_zwave_thermostat_setpoint_set(
898
- self, node_id: str, mode: ThermostatMode, setpoint: int
994
+ self, node_id: str, endpoint: str, mode: ThermostatSetpointMode, setpoint: int
899
995
  ) -> dict[str, Any] | None:
900
996
  zwave_node = self.state.zwave_device(node_id)
901
997
  if not zwave_node:
@@ -903,7 +999,7 @@ class QolsysController:
903
999
  return None
904
1000
 
905
1001
  if not isinstance(zwave_node, QolsysThermostat):
906
- LOGGER.error("thermostat_setpoint_set - Z-Wane node is not a thermostat %s", node_id)
1002
+ LOGGER.error("thermostat_setpoint_set - Z-Wave node is not a thermostat %s", node_id)
907
1003
  return None
908
1004
 
909
1005
  scale: int = 0
@@ -916,85 +1012,32 @@ class QolsysController:
916
1012
  temp_int = int(round(setpoint * (10**precision)))
917
1013
  temp_bytes = temp_int.to_bytes(size, byteorder="big", signed=True)
918
1014
 
919
- zwave_bytes: list[int] = [
920
- 0x43, # Thermostat Setpoint
921
- 0x03, # SET
922
- mode.value, # Heating
923
- pss,
924
- ] + list(temp_bytes)
925
-
926
- zwave_bytes2: list[int] = [
927
- 0x43, # Thermostat Setpoint
928
- 0x01, # SET
929
- mode.value, # Heating
930
- pss,
931
- ] + list(temp_bytes)
1015
+ setpointmode = ThermostatSetpointMode.HEATING
1016
+ if mode == ThermostatSetpointMode.COOLING:
1017
+ setpointmode = mode
932
1018
 
933
- zwave_bytes3: list[int] = [
1019
+ zwave_bytes: list[int] = [
934
1020
  0x43, # Thermostat Setpoint
935
1021
  0x01, # SET
936
- 0x03, # Furnace
937
- pss,
938
- ] + list(temp_bytes)
939
-
940
- zwave_bytes4: list[int] = [
941
- 0x43, # Thermostat Setpoint
942
- 0x03, # SET
943
- 0x03, # Furnace
1022
+ setpointmode.value,
944
1023
  pss,
945
1024
  ] + list(temp_bytes)
946
1025
 
947
1026
  LOGGER.debug(
948
- "MQTT: Sending zwave_thermostat_setpoint_set 0x03 - Node(%s) - Mode(%s) - Setpoint(%s): %s",
1027
+ "MQTT: Sending zwave_thermostat_setpoint_set - Node(%s) - Mode(%s) - Setpoint(%s): %s",
949
1028
  node_id,
950
- mode,
1029
+ mode.value,
951
1030
  setpoint,
952
1031
  zwave_bytes,
953
1032
  )
954
- command = MQTTCommand_ZWave(self, node_id, zwave_bytes)
1033
+ command = MQTTCommand_ZWave(self, node_id, endpoint, zwave_bytes)
955
1034
  response = await command.send_command()
956
1035
  LOGGER.debug("MQTT: Receiving zwave_thermostat_mode_set command:%s", response)
957
-
958
- await asyncio.sleep(3)
959
-
960
- LOGGER.debug(
961
- "MQTT: Sending zwave_thermostat_setpoint_set 0x01 - Node(%s) - Mode(%s) - Setpoint(%s): %s",
962
- node_id,
963
- mode,
964
- setpoint,
965
- zwave_bytes2,
966
- )
967
- command2 = MQTTCommand_ZWave(self, node_id, zwave_bytes2)
968
- response2 = await command2.send_command()
969
- LOGGER.debug("MQTT: Receiving zwave_thermostat_mode_set command:%s", response2)
970
-
971
- await asyncio.sleep(3)
972
-
973
- LOGGER.debug(
974
- "MQTT: Sending zwave_thermostat_setpoint_set 0x01 - Node(%s) - Mode(0x03) - Setpoint(%s): %s",
975
- node_id,
976
- setpoint,
977
- zwave_bytes3,
978
- )
979
- command2 = MQTTCommand_ZWave(self, node_id, zwave_bytes2)
980
- response2 = await command2.send_command()
981
- LOGGER.debug("MQTT: Receiving zwave_thermostat_mode_set command:%s", response2)
982
-
983
- await asyncio.sleep(3)
984
-
985
- LOGGER.debug(
986
- "MQTT: Sending zwave_thermostat_setpoint_set 0x03 - Node(%s) - Mode(0x3) - Setpoint(%s): %s",
987
- node_id,
988
- setpoint,
989
- zwave_bytes4,
990
- )
991
- command2 = MQTTCommand_ZWave(self, node_id, zwave_bytes2)
992
- response2 = await command2.send_command()
993
- LOGGER.debug("MQTT: Receiving zwave_thermostat_mode_set command:%s", response2)
994
-
995
1036
  return response
996
1037
 
997
- 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:
998
1041
  LOGGER.debug("MQTT: Sending zwave_thermostat_mode_set command - Node(%s) - Mode(%s)", node_id, mode)
999
1042
 
1000
1043
  thermostat = self.state.zwave_thermostat(node_id)
@@ -1002,16 +1045,17 @@ class QolsysController:
1002
1045
  LOGGER.error("zwave_thermostat_mode_set - Invalid node_id %s", node_id)
1003
1046
  return None
1004
1047
 
1005
- LOGGER.debug("thermostat_mode: %s", int(mode))
1006
1048
  if mode not in thermostat.available_thermostat_mode():
1007
1049
  LOGGER.error("thermostat_mode_set - Invalid mode %s", mode)
1008
1050
 
1009
- command = MQTTCommand_ZWave(self, node_id, [ZwaveCommandClass.ThermostatMode, 1, int(mode)])
1051
+ command = MQTTCommand_ZWave(self, node_id, endpoint, [ZwaveCommandClass.ThermostatMode, 1, int(mode)])
1010
1052
  response = await command.send_command()
1011
1053
  LOGGER.debug("MQTT: Receiving zwave_thermostat_mode_set command")
1012
1054
  return response
1013
1055
 
1014
- 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:
1015
1059
  LOGGER.debug("MQTT: Sending zwave_thermostat_fan_mode_set command - Node(%s) - FanMode(%s)", node_id, fan_mode)
1016
1060
 
1017
1061
  zwave_node = self.state.zwave_device(node_id)
@@ -1019,7 +1063,7 @@ class QolsysController:
1019
1063
  LOGGER.error("thermostat_fan_mode_set - Invalid node_id %s", node_id)
1020
1064
  return None
1021
1065
 
1022
- command = MQTTCommand_ZWave(self, node_id, [ZwaveCommandClass.ThermostatFanMode, 1, fan_mode])
1066
+ command = MQTTCommand_ZWave(self, node_id, endpoint, [ZwaveCommandClass.ThermostatFanMode, 1, fan_mode])
1023
1067
  response = await command.send_command()
1024
1068
  LOGGER.debug("MQTT: Receiving zwave_thermostat_fan_mode_set command")
1025
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()
@@ -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()
@@ -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,23 +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
- ARM_AWAY = "Arm-Away"
68
79
 
69
80
 
70
81
  class DeviceCapability(StrEnum):
@@ -72,37 +83,39 @@ class DeviceCapability(StrEnum):
72
83
  WIFI = "WiFi"
73
84
  POWERG = "POWERG"
74
85
  ZWAVE = "Z-Wave"
86
+ S_LINE = "S-Line"
75
87
 
76
88
 
77
89
  class ZoneSensorType(StrEnum):
78
- DOOR_WINDOW = "Door_Window"
90
+ AUXILIARY_PENDANT = "Auxiliary Pendant"
91
+ BLUETOOTH = "Bluetooth"
92
+ CO_DETECTOR = "CODetector"
79
93
  DOORBELL = "Doorbell"
80
- MOTION = "Motion"
94
+ DOOR_WINDOW = "Door_Window"
95
+ DOOR_WINDOW_M = "Door_Window_M"
96
+ FREEZE = "Freeze"
81
97
  GLASS_BREAK = "GlassBreak"
98
+ # HARDWIRE_TRANSLATOR = "" # TBD
99
+ HEAT = "Heat"
100
+ # HIGH_TEMPERATURE = "" # TBD
82
101
  KEY_FOB = "KeyFob"
83
102
  KEYPAD = "Keypad"
84
- AUXILIARY_PENDANT = "Auxiliary Pendant"
85
- SMOKE_DETECTOR = "SmokeDetector"
86
- CO_DETECTOR = "CODetector"
87
- # HARDWIRE_TRANSLATOR = "" # TBD
103
+ MOTION = "Motion"
104
+ # OCCUPANCY = "" #TBD
105
+ PANEL_GLASS_BREAK = "Panel Glass Break"
106
+ PANEL_MOTION = "Panel Motion"
88
107
  # WIRELESS_TRANSLATOR = "" #TBD
89
- TEMPERATURE = "Temperature"
90
- HEAT = "Heat"
91
- WATER = "Water"
108
+ SIREN = "Siren"
92
109
  SHOCK = "Shock"
93
- FREEZE = "Freeze"
94
- TILT = "Tilt"
110
+ SMOKE_DETECTOR = "SmokeDetector"
95
111
  SMOKE_M = "Smoke_M"
96
- # DOOR_WINDOW_M = "" #TBD
97
- # OCCUPANCY = "" #TBD
98
- SIREN = "Siren"
99
- # HIGH_TEMPERATURE = "" # TBD
100
- PANEL_MOTION = "Panel Motion"
101
- PANEL_GLASS_BREAK = "Panel Glass Break"
102
- BLUETOOTH = "Bluetooth"
103
112
  TAKEOVER_MODULE = "TakeoverModule"
104
- TRANSLATOR = "Translator"
105
113
  TAMPER = "Tamper Sensor"
114
+ TEMPERATURE = "Temperature"
115
+ TILT = "Tilt"
116
+ TRANSLATOR = "Translator"
117
+ UNKNOWN = "Unknown"
118
+ WATER = "Water"
106
119
 
107
120
 
108
121
  class ZoneSensorGroup(StrEnum):