qolsys-controller 0.0.62__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.

Files changed (37) hide show
  1. qolsys_controller/adc_device.py +202 -0
  2. qolsys_controller/adc_service.py +139 -0
  3. qolsys_controller/adc_service_garagedoor.py +35 -0
  4. qolsys_controller/controller.py +245 -34
  5. qolsys_controller/database/db.py +60 -0
  6. qolsys_controller/database/table.py +1 -0
  7. qolsys_controller/database/table_alarmedsensor.py +2 -0
  8. qolsys_controller/database/table_history.py +2 -2
  9. qolsys_controller/database/table_smartsocket.py +12 -1
  10. qolsys_controller/database/table_thermostat.py +3 -0
  11. qolsys_controller/database/table_virtual_device.py +13 -1
  12. qolsys_controller/database/table_zwave_node.py +3 -0
  13. qolsys_controller/enum.py +5 -1
  14. qolsys_controller/enum_adc.py +28 -0
  15. qolsys_controller/enum_zwave.py +158 -29
  16. qolsys_controller/errors.py +5 -0
  17. qolsys_controller/mqtt_command.py +6 -0
  18. qolsys_controller/panel.py +109 -5
  19. qolsys_controller/partition.py +22 -2
  20. qolsys_controller/state.py +163 -1
  21. qolsys_controller/task_manager.py +4 -3
  22. qolsys_controller/zone.py +2 -1
  23. qolsys_controller/zwave_device.py +132 -4
  24. qolsys_controller/zwave_dimmer.py +3 -0
  25. qolsys_controller/zwave_energy_clamp.py +15 -0
  26. qolsys_controller/zwave_garagedoor.py +3 -0
  27. qolsys_controller/zwave_generic.py +3 -0
  28. qolsys_controller/zwave_lock.py +4 -0
  29. qolsys_controller/zwave_outlet.py +3 -0
  30. qolsys_controller/zwave_service_meter.py +192 -0
  31. qolsys_controller/zwave_service_multilevelsensor.py +119 -0
  32. qolsys_controller/zwave_thermometer.py +21 -0
  33. qolsys_controller/zwave_thermostat.py +150 -38
  34. {qolsys_controller-0.0.62.dist-info → qolsys_controller-0.0.87.dist-info}/METADATA +1 -1
  35. {qolsys_controller-0.0.62.dist-info → qolsys_controller-0.0.87.dist-info}/RECORD +37 -29
  36. {qolsys_controller-0.0.62.dist-info → qolsys_controller-0.0.87.dist-info}/WHEEL +0 -0
  37. {qolsys_controller-0.0.62.dist-info → qolsys_controller-0.0.87.dist-info}/licenses/LICENSE +0 -0
@@ -1,49 +1,178 @@
1
1
  from enum import Enum, IntEnum
2
2
 
3
3
 
