qolsys-controller 0.0.28__py3-none-any.whl → 0.0.51__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.

@@ -1,37 +1,42 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
2
4
  import base64
5
+ import contextlib
3
6
  import datetime
4
7
  import json
5
8
  import logging
6
9
  import random
7
10
  import ssl
8
11
  import uuid
12
+ from typing import TYPE_CHECKING
9
13
 
14
+ import aiofiles
10
15
  import aiomqtt
11
16
 
12
17
  from .enum import PartitionAlarmState, PartitionSystemStatus
13
- from .enum_zwave import ThermostatFanMode, ThermostatMode
14
18
  from .errors import QolsysMqttError, QolsysSslError
15
19
  from .mdns import QolsysMDNS
16
20
  from .mqtt_command_queue import QolsysMqttCommandQueue
17
- from .panel import QolsysPanel
18
21
  from .pki import QolsysPKI
19
22
  from .plugin import QolsysPlugin
20
- from .settings import QolsysSettings
21
- from .state import QolsysState
22
23
  from .task_manager import QolsysTaskManager
23
24
  from .utils_mqtt import generate_random_mac
24
25
 
25
26
  LOGGER = logging.getLogger(__name__)
26
27
 
28
+ if TYPE_CHECKING:
29
+ from .controller import QolsysController
30
+ from .enum_zwave import ThermostatFanMode, ThermostatMode
31
+
27
32
 
28
33
  class QolsysPluginRemote(QolsysPlugin):
29
34
 
30
- def __init__(self, state: QolsysState, panel: QolsysPanel, settings: QolsysSettings) -> None:
31
- super().__init__(state, panel, settings)
35
+ def __init__(self, controller: QolsysController) -> None:
36
+ super().__init__(controller=controller)
32
37
 
33
38
  # PKI
34
- self._pki = QolsysPKI(settings=settings)
39
+ self._pki = QolsysPKI(settings=controller.settings)
35
40
  self._auto_discover_pki = True
36
41
 
37
42
  # Plugin
@@ -82,20 +87,14 @@ class QolsysPluginRemote(QolsysPlugin):
82
87
  self._auto_discover_pki = value
83
88
 
84
89
  def is_paired(self) -> bool:
85
- # Check if plugin is paired:
86
- # 1- random_mac set
87
- # 2- KEY file present
88
- # 3- Signed certificate file present
89
- # 4- Qolsys certificate present
90
- # 5- Qolsys Panel IP present
91
90
  return (
92
91
  self._pki.id != "" and
93
92
  self._pki.check_key_file() and
94
93
  self._pki.check_cer_file() and
95
94
  self._pki.check_qolsys_cer_file() and
96
95
  self._pki.check_secure_file() and
97
- self.settings.check_panel_ip() and
98
- self.settings.check_plugin_ip()
96
+ self._controller.settings.check_panel_ip() and
97
+ self._controller.settings.check_plugin_ip()
99
98
  )
100
99
 
101
100
  async def config(self, start_pairing: bool) -> bool:
@@ -106,24 +105,24 @@ class QolsysPluginRemote(QolsysPlugin):
106
105
  super().config()
107
106
 
108
107
  # Check and created config_directory
109
- if not self.settings.check_config_directory(create=start_pairing):
108
+ if not self._controller.settings.check_config_directory(create=start_pairing):
110
109
  return False
111
110
 
112
111
  # Read user file for access code
113
112
  loop = asyncio.get_running_loop()
114
- if not loop.run_in_executor(None, self.panel.read_users_file):
113
+ if not loop.run_in_executor(None, self._controller.panel.read_users_file):
115
114
  return False
116
115
 
117
116
  # Config PKI
118
117
  if self._auto_discover_pki:
119
118
  if self._pki.auto_discover_pki():
120
- self.settings.random_mac = self._pki.formatted_id()
119
+ self._controller.settings.random_mac = self._pki.formatted_id()
121
120
  else:
122
- self._pki.set_id(self.settings.random_mac)
121
+ self._pki.set_id(self._controller.settings.random_mac)
123
122
 
124
123
  # Set mqtt_remote_client_id
125
- self.settings.mqtt_remote_client_id = "qolsys-controller-" + self._pki.formatted_id()
126
- LOGGER.debug("Using MQTT remoteClientID: %s", self.settings.mqtt_remote_client_id)
124
+ self._controller.settings.mqtt_remote_client_id = "qolsys-controller-" + self._pki.formatted_id()
125
+ LOGGER.debug("Using MQTT remoteClientID: %s", self._controller.settings.mqtt_remote_client_id)
127
126
 
128
127
  # Check if plugin is paired
129
128
  if self.is_paired():
@@ -146,7 +145,7 @@ class QolsysPluginRemote(QolsysPlugin):
146
145
  return True
147
146
 
148
147
  async def start_operation(self) -> None:
149
- await self._task_manager.run(self.mqtt_connect_task(reconnect=True), self._mqtt_task_connect_label)
148
+ await self._task_manager.run(self.mqtt_connect_task(reconnect=True, run_forever=True), self._mqtt_task_connect_label)
150
149
 
151
150
  async def stop_operation(self) -> None:
152
151
  LOGGER.debug("Stopping Plugin Operation")
@@ -166,7 +165,7 @@ class QolsysPluginRemote(QolsysPlugin):
166
165
  self.connected = False
167
166
  self.connected_observer.notify()
168
167
 
169
- async def mqtt_connect_task(self, reconnect: bool) -> None:
168
+ async def mqtt_connect_task(self, reconnect: bool, run_forever: bool) -> None:
170
169
  # Configure TLS parameters for MQTT connection
