qolsys-controller 0.0.12__tar.gz → 0.0.76__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 (112) hide show
  1. qolsys_controller-0.0.76/.github/workflows/build.yml +30 -0
  2. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/.github/workflows/publish.yml +1 -0
  3. qolsys_controller-0.0.76/PKG-INFO +89 -0
  4. qolsys_controller-0.0.76/README.md +73 -0
  5. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/bin/qolsys.py +16 -15
  6. qolsys_controller-0.0.76/example.py +78 -0
  7. qolsys_controller-0.0.76/mypy.ini +6 -0
  8. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/pyproject.toml +8 -4
  9. qolsys_controller-0.0.76/qolsys_controller/adc_device.py +202 -0
  10. qolsys_controller-0.0.76/qolsys_controller/adc_service.py +139 -0
  11. qolsys_controller-0.0.76/qolsys_controller/adc_service_garagedoor.py +35 -0
  12. qolsys_controller-0.0.76/qolsys_controller/controller.py +941 -0
  13. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/db.py +134 -27
  14. qolsys_controller-0.0.76/qolsys_controller/database/table.py +218 -0
  15. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_alarmedsensor.py +4 -17
  16. qolsys_controller-0.0.76/qolsys_controller/database/table_automation.py +45 -0
  17. qolsys_controller-0.0.76/qolsys_controller/database/table_country_locale.py +36 -0
  18. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_dashboard_msgs.py +2 -19
  19. qolsys_controller-0.0.76/qolsys_controller/database/table_dimmerlight.py +35 -0
  20. qolsys_controller-0.0.76/qolsys_controller/database/table_doorlock.py +38 -0
  21. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_eu_event.py +12 -7
  22. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_heat_map.py +1 -16
  23. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_history.py +6 -18
  24. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_iqremotesettings.py +1 -15
  25. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_iqrouter_network_config.py +1 -7
  26. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_iqrouter_user_device.py +1 -7
  27. qolsys_controller-0.0.76/qolsys_controller/database/table_master_slave.py +44 -0
  28. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_nest_device.py +1 -7
  29. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_output_rules.py +1 -7
  30. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_partition.py +1 -12
  31. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_pgm_outputs.py +1 -7
  32. qolsys_controller-0.0.76/qolsys_controller/database/table_powerg_device.py +45 -0
  33. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_qolsyssettings.py +2 -13
  34. qolsys_controller-0.0.76/qolsys_controller/database/table_scene.py +34 -0
  35. qolsys_controller-0.0.76/qolsys_controller/database/table_sensor.py +70 -0
  36. qolsys_controller-0.0.76/qolsys_controller/database/table_sensor_group.py +23 -0
  37. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_shades.py +1 -7
  38. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_smartsocket.py +1 -7
  39. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_state.py +1 -14
  40. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_tcc.py +1 -9
  41. qolsys_controller-0.0.76/qolsys_controller/database/table_thermostat.py +54 -0
  42. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_trouble_conditions.py +1 -16
  43. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_user.py +13 -19
  44. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_virtual_device.py +13 -7
  45. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_weather.py +1 -18
  46. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_zigbee_device.py +1 -7
  47. qolsys_controller-0.0.76/qolsys_controller/database/table_zwave_association_group.py +33 -0
  48. qolsys_controller-0.0.76/qolsys_controller/database/table_zwave_history.py +36 -0
  49. qolsys_controller-0.0.76/qolsys_controller/database/table_zwave_node.py +85 -0
  50. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/database/table_zwave_other.py +14 -7
  51. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/enum.py +56 -19
  52. qolsys_controller-0.0.76/qolsys_controller/enum_adc.py +28 -0
  53. qolsys_controller-0.0.76/qolsys_controller/enum_zwave.py +181 -0
  54. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/errors.py +14 -12
  55. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/mdns.py +7 -4
  56. qolsys_controller-0.0.76/qolsys_controller/mqtt_command.py +119 -0
  57. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/mqtt_command_queue.py +5 -4
  58. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/observable.py +2 -2
  59. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/panel.py +357 -143
  60. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/partition.py +158 -112
  61. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/pki.py +69 -97
  62. qolsys_controller-0.0.76/qolsys_controller/scene.py +81 -0
  63. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/settings.py +115 -39
  64. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/state.py +285 -33
  65. qolsys_controller-0.0.76/qolsys_controller/task_manager.py +54 -0
  66. qolsys_controller-0.0.76/qolsys_controller/users.py +25 -0
  67. qolsys_controller-0.0.76/qolsys_controller/utils_mqtt.py +16 -0
  68. qolsys_controller-0.0.76/qolsys_controller/weather.py +71 -0
  69. qolsys_controller-0.0.76/qolsys_controller/zone.py +491 -0
  70. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/zwave_device.py +151 -94
  71. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/zwave_dimmer.py +55 -49
  72. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/zwave_garagedoor.py +3 -1
  73. qolsys_controller-0.0.76/qolsys_controller/zwave_generic.py +13 -0
  74. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/zwave_lock.py +51 -44
  75. qolsys_controller-0.0.76/qolsys_controller/zwave_meter.py +272 -0
  76. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/zwave_outlet.py +3 -1
  77. qolsys_controller-0.0.76/qolsys_controller/zwave_thermometer.py +21 -0
  78. qolsys_controller-0.0.76/qolsys_controller/zwave_thermostat.py +402 -0
  79. qolsys_controller-0.0.76/requirements.txt +8 -0
  80. qolsys_controller-0.0.12/.github/workflows/build.yml +0 -36
  81. qolsys_controller-0.0.12/PKG-INFO +0 -91
  82. qolsys_controller-0.0.12/README.md +0 -75
  83. qolsys_controller-0.0.12/example.py +0 -96
  84. qolsys_controller-0.0.12/qolsys_controller/controller.py +0 -50
  85. qolsys_controller-0.0.12/qolsys_controller/database/table.py +0 -141
  86. qolsys_controller-0.0.12/qolsys_controller/database/table_automation.py +0 -77
  87. qolsys_controller-0.0.12/qolsys_controller/database/table_country_locale.py +0 -60
  88. qolsys_controller-0.0.12/qolsys_controller/database/table_dimmerlight.py +0 -57
  89. qolsys_controller-0.0.12/qolsys_controller/database/table_doorlock.py +0 -59
  90. qolsys_controller-0.0.12/qolsys_controller/database/table_master_slave.py +0 -73
  91. qolsys_controller-0.0.12/qolsys_controller/database/table_powerg_device.py +0 -27
  92. qolsys_controller-0.0.12/qolsys_controller/database/table_scene.py +0 -55
  93. qolsys_controller-0.0.12/qolsys_controller/database/table_sensor.py +0 -121
  94. qolsys_controller-0.0.12/qolsys_controller/database/table_thermostat.py +0 -94
  95. qolsys_controller-0.0.12/qolsys_controller/database/table_zwave_association_group.py +0 -53
  96. qolsys_controller-0.0.12/qolsys_controller/database/table_zwave_history.py +0 -59
  97. qolsys_controller-0.0.12/qolsys_controller/database/table_zwave_node.py +0 -150
  98. qolsys_controller-0.0.12/qolsys_controller/plugin.py +0 -34
  99. qolsys_controller-0.0.12/qolsys_controller/plugin_c4.py +0 -17
  100. qolsys_controller-0.0.12/qolsys_controller/plugin_remote.py +0 -971
  101. qolsys_controller-0.0.12/qolsys_controller/task_manager.py +0 -40
  102. qolsys_controller-0.0.12/qolsys_controller/utils_mqtt.py +0 -24
  103. qolsys_controller-0.0.12/qolsys_controller/zone.py +0 -324
  104. qolsys_controller-0.0.12/qolsys_controller/zwave_generic.py +0 -11
  105. qolsys_controller-0.0.12/qolsys_controller/zwave_thermostat.py +0 -251
  106. qolsys_controller-0.0.12/requirements.txt +0 -3
  107. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/.gitignore +0 -0
  108. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/Info_mqtt.md +0 -0
  109. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/LICENSE +0 -0
  110. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/info_pairing.md +0 -0
  111. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/info_qolsys.md +0 -0
  112. {qolsys_controller-0.0.12 → qolsys_controller-0.0.76}/qolsys_controller/__init__.py +0 -0
