pyg90alarm 1.10.0__py3-none-any.whl → 1.12.1__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.
- pyg90alarm/alarm.py +192 -35
- pyg90alarm/base_cmd.py +49 -83
- pyg90alarm/const.py +30 -0
- pyg90alarm/definitions/sensors.py +241 -1
- pyg90alarm/device_notifications.py +103 -82
- pyg90alarm/discovery.py +11 -43
- pyg90alarm/entities/sensor.py +20 -0
- pyg90alarm/history.py +174 -12
- pyg90alarm/targeted_discovery.py +9 -44
- {pyg90alarm-1.10.0.dist-info → pyg90alarm-1.12.1.dist-info}/METADATA +13 -6
- pyg90alarm-1.12.1.dist-info/RECORD +26 -0
- {pyg90alarm-1.10.0.dist-info → pyg90alarm-1.12.1.dist-info}/WHEEL +1 -1
- pyg90alarm-1.10.0.dist-info/RECORD +0 -26
- {pyg90alarm-1.10.0.dist-info → pyg90alarm-1.12.1.dist-info}/LICENSE +0 -0
- {pyg90alarm-1.10.0.dist-info → pyg90alarm-1.12.1.dist-info}/top_level.txt +0 -0
pyg90alarm/alarm.py
CHANGED
|
@@ -48,12 +48,13 @@ G90HostInfo(host_guid='<...>',
|
|
|
48
48
|
wifi_signal_level=100)
|
|
49
49
|
|
|
50
50
|
"""
|
|
51
|
-
|
|
51
|
+
import asyncio
|
|
52
52
|
import logging
|
|
53
53
|
from .const import (
|
|
54
54
|
G90Commands, REMOTE_PORT,
|
|
55
55
|
REMOTE_TARGETED_DISCOVERY_PORT,
|
|
56
56
|
LOCAL_TARGETED_DISCOVERY_PORT,
|
|
57
|
+
NOTIFICATIONS_PORT,
|
|
57
58
|
G90ArmDisarmTypes,
|
|
58
59
|
)
|
|
59
60
|
from .base_cmd import G90BaseCommand
|
|
@@ -71,11 +72,14 @@ from .config import (G90AlertConfig, G90AlertConfigFlags)
|
|
|
71
72
|
from .history import G90History
|
|
72
73
|
from .user_data_crc import G90UserDataCRC
|
|
73
74
|
from .callback import G90Callback
|
|
75
|
+
from .exceptions import G90Error, G90TimeoutError
|
|
74
76
|
|
|
75
77
|
_LOGGER = logging.getLogger(__name__)
|
|
76
78
|
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
# pylint: disable=too-many-public-methods
|
|
81
|
+
class G90Alarm(G90DeviceNotifications):
|
|
82
|
+
|
|
79
83
|
"""
|
|
80
84
|
Allows to interact with G90 alarm panel.
|
|
81
85
|
|
|
@@ -87,30 +91,31 @@ class G90Alarm: # pylint: disable=too-many-public-methods
|
|
|
87
91
|
protocol commands on WiFi interface, currently the devices don't allow it
|
|
88
92
|
to be customized
|
|
89
93
|
:type port: int, optional
|
|
90
|
-
:param sock: The existing socket to operate on, instead of
|
|
91
|
-
creating one internally. Primarily used by the tests to mock the network
|
|
92
|
-
traffic
|
|
93
|
-
:type sock: socket.socket or None, optional
|
|
94
94
|
:param reset_occupancy_interval: The interval upon that the sensors are
|
|
95
95
|
simulated to go into inactive state.
|
|
96
96
|
:type reset_occupancy_interval: int, optional
|
|
97
97
|
"""
|
|
98
|
-
# pylint: disable=too-many-instance-attributes
|
|
99
|
-
def __init__(self, host, port=REMOTE_PORT,
|
|
100
|
-
reset_occupancy_interval=3
|
|
98
|
+
# pylint: disable=too-many-instance-attributes,too-many-arguments
|
|
99
|
+
def __init__(self, host, port=REMOTE_PORT,
|
|
100
|
+
reset_occupancy_interval=3,
|
|
101
|
+
notifications_host='0.0.0.0',
|
|
102
|
+
notifications_port=NOTIFICATIONS_PORT):
|
|
103
|
+
super().__init__(host=notifications_host, port=notifications_port)
|
|
101
104
|
self._host = host
|
|
102
105
|
self._port = port
|
|
103
106
|
self._sensors = []
|
|
104
107
|
self._devices = []
|
|
105
108
|
self._notifications = None
|
|
106
|
-
self._sock = sock
|
|
107
109
|
self._sensor_cb = None
|
|
108
110
|
self._armdisarm_cb = None
|
|
109
111
|
self._door_open_close_cb = None
|
|
110
112
|
self._alarm_cb = None
|
|
113
|
+
self._low_battery_cb = None
|
|
111
114
|
self._reset_occupancy_interval = reset_occupancy_interval
|
|
112
115
|
self._alert_config = None
|
|
113
116
|
self._sms_alert_when_armed = False
|
|
117
|
+
self._alert_simulation_task = None
|
|
118
|
+
self._alert_simulation_start_listener_back = False
|
|
114
119
|
|
|
115
120
|
async def command(self, code, data=None):
|
|
116
121
|
"""
|
|
@@ -124,7 +129,7 @@ class G90Alarm: # pylint: disable=too-many-public-methods
|
|
|
124
129
|
command invocation
|
|
125
130
|
"""
|
|
126
131
|
cmd = await G90BaseCommand(
|
|
127
|
-
self._host, self._port, code, data
|
|
132
|
+
self._host, self._port, code, data).process()
|
|
128
133
|
return cmd.result
|
|
129
134
|
|
|
130
135
|
def paginated_result(self, code, start=1, end=None):
|
|
@@ -141,7 +146,7 @@ class G90Alarm: # pylint: disable=too-many-public-methods
|
|
|
141
146
|
yields :class:`.G90PaginatedResponse` instance
|
|
142
147
|
"""
|
|
143
148
|
return G90PaginatedResult(
|
|
144
|
-
self._host, self._port, code, start, end
|
|
149
|
+
self._host, self._port, code, start, end
|
|
145
150
|
).process()
|
|
146
151
|
|
|
147
152
|
@classmethod
|
|
@@ -404,10 +409,15 @@ class G90Alarm: # pylint: disable=too-many-public-methods
|
|
|
404
409
|
"""
|
|
405
410
|
res = self.paginated_result(G90Commands.GETHISTORY,
|
|
406
411
|
start, count)
|
|
407
|
-
history = [G90History(*x.data) async for x in res]
|
|
408
|
-
return history
|
|
409
412
|
|
|
410
|
-
|
|
413
|
+
# Sort the history entries from older to newer - device typically does
|
|
414
|
+
# that, but apparently that is not guaranteed
|
|
415
|
+
return sorted(
|
|
416
|
+
[G90History(*x.data) async for x in res],
|
|
417
|
+
key=lambda x: x.datetime, reverse=True
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
async def on_sensor_activity(self, idx, name, occupancy=True):
|
|
411
421
|
"""
|
|
412
422
|
Callback that invoked both for sensor notifications and door open/close
|
|
413
423
|
alerts, since the logic for both is same and could be reused. Please
|
|
@@ -424,6 +434,7 @@ class G90Alarm: # pylint: disable=too-many-public-methods
|
|
|
424
434
|
alerts (only for `door` type sensors, if door open/close alerts are
|
|
425
435
|
enabled)
|
|
426
436
|
"""
|
|
437
|
+
_LOGGER.debug('on_sensor_acitvity: %s %s %s', idx, name, occupancy)
|
|
427
438
|
sensor = await self.find_sensor(idx, name)
|
|
428
439
|
if sensor:
|
|
429
440
|
_LOGGER.debug('Setting occupancy to %s (previously %s)',
|
|
@@ -481,19 +492,21 @@ class G90Alarm: # pylint: disable=too-many-public-methods
|
|
|
481
492
|
def sensor_callback(self, value):
|
|
482
493
|
self._sensor_cb = value
|
|
483
494
|
|
|
484
|
-
async def
|
|
495
|
+
async def on_door_open_close(self, event_id, zone_name, is_open):
|
|
485
496
|
"""
|
|
486
497
|
Callback that invoked when door open/close alert comes from the alarm
|
|
487
498
|
panel. Please note the callback is for internal use by the class.
|
|
488
499
|
|
|
489
|
-
.. seealso:: `method`:
|
|
500
|
+
.. seealso:: `method`:on_sensor_activity for arguments
|
|
490
501
|
"""
|
|
491
502
|
# Same internal callback is reused both for door open/close alerts and
|
|
492
503
|
# sensor notifications. The former adds reporting when a door is
|
|
493
504
|
# closed, since the notifications aren't sent for such events
|
|
494
|
-
await self.
|
|
505
|
+
await self.on_sensor_activity(event_id, zone_name, is_open)
|
|
495
506
|
# Invoke user specified callback if any
|
|
496
|
-
G90Callback.invoke(
|
|
507
|
+
G90Callback.invoke(
|
|
508
|
+
self._door_open_close_cb, event_id, zone_name, is_open
|
|
509
|
+
)
|
|
497
510
|
|
|
498
511
|
@property
|
|
499
512
|
def door_open_close_callback(self):
|
|
@@ -513,7 +526,7 @@ class G90Alarm: # pylint: disable=too-many-public-methods
|
|
|
513
526
|
"""
|
|
514
527
|
self._door_open_close_cb = value
|
|
515
528
|
|
|
516
|
-
async def
|
|
529
|
+
async def on_armdisarm(self, state):
|
|
517
530
|
"""
|
|
518
531
|
Callback that invoked when the device is armed or disarmed. Please note
|
|
519
532
|
the callback is for internal use by the class.
|
|
@@ -549,7 +562,7 @@ class G90Alarm: # pylint: disable=too-many-public-methods
|
|
|
549
562
|
def armdisarm_callback(self, value):
|
|
550
563
|
self._armdisarm_cb = value
|
|
551
564
|
|
|
552
|
-
async def
|
|
565
|
+
async def on_alarm(self, event_id, zone_name):
|
|
553
566
|
"""
|
|
554
567
|
Callback that invoked when alarm is triggered. Fires alarm callback if
|
|
555
568
|
set by the user with `:property:G90Alarm.alarm_callback`.
|
|
@@ -558,14 +571,14 @@ class G90Alarm: # pylint: disable=too-many-public-methods
|
|
|
558
571
|
:param int: Index of the sensor triggered alarm
|
|
559
572
|
:param str: Sensor name
|
|
560
573
|
"""
|
|
561
|
-
sensor = await self.find_sensor(
|
|
574
|
+
sensor = await self.find_sensor(event_id, zone_name)
|
|
562
575
|
# The callback is still delivered to the caller even if the sensor
|
|
563
576
|
# isn't found, only `extra_data` is skipped. That is to ensur the
|
|
564
577
|
# important callback isn't filtered
|
|
565
578
|
extra_data = sensor.extra_data if sensor else None
|
|
566
579
|
|
|
567
580
|
G90Callback.invoke(
|
|
568
|
-
self._alarm_cb,
|
|
581
|
+
self._alarm_cb, event_id, zone_name, extra_data
|
|
569
582
|
)
|
|
570
583
|
|
|
571
584
|
@property
|
|
@@ -585,27 +598,49 @@ class G90Alarm: # pylint: disable=too-many-public-methods
|
|
|
585
598
|
def alarm_callback(self, value):
|
|
586
599
|
self._alarm_cb = value
|
|
587
600
|
|
|
588
|
-
async def
|
|
601
|
+
async def on_low_battery(self, event_id, zone_name):
|
|
602
|
+
"""
|
|
603
|
+
Callback that invoked when the sensor reports on low battery. Fires
|
|
604
|
+
corresponding callback if set by the user with
|
|
605
|
+
`:property:G90Alarm.on_low_battery_callback`.
|
|
606
|
+
Please note the callback is for internal use by the class.
|
|
607
|
+
|
|
608
|
+
:param int: Index of the sensor triggered alarm
|
|
609
|
+
:param str: Sensor name
|
|
610
|
+
"""
|
|
611
|
+
sensor = await self.find_sensor(event_id, zone_name)
|
|
612
|
+
if sensor:
|
|
613
|
+
# Invoke per-sensor callback if provided
|
|
614
|
+
G90Callback.invoke(sensor.low_battery_callback)
|
|
615
|
+
|
|
616
|
+
G90Callback.invoke(self._low_battery_cb, event_id, zone_name)
|
|
617
|
+
|
|
618
|
+
@property
|
|
619
|
+
def low_battery_callback(self):
|
|
620
|
+
"""
|
|
621
|
+
Get or set low battery callback, the callback is invoked when sensor
|
|
622
|
+
the condition is reported by a sensor.
|
|
623
|
+
|
|
624
|
+
:type: .. py:function:: ()(idx, name)
|
|
625
|
+
"""
|
|
626
|
+
return self._low_battery_cb
|
|
627
|
+
|
|
628
|
+
@low_battery_callback.setter
|
|
629
|
+
def low_battery_callback(self, value):
|
|
630
|
+
self._low_battery_cb = value
|
|
631
|
+
|
|
632
|
+
async def listen_device_notifications(self):
|
|
589
633
|
"""
|
|
590
634
|
Starts internal listener for device notifications/alerts.
|
|
591
635
|
|
|
592
|
-
:param sock: socket instance to listen on, mostly used by tests
|
|
593
|
-
:type: socket.socket
|
|
594
636
|
"""
|
|
595
|
-
self.
|
|
596
|
-
sensor_cb=self._internal_sensor_cb,
|
|
597
|
-
door_open_close_cb=self._internal_door_open_close_cb,
|
|
598
|
-
armdisarm_cb=self._internal_armdisarm_cb,
|
|
599
|
-
alarm_cb=self._internal_alarm_cb,
|
|
600
|
-
sock=sock)
|
|
601
|
-
await self._notifications.listen()
|
|
637
|
+
await self.listen()
|
|
602
638
|
|
|
603
639
|
def close_device_notifications(self):
|
|
604
640
|
"""
|
|
605
641
|
Closes the listener for device notifications/alerts.
|
|
606
642
|
"""
|
|
607
|
-
|
|
608
|
-
self._notifications.close()
|
|
643
|
+
self.close()
|
|
609
644
|
|
|
610
645
|
async def arm_away(self):
|
|
611
646
|
"""
|
|
@@ -642,3 +677,125 @@ class G90Alarm: # pylint: disable=too-many-public-methods
|
|
|
642
677
|
@sms_alert_when_armed.setter
|
|
643
678
|
def sms_alert_when_armed(self, value):
|
|
644
679
|
self._sms_alert_when_armed = value
|
|
680
|
+
|
|
681
|
+
async def start_simulating_alerts_from_history(
|
|
682
|
+
self, interval=5, history_depth=5
|
|
683
|
+
):
|
|
684
|
+
"""
|
|
685
|
+
Starts the separate task to simulate device alerts from history
|
|
686
|
+
entries.
|
|
687
|
+
|
|
688
|
+
The listener for device notifications will be stopped, so device
|
|
689
|
+
notifications will not be processed thus resulting in possible
|
|
690
|
+
duplicated if those could be received from the network.
|
|
691
|
+
|
|
692
|
+
:param int interval: Interval (in seconds) between polling for newer
|
|
693
|
+
history entities
|
|
694
|
+
:param int history_depth: Amount of history entries to fetch during
|
|
695
|
+
each polling cycle
|
|
696
|
+
"""
|
|
697
|
+
# Remember if device notifications listener has been started already
|
|
698
|
+
self._alert_simulation_start_listener_back = self.listener_started
|
|
699
|
+
# And then stop it
|
|
700
|
+
self.close()
|
|
701
|
+
|
|
702
|
+
# Start the task
|
|
703
|
+
self._alert_simulation_task = asyncio.create_task(
|
|
704
|
+
self._simulate_alerts_from_history(interval, history_depth)
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
async def stop_simulating_alerts_from_history(self):
|
|
708
|
+
"""
|
|
709
|
+
Stops the task simulating device alerts from history entries.
|
|
710
|
+
|
|
711
|
+
The listener for device notifications will be started back, if it was
|
|
712
|
+
running when simulation has been started.
|
|
713
|
+
"""
|
|
714
|
+
# Stop the task simulating the device alerts from history if it was
|
|
715
|
+
# running
|
|
716
|
+
if self._alert_simulation_task:
|
|
717
|
+
self._alert_simulation_task.cancel()
|
|
718
|
+
self._alert_simulation_task = None
|
|
719
|
+
|
|
720
|
+
# Start device notifications listener back if it was running when
|
|
721
|
+
# simulated alerts have been enabled
|
|
722
|
+
if self._alert_simulation_start_listener_back:
|
|
723
|
+
await self.listen()
|
|
724
|
+
|
|
725
|
+
async def _simulate_alerts_from_history(self, interval, history_depth):
|
|
726
|
+
"""
|
|
727
|
+
Periodically fetches history entries from the device and simulates
|
|
728
|
+
device alerts off of those.
|
|
729
|
+
|
|
730
|
+
Only the history entries occur after the process is started are
|
|
731
|
+
handled, to avoid triggering callbacks retrospectively.
|
|
732
|
+
|
|
733
|
+
See :method:`start_simulating_alerts_from_history` for the parameters.
|
|
734
|
+
"""
|
|
735
|
+
last_history_ts = None
|
|
736
|
+
|
|
737
|
+
_LOGGER.debug(
|
|
738
|
+
'Simulating device alerts from history:'
|
|
739
|
+
' interval %s, history depth %s',
|
|
740
|
+
interval, history_depth
|
|
741
|
+
)
|
|
742
|
+
while True:
|
|
743
|
+
try:
|
|
744
|
+
# Retrieve the history entries of the specified amount - full
|
|
745
|
+
# history retrieval might be an unnecessary long operation
|
|
746
|
+
history = await self.history(count=history_depth)
|
|
747
|
+
|
|
748
|
+
# Initial iteration where no timestamp of most recent history
|
|
749
|
+
# entry is recorded - do that and skip to next iteration, since
|
|
750
|
+
# it isn't yet known what entries would be considered as new
|
|
751
|
+
# ones.
|
|
752
|
+
# Empty history will skip recording the timestamp and the
|
|
753
|
+
# looping over entries below, effectively skipping to next
|
|
754
|
+
# iteration
|
|
755
|
+
if not last_history_ts and history:
|
|
756
|
+
# First entry in the list is assumed to be the most recent
|
|
757
|
+
# one
|
|
758
|
+
last_history_ts = history[0].datetime
|
|
759
|
+
_LOGGER.debug(
|
|
760
|
+
'Initial time stamp of last history entry: %s',
|
|
761
|
+
last_history_ts
|
|
762
|
+
)
|
|
763
|
+
continue
|
|
764
|
+
|
|
765
|
+
# Process history entries from older to newer to preserve the
|
|
766
|
+
# order of happenings
|
|
767
|
+
for item in reversed(history):
|
|
768
|
+
# Process only the entries newer than one been recorded as
|
|
769
|
+
# most recent one
|
|
770
|
+
if item.datetime > last_history_ts:
|
|
771
|
+
_LOGGER.debug(
|
|
772
|
+
'Found newer history entry: %s, simulating alert',
|
|
773
|
+
repr(item)
|
|
774
|
+
)
|
|
775
|
+
# Send the history entry down the device notification
|
|
776
|
+
# code as alert, as if it came from the device and its
|
|
777
|
+
# notifications port
|
|
778
|
+
self._handle_alert(
|
|
779
|
+
(self._host, self._notifications_port),
|
|
780
|
+
item.as_device_alert()
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
# Record the entry as most recent one
|
|
784
|
+
last_history_ts = item.datetime
|
|
785
|
+
_LOGGER.debug(
|
|
786
|
+
'Time stamp of last history entry: %s',
|
|
787
|
+
last_history_ts
|
|
788
|
+
)
|
|
789
|
+
except (G90Error, G90TimeoutError) as exc:
|
|
790
|
+
_LOGGER.debug(
|
|
791
|
+
'Error interacting with device, ignoring %s', repr(exc)
|
|
792
|
+
)
|
|
793
|
+
except Exception as exc:
|
|
794
|
+
_LOGGER.error(
|
|
795
|
+
'Exception simulating device alerts from history: %s',
|
|
796
|
+
repr(exc)
|
|
797
|
+
)
|
|
798
|
+
raise exc
|
|
799
|
+
|
|
800
|
+
# Sleep to next iteration
|
|
801
|
+
await asyncio.sleep(interval)
|
pyg90alarm/base_cmd.py
CHANGED
|
@@ -44,62 +44,6 @@ class G90Header(NamedTuple):
|
|
|
44
44
|
data: Optional[str] = None
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
class G90DeviceProtocol:
|
|
48
|
-
"""
|
|
49
|
-
tbd
|
|
50
|
-
|
|
51
|
-
:meta private:
|
|
52
|
-
"""
|
|
53
|
-
def __init__(self):
|
|
54
|
-
"""
|
|
55
|
-
tbd
|
|
56
|
-
"""
|
|
57
|
-
self._data = None
|
|
58
|
-
|
|
59
|
-
@property
|
|
60
|
-
def future_data(self):
|
|
61
|
-
"""
|
|
62
|
-
tbd
|
|
63
|
-
"""
|
|
64
|
-
return self._data
|
|
65
|
-
|
|
66
|
-
@future_data.setter
|
|
67
|
-
def future_data(self, value):
|
|
68
|
-
"""
|
|
69
|
-
tbd
|
|
70
|
-
"""
|
|
71
|
-
self._data = value
|
|
72
|
-
|
|
73
|
-
def connection_made(self, transport):
|
|
74
|
-
"""
|
|
75
|
-
tbd
|
|
76
|
-
"""
|
|
77
|
-
|
|
78
|
-
def connection_lost(self, exc):
|
|
79
|
-
"""
|
|
80
|
-
tbd
|
|
81
|
-
"""
|
|
82
|
-
|
|
83
|
-
def datagram_received(self, data, addr):
|
|
84
|
-
"""
|
|
85
|
-
tbd
|
|
86
|
-
"""
|
|
87
|
-
if asyncio.isfuture(self._data):
|
|
88
|
-
if self._data.done():
|
|
89
|
-
_LOGGER.warning('Excessive packet received'
|
|
90
|
-
' from %s:%s: %s',
|
|
91
|
-
addr[0], addr[1], data)
|
|
92
|
-
return
|
|
93
|
-
self._data.set_result((*addr, data))
|
|
94
|
-
|
|
95
|
-
def error_received(self, exc):
|
|
96
|
-
"""
|
|
97
|
-
tbd
|
|
98
|
-
"""
|
|
99
|
-
if asyncio.isfuture(self._data) and not self._data.done():
|
|
100
|
-
self._data.set_exception(exc)
|
|
101
|
-
|
|
102
|
-
|
|
103
47
|
class G90BaseCommand:
|
|
104
48
|
"""
|
|
105
49
|
tbd
|
|
@@ -110,7 +54,7 @@ class G90BaseCommand:
|
|
|
110
54
|
|
|
111
55
|
def __init__(self, host, port, code,
|
|
112
56
|
data=None, local_port=None,
|
|
113
|
-
timeout=3.0, retries=3
|
|
57
|
+
timeout=3.0, retries=3):
|
|
114
58
|
"""
|
|
115
59
|
tbd
|
|
116
60
|
"""
|
|
@@ -123,6 +67,7 @@ class G90BaseCommand:
|
|
|
123
67
|
self._retries = retries
|
|
124
68
|
self._data = '""'
|
|
125
69
|
self._result = None
|
|
70
|
+
self._connection_result = None
|
|
126
71
|
if data:
|
|
127
72
|
self._data = json.dumps([code, data],
|
|
128
73
|
# No newlines to be inserted
|
|
@@ -130,13 +75,40 @@ class G90BaseCommand:
|
|
|
130
75
|
# No whitespace around entities
|
|
131
76
|
separators=(',', ':'))
|
|
132
77
|
self._resp = G90Header()
|
|
133
|
-
self._sock = sock
|
|
134
78
|
|
|
135
|
-
|
|
79
|
+
# Implementation of datagram protocol,
|
|
80
|
+
# https://docs.python.org/3/library/asyncio-protocol.html#datagram-protocols
|
|
81
|
+
def connection_made(self, transport):
|
|
82
|
+
"""
|
|
83
|
+
tbd
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def connection_lost(self, exc):
|
|
87
|
+
"""
|
|
88
|
+
tbd
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def datagram_received(self, data, addr):
|
|
92
|
+
"""
|
|
93
|
+
tbd
|
|
94
|
+
"""
|
|
95
|
+
if asyncio.isfuture(self._connection_result):
|
|
96
|
+
if self._connection_result.done():
|
|
97
|
+
_LOGGER.warning('Excessive packet received'
|
|
98
|
+
' from %s:%s: %s',
|
|
99
|
+
addr[0], addr[1], data)
|
|
100
|
+
return
|
|
101
|
+
self._connection_result.set_result((*addr, data))
|
|
102
|
+
|
|
103
|
+
def error_received(self, exc):
|
|
136
104
|
"""
|
|
137
105
|
tbd
|
|
138
106
|
"""
|
|
139
|
-
|
|
107
|
+
if (
|
|
108
|
+
asyncio.isfuture(self._connection_result) and not
|
|
109
|
+
self._connection_result.done()
|
|
110
|
+
):
|
|
111
|
+
self._connection_result.set_exception(exc)
|
|
140
112
|
|
|
141
113
|
async def _create_connection(self):
|
|
142
114
|
"""
|
|
@@ -147,23 +119,17 @@ class G90BaseCommand:
|
|
|
147
119
|
except AttributeError:
|
|
148
120
|
loop = asyncio.get_event_loop()
|
|
149
121
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
transport, protocol = await loop.create_datagram_endpoint(
|
|
163
|
-
self._proto_factory,
|
|
164
|
-
remote_addr=(self.host, self.port),
|
|
165
|
-
**extra_kwargs,
|
|
166
|
-
allow_broadcast=True)
|
|
122
|
+
_LOGGER.debug('Creating UDP endpoint for %s:%s',
|
|
123
|
+
self.host, self.port)
|
|
124
|
+
extra_kwargs = {}
|
|
125
|
+
if self._local_port:
|
|
126
|
+
extra_kwargs['local_addr'] = ('0.0.0.0', self._local_port)
|
|
127
|
+
|
|
128
|
+
transport, protocol = await loop.create_datagram_endpoint(
|
|
129
|
+
lambda: self,
|
|
130
|
+
remote_addr=(self.host, self.port),
|
|
131
|
+
**extra_kwargs,
|
|
132
|
+
allow_broadcast=True)
|
|
167
133
|
|
|
168
134
|
return transport, protocol
|
|
169
135
|
|
|
@@ -248,7 +214,7 @@ class G90BaseCommand:
|
|
|
248
214
|
tbd
|
|
249
215
|
"""
|
|
250
216
|
|
|
251
|
-
transport,
|
|
217
|
+
transport, _ = await self._create_connection()
|
|
252
218
|
attempts = self._retries
|
|
253
219
|
while True:
|
|
254
220
|
attempts = attempts - 1
|
|
@@ -256,24 +222,24 @@ class G90BaseCommand:
|
|
|
256
222
|
loop = asyncio.get_running_loop()
|
|
257
223
|
except AttributeError:
|
|
258
224
|
loop = asyncio.get_event_loop()
|
|
259
|
-
|
|
225
|
+
self._connection_result = loop.create_future()
|
|
260
226
|
async with self._sk_lock:
|
|
261
227
|
_LOGGER.debug('(code %s) Sending request to %s:%s',
|
|
262
228
|
self._code, self.host, self.port)
|
|
263
229
|
transport.sendto(self.to_wire())
|
|
264
|
-
done, _ = await asyncio.wait([
|
|
230
|
+
done, _ = await asyncio.wait([self._connection_result],
|
|
265
231
|
timeout=self._timeout)
|
|
266
|
-
if
|
|
232
|
+
if self._connection_result in done:
|
|
267
233
|
break
|
|
268
234
|
# Cancel the future to signal protocol handler it is no longer
|
|
269
235
|
# valid, the future will be re-created on next retry
|
|
270
|
-
|
|
236
|
+
self._connection_result.cancel()
|
|
271
237
|
if not attempts:
|
|
272
238
|
transport.close()
|
|
273
239
|
raise G90TimeoutError()
|
|
274
240
|
_LOGGER.debug('Timed out, retrying')
|
|
275
241
|
transport.close()
|
|
276
|
-
(host, port, data) =
|
|
242
|
+
(host, port, data) = self._connection_result.result()
|
|
277
243
|
_LOGGER.debug('Received response from %s:%s', host, port)
|
|
278
244
|
if self.host != '255.255.255.255':
|
|
279
245
|
if self.host != host or host == '255.255.255.255':
|
pyg90alarm/const.py
CHANGED
|
@@ -27,6 +27,7 @@ from enum import IntEnum
|
|
|
27
27
|
REMOTE_PORT = 12368
|
|
28
28
|
REMOTE_TARGETED_DISCOVERY_PORT = 12900
|
|
29
29
|
LOCAL_TARGETED_DISCOVERY_PORT = 12901
|
|
30
|
+
NOTIFICATIONS_PORT = 12901
|
|
30
31
|
|
|
31
32
|
CMD_PAGE_SIZE = 10
|
|
32
33
|
|
|
@@ -184,10 +185,21 @@ class G90AlertSources(IntEnum):
|
|
|
184
185
|
"""
|
|
185
186
|
Defines possible sources of the alert sent by the panel.
|
|
186
187
|
"""
|
|
188
|
+
DEVICE = 0
|
|
187
189
|
SENSOR = 1
|
|
188
190
|
DOORBELL = 12
|
|
189
191
|
|
|
190
192
|
|
|
193
|
+
class G90AlertStates(IntEnum):
|
|
194
|
+
"""
|
|
195
|
+
Defines possible states of the alert sent by the panel.
|
|
196
|
+
"""
|
|
197
|
+
DOOR_CLOSE = 0
|
|
198
|
+
DOOR_OPEN = 1
|
|
199
|
+
TAMPER = 3
|
|
200
|
+
LOW_BATTERY = 4
|
|
201
|
+
|
|
202
|
+
|
|
191
203
|
class G90AlertStateChangeTypes(IntEnum):
|
|
192
204
|
"""
|
|
193
205
|
Defines types of alert for device state changes.
|
|
@@ -200,3 +212,21 @@ class G90AlertStateChangeTypes(IntEnum):
|
|
|
200
212
|
LOW_BATTERY = 6
|
|
201
213
|
WIFI_CONNECTED = 7
|
|
202
214
|
WIFI_DISCONNECTED = 8
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class G90HistoryStates(IntEnum):
|
|
218
|
+
"""
|
|
219
|
+
Defines possible states for history entities.
|
|
220
|
+
"""
|
|
221
|
+
DOOR_CLOSE = 1
|
|
222
|
+
DOOR_OPEN = 2
|
|
223
|
+
TAMPER = 3
|
|
224
|
+
ALARM = 4
|
|
225
|
+
AC_POWER_FAILURE = 5
|
|
226
|
+
AC_POWER_RECOVER = 6
|
|
227
|
+
DISARM = 7
|
|
228
|
+
ARM_AWAY = 8
|
|
229
|
+
ARM_HOME = 9
|
|
230
|
+
LOW_BATTERY = 10
|
|
231
|
+
WIFI_CONNECTED = 11
|
|
232
|
+
WIFI_DISCONNECTED = 12
|