4
+ class MeterType(IntEnum):
5
+ UNKNOWN = 0x00
6
+ ELECTRIC_METER = 0x01
7
+ GAZ_METER = 0x02
8
+ WATER_METER = 0x03
9
+ HEATING = 0x04
10
+ COOLING = 0x05
11
+ RESERVED = 0x6
12
+
13
+
14
+ class MeterRateType(IntEnum):
15
+ UNSPECIFIED = 0x00
16
+ IMPORT = 0x01
17
+ EXPORT = 0x02
18
+ RESERVED = 0x03
19
+
20
+
21
+ class ZWaveMultilevelSensorScale(Enum):
22
+ TEMPERATURE_CELSIUS = "temperature_celsius"
23
+ TEMPERATURE_FAHRENHEIT = "temperature_fahrenheit"
24
+ RELATIVE_HUMIDITY = "relative_humidity"
25
+ WIND_DIRECTION = "wind_direction"
26
+ BAROMETRIC_PRESSURE = "barometric_pressure"
27
+ DEW_POINT = "dew_point"
28
+ RAIN_RATE = "rain_rate"
29
+ TIDE_LEVEL = "tide_level"
30
+ WEIGHT = "weight"
31
+ VOLTS = "volts"
32
+ AMPS = "amps"
33
+ WATTS = "watts"
34
+ DISTANCE = "distance"
35
+ ANGLE_POSITION = "angle_position"
36
+ ROTATION = "rotation"
37
+ WATER_TEMPERATURE_CELSIUS = "water_temperature_celsius"
38
+ WATER_TEMPERATURE_FAHRENHEIT = "water_temperature_fahrenheit"
39
+ LUMINOSITY_LUX = "luminosity_lux"
40
+ UNKNOWN = "unknown"
41
+
42
+
43
+ class ZWaveUnknownMeterScale(IntEnum):
44
+ UNKNOWN = 0
45
+
46
+
47
+ class ZWaveElectricMeterScale(IntEnum):
48
+ KWH = 0
49
+ KVAH = 1
50
+ WATTS = 2
51
+ PULSE_COUNT = 3
52
+ VOLTS = 4
53
+ AMPS = 5
54
+ POWER_FACTOR = 6
55
+ KVAR = 7
56
+ KVARH = 8
57
+
58
+
59
+ class ZWaveGasMeterScale(IntEnum):
60
+ CUBIC_METERS = 0
61
+ CUBIC_FEET = 1
62
+ PULSE_COUNT = 3
63
+
64
+
65
+ class ZWaveWaterMeterScale(IntEnum):
66
+ CUBIC_METERS = 0
67
+ CUBIC_FEET = 1
68
+ US_GALLONS = 2
69
+ PULSE_COUNT = 3
70
+
71
+
72
+ class ZWaveThermalMeterScale(IntEnum):
73
+ KWH = 0
74
+ PULSE_COUNT = 3
75
+
76
+
4
77
  class ThermostatMode(IntEnum):
5
- OFF = 0x0001
6
- HEAT = 0x0002
7
- COOL = 0x0004
8
- AUTO = 0x0008
9
- AUX_HEAT = 0x0010
10
- RESUME = 0x0020
11
- FAN_ONLY = 0x0040
12
- FURNACE = 0x0080
13
- DRY_AIR = 0x0100
14
- MOIST_AIR = 0x0200
15
- AUTO_CHANGEOVER = 0x0400
16
- ENERGY_SAVE_HEAT = 0x0800
17
- ENERGY_SAVE_COOL = 0x1000
18
- AWAY = 0x2000
19
- FULL_POWER = 0x4000
20
- MANUFACTURER_SPECEFIC = 0x8000
78
+ OFF = 0x00
79
+ HEAT = 0x01
80
+ COOL = 0x02
81
+ AUTO = 0x03
82
+ AUX_HEAT = 0x04
83
+ RESUME = 0x05
84
+ FAN_ONLY = 0x06
85
+ FURNACE = 0x07
86
+ DRY_AIR = 0x08
87
+ MOIST_AIR = 0x09
88
+ AUTO_CHANGEOVER = 0x0A
89
+ ENERGY_SAVE_HEAT = 0x0B
90
+ ENERGY_SAVE_COOL = 0x0C
91
+ AWAY = 0x0F
92
+
93
+
94
+ BITMASK_SUPPORTED_THERMOSTAT_MODE = {
95
+ 0: ThermostatMode.OFF,
96
+ 1: ThermostatMode.HEAT,
97
+ 2: ThermostatMode.COOL,
98
+ 3: ThermostatMode.AUTO,
99
+ 4: ThermostatMode.AUX_HEAT,
100
+ 5: ThermostatMode.RESUME,
101
+ 6: ThermostatMode.FAN_ONLY,
102
+ 7: ThermostatMode.FURNACE,
103
+ }
104
+
105
+
106
+ class ThermostatSetpointMode(IntEnum):
107
+ HEATING = 0x01
108
+ COOLING = 0x02
109
+ FURNACE = 0x03
110
+ DRY_AIR = 0x04
111
+ MOIST_AIR = 0x05
112
+ AUTO_CHANGEOVER = 0x06
113
+ ECO_HEATING = 0x07
114
+ ECO_COOLING = 0x08
115
+ AWAY_HEATING = 0x09
116
+ AWAY_COOLING = 0x10
117
+
118
+
119
+ BITMASK_SUPPORTED_THERMOSTAT_SETPOINT_MODE = {
120
+ 0: ThermostatSetpointMode.HEATING,
121
+ 1: ThermostatSetpointMode.COOLING,
122
+ 2: ThermostatSetpointMode.FURNACE,
123
+ 3: ThermostatSetpointMode.DRY_AIR,
124
+ 4: ThermostatSetpointMode.MOIST_AIR,
125
+ 5: ThermostatSetpointMode.AUTO_CHANGEOVER,
126
+ 6: ThermostatSetpointMode.ECO_HEATING,
127
+ 7: ThermostatSetpointMode.ECO_COOLING,
128
+ 8: ThermostatSetpointMode.AWAY_HEATING,
129
+ 9: ThermostatSetpointMode.AWAY_COOLING,
130
+ }
21
131
 