171
170
  tls_params = aiomqtt.TLSParameters(
172
171
  ca_certs=self._pki.qolsys_cer_file_path,
@@ -179,73 +178,79 @@ class QolsysPluginRemote(QolsysPlugin):
179
178
 
180
179
  LOGGER.debug("MQTT: Connecting ...")
181
180
 
181
+ self._task_manager.cancel(self._mqtt_task_listen_label)
182
+ self._task_manager.cancel(self._mqtt_task_ping_label)
183
+
182
184
  while True:
183
185
  try:
184
186
  self.aiomqtt = aiomqtt.Client(
185
- hostname=self.settings.panel_ip,
187
+ hostname=self._controller.settings.panel_ip,
186
188
  port=8883,
187
189
  tls_params=tls_params,
188
190
  tls_insecure=True,
189
191
  clean_session=True,
190
- timeout=self.settings.mqtt_timeout,
191
- identifier="QolsysController",
192
+ timeout=self._controller.settings.mqtt_timeout,
193
+ identifier= self._controller.settings.mqtt_remote_client_id,
192
194
  )
193
195
 
194
196
  await self.aiomqtt.__aenter__()
195
197
 
196
198
  LOGGER.info("MQTT: Client Connected")
197
199
 
198
- # Subscribe to panel internal databse updates
200
+ # Subscribe to panel internal database updates
199
201
  await self.aiomqtt.subscribe("iq2meid")
200
202
 
201
- # Subscribte to MQTT commands response
202
- await self.aiomqtt.subscribe("response_" + self.settings.random_mac, qos=self.settings.mqtt_qos)
203
+ # Subscribte to MQTT private response
204
+ await self.aiomqtt.subscribe("response_" + self._controller.settings.random_mac, qos=self._controller.settings.mqtt_qos)
203
205
 
204
206
  # Subscribe to Z-Wave response
205
- await self.aiomqtt.subscribe("ZWAVE_RESPONSE", qos=self.settings.mqtt_qos)
207
+ await self.aiomqtt.subscribe("ZWAVE_RESPONSE", qos=self._controller.settings.mqtt_qos)
206
208
 
207
209
  # Only log all traffic for debug purposes
208
210
  if self.log_mqtt_mesages:
209
211
  # Subscribe to MQTT commands send to panel by other devices
210
- #await self.aiomqtt.subscribe("mastermeid", qos=self.settings.mqtt_qos)
212
+ await self.aiomqtt.subscribe("mastermeid", qos=self._controller.settings.mqtt_qos)
211
213
 
212
214
  # Subscribe to all topics
213
- await self.aiomqtt.subscribe("#", qos=self.settings.mqtt_qos)
215
+ # await self.aiomqtt.subscribe("#", qos=self._controller.settings.mqtt_qos)
214
216
 
215
- # Start mqtt_listent_task and mqtt_ping_task
216
- self._task_manager.cancel(self._mqtt_task_listen_label)
217
- self._task_manager.cancel(self._mqtt_task_ping_label)
218
217
  self._task_manager.run(self.mqtt_listen_task(), self._mqtt_task_listen_label)
219
218
  self._task_manager.run(self.mqtt_ping_task(), self._mqtt_task_ping_label)
220
219
 
221
220
  response_connect = await self.command_connect()
222
- self.panel.imei = response_connect.get("master_imei", "")
223
- self.panel.product_type = response_connect.get("primary_product_type", "")
221
+ self._controller.panel.imei = response_connect.get("master_imei", "")
222
+ self._controller.panel.product_type = response_connect.get("primary_product_type", "")
224
223
 
225
224
  await self.command_pingevent()
226
- await self.command_timesync()
227
225
  await self.command_pair_status_request()
228
226
 
229
227
  response_database = await self.command_sync_database()
230
228
  LOGGER.debug("MQTT: Updating State from syncdatabase")
231
- self.panel.load_database(response_database.get("fulldbdata"))
232
- self.panel.dump()
233
- self.state.dump()
229
+ self._controller.panel.load_database(response_database.get("fulldbdata"))
230
+ self._controller.panel.dump()
231
+ self._controller.state.dump()
234
232
 
235
233
  self.connected = True
236
234
  self.connected_observer.notify()
235
+
236
+ if not run_forever:
237
+ self.connected = False
238
+ self.connected_observer.notify()
239
+ self._task_manager.cancel(self._mqtt_task_listen_label)
240
+ self._task_manager.cancel(self._mqtt_task_ping_label)
241
+ await self.aiomqtt.__aexit__(None,None,None)
242
+
237
243
  break
238
244
 
239
245
  except aiomqtt.MqttError as err:
240
246
  # Receive pannel network error
241
247
  self.connected = False
242
248
  self.connected_observer.notify()
243
- await self.aiomqtt.__aexit__(None, None, None)
244
249
  self.aiomqtt = None
245
250
 
246
251
  if reconnect:
247
- LOGGER.debug("MQTT Error - %s: Reconnecting in %s seconds ...", err, self.settings.mqtt_timeout)
248
- await asyncio.sleep(self.settings.mqtt_timeout)
252
+ LOGGER.debug("MQTT Error - %s: Connect - Reconnecting in %s seconds ...", err, self._controller.settings.mqtt_timeout)
253
+ await asyncio.sleep(self._controller.settings.mqtt_timeout)
249
254
  else:
250
255
  raise QolsysMqttError from err
251
256
 
@@ -255,16 +260,16 @@ class QolsysPluginRemote(QolsysPlugin):
255
260
  # Pannels need to be re-paired
256
261
  self.connected = False
257
262
  self.connected_observer.notify()
258
- await self.aiomqtt.__aexit__(None, None, None)
259
263
  self.aiomqtt = None
260
264
  raise QolsysSslError from err
261
265
 
262
266
  async def mqtt_ping_task(self) -> None:
263
267
  while True:
264
268
  if self.aiomqtt is not None and self.connected:
265
- await self.command_pingevent()
269
+ with contextlib.suppress(aiomqtt.MqttError):
270
+ await self.command_pingevent()
266
271
 
267
- await asyncio.sleep(self.settings.mqtt_ping)
272
+ await asyncio.sleep(self._controller.settings.mqtt_ping)
268
273
 
269
274
  async def mqtt_listen_task(self) -> None:
270
275
  try:
@@ -274,17 +279,15 @@ class QolsysPluginRemote(QolsysPlugin):
274
279
  LOGGER.debug("MQTT TOPIC: %s\n%s", message.topic, message.payload.decode())
275
280
 
276
281
  # Panel response to MQTT Commands
277
- if message.topic.matches("response_" + self.settings.random_mac):
282
+ if message.topic.matches("response_" + self._controller.settings.random_mac):
278
283
  data = message.payload.decode()
279
- # data = message.payload.decode().replace("\\\\", "\\")
280
- # data = fix_json_string(data)
281
284
  data = json.loads(data)
282
285
  await self._mqtt_command_queue.handle_response(data)
283
286
 
284
287
  # Panel updates to IQ2MEID database
285
288
  if message.topic.matches("iq2meid"):
286
289
  data = json.loads(message.payload.decode())
287
- self.panel.parse_iq2meid_message(data)
290
+ self._controller.panel.parse_iq2meid_message(data)
288
291
 
289
292
  # Panel Z-Wave response
290
293
  if message.topic.matches("ZWAVE_RESPONSE"):
@@ -293,34 +296,23 @@ class QolsysPluginRemote(QolsysPlugin):
293
296
  decoded_payload = base64.b64decode(zwave.get("ZWAVE_PAYLOAD","")).hex()
294
297
  LOGGER.debug("Z-Wave Response: Node(%s) - Status(%s) - Payload(%s)",zwave.get("NODE_ID",""),zwave.get("ZWAVE_COMMAND_STATUS",""),decoded_payload)
295
298
 
296
-
297
299
  except aiomqtt.MqttError as err:
298
300
  self.connected = False
299
301
  self.connected_observer.notify()
300
302
 
301
- LOGGER.debug("%s: Reconnecting in %s seconds ...", err, self.settings.mqtt_timeout)
302
- await asyncio.sleep(self.settings.mqtt_timeout)
303
- self._task_manager.run(self.mqtt_connect_task(reconnect=True), self._mqtt_task_connect_label)
304
-
305
- except ssl.SSLError as err:
306
- # SSL error is and authentication error with invalid certificates en pki
307
- # We cannot recover from this error automaticly
308
- # Pannels need to be re-paired
309
- self.connected = False
310
- self.connected_observer.notify()
311
- await self.aiomqtt.__aexit__(None, None, None)
312
- self.aiomqtt = None
313
- raise QolsysSslError from err
303
+ LOGGER.debug("%s: Listen - Reconnecting in %s seconds ...", err, self._controller.settings.mqtt_timeout)
304
+ await asyncio.sleep(self._controller.settings.mqtt_timeout)
305
+ self._task_manager.run(self.mqtt_connect_task(reconnect=True, run_forever=True), self._mqtt_task_connect_label)
314
306
 
315
307
  async def start_initial_pairing(self) -> bool:
316
308
  # check if random_mac exist
317
- if self.settings.random_mac == "":
309
+ if self._controller.settings.random_mac == "":
318
310
  LOGGER.debug("Creating random_mac")
319
- self.settings.random_mac = generate_random_mac()
320
- self._pki.create(self.settings.random_mac, key_size=self.settings.key_size)
311
+ self._controller.settings.random_mac = generate_random_mac()
312
+ self._pki.create(self._controller.settings.random_mac, key_size=self._controller.settings.key_size)
321
313
 
322
314
  # Check if PKI is valid
323
- self._pki.set_id(self.settings.random_mac)
315
+ self._pki.set_id(self._controller.settings.random_mac)
324
316
  LOGGER.debug("Checking PKI")
325
317
  if not (
326
318
  self._pki.check_key_file() and
@@ -332,19 +324,19 @@ class QolsysPluginRemote(QolsysPlugin):
332
324
 
333
325
  LOGGER.debug("Starting Pairing Process")
334
326
 
335
- if not self.settings.check_plugin_ip():
327
+ if not self._controller.settings.check_plugin_ip():
336
328
  LOGGER.error("Plugin IP Address not configured")
337
329
  return False
338
330
 
339
331
  # If we dont allready have client signed certificate, start the pairing server
340
- if not self._pki.check_secure_file() or not self._pki.check_qolsys_cer_file() or not self.settings.check_panel_ip():
332
+ if not self._pki.check_secure_file() or not self._pki.check_qolsys_cer_file() or not self._controller.settings.check_panel_ip():
341
333
 
342
334
  # High Level Random Pairing Port
343
335
  pairing_port = random.randint(50000, 55000)
344
336
 
345
337
  # Start Pairing mDNS Brodcast
346
- LOGGER.debug("Starting mDNS Service Discovery: %s:%s", self.settings.plugin_ip, str(pairing_port))
347
- mdns_server = QolsysMDNS(self.settings.plugin_ip, pairing_port)
338
+ LOGGER.debug("Starting mDNS Service Discovery: %s:%s", self._controller.settings.plugin_ip, str(pairing_port))
339
+ mdns_server = QolsysMDNS(self._controller.settings.plugin_ip, pairing_port)
348
340
  await mdns_server.start_mdns()
349
341
 
350
342
  # Start Key Exchange Server
@@ -352,8 +344,7 @@ class QolsysPluginRemote(QolsysPlugin):
352
344
  context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
353
345
  context.load_cert_chain(certfile=self._pki.cer_file_path, keyfile=self._pki.key_file_path)
354
346
  self.certificate_exchange_server = await asyncio.start_server(self.handle_key_exchange_client,
355
- self.settings.plugin_ip, pairing_port, ssl=context)
356
-
347
+ self._controller.settings.plugin_ip, pairing_port, ssl=context)
357
348
  LOGGER.debug("Certificate Exchange Server Waiting for Panel")
358
349
  LOGGER.debug("Press Pair Button in IQ Remote Config Page ...")
359
350
 
@@ -363,6 +354,7 @@ class QolsysPluginRemote(QolsysPlugin):
363
354
 
364
355
  except asyncio.CancelledError:
365
356
  LOGGER.debug("Stoping Certificate Exchange Server")
357
+ await self.certificate_exchange_server.wait_closed()
366
358
  LOGGER.debug("Stoping mDNS Service Discovery")
367
359
  await mdns_server.stop_mdns()
368
360
 
@@ -370,19 +362,16 @@ class QolsysPluginRemote(QolsysPlugin):
370
362
 
371
363
  # We have client sgined certificate at this point
372
364
  # Connect to Panel MQTT to send pairing command
373
- await self._task_manager.run(self.mqtt_connect_task(reconnect=False), self._mqtt_task_connect_label)
365
+ await self._task_manager.run(self.mqtt_connect_task(reconnect=False, run_forever=False), self._mqtt_task_connect_label)
374
366
  LOGGER.debug("Plugin Pairing Completed ")
375
367
  return True
376
368
 
377
- async def handle_key_exchange_client(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
369
+ async def handle_key_exchange_client(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: # noqa: PLR0915
378
370
 
379
371
  received_panel_mac = False
380
372
  received_signed_client_certificate = False
381
373
  received_qolsys_cer = False
382
374
 
383
- signed_certificate_data = ""
384
- qolsys_certificate_data = ""
385
-
386
375
  try:
387
376
  continue_pairing = True
388
377
  while continue_pairing:
@@ -398,19 +387,19 @@ class QolsysPluginRemote(QolsysPlugin):
398
387
  LOGGER.debug("Receiving from Panel: %s", mac)
399
388
 
400
389
  # Remove \x00 and \x01 from received string
401
- self.settings.panel_mac = "".join(char for char in mac if char.isprintable())
402
- self.settings.panel_ip = address
390
+ self._controller.settings.panel_mac = "".join(char for char in mac if char.isprintable())
391
+ self._controller.settings.panel_ip = address
403
392
  received_panel_mac = True
404
393
 
405
394
  # Sending random_mac to panel
406
- message = b"\x00\x11" + self.settings.random_mac.encode()
395
+ message = b"\x00\x11" + self._controller.settings.random_mac.encode()
407
396
  LOGGER.debug("Sending to Panel: %s", message.decode())
408
397
  writer.write(message)
409
398
  await writer.drain()
410
399
 
411
400
  # Sending CSR File to panel
412
- with open(self._pki.csr_file_path, "rb") as file:
413
- content = file.read()
401
+ async with aiofiles.open(self._pki.csr_file_path, mode="rb") as f:
402
+ content = await f.read()
414
403
  LOGGER.debug("Sending to Panel: [CSR File Content]")
415
404
  writer.write(content)
416
405
  writer.write(b"sent")
@@ -420,36 +409,26 @@ class QolsysPluginRemote(QolsysPlugin):
420
409
 
421
410
  # Read signed certificate data
422
411
  if (received_panel_mac and not received_signed_client_certificate and not received_qolsys_cer):
412
+ request = await reader.readuntil(b"sent")
413
+ if request.endswith(b"sent"):
414
+ request = request[:-4]
423
415
 
424
- request = (await reader.readline())
425
- signed_certificate_data += request.decode()
426
-
427
- if "sent" in request.decode():
428
- certificates = [item for item in signed_certificate_data.split("sent") if item]
429
- LOGGER.debug("Saving [Signed Client Certificate]")
430
- with open(self._pki.secure_file_path, "wb") as f:
431
- f.write(certificates[0].encode())
432
- received_signed_client_certificate = True
433
-
434
- if len(certificates) > 1:
435
- qolsys_certificate_data += certificates[1]
416
+ LOGGER.debug("Saving [Signed Client Certificate]")
417
+ async with aiofiles.open(self._pki.secure_file_path, mode="wb") as f:
418
+ await f.write(request)
419
+ received_signed_client_certificate = True
436
420
 
437
421
  # Read qolsys certificate data
438
422
  if (received_panel_mac and received_signed_client_certificate and not received_qolsys_cer):
439
-
440
- request = (await reader.readline())
441
- qolsys_certificate_data += request.decode()
442
-
443
- if "sent" in request.decode():
423
+ request = await reader.readuntil(b"sent")
424
+ if request.endswith(b"sent"):
425
+ request = request[:-4]
426
+
427
+ LOGGER.debug("Saving [Qolsys Certificate]")
428
+ async with aiofiles.open(self._pki.qolsys_cer_file_path, mode="wb") as f:
429
+ await f.write(request)
430
+ received_qolsys_cer = True
444
431
  continue_pairing = False
445
- certificates = [item for item in qolsys_certificate_data.split("sent") if item]
446
-
447
- LOGGER.debug("Saving [Qolsys Certificate]")
448
- with open(self._pki.qolsys_cer_file_path, "w") as f:
449
- f.write(certificates[0])
450
- received_qolsys_cer = True
451
- continue_pairing = False
452
- writer.close()
453
432
 
454
433
  continue
455
434
 
@@ -461,6 +440,7 @@ class QolsysPluginRemote(QolsysPlugin):
461
440
 
462
441
  finally:
463
442
  writer.close()
443
+ await writer.wait_closed()
464
444
  self.certificate_exchange_server.close()
465
445
 
466
446
  async def send_command(self, topic: str, json_payload: str, request_id: str) -> dict:
@@ -468,17 +448,17 @@ class QolsysPluginRemote(QolsysPlugin):
468
448
  LOGGER.error("MQTT Client not configured")
469
449
  raise QolsysMqttError
470
450
 
471
- await self.aiomqtt.publish(topic=topic, payload=json.dumps(json_payload), qos=self.settings.mqtt_qos)
451
+ await self.aiomqtt.publish(topic=topic, payload=json.dumps(json_payload), qos=self._controller.settings.mqtt_qos)
472
452
  return await self._mqtt_command_queue.wait_for_response(request_id)
473
453
 
474
454
  async def command_connect(self) -> dict:
475
455
  LOGGER.debug("MQTT: Sending connect command")
476
456
 
477
457
  topic = "mastermeid"
478
- ipAddress = self.settings.plugin_ip
458
+ ipAddress = self._controller.settings.plugin_ip
479
459
  eventName = "connect_v204"
480
- macAddress = self.settings.random_mac
481
- remoteClientID = self.settings.mqtt_remote_client_id
460
+ macAddress = self._controller.settings.random_mac
461
+ remoteClientID = self._controller.settings.mqtt_remote_client_id
482
462
  softwareVersion = "4.4.1"
483
463
  producType = "tab07_rk68"
484
464
  bssid = ""
@@ -497,8 +477,8 @@ class QolsysPluginRemote(QolsysPlugin):
497
477
  remote_panel_plugged = 1
498
478
  remote_panel_battery_temperature = 430
499
479
  requestID = str(uuid.uuid4())
500
- responseTopic = "response_" + self.settings.random_mac
501
- remoteMacAddress = self.settings.random_mac
480
+ responseTopic = "response_" + self._controller.settings.random_mac
481
+ remoteMacAddress = self._controller.settings.random_mac
502
482
 
503
483
  dhcpInfo = {
504
484
  "ipaddress": "",
@@ -548,9 +528,9 @@ class QolsysPluginRemote(QolsysPlugin):
548
528
 
549
529
  topic = "mastermeid"
550
530
  eventName = "pingevent"
551
- macAddress = self.settings.random_mac
531
+ macAddress = self._controller.settings.random_mac
552
532
  remote_panel_status = "Active"
553
- ipAddress = self.settings.plugin_ip
533
+ ipAddress = self._controller.settings.plugin_ip
554
534
  current_battery_status = "Normal"
555
535
  remote_panel_battery_percentage = 100
556
536
  remote_panel_battery_temperature = 430
@@ -563,8 +543,8 @@ class QolsysPluginRemote(QolsysPlugin):
563
543
  remote_panel_battery_health = 2
564
544
  remote_panel_plugged = 1
565
545
  requestID = str(uuid.uuid4())
566
- remoteMacAddress = self.settings.random_mac
567
- responseTopic = "response_" + self.settings.random_mac
546
+ remoteMacAddress = self._controller.settings.random_mac
547
+ responseTopic = "response_" + self._controller.settings.random_mac
568
548
 
569
549
  payload = {
570
550
  "eventName": eventName,
@@ -597,8 +577,8 @@ class QolsysPluginRemote(QolsysPlugin):
597
577
  eventName = "timeSync"
598
578
  startTimestamp = datetime.datetime.now().timestamp()
599
579
  requestID = str(uuid.uuid4())
600
- responseTopic = "response_" + self.settings.random_mac
601
- remoteMacAddress = self.settings.random_mac
580
+ responseTopic = "response_" + self._controller.settings.random_mac
581
+ remoteMacAddress = self._controller.settings.random_mac
602
582
 
603
583
  payload = {
604
584
  "eventName": eventName,
@@ -617,8 +597,8 @@ class QolsysPluginRemote(QolsysPlugin):
617
597
  topic = "mastermeid"
618
598
  eventName = "syncdatabase"
619
599
  requestID = str(uuid.uuid4())
620
- responseTopic = "response_" + self.settings.random_mac
621
- remoteMacAddress = self.settings.random_mac
600
+ responseTopic = "response_" + self._controller.settings.random_mac
601
+ remoteMacAddress = self._controller.settings.random_mac
622
602
 
623
603
  payload = {
624
604
  "eventName": eventName,
@@ -637,8 +617,8 @@ class QolsysPluginRemote(QolsysPlugin):
637
617
  topic = "mastermeid"
638
618
  eventName = "acStatus"
639
619
  requestID = str(uuid.uuid4())
640
- responseTopic = "response_" + self.settings.random_mac
641
- remoteMacAddress = self.settings.random_mac
620
+ responseTopic = "response_" + self._controller.settings.random_mac
621
+ remoteMacAddress = self._controller.settings.random_mac
642
622
  acStatus = "Connected"
643
623
 
644
624
  payload = {"eventName": eventName,
@@ -655,8 +635,8 @@ class QolsysPluginRemote(QolsysPlugin):
655
635
  topic = "mastermeid"
656
636
  eventName = "dealerLogo"
657
637
  requestID = str(uuid.uuid4())
658
- responseTopic = "response_" + self.settings.random_mac
659
- remoteMacAddress = self.settings.random_mac
638
+ responseTopic = "response_" + self._controller.settings.random_mac
639
+ remoteMacAddress = self._controller.settings.random_mac
660
640
 
661
641
  payload = {
662
642
  "eventName": eventName,
@@ -672,9 +652,9 @@ class QolsysPluginRemote(QolsysPlugin):
672
652
 
673
653
  topic = "mastermeid"
674
654
  eventName = "pair_status_request"
675
- remoteMacAddress = self.settings.random_mac
655
+ remoteMacAddress = self._controller.settings.random_mac
676
656
  requestID = str(uuid.uuid4())
677
- responseTopic = "response_" + self.settings.random_mac
657
+ responseTopic = "response_" + self._controller.settings.random_mac
678
658
 
679
659
  payload = {
680
660
  "eventName": eventName,
@@ -691,9 +671,9 @@ class QolsysPluginRemote(QolsysPlugin):
691
671
 
692
672
  topic = "mastermeid"
693
673
  eventName = "disconnect"
694
- remoteClientID = self.settings.mqtt_remote_client_id
674
+ remoteClientID = self._controller.settings.mqtt_remote_client_id
695
675
  requestID = str(uuid.uuid4())
696
- remoteMacAddress = self.settings.random_mac
676
+ remoteMacAddress = self._controller.settings.random_mac
697
677
 
698
678
  payload = {
699
679
  "eventName": eventName,
@@ -710,9 +690,9 @@ class QolsysPluginRemote(QolsysPlugin):
710
690
  topic = "mastermeid"
711
691
  eventName = "connect_v204"
712
692
  pairing_request = True
713
- ipAddress = self.settings.plugin_ip
714
- macAddress = self.settings.random_mac
715
- remoteClientID = self.settings.mqtt_remote_client_id
693
+ ipAddress = self._controller.settings.plugin_ip
694
+ macAddress = self._controller.settings.random_mac
695
+ remoteClientID = self._controller.settings.mqtt_remote_client_id
716
696
  softwareVersion = "4.4.1"
717
697
  productType = "tab07_rk68"
718
698
  bssid = ""
@@ -720,8 +700,8 @@ class QolsysPluginRemote(QolsysPlugin):
720
700
  dealerIconsCheckSum = ""
721
701
  remote_feature_support_version = "1"
722
702
  requestID = str(uuid.uuid4())
723
- responseTopic = "response_" + self.settings.random_mac
724
- remoteMacAddress = self.settings.random_mac
703
+ responseTopic = "response_" + self._controller.settings.random_mac
704
+ remoteMacAddress = self._controller.settings.random_mac
725
705
 
726
706
  dhcpInfo = {
727
707
  "ipaddress": "",
@@ -759,7 +739,7 @@ class QolsysPluginRemote(QolsysPlugin):
759
739
  LOGGER.debug("MQTT: Sending ui_delay command")
760
740
 
761
741
  # partition state needs to be sent for ui_delay to work
762
- partition = self.state.partition(partition_id)
742
+ partition = self._controller.state.partition(partition_id)
763
743
 
764
744
  arming_command = {
765
745
  "operation_name": "ui_delay",
@@ -768,7 +748,7 @@ class QolsysPluginRemote(QolsysPlugin):
768
748
  "partitionID": partition_id, # STR EXPECTED
769
749
  "silentDisarming":silent_disarming,
770
750
  "operation_source": 1,
771
- "macAddress": self.settings.random_mac,
751
+ "macAddress": self._controller.settings.random_mac,
772
752
  }
773
753
 
774
754
  topic = "mastermeid"
@@ -777,8 +757,8 @@ class QolsysPluginRemote(QolsysPlugin):
777
757
  ipcInterfaceName = "android.os.IQInternalService"
778
758
  ipcTransactionID = 7
779
759
  requestID = str(uuid.uuid4())
780
- remoteMacAddress = self.settings.random_mac
781
- responseTopic = "response_" + self.settings.random_mac
760
+ remoteMacAddress = self._controller.settings.random_mac
761
+ responseTopic = "response_" + self._controller.settings.random_mac
782
762
 
783
763
  payload = {
784
764
  "eventName": eventName,
@@ -798,7 +778,7 @@ class QolsysPluginRemote(QolsysPlugin):
798
778
  LOGGER.debug("MQTT: Receiving ui_delay command")
799
779
 
800
780
  async def command_disarm(self, partition_id: str, user_code: str = "", silent_disarming: bool = False) -> bool:
801
- partition = self.state.partition(partition_id)
781
+ partition = self._controller.state.partition(partition_id)
802
782
  if not partition:
803
783
  LOGGER.debug("MQTT: disarm command error - Unknow Partition")
804
784
  return False
@@ -806,7 +786,7 @@ class QolsysPluginRemote(QolsysPlugin):
806
786
  # Do local user code verification
807
787
  user_id = 1
808
788
  if self.check_user_code_on_disarm:
809
- user_id = self.panel.check_user(user_code)
789
+ user_id = self._controller.panel.check_user(user_code)
810
790
  if user_id == -1:
811
791
  LOGGER.debug("MQTT: disarm command error - user_code error")
812
792
  return False
@@ -834,7 +814,7 @@ class QolsysPluginRemote(QolsysPlugin):
834
814
  "userID": user_id,
835
815
  "partitionID": int(partition_id), # INT EXPECTED
836
816
  "operation_source": 1,
837
- "macAddress": self.settings.random_mac,
817
+ "macAddress": self._controller.settings.random_mac,
838
818
  }
839
819
 
840
820
  topic = "mastermeid"
@@ -843,8 +823,8 @@ class QolsysPluginRemote(QolsysPlugin):
843
823
  ipcInterfaceName = "android.os.IQInternalService"
844
824
  ipcTransactionID = 7
845
825
  requestID = str(uuid.uuid4())
846
- remoteMacAddress = self.settings.random_mac
847
- responseTopic = "response_" + self.settings.random_mac
826
+ remoteMacAddress = self._controller.settings.random_mac
827
+ responseTopic = "response_" + self._controller.settings.random_mac
848
828
 
849
829
  payload = {"eventName": eventName,
850
830
  "ipcServiceName": ipcServiceName,
@@ -867,10 +847,10 @@ class QolsysPluginRemote(QolsysPlugin):
867
847
  LOGGER.debug("MQTT: Sending zwave_doorlock_set command: EXPERIMENTAL")
868
848
  LOGGER.debug("MQTT: Sending zwave_doorlock_set command - Node(%s) - Locked(%s)",node_id,locked)
869
849
 
870
- command = 68
850
+ command = 98
871
851
 
872
852
  # 0 unlocked
873
- # 255 lockeck
853
+ # 255 locked
874
854
  lock_mode = 0
875
855
  if locked:
876
856
  lock_mode = 255
@@ -885,7 +865,7 @@ class QolsysPluginRemote(QolsysPlugin):
885
865
  },
886
866
  {
887
867
  "dataType": "byteArray",
888
- "dataValue": [command,lock_mode],
868
+ "dataValue": [command,1,lock_mode],
889
869
  },
890
870
  {
891
871
  "dataType": "int",
@@ -907,8 +887,8 @@ class QolsysPluginRemote(QolsysPlugin):
907
887
  ipcInterfaceName = "android.os.IQZwaveService"
908
888
  ipcTransactionID = 47
909
889
  requestID = str(uuid.uuid4())
910
- remoteMacAddress = self.settings.random_mac
911
- responseTopic = "response_" + self.settings.random_mac
890
+ remoteMacAddress = self._controller.settings.random_mac
891
+ responseTopic = "response_" + self._controller.settings.random_mac
912
892
 
913
893
  payload = {
914
894
  "eventName": eventName,
@@ -927,7 +907,7 @@ class QolsysPluginRemote(QolsysPlugin):
927
907
  async def command_zwave_thermostat_setpoint_set(self, node_id: int, mode:ThermostatMode, setpoint:float) -> None:
928
908
  # Command 67
929
909
  LOGGER.debug("MQTT: Sending zwave_thermostat_setpoint_set command: EXPERIMENTAL")
930
- LOGGER.debug("MQTT: Sending zwave_thermostat_setpoint_set command")
910
+ LOGGER.debug("MQTT: Sending zwave_thermostat_setpoint_set - Node(%s) - Mode(%s) - Setpoint(%s)",node_id,mode,setpoint)
931
911
  ipcRequest = [{
932
912
  "dataType": "int",
933
913
  "dataValue": node_id,
@@ -960,8 +940,8 @@ class QolsysPluginRemote(QolsysPlugin):
960
940
  ipcInterfaceName = "android.os.IQZwaveService"
961
941
  ipcTransactionID = 47
962
942
  requestID = str(uuid.uuid4())
963
- remoteMacAddress = self.settings.random_mac
964
- responseTopic = "response_" + self.settings.random_mac
943
+ remoteMacAddress = self._controller.settings.random_mac
944
+ responseTopic = "response_" + self._controller.settings.random_mac
965
945
 
966
946
  payload = {"eventName": eventName,
967
947
  "ipcServiceName": ipcServiceName,
@@ -978,7 +958,7 @@ class QolsysPluginRemote(QolsysPlugin):
978
958
  async def command_zwave_thermostat_mode_set(self, node_id: int, mode:ThermostatMode) -> None:
979
959
  # Command 64
980
960
  LOGGER.debug("MQTT: Sending zwave_thermostat_mode_set command: EXPERIMENTAL")
981
- LOGGER.debug("MQTT: Sending zwave_thermostat_mode_set command")
961
+ LOGGER.debug("MQTT: Sending zwave_thermostat_mode_set command - Node(%s) - Mode(%s)",node_id,mode)
982
962
  ipcRequest = [{
983
963
  "dataType": "int",
984
964
  "dataValue": node_id,
@@ -1011,8 +991,8 @@ class QolsysPluginRemote(QolsysPlugin):
1011
991
  ipcInterfaceName = "android.os.IQZwaveService"
1012
992
  ipcTransactionID = 47
1013
993
  requestID = str(uuid.uuid4())
1014
- remoteMacAddress = self.settings.random_mac
1015
- responseTopic = "response_" + self.settings.random_mac
994
+ remoteMacAddress = self._controller.settings.random_mac
995
+ responseTopic = "response_" + self._controller.settings.random_mac
1016
996
 
1017
997
  payload = {
1018
998
  "eventName": eventName,
@@ -1022,7 +1002,7 @@ class QolsysPluginRemote(QolsysPlugin):
1022
1002
  "ipcRequest": ipcRequest,
1023
1003
  "requestID": requestID,
1024
1004
  "responseTopic": responseTopic,
1025
- "remoteMacAddress": remoteMacAddress
1005
+ "remoteMacAddress": remoteMacAddress,
1026
1006
  }
1027
1007
 
1028
1008
  await self.send_command(topic, payload, requestID)
@@ -1031,7 +1011,7 @@ class QolsysPluginRemote(QolsysPlugin):
1031
1011
  async def command_zwave_thermostat_fan_mode_set(self, node_id: int, fan_mode:ThermostatFanMode) -> None:
1032
1012
  # Command 68
1033
1013
  LOGGER.debug("MQTT: Sending zwave_thermostat_fan_mode_set command: EXPERIMENTAL")
1034
- LOGGER.debug("MQTT: Sending zwave_thermostat_fan_mode_set command")
1014
+ LOGGER.debug("MQTT: Sending zwave_thermostat_fan_mode_set command - Node(%s) - FanMode(%s)",node_id,fan_mode)
1035
1015
  ipcRequest = [{
1036
1016
  "dataType": "int",
1037
1017
  "dataValue": node_id,
@@ -1064,8 +1044,8 @@ class QolsysPluginRemote(QolsysPlugin):
1064
1044
  ipcInterfaceName = "android.os.IQZwaveService"
1065
1045
  ipcTransactionID = 47
1066
1046
  requestID = str(uuid.uuid4())
1067
- remoteMacAddress = self.settings.random_mac
1068
- responseTopic = "response_" + self.settings.random_mac
1047
+ remoteMacAddress = self._controller.settings.random_mac
1048
+ responseTopic = "response_" + self._controller.settings.random_mac
1069
1049
 
1070
1050
  payload = {"eventName": eventName,
1071
1051
  "ipcServiceName": ipcServiceName,
@@ -1080,7 +1060,7 @@ class QolsysPluginRemote(QolsysPlugin):
1080
1060
  LOGGER.debug("MQTT: Receiving zwave_thermostat_fan_mode_set command")
1081
1061
 
1082
1062
  async def command_zwave_switch_multi_level(self, node_id: int, level: int) -> None:
1083
- LOGGER.debug("MQTT: Sending zwave_switch_multi_level command")
1063
+ LOGGER.debug("MQTT: Sending zwave_switch_multi_level command - Node(%s) - Level(%s)",node_id,level)
1084
1064
  ipcRequest = [{
1085
1065
  "dataType": "int", # Node ID
1086
1066
  "dataValue": node_id,
@@ -1114,8 +1094,8 @@ class QolsysPluginRemote(QolsysPlugin):
1114
1094
  ipcInterfaceName = "android.os.IQZwaveService"
1115
1095
  ipcTransactionID = 47
1116
1096
  requestID = str(uuid.uuid4())
1117
- remoteMacAddress = self.settings.random_mac
1118
- responseTopic = "response_" + self.settings.random_mac
1097
+ remoteMacAddress = self._controller.settings.random_mac
1098
+ responseTopic = "response_" + self._controller.settings.random_mac
1119
1099
 
1120
1100
  payload = {"eventName": eventName,
1121
1101
  "ipcServiceName": ipcServiceName,
@@ -1168,8 +1148,8 @@ class QolsysPluginRemote(QolsysPlugin):
1168
1148
  ipcInterfaceName = "android.os.IQZwaveService"
1169
1149
  ipcTransactionID = 47
1170
1150
  requestID = str(uuid.uuid4())
1171
- remoteMacAddress = self.settings.random_mac
1172
- responseTopic = "response_" + self.settings.random_mac
1151
+ remoteMacAddress = self._controller.settings.random_mac
1152
+ responseTopic = "response_" + self._controller.settings.random_mac
1173
1153
 
1174
1154
  payload = {"eventName": eventName,
1175
1155
  "ipcServiceName": ipcServiceName,
@@ -1184,22 +1164,22 @@ class QolsysPluginRemote(QolsysPlugin):
1184
1164
  LOGGER.debug("MQTT:Receiving zwave_switch_binary command")
1185
1165
 
1186
1166
 
1187
- async def command_arm(self, partition_id: str, arming_type: str, user_code: str = "", exit_sounds: bool = False,
1188
- instant_arm: bool = False) -> bool:
1167
+ async def command_arm(self, partition_id: str, arming_type: str, user_code: str = "", exit_sounds: bool = False, # noqa: PLR0913
1168
+ instant_arm: bool = False, entry_delay: bool = True) -> bool:
1189
1169
 
1190
- LOGGER.debug("MQTT: Sending arm command: partition%s, arming_type:%s, secure_arm:%s",
1191
- partition_id, arming_type, self.panel.SECURE_ARMING)
1170
+ LOGGER.debug("MQTT: Sending arm command: partition%s, arming_type:%s, exit_sounds:%s, instant_arm: %s, entry_delay:%s",
1171
+ partition_id, arming_type,exit_sounds,instant_arm,entry_delay)
1192
1172
 
1193
1173
  user_id = 0
1194
1174
 
1195
- partition = self.state.partition(partition_id)
1175
+ partition = self._controller.state.partition(partition_id)
1196
1176
  if not partition:
1197
1177
  LOGGER.debug("MQTT: arm command error - Unknow Partition")
1198
1178
  return False
1199
1179
 
1200
- if self.panel.SECURE_ARMING == "true" and self.check_user_code_on_arm:
1180
+ if self._controller.panel.SECURE_ARMING == "true" and self.check_user_code_on_arm:
1201
1181
  # Do local user code verification to arm if secure arming is enabled
1202
- user_id = self.panel.check_user(user_code)
1182
+ user_id = self._controller.panel.check_user(user_code)
1203
1183
  if user_id == -1:
1204
1184
  LOGGER.debug("MQTT: arm command error - user_code error")
1205
1185
  return False
@@ -1223,19 +1203,23 @@ class QolsysPluginRemote(QolsysPlugin):
1223
1203
  if not exit_sounds:
1224
1204
  exitSoundValue = "OFF"
1225
1205
 
1206
+ entryDelay = "ON"
1207
+ if not entry_delay:
1208
+ entryDelay = "OFF"
1209
+
1226
1210
  arming_command = {
1227
1211
  "operation_name": mqtt_arming_type,
1228
1212
  "bypass_zoneid_set": "[]",
1229
1213
  "userID": user_id,
1230
1214
  "partitionID": int(partition_id),
1231
1215
  "exitSoundValue": exitSoundValue,
1232
- "entryDelayValue": "OFF",
1216
+ "entryDelayValue": entryDelay,
1233
1217
  "multiplePartitionsSelected": False,
1234
1218
  "instant_arming": instant_arm,
1235
1219
  "final_exit_arming_selected": False,
1236
1220
  "manually_selected_zones": "[]",
1237
1221
  "operation_source": 1,
1238
- "macAddress": self.settings.random_mac,
1222
+ "macAddress": self._controller.settings.random_mac,
1239
1223
  }
1240
1224
 
1241
1225
  topic = "mastermeid"
@@ -1244,8 +1228,8 @@ class QolsysPluginRemote(QolsysPlugin):
1244
1228
  ipcInterfaceName = "android.os.IQInternalService"
1245
1229
  ipcTransactionID = 7
1246
1230
  requestID = str(uuid.uuid4())
1247
- remoteMacAddress = self.settings.random_mac
1248
- responseTopic = "response_" + self.settings.random_mac
1231
+ remoteMacAddress = self._controller.settings.random_mac
1232
+ responseTopic = "response_" + self._controller.settings.random_mac
1249
1233
 
1250
1234
  payload = {
1251
1235
  "eventName": eventName,
@@ -1268,7 +1252,7 @@ class QolsysPluginRemote(QolsysPlugin):
1268
1252
  async def command_execute_scene(self,scene_id:str) -> bool:
1269
1253
  LOGGER.debug("MQTT: Sending execute_scene command")
1270
1254
 
1271
- scene = self.state.scene(scene_id)
1255
+ scene = self._controller.state.scene(scene_id)
1272
1256
  if not scene:
1273
1257
  LOGGER.debug("MQTT: command_execute_scene Erro - Unknow Scene: %s", scene_id)
1274
1258
  return False
@@ -1277,7 +1261,7 @@ class QolsysPluginRemote(QolsysPlugin):
1277
1261
  "operation_name": "execute_scene",
1278
1262
  "scene_id": scene.scene_id,
1279
1263
  "operation_source": 1,
1280
- "macAddress": self.settings.random_mac,
1264
+ "macAddress": self._controller.settings.random_mac,
1281
1265
  }
1282
1266
 
1283
1267
  topic = "mastermeid"
@@ -1286,8 +1270,8 @@ class QolsysPluginRemote(QolsysPlugin):
1286
1270
  ipcInterfaceName = "android.os.IQInternalService"
1287
1271
  ipcTransactionID = 7
1288
1272
  requestID = str(uuid.uuid4())
1289
- remoteMacAddress = self.settings.random_mac
1290
- responseTopic = "response_" + self.settings.random_mac
1273
+ remoteMacAddress = self._controller.settings.random_mac
1274
+ responseTopic = "response_" + self._controller.settings.random_mac
1291
1275
 
1292
1276
  payload = {
1293
1277
  "eventName": eventName,