qolsys-controller 0.0.12__tar.gz → 0.0.81__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.
- qolsys_controller-0.0.81/.github/workflows/build.yml +30 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/.github/workflows/publish.yml +1 -0
- qolsys_controller-0.0.81/PKG-INFO +89 -0
- qolsys_controller-0.0.81/README.md +73 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/bin/qolsys.py +16 -15
- qolsys_controller-0.0.81/example.py +78 -0
- qolsys_controller-0.0.81/mypy.ini +6 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/pyproject.toml +8 -4
- qolsys_controller-0.0.81/qolsys_controller/adc_device.py +202 -0
- qolsys_controller-0.0.81/qolsys_controller/adc_service.py +139 -0
- qolsys_controller-0.0.81/qolsys_controller/adc_service_garagedoor.py +35 -0
- qolsys_controller-0.0.81/qolsys_controller/controller.py +1025 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/db.py +134 -27
- qolsys_controller-0.0.81/qolsys_controller/database/table.py +218 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_alarmedsensor.py +4 -17
- qolsys_controller-0.0.81/qolsys_controller/database/table_automation.py +45 -0
- qolsys_controller-0.0.81/qolsys_controller/database/table_country_locale.py +36 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_dashboard_msgs.py +2 -19
- qolsys_controller-0.0.81/qolsys_controller/database/table_dimmerlight.py +35 -0
- qolsys_controller-0.0.81/qolsys_controller/database/table_doorlock.py +38 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_eu_event.py +12 -7
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_heat_map.py +1 -16
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_history.py +6 -18
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_iqremotesettings.py +1 -15
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_iqrouter_network_config.py +1 -7
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_iqrouter_user_device.py +1 -7
- qolsys_controller-0.0.81/qolsys_controller/database/table_master_slave.py +44 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_nest_device.py +1 -7
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_output_rules.py +1 -7
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_partition.py +1 -12
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_pgm_outputs.py +1 -7
- qolsys_controller-0.0.81/qolsys_controller/database/table_powerg_device.py +45 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_qolsyssettings.py +2 -13
- qolsys_controller-0.0.81/qolsys_controller/database/table_scene.py +34 -0
- qolsys_controller-0.0.81/qolsys_controller/database/table_sensor.py +70 -0
- qolsys_controller-0.0.81/qolsys_controller/database/table_sensor_group.py +23 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_shades.py +1 -7
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_smartsocket.py +1 -7
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_state.py +1 -14
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_tcc.py +1 -9
- qolsys_controller-0.0.81/qolsys_controller/database/table_thermostat.py +55 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_trouble_conditions.py +1 -16
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_user.py +13 -19
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_virtual_device.py +13 -7
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_weather.py +1 -18
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_zigbee_device.py +1 -7
- qolsys_controller-0.0.81/qolsys_controller/database/table_zwave_association_group.py +33 -0
- qolsys_controller-0.0.81/qolsys_controller/database/table_zwave_history.py +36 -0
- qolsys_controller-0.0.81/qolsys_controller/database/table_zwave_node.py +87 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/database/table_zwave_other.py +14 -7
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/enum.py +57 -19
- qolsys_controller-0.0.81/qolsys_controller/enum_adc.py +28 -0
- qolsys_controller-0.0.81/qolsys_controller/enum_zwave.py +203 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/errors.py +14 -12
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/mdns.py +7 -4
- qolsys_controller-0.0.81/qolsys_controller/mqtt_command.py +125 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/mqtt_command_queue.py +5 -4
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/observable.py +2 -2
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/panel.py +357 -143
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/partition.py +158 -112
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/pki.py +69 -97
- qolsys_controller-0.0.81/qolsys_controller/scene.py +81 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/settings.py +115 -39
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/state.py +309 -33
- qolsys_controller-0.0.81/qolsys_controller/task_manager.py +54 -0
- qolsys_controller-0.0.81/qolsys_controller/users.py +25 -0
- qolsys_controller-0.0.81/qolsys_controller/utils_mqtt.py +16 -0
- qolsys_controller-0.0.81/qolsys_controller/weather.py +71 -0
- qolsys_controller-0.0.81/qolsys_controller/zone.py +491 -0
- qolsys_controller-0.0.81/qolsys_controller/zwave_device.py +343 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/zwave_dimmer.py +55 -49
- qolsys_controller-0.0.81/qolsys_controller/zwave_energy_clamp.py +15 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/zwave_garagedoor.py +3 -1
- qolsys_controller-0.0.81/qolsys_controller/zwave_generic.py +13 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/zwave_lock.py +51 -44
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/qolsys_controller/zwave_outlet.py +3 -1
- qolsys_controller-0.0.81/qolsys_controller/zwave_service_meter.py +192 -0
- qolsys_controller-0.0.81/qolsys_controller/zwave_service_multilevelsensor.py +119 -0
- qolsys_controller-0.0.81/qolsys_controller/zwave_thermometer.py +21 -0
- qolsys_controller-0.0.81/qolsys_controller/zwave_thermostat.py +396 -0
- qolsys_controller-0.0.81/requirements.txt +8 -0
- qolsys_controller-0.0.12/.github/workflows/build.yml +0 -36
- qolsys_controller-0.0.12/PKG-INFO +0 -91
- qolsys_controller-0.0.12/README.md +0 -75
- qolsys_controller-0.0.12/example.py +0 -96
- qolsys_controller-0.0.12/qolsys_controller/controller.py +0 -50
- qolsys_controller-0.0.12/qolsys_controller/database/table.py +0 -141
- qolsys_controller-0.0.12/qolsys_controller/database/table_automation.py +0 -77
- qolsys_controller-0.0.12/qolsys_controller/database/table_country_locale.py +0 -60
- qolsys_controller-0.0.12/qolsys_controller/database/table_dimmerlight.py +0 -57
- qolsys_controller-0.0.12/qolsys_controller/database/table_doorlock.py +0 -59
- qolsys_controller-0.0.12/qolsys_controller/database/table_master_slave.py +0 -73
- qolsys_controller-0.0.12/qolsys_controller/database/table_powerg_device.py +0 -27
- qolsys_controller-0.0.12/qolsys_controller/database/table_scene.py +0 -55
- qolsys_controller-0.0.12/qolsys_controller/database/table_sensor.py +0 -121
- qolsys_controller-0.0.12/qolsys_controller/database/table_thermostat.py +0 -94
- qolsys_controller-0.0.12/qolsys_controller/database/table_zwave_association_group.py +0 -53
- qolsys_controller-0.0.12/qolsys_controller/database/table_zwave_history.py +0 -59
- qolsys_controller-0.0.12/qolsys_controller/database/table_zwave_node.py +0 -150
- qolsys_controller-0.0.12/qolsys_controller/plugin.py +0 -34
- qolsys_controller-0.0.12/qolsys_controller/plugin_c4.py +0 -17
- qolsys_controller-0.0.12/qolsys_controller/plugin_remote.py +0 -971
- qolsys_controller-0.0.12/qolsys_controller/task_manager.py +0 -40
- qolsys_controller-0.0.12/qolsys_controller/utils_mqtt.py +0 -24
- qolsys_controller-0.0.12/qolsys_controller/zone.py +0 -324
- qolsys_controller-0.0.12/qolsys_controller/zwave_device.py +0 -214
- qolsys_controller-0.0.12/qolsys_controller/zwave_generic.py +0 -11
- qolsys_controller-0.0.12/qolsys_controller/zwave_thermostat.py +0 -251
- qolsys_controller-0.0.12/requirements.txt +0 -3
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/.gitignore +0 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/Info_mqtt.md +0 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/LICENSE +0 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/info_pairing.md +0 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/info_qolsys.md +0 -0
- {qolsys_controller-0.0.12 → qolsys_controller-0.0.81}/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 .
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qolsys-controller
|
|
3
|
+
Version: 0.0.81
|
|
4
|
+
Summary: A Python module that emulates a virtual IQ Remote device, enabling full local control of a Qolsys IQ Panel
|
|
5
|
+
Project-URL: Homepage, https://github.com/EHylands/QolsysController
|
|
6
|
+
Project-URL: Issues, https://github.com/EHylands/QolsysController/issues
|
|
7
|
+
Project-URL: Repository, https://github.com/EHylands/QolsysController.git
|
|
8
|
+
Author: Eric Hylands
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Topic :: Home Automation
|
|
14
|
+
Requires-Python: >=3.12
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Qolsys Controller - qolsys-controller
|
|
18
|
+
|
|
19
|
+
[](https://github.com/EHylands/QolsysController/actions/workflows/build.yml)
|
|
20
|
+
|
|
21
|
+
A Python module that emulates a virtual IQ Remote device, enabling full **local control** of a Qolsys IQ Panel over MQTT — no cloud access required.
|
|
22
|
+
|
|
23
|
+
## QolsysController
|
|
24
|
+
- ✅ Connects directly to the **Qolsys Panel's local MQTT server as an IQ Remote**
|
|
25
|
+
- 🔐 Pairs by only using **Installer Code** (same procedure as standard IQ Remote pairing)
|
|
26
|
+
- 🔢 Supports **4-digit user codes**
|
|
27
|
+
- ⚠️ Uses a **custom local usercode database** — panel's internal user code verification process is not yet supported
|
|
28
|
+
|
|
29
|
+
## ✨ Functionality Highlights
|
|
30
|
+
|
|
31
|
+
| Category | Feature | Status |
|
|
32
|
+
|------------------------|--------------------------------------|--------|
|
|
33
|
+
| **Panel** | Diagnostic Sensors | ✅ |
|
|
34
|
+
| | Panel Scenes | ✅ |
|
|
35
|
+
| | Weather Forecast | ✅ |
|
|
36
|
+
| | (Alarm.com Weather to Panel) | |
|
|
37
|
+
| **Partition** | Arming Status | ✅ |
|
|
38
|
+
| | Alarm State | ✅ |
|
|
39
|
+
| | Home Instant Arming | ✅ |
|
|
40
|
+
| | Home Silent Disarming (Firmware 4.6.1)| ✅ |
|
|
41
|
+
| | Set Exit Sounds | ✅ |
|
|
42
|
+
| | Set Entry Delay | ✅ |
|
|
43
|
+
| | TTS | 🛠️ |
|
|
44
|
+
| **Zones** | Sensor Status | ✅ |
|
|
45
|
+
| | Tamper State | ✅ |
|
|
46
|
+
| | Battery Level | ✅ |
|
|
47
|
+
| | Temperature (supported PowerG device)| ✅ |
|
|
48
|
+
| | Light (supported PowerG device) | ✅ |
|
|
49
|
+
| | Average dBm | ✅ |
|
|
50
|
+
| | Latest dBm | ✅ |
|
|
51
|
+
| **Z-Wave Devices** | Battery Level | ✅ |
|
|
52
|
+
| | Node Status | ✅ |
|
|
53
|
+
| | Control Generic Devices | TBD |
|
|
54
|
+
| **Z-Wave Dimmers** | Binary Switch | ✅ |
|
|
55
|
+
| | Multi Level Dimmer | ✅ |
|
|
56
|
+
| **Z-Wave Door Locks** | Lock, Unlock | ✅ |
|
|
57
|
+
| **Z-Wave Thermostats** | Read device status | ✅ |
|
|
58
|
+
| | Write device status | ✅ |
|
|
59
|
+
| **Z-Wave Garage Doors**| | 🛠️ |
|
|
60
|
+
| **Z-Wave Outlets** | | 🛠️ |
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## ⚠️ Certificate Warning
|
|
64
|
+
|
|
65
|
+
During pairing, the main panel issues **only one signed client certificate** per virtual IQ Remote. If any key files are lost or deleted, re-pairing may become impossible.
|
|
66
|
+
|
|
67
|
+
A new PKI, including a new private key, can be recreated under specific circumstances, though the precise conditions remain unknown at this time.
|
|
68
|
+
|
|
69
|
+
**Important:**
|
|
70
|
+
Immediately back up the following files from the `pki/` directory after initial pairing:
|
|
71
|
+
|
|
72
|
+
- `.key` (private key)
|
|
73
|
+
- `.cer` (certificate)
|
|
74
|
+
- `.csr` (certificate signing request)
|
|
75
|
+
- `.secure` (signed client certificate)
|
|
76
|
+
- `.qolsys` (Qolsys Panel public certificate)
|
|
77
|
+
|
|
78
|
+
Store these files securely.
|
|
79
|
+
|
|
80
|
+
## 📦 Installation
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
git clone https://github.com/EHylands/QolsysController.git
|
|
84
|
+
cd qolsys_controller
|
|
85
|
+
pip3.12 install -r requirements.txt
|
|
86
|
+
|
|
87
|
+
# Change panel_ip and plugin_in in main.py file
|
|
88
|
+
python3.12 example.py
|
|
89
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Qolsys Controller - qolsys-controller
|
|
2
|
+
|
|
3
|
+
[](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.
|
|
36
|
-
Panel.
|
|
37
|
-
Panel.
|
|
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.
|
|
41
|
-
Panel.
|
|
42
|
-
Panel.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "qolsys-controller"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.81"
|
|
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 =
|
|
30
|
+
line-length = 127
|
|
31
|
+
target-version = "py312"
|
|
32
|
+
|
|
31
33
|
|
|
32
34
|
[tool.ruff.lint]
|
|
33
|
-
select = [
|
|
34
|
-
ignore = ["E501"
|
|
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
|
+
}
|