22
132
 
23
133
  class ThermostatFanMode(IntEnum):
24
- AUTO_LOW = 0x0001
25
- LOW = 0x0002
26
- AUTO_HIGH = 0x0004
27
- HIGH = 0x0008
28
- AUTO_MEDIUM = 0x0010
29
- MEDIUM = 0x0020
30
- CIRCULATION = 0x4000
31
- HUMIDITY_CIRCULATION = 0x0080
32
- LEFT_RIGHT = 0x0100
33
- UP_DOWN = 0x0200
34
- QUIET = 0x0400
134
+ AUTO_LOW = 0x00
135
+ LOW = 0x01
136
+ AUTO_HIGH = 0x02
137
+ HIGH = 0x03
138
+ AUTO_MEDIUM = 0x04
139
+ MEDIUM = 0x05
140
+ CIRCULATION = 0x06
141
+ HUMIDITY_CIRCULATION = 0x07
142
+ LEFT_RIGHT = 0x08
143
+ UP_DOWN = 0x09
144
+ QUIET = 0x0A
35
145
  EXTERNAL_CIRCULATION = 0x0800
36
- MANUFACTURER_SPECEFIC = 0x1000
37
146
 
38
147
 
39
- class ZwaveCommand(IntEnum):
148
+ BITMASK_SUPPORTED_THERMOSTAT_FAN_MODE = {
149
+ 0: ThermostatFanMode.AUTO_LOW,
150
+ 1: ThermostatFanMode.LOW,
151
+ 2: ThermostatFanMode.AUTO_HIGH,
152
+ 3: ThermostatFanMode.HIGH,
153
+ 4: ThermostatFanMode.AUTO_MEDIUM,
154
+ 5: ThermostatFanMode.MEDIUM,
155
+ 6: ThermostatFanMode.CIRCULATION,
156
+ 7: ThermostatFanMode.HUMIDITY_CIRCULATION,
157
+ }
158
+
159
+
160
+ class ZwaveCommandClass(IntEnum):
40
161
  SwitchBinary = 0x25
41
162
  SwitchMultilevel = 0x26
163
+ SensorMultiLevel = 0x31
164
+ Meter = 0x32
42
165
  ThermostatMode = 0x40
43
166
  ThermostatSetPoint = 0x43
44
167
  ThermostatFanMode = 0x44
45
168
  ThermostatFanState = 0x45
46
169
  DoorLock = 0x62
170
+ Alarm = 0x71
171
+
172
+
173
+ class ZwaveCommand(IntEnum):
174
+ SET = 0x01
175
+ GET = 0x02
47
176
 
48
177
 
49
178
  class ZwaveDeviceClass(Enum):
@@ -9,6 +9,11 @@ class QolsysError(Exception):
9
9
  super().__init__(*args, **kwargs)
10
10
 
11
11
 
12
+ class QolsysUserCodeError(QolsysError):
13
+ def __init__(self) -> None:
14
+ super().__init__("QolsysUserCodeError")
15
+
16
+
12
17
  class QolsysSslError(QolsysError):
13
18
  def __init__(self) -> None:
14
19
  super().__init__("QolsysSslError")
@@ -91,26 +91,32 @@ class MQTTCommand_ZWave(MQTTCommand_IpcCall):
91
91
 