@@ -0,0 +1,30 @@
1
+ name: Build
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ lint_and_typecheck:
9
+ runs-on: ubuntu-latest
10
+
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.12"
17
+
18
+ - name: Install dependencies
19
+ run: |
20
+ pip install -r requirements.txt
21
+ pip install ruff mypy
22
+
23
+ - name: Ruff lint
24
+ run: ruff check .
25
+
26
+ - name: Ruff format check
27
+ run: ruff format --check .
28
+
29
+ - name: Mypy type checking
30
+ run: mypy .
@@ -1,6 +1,7 @@
1
1
  name: Publish Python package to PyPI
2
2
 
3
3
  on:
4
+ workflow_dispatch:
4
5
  release:
5
6
  types: [published]
6
7
 
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.4
2
+ Name: qolsys-controller
3
+ Version: 0.0.76
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
+ [![Build](https://github.com/EHylands/QolsysController/actions/workflows/build.yml/badge.svg)](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
+ ```
@@ -0,0 +1,73 @@
1
+ # Qolsys Controller - qolsys-controller
2
+
3
+ [![Build](https://github.com/EHylands/QolsysController/actions/workflows/build.yml/badge.svg)](https://github.com/EHylands/QolsysController/actions/workflows/build.yml)
4
+
5
+ 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.
6
+
7
+ ## QolsysController
8
+ - ✅ Connects directly to the **Qolsys Panel's local MQTT server as an IQ Remote**
9
+ - 🔐 Pairs by only using **Installer Code** (same procedure as standard IQ Remote pairing)
10
+ - 🔢 Supports **4-digit user codes**
11
+ - ⚠️ Uses a **custom local usercode database** — panel's internal user code verification process is not yet supported
12
+
13
+ ## ✨ Functionality Highlights
14
+
15
+ | Category | Feature | Status |
16
+ |------------------------|--------------------------------------|--------|
17
+ | **Panel** | Diagnostic Sensors | ✅ |
18
+ | | Panel Scenes | ✅ |
19
+ | | Weather Forecast | ✅ |
20
+ | | (Alarm.com Weather to Panel) | |
21
+ | **Partition** | Arming Status | ✅ |
22
+ | | Alarm State | ✅ |
23
+ | | Home Instant Arming | ✅ |
24
+ | | Home Silent Disarming (Firmware 4.6.1)| ✅ |
25
+ | | Set Exit Sounds | ✅ |
26
+ | | Set Entry Delay | ✅ |
27
+ | | TTS | 🛠️ |
28
+ | **Zones** | Sensor Status | ✅ |
29
+ | | Tamper State | ✅ |
30
+ | | Battery Level | ✅ |
31
+ | | Temperature (supported PowerG device)| ✅ |
32
+ | | Light (supported PowerG device) | ✅ |
33
+ | | Average dBm | ✅ |
34
+ | | Latest dBm | ✅ |
35
+ | **Z-Wave Devices** | Battery Level | ✅ |
36
+ | | Node Status | ✅ |
37
+ | | Control Generic Devices | TBD |
38
+ | **Z-Wave Dimmers** | Binary Switch | ✅ |
39
+ | | Multi Level Dimmer | ✅ |
40
+ | **Z-Wave Door Locks** | Lock, Unlock | ✅ |
41
+ | **Z-Wave Thermostats** | Read device status | ✅ |
42
+ | | Write device status | ✅ |
43
+ | **Z-Wave Garage Doors**| | 🛠️ |
44
+ | **Z-Wave Outlets** | | 🛠️ |
45
+
46
+
47
+ ## ⚠️ Certificate Warning
48
+
49
+ 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.
50
+
51
+ A new PKI, including a new private key, can be recreated under specific circumstances, though the precise conditions remain unknown at this time.
52
+
53
+ **Important:**
54
+ Immediately back up the following files from the `pki/` directory after initial pairing:
55
+
56
+ - `.key` (private key)
57
+ - `.cer` (certificate)
58
+ - `.csr` (certificate signing request)
59
+ - `.secure` (signed client certificate)
60
+ - `.qolsys` (Qolsys Panel public certificate)
61
+
62
+ Store these files securely.
63
+
64
+ ## 📦 Installation
65
+
66
+ ```bash
67
+ git clone https://github.com/EHylands/QolsysController.git
68
+ cd qolsys_controller
69
+ pip3.12 install -r requirements.txt
70
+
71
+ # Change panel_ip and plugin_in in main.py file
72
+ python3.12 example.py
73
+ ```
@@ -6,12 +6,11 @@ import logging
6
6
  import os
7
7
  import sys
8
8
 
9
- from qolsys_controller import qolsys_controller
9
+ from qolsys_controller import qolsys_controller # type: ignore[attr-defined]
10
10
  from qolsys_controller.errors import QolsysMqttError, QolsysSqlError, QolsysSslError
11
11
 
12
12
 
13
13
  async def main() -> None: # noqa: D103
14
-
15
14
  cli_parser = argparse.ArgumentParser()
16
15
  cli_parser.add_argument("--panel-ip", help="Qolsys Panel IP", default="")
17
16
  cli_parser.add_argument("--plugin-ip", help="Plugin IP", default="")
@@ -28,26 +27,23 @@ async def main() -> None: # noqa: D103
28
27
  asyncio.set_event_loop(loop)
29
28
 
30
29
  Panel = qolsys_controller()
31
-
32
- # Select plugin
33
- Panel.select_plugin("remote")
34
30
  Panel.settings.config_directory = args.config_dir
35
- Panel.plugin.settings.panel_ip = args.panel_ip
36
- Panel.plugin.settings.plugin_ip = args.plugin_ip
37
- Panel.plugin.settings.random_mac = ""
31
+ Panel.settings.panel_ip = args.panel_ip
32
+ Panel.settings.plugin_ip = args.plugin_ip
33
+ Panel.settings.random_mac = ""
38
34
 
39
35
  # Additionnal remote plugin config
40
- Panel.plugin.check_user_code_on_disarm = False
41
- Panel.plugin.log_mqtt_mesages = args.debug
42
- Panel.plugin.auto_discover_pki = args.pki_autodiscovery
36
+ Panel.settings.check_user_code_on_disarm = False
37
+ Panel.settings.log_mqtt_mesages = args.debug
38
+ Panel.settings.auto_discover_pki = args.pki_autodiscovery
43
39
 
44
40
  # Configure remote plugin
45
- if not await Panel.plugin.config(start_pairing=True):
41
+ if not await Panel.config(start_pairing=True):
46
42
  LOGGER.debug("Error Configuring remote plugin")
47
43
  return
48
44
 
49
45
  try:
50
- await Panel.plugin.start_operation()
46
+ await Panel.start_operation()
51
47
 
52
48
  except QolsysMqttError:
53
49
  LOGGER.debug("QolsysMqttError")
@@ -58,7 +54,7 @@ async def main() -> None: # noqa: D103
58
54
  except QolsysSqlError:
59
55
  LOGGER.debug("QolsysSqlError")
60
56
 
61
- if not Panel.plugin.connected:
57
+ if not Panel.connected:
62
58
  LOGGER.error("Panel not ready for operation")
63
59
  return
64
60
 
@@ -67,9 +63,14 @@ async def main() -> None: # noqa: D103
67
63
  stop_event = asyncio.Event()
68
64
  await stop_event.wait()
69
65
 
66
+
70
67
  # Change to the "Selector" event loop if platform is Windows
71
68
  if sys.platform.lower() == "win32" or os.name.lower() == "nt":
72
- from asyncio import WindowsSelectorEventLoopPolicy, set_event_loop_policy
69
+ from asyncio import ( # type: ignore[attr-defined]
70
+ WindowsSelectorEventLoopPolicy,
71
+ set_event_loop_policy,
72
+ )
73
+
73
74
  set_event_loop_policy(WindowsSelectorEventLoopPolicy())
74
75
 
75
76
  asyncio.run(main())
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import asyncio
4
+ import logging
5
+ import os
6
+ import sys
7
+
8
+ from qolsys_controller.controller import QolsysController
9
+ from qolsys_controller.errors import QolsysMqttError, QolsysSqlError, QolsysSslError
10
+
11
+ logging.basicConfig(level=logging.DEBUG, format="%(levelname)s - %(module)s: %(message)s")
12
+ LOGGER = logging.getLogger(__name__)
13
+
14
+
15
+ async def main() -> None: # noqa: D103
16
+ remote = QolsysController()
17
+ remote.settings.config_directory = "./config/"
18
+ remote.settings.panel_ip = "****"
19
+ remote.settings.plugin_ip = "****"
20
+ remote.settings.random_mac = "" # Example: F2:16:3E:33:ED:20
21
+
22
+ # Additionnal remote plugin config
23
+ remote.settings.check_user_code_on_disarm = False # Check user code in user.conf file
24
+ remote.settings.log_mqtt_mesages = False # Enable for MQTT debug purposes
25
+ remote.settings.auto_discover_pki = True
26
+
27
+ # Configure remote plugin
28
+ if not await remote.config(start_pairing=True):
29
+ LOGGER.debug("Error Configuring remote plugin")
30
+ return
31
+
32
+ try:
33
+ await remote.start_operation()
34
+
35
+ except QolsysMqttError:
36
+ LOGGER.debug("QolsysMqttError")
37
+
38
+ except QolsysSslError:
39
+ LOGGER.debug("QolsysSslError")
40
+
41
+ except QolsysSqlError:
42
+ LOGGER.debug("QolsysSqlError")
43
+
44
+ if not remote.connected:
45
+ LOGGER.error("Panel not ready for operation")
46
+ return
47
+
48
+ LOGGER.debug("Qolsys Panel Ready for operation")
49
+
50
+ await asyncio.sleep(5)
51
+ # await remote.command_zwave_switch_multi_level(node_id="6",level=99)
52
+ # await remote.command_zwave_switch_binary_set(node_id="8", status=True)
53
+ # await remote.plugin.command_arm(
54
+ # partition_id='0',
55
+ # arming_type=PartitionArmingType:ARM_NIGHT,
56
+ # user_code="1111",
57
+ # exit_sounds=False,
58
+ # instant_arm=True)
59
+ # await remote.command_disarm(partition_id="0", user_code="1111")
60
+ # await remote.command_panel_execute_scene(scene_id="3")
61
+ # await remote.command_zwave_doorlock_set(node_id="7",locked=True)
62
+ # await remote.stop_operation()
63
+
64
+ # Use an asyncio.Event to keep the program running efficiently
65
+ stop_event = asyncio.Event()
66
+ await stop_event.wait()
67
+
68
+
69
+ # Change to the "Selector" event loop if platform is Windows
70
+ if sys.platform.lower() == "win32" or os.name.lower() == "nt":
71
+ from asyncio import ( # type: ignore[attr-defined]
72
+ WindowsSelectorEventLoopPolicy,
73
+ set_event_loop_policy,
74
+ )
75
+
76
+ set_event_loop_policy(WindowsSelectorEventLoopPolicy())
77
+
78
+ asyncio.run(main())
@@ -0,0 +1,6 @@
1
+ [mypy]
2
+ python_version = 3.12
3
+ warn_unused_configs = True
4
+ disallow_untyped_defs = True
5
+ ignore_missing_imports = True
6
+ strict = True
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "qolsys-controller"
3
- version = "0.0.12"
3
+ version = "0.0.76"
4
4
  authors = [
5
5
  { name="Eric Hylands", email="" },
6
6
  ]
@@ -27,9 +27,13 @@ build-backend = "hatchling.build"
27
27
  packages = ["qolsys_controller/"]
28
28
 
29
29
  [tool.ruff]
30
- line-length = 88
30
+ line-length = 127
31
+ target-version = "py312"
32
+
31
33
 
32
34
  [tool.ruff.lint]
33
- select = ['ALL']
34
- ignore = ["E501","S608","S311","S104","D100","D101", "D102","D107","ERA001", "FBT001","FBT002", "N802","N806","PERF401"]
35
+ select = ["E", "F", "W", "I"]
36
+ ignore = ["E501"]
35
37
 
38
+ [tool.ruff.lint.mccabe]
39
+ max-complexity = 25
@@ -0,0 +1,202 @@
1
+ import json
2
+ import logging
3
+ from typing import TYPE_CHECKING
4
+
5
+ from .adc_service import QolsysAdcService
6
+ from .adc_service_garagedoor import QolsysAdcGarageDoorService
7
+ from .enum_adc import vdFuncLocalControl, vdFuncName, vdFuncState, vdFuncType
8
+ from .observable import QolsysObservable
9
+
10
+ if TYPE_CHECKING:
11
+ from .adc_service import QolsysAdcService
12
+
13
+ LOGGER = logging.getLogger(__name__)
14
+
15
+
16
+ class QolsysAdcDevice(QolsysObservable):
17
+ def __init__(self, adc_dict: dict[str, str]) -> None:
18
+ super().__init__()
19
+
20
+ self._services: list[QolsysAdcService] = []
21
+
22
+ self._id: str = adc_dict.get("_id", "")
23
+ self._partition_id: str = adc_dict.get("partition_id", "")
24
+ self._device_id: str = adc_dict.get("device_id", "")
25
+ self._name: str = adc_dict.get("name", "")
26
+ self._type: str = adc_dict.get("type", "")
27
+ self._create_time: str = adc_dict.get("create_time", "")
28
+ self._created_by: str = adc_dict.get("created_by", "")
29
+ self._update_time: str = adc_dict.get("update_time", "")
30
+ self._updated_by: str = adc_dict.get("updated_by", "")
31
+ self._device_zone_list: str = adc_dict.get("device_zone_list", "")
32
+ self._func_list = ""
33
+ self.func_list = adc_dict.get("func_list", "")
34
+
35
+ def update_adc_device(self, data: dict[str, str]) -> None:
36
+ # Check if we are updating same device_id
37
+ device_id_update = data.get("device_id", "")
38
+ if device_id_update != self._device_id:
39
+ LOGGER.error(
40
+ "Updating ADC%s (%s) with ADC%s (different device_id)",
41
+ self._device_id,
42
+ self._name,
43
+ device_id_update,
44
+ )
45
+ return
46
+
47
+ self.start_batch_update()
48
+
49
+ if "partition_id" in data:
50
+ self.partition_id = data.get("partition_id", "")
51
+
52
+ if "name" in data:
53
+ self.name = data.get("name", "")
54
+
55
+ if "type" in data:
56
+ self.type = data.get("type", "")
57
+
58
+ self.end_batch_update()
59
+
60
+ if "func_list" in data:
61
+ self.func_list = data.get("func_list", "")
62
+
63
+ def get_adc_service(self, id: int) -> QolsysAdcService | None:
64
+ for service in self._services:
65
+ if service.id == id:
66
+ return service
67
+ return None
68
+
69
+ def add_adc_service(
70
+ self,
71
+ id: int,
72
+ local_control: vdFuncLocalControl,
73
+ func_name: vdFuncName,
74
+ func_type: vdFuncType,
75
+ func_state: vdFuncState,
76
+ timestamp: str,
77
+ ) -> None:
78
+ # Garage Door
79
+ if func_name == vdFuncName.OPEN_CLOSE and func_type == vdFuncType.BINARY_ACTUATOR:
80
+ LOGGER.debug("ADC%s (%s) - Adding garage door service", self.device_id, self.name)
81
+ self._services.append(
82
+ QolsysAdcGarageDoorService(self, id, func_name, local_control, func_type, func_state, timestamp)
83
+ )
84
+ self.notify()
85
+ return
86
+
87
+ # Add generic service if other services have beed identified
88
+ LOGGER.debug("ADC%s (%s) - Adding generic service", self.device_id, self.name)
89
+ self._services.append(QolsysAdcService(self, id, func_name, local_control, func_type, func_state, timestamp))
90
+ self.notify()
91
+ return
92
+
93
+ # -----------------------------
94
+ # properties + setters
95
+ # -----------------------------
96
+
97
+ @property
98
+ def device_id(self) -> str:
99
+ return self._device_id
100
+
101
+ @device_id.setter
102
+ def device_id(self, value: str) -> None:
103
+ self._device_id = value
104
+
105
+ @property
106
+ def services(self) -> list[QolsysAdcService]:
107
+ return self._services
108
+
109
+ @property
110
+ def partition_id(self) -> str:
111
+ return self._partition_id
112
+
113
+ @partition_id.setter
114
+ def partition_id(self, value: str) -> None:
115
+ if self._partition_id != value:
116
+ LOGGER.debug("ADC%s (%s) - partition_id: %s", self.device_id, self.name, value)
117
+ self._partition_id = value
118
+ self.notify()
119
+
120
+ @property
121
+ def name(self) -> str:
122
+ return self._name
123
+
124
+ @name.setter
125
+ def name(self, value: str) -> None:
126
+ if self._name != value:
127
+ LOGGER.debug("ADC%s (%s) - name: %s", self.device_id, self.name, value)
128
+ self._name = value
129
+ self.notify()
130
+
131
+ @property
132
+ def type(self) -> str:
133
+ return self._type
134
+
135
+ @type.setter
136
+ def type(self, value: str) -> None:
137
+ if self._type != value:
138
+ LOGGER.debug("ADC%s (%s) - type: %s", self.device_id, self.name, value)
139
+ self._type = value
140
+ self.notify()
141
+
142
+ @property
143
+ def func_list(self) -> str:
144
+ return self._func_list
145
+
146
+ @func_list.setter
147
+ def func_list(self, value: str) -> None:
148
+ if self._func_list != value:
149
+ LOGGER.debug("ADC%s (%s) - func_list: %s", self.device_id, self.name, value)
150
+ self._func_list = value
151
+
152
+ try:
153
+ json_func_list = json.loads(self._func_list)
154
+ new_service_id: list[int] = []
155
+ self.start_batch_update()
156
+
157
+ for function in json_func_list:
158
+ try:
159
+ id = function.get("vdFuncId")
160
+ local_control = vdFuncLocalControl(function.get("vdFuncLocalControl"))
161
+ func_name = vdFuncName(function.get("vdFuncName"))
162
+ func_type = vdFuncType(function.get("vdFuncType"))
163
+ func_state = vdFuncState(function.get("vdFuncState"))
164
+ timestamp = function.get("vdFuncBackendTimestamp")
165
+ new_service_id.append(id)
166
+
167
+ service = self.get_adc_service(id)
168
+ if service is not None:
169
+ service.update_adc_service(func_name, local_control, func_type, func_state, timestamp)
170
+
171
+ if service is None:
172
+ self.add_adc_service(id, local_control, func_name, func_type, func_state, timestamp)
173
+
174
+ except ValueError as e:
175
+ LOGGER.error("Error converting value:", e)
176
+ continue
177
+
178
+ # Check if service have been removed
179
+ for service in self._services:
180
+ if service.id not in new_service_id:
181
+ self._services.remove(service)
182
+ self.notify()
183
+
184
+ self.end_batch_update()
185
+
186
+ except json.JSONDecodeError as e:
187
+ LOGGER.error("ADC%s - Error parsing JSON:", self.device_id, e)
188
+
189
+ def to_dict_adc(self) -> dict[str, str]:
190
+ return {
191
+ "_id": self._id,
192
+ "partition_id": self.partition_id,
193
+ "device_id": self.device_id,
194
+ "name": self.name,
195
+ "type": self.type,
196
+ "func_list": self.func_list,
197
+ "create_time": self._create_time,
198
+ "created_by": self._created_by,
199
+ "update_time": self._update_time,
200
+ "updated_by": self._updated_by,
201
+ "device_zone_list": self._device_zone_list,
202
+ }