92
92
  ipc_request: list[dict[str, Any]] = [
93
93
  {
94
+ # Node ID
94
95
  "dataType": "int",
95
96
  "dataValue": int(node_id),
96
97
  },
97
98
  {
99
+ # End Point
98
100
  "dataType": "int",
99
101
  "dataValue": 0,
100
102
  },
101
103
  {
104
+ # Z-Wave Payload
102
105
  "dataType": "byteArray",
103
106
  "dataValue": zwave_command,
104
107
  },
105
108
  {
109
+ # Transmit option ?
106
110
  "dataType": "int",
107
111
  "dataValue": 0,
108
112
  },
109
113
  {
114
+ # Priority
110
115
  "dataType": "int",
111
116
  "dataValue": 106,
112
117
  },
113
118
  {
119
+ # Callback ?
114
120
  "dataType": "byteArray",
115
121
  "dataValue": [0],
116
122
  },
@@ -1,9 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import base64
3
4
  import json
4
5
  import logging
5
6
  from typing import TYPE_CHECKING, Any
6
7
 
8
+ from qolsys_controller.adc_device import QolsysAdcDevice
9
+ from qolsys_controller.zwave_energy_clamp import QolsysEnergyClamp
10
+ from qolsys_controller.zwave_thermometer import QolsysThermometer
11
+
7
12
  from .database.db import QolsysDB
8
13
  from .enum import (
9
14
  PartitionAlarmState,
@@ -386,9 +391,54 @@ class QolsysPanel(QolsysObservable):
386
391
  self._controller.state.sync_partitions_data(self.get_partitions_from_db())
387
392
  self._controller.state.sync_zones_data(self.get_zones_from_db())
388
393
  self._controller.state.sync_zwave_devices_data(self.get_zwave_devices_from_db())
394
+ self._controller.state.sync_adc_devices_data(self.get_adc_devices_from_db())
389
395
  self._controller.state.sync_scenes_data(self.get_scenes_from_db())
390
396
  self._controller.state.sync_weather_data(self.get_weather_from_db())
391
397
 
398
+ # Validate all local user match a Qolsys Panel user
399
+ qolsys_users = self.db.get_users()
400
+ qolsys_user_list = []
401
+ for qolsys_user in qolsys_users:
402
+ try:
403
+ userid = int(qolsys_user.get("userid", ""))
404
+ except ValueError:
405
+ LOGGER.error("Invalid userid in panel database: %s", qolsys_user.get("userid", ""))
406
+ userid = -1
407
+
408
+ qolsys_user_list.append(userid)
409
+
410
+ for local_user in self._users:
411
+ if local_user.id not in qolsys_user_list:
412
+ LOGGER.error("ID %s from users.conf file not found in panel database", local_user.id)
413
+
414
+ # Check associated zone_id in iqremotesettins table
415
+ LOGGER.debug("Checking iqremotesettings table for zone_id matching panel MAC address")
416
+ iqremote_settings_list = self.db.get_iqremote_settings()
417
+ for iqremote in iqremote_settings_list:
418
+ if (
419
+ self._controller.settings.random_mac.replace(":", "").lower()
420
+ == iqremote.get("mac_address", "").replace(":", "").lower()
421
+ ):
422
+ self._controller._zone_id = iqremote.get("zone_id", "")
423
+ LOGGER.debug("Found matching zone_id: %s", self._controller._zone_id)
424
+ break
425
+
426
+ # Parse Z-Wave message
427
+ def parse_zwave_message(self, data: dict[str, Any]) -> None:
428
+ zwave = data.get("ZWAVE_RESPONSE", "")
429
+ payload = base64.b64decode(zwave.get("ZWAVE_PAYLOAD", "")).hex()
430
+ # LOGGER.debug(
431
+ # "Z-Wave Response: Node(%s) - Status(%s) - Payload(%s)",
432
+ # zwave.get("NODE_ID", ""),
433
+ # zwave.get("ZWAVE_COMMAND_STATUS", ""),
434
+ # payload,
435
+ # )
436
+
437
+ node_id: str = str(zwave.get("NODE_ID", 0))
438
+ node = self._controller.state.zwave_device(node_id)
439
+ if node is not None:
440
+ node.update_raw(bytes.fromhex(payload))
441
+
392
442
  # Parse panel update to database
393
443
  def parse_iq2meid_message(self, data: dict[str, Any]) -> None: # noqa: C901, PLR0912, PLR0915
394
444
  eventName = data.get("eventName")
@@ -564,6 +614,22 @@ class QolsysPanel(QolsysObservable):
564
614
  case self.db.table_zwave_association_goup.uri:
565
615
  self.db.table_zwave_association_goup.update(selection, selection_argument, content_values)
566
616
 
617
+ # Update Zwave Other
618
+ case self.db.table_zwave_other.uri:
619
+ self.db.table_zwave_other.update(selection, selection_argument, content_values)
620
+
621
+ # Country Locale
622
+ case self.db.table_country_locale.uri:
623
+ self.db.table_country_locale.update(selection, selection_argument, content_values)
624
+
625
+ # Virtual device
626
+ case self.db.table_virtual_device.uri:
627
+ self.db.table_virtual_device.update(selection, selection_argument, content_values)
628
+ adc_id = content_values.get("device_id", "")
629
+ adc_device = self._controller.state.adc_device(adc_id)
630
+ if adc_device is not None:
631
+ adc_device.update_adc_device(content_values)
632
+
567
633
  case _:
568
634
  LOGGER.debug("iq2meid updating unknow uri:%s", uri)
569
635
  LOGGER.debug(data)
@@ -641,6 +707,13 @@ class QolsysPanel(QolsysObservable):
641
707
  case self.db.table_zwave_association_goup.uri:
642
708
  self.db.table_zwave_association_goup.delete(selection, selection_argument)
643
709
 
710
+ case self.db.table_virtual_device.uri:
711
+ self.db.table_virtual_device.delete(selection, selection_argument)
712
+ self._controller.state.sync_adc_devices_data(self.get_adc_devices_from_db())
713
+
714
+ case self.db.table_zwave_other.uri:
715
+ self.db.table_zwave_other.delete(selection, selection_argument)
716
+
644
717
  case _:
645
718
  LOGGER.debug("iq2meid deleting unknown uri:%s", uri)
646
719
  LOGGER.debug(data)
@@ -771,9 +844,21 @@ class QolsysPanel(QolsysObservable):
771
844
  self.db.table_weather.insert(data=content_values)
772
845
  self._controller.state.sync_weather_data(self.get_weather_from_db())
773
846
 
847
+ # ZWave Association Group
774
848
  case self.db.table_zwave_association_goup.uri:
775
849
  self.db.table_zwave_association_goup.insert(data=content_values)
776
850
 
851
+ # Zwave Other
852
+ case self.db.table_zwave_other.uri:
853
+ self.db.table_zwave_other.insert(data=content_values)
854
+ LOGGER.debug("New Z-Wave Other information")
855
+ LOGGER.debug(content_values)
856
+
857
+ # Virtual Device
858
+ case self.db.table_virtual_device.uri:
859
+ self.db.table_virtual_device.insert(data=content_values)
860
+ self._controller.state.sync_adc_devices_data(self.get_adc_devices_from_db())
861
+
777
862
  case _:
778
863
  LOGGER.debug("iq2meid inserting unknow uri:%s", uri)
779
864
  LOGGER.debug(data)
@@ -793,6 +878,15 @@ class QolsysPanel(QolsysObservable):
793
878
  # No valid user code found
794
879
  return -1
795
880
 
881
+ def get_adc_devices_from_db(self) -> list[QolsysAdcDevice]:
882
+ adc_devices: list[QolsysAdcDevice] = []
883
+ devices_list = self.db.get_adc_devices()
884
+
885
+ for device in devices_list:
886
+ adc_devices.append(QolsysAdcDevice(device))
887
+
888
+ return adc_devices
889
+
796
890
  def get_zwave_devices_from_db(self) -> list[QolsysZWaveDevice]:
797
891
  devices: list[QolsysZWaveDevice] = []
798
892
  devices_list = self.db.get_zwave_devices()
@@ -802,8 +896,21 @@ class QolsysPanel(QolsysObservable):
802
896
 
803
897
  for device in devices_list:
804
898
  device_added = False
805
-
806
899
  zwave_node_id = device.get("node_id", "")
900
+
901
+ # Check if z-wave device is an Energy Clamp
902
+ if device.get("node_type", "") == "Energy Clamp":
903
+ LOGGER.debug(device)
904
+ qolsys_meter_device = QolsysEnergyClamp(device)
905
+ devices.append(qolsys_meter_device)
906
+ device_added = True
907
+
908
+ # Check if z-wave device is a thermometer
909
+ if device.get("node_type", "") == "Thermometer":
910
+ qolsys_thermometer = QolsysThermometer(device)
911
+ devices.append(qolsys_thermometer)
912
+ device_added = True
913
+
807
914
  # Check if z-wave device is a Dimmer
808
915
  for d in dimmers_list:
809
916
  dimmer_node_id = d.get("node_id", "")
@@ -907,10 +1014,7 @@ class QolsysPanel(QolsysObservable):
907
1014
 
908
1015
  alarm_type = []
909
1016
  for alarm in self.db.get_alarm_type(partition_id):
910
- if alarm == "":
911
- alarm_type.append(PartitionAlarmType("Police Emergency"))
912
- else:
913
- alarm_type.append(PartitionAlarmType(alarm))
1017
+ alarm_type.append(PartitionAlarmType(alarm))
914
1018
 
915
1019
  alarm_state = PartitionAlarmState(self.db.get_state_partition("ALARM_STATE", partition_id) or "UNKNOWN")
916
1020
 
@@ -38,7 +38,8 @@ class QolsysPartition(QolsysObservable):
38
38
  self._alarm_state: PartitionAlarmState = alarm_state
39
39
 
40
40
  # Alarm Type (alarmedsensor table)
41
- self._alarm_type_array: list[PartitionAlarmType] = alarm_type_array
41
+ self._alarm_type_array: list[PartitionAlarmType] = []
42
+ self.append_alarm_type(alarm_type_array)
42
43
 
43
44
  # Other
44
45
  self._command_exit_sounds: bool = True
@@ -72,7 +73,7 @@ class QolsysPartition(QolsysObservable):
72
73
 
73
74
  # Update system_status
74
75
  if "SYSTEM_STATUS" in data:
75
- self.system_status = PartitionSystemStatus[data.get("SYSTEM_STATUS", "")]
76
+ self.system_status = PartitionSystemStatus(data.get("SYSTEM_STATUS", ""))
76
77
 
77
78
  # Update system_status_changed_time
78
79
  if "SYSTEM_STATUS_CHANGED_TIME" in data:
@@ -250,6 +251,25 @@ class QolsysPartition(QolsysObservable):
250
251
  data_changed = False
251
252
 
252
253
  for new_alarm_type in new_alarm_type_array:
254
+ # Map values to Police Emergency if needed
255
+ if new_alarm_type in {
256
+ PartitionAlarmType.GLASS_BREAK,
257
+ PartitionAlarmType.GLASS_BREAK_AWAY_ONLY,
258
+ PartitionAlarmType.ENTRY_EXIT_LONG_DELAY,
259
+ PartitionAlarmType.ENTRY_EXIT_NORMAL_DELAY,
260
+ PartitionAlarmType.INSTANT_PERIMETER_DW,
261
+ PartitionAlarmType.INSTANT_INTERIOR_DOOR,
262
+ PartitionAlarmType.AWAY_DELAY_MOTION,
263
+ PartitionAlarmType.AWAY_INSTANT_MOTION,
264
+ PartitionAlarmType.REPORTING_SAFETY_SENSOR,
265
+ PartitionAlarmType.DELAYED_REPORTING_SAFETY_SENSOR,
266
+ PartitionAlarmType.AWAY_INSTANT_FOLLOWER_DELAY,
267
+ PartitionAlarmType.STAY_INSTANT_MOTION,
268
+ PartitionAlarmType.STAY_DELAY_MOTION,
269
+ PartitionAlarmType.EMPTY,
270
+ }:
271
+ new_alarm_type = PartitionAlarmType.POLICE_EMERGENCY
272
+
253
273
  # Value already in array
254
274
  if new_alarm_type in self._alarm_type_array:
255
275
  continue