pyg90alarm 2.3.0__py3-none-any.whl → 2.4.0__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/__init__.py +1 -1
- pyg90alarm/alarm.py +105 -17
- pyg90alarm/const.py +53 -1
- pyg90alarm/entities/base_list.py +15 -10
- pyg90alarm/event_mapping.py +99 -0
- pyg90alarm/local/alarm_phones.py +77 -0
- pyg90alarm/local/alert_config.py +190 -0
- pyg90alarm/local/config.py +11 -136
- pyg90alarm/local/dataclass_load_save.py +131 -0
- pyg90alarm/local/history.py +55 -13
- pyg90alarm/local/host_config.py +167 -0
- pyg90alarm/local/net_config.py +127 -0
- pyg90alarm/notifications/base.py +18 -1
- pyg90alarm/notifications/protocol.py +12 -0
- {pyg90alarm-2.3.0.dist-info → pyg90alarm-2.4.0.dist-info}/METADATA +1 -1
- {pyg90alarm-2.3.0.dist-info → pyg90alarm-2.4.0.dist-info}/RECORD +19 -13
- {pyg90alarm-2.3.0.dist-info → pyg90alarm-2.4.0.dist-info}/WHEEL +0 -0
- {pyg90alarm-2.3.0.dist-info → pyg90alarm-2.4.0.dist-info}/licenses/LICENSE +0 -0
- {pyg90alarm-2.3.0.dist-info → pyg90alarm-2.4.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Copyright (c) 2021 Ilia Sotnikov
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
|
11
|
+
# all copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
Represents various configuration aspects of the alarm panel.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
from typing import TYPE_CHECKING, Optional
|
|
26
|
+
import logging
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
from enum import IntFlag
|
|
29
|
+
|
|
30
|
+
from pyg90alarm.exceptions import G90Error
|
|
31
|
+
from ..const import G90Commands
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from ..alarm import G90Alarm
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class G90AlertConfigFlags(IntFlag):
|
|
37
|
+
"""
|
|
38
|
+
Alert configuration flags, used bitwise
|
|
39
|
+
"""
|
|
40
|
+
AC_POWER_FAILURE = 1
|
|
41
|
+
AC_POWER_RECOVER = 2
|
|
42
|
+
ARM_DISARM = 4
|
|
43
|
+
HOST_LOW_VOLTAGE = 8
|
|
44
|
+
SENSOR_LOW_VOLTAGE = 16
|
|
45
|
+
WIFI_AVAILABLE = 32
|
|
46
|
+
WIFI_UNAVAILABLE = 64
|
|
47
|
+
DOOR_OPEN = 128
|
|
48
|
+
DOOR_CLOSE = 256
|
|
49
|
+
SMS_PUSH = 512
|
|
50
|
+
UNKNOWN1 = 2048
|
|
51
|
+
UNKNOWN2 = 8192
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
_LOGGER = logging.getLogger(__name__)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class G90AlertConfigData:
|
|
59
|
+
"""
|
|
60
|
+
Represents alert configuration data as received from the alarm panel.
|
|
61
|
+
"""
|
|
62
|
+
flags_data: int
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def flags(self) -> G90AlertConfigFlags:
|
|
66
|
+
"""
|
|
67
|
+
:return: The alert configuration flags
|
|
68
|
+
"""
|
|
69
|
+
return G90AlertConfigFlags(self.flags_data)
|
|
70
|
+
|
|
71
|
+
@flags.setter
|
|
72
|
+
def flags(self, value: G90AlertConfigFlags) -> None:
|
|
73
|
+
"""
|
|
74
|
+
:param value: The alert configuration flags
|
|
75
|
+
"""
|
|
76
|
+
self.flags_data = value.value
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class G90AlertConfig:
|
|
80
|
+
"""
|
|
81
|
+
Represents alert configuration as received from the alarm panel.
|
|
82
|
+
"""
|
|
83
|
+
def __init__(self, parent: G90Alarm) -> None:
|
|
84
|
+
self.parent = parent
|
|
85
|
+
self._cached_data: Optional[G90AlertConfigData] = None
|
|
86
|
+
|
|
87
|
+
async def _get(self) -> G90AlertConfigData:
|
|
88
|
+
"""
|
|
89
|
+
Retrieves the alert configuration flags directly from the device.
|
|
90
|
+
|
|
91
|
+
:return: The alerts configured
|
|
92
|
+
"""
|
|
93
|
+
_LOGGER.debug('Retrieving alert configuration from the device')
|
|
94
|
+
res = await self.parent.command(G90Commands.GETNOTICEFLAG)
|
|
95
|
+
data = G90AlertConfigData(*res)
|
|
96
|
+
_LOGGER.debug(
|
|
97
|
+
'Alert configuration: %s, flags: %s', data,
|
|
98
|
+
repr(data.flags)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Cache the retrieved data for `flags_with_fallback` property
|
|
102
|
+
self._cached_data = data
|
|
103
|
+
|
|
104
|
+
return data
|
|
105
|
+
|
|
106
|
+
async def set(self, flags: G90AlertConfigFlags) -> None:
|
|
107
|
+
"""
|
|
108
|
+
.. deprecated:: 2.3.0
|
|
109
|
+
|
|
110
|
+
This method is deprecated and will always raise a RuntimeError.
|
|
111
|
+
Please use :meth:`set_flag` to set individual flags.
|
|
112
|
+
"""
|
|
113
|
+
raise RuntimeError(
|
|
114
|
+
'The set() method is deprecated. Please use set_flag() to set'
|
|
115
|
+
' individual flags instead.'
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
async def _set(self, flags: G90AlertConfigFlags) -> None:
|
|
119
|
+
"""
|
|
120
|
+
Sets the alert configuration flags on the device.
|
|
121
|
+
"""
|
|
122
|
+
_LOGGER.debug('Setting alert configuration to %s', repr(flags))
|
|
123
|
+
await self.parent.command(G90Commands.SETNOTICEFLAG, [flags.value])
|
|
124
|
+
|
|
125
|
+
async def get_flag(self, flag: G90AlertConfigFlags) -> bool:
|
|
126
|
+
"""
|
|
127
|
+
:param flag: The flag to check
|
|
128
|
+
"""
|
|
129
|
+
return flag in await self.flags
|
|
130
|
+
|
|
131
|
+
async def set_flag(self, flag: G90AlertConfigFlags, value: bool) -> None:
|
|
132
|
+
"""
|
|
133
|
+
Sets the given flag to the desired value.
|
|
134
|
+
|
|
135
|
+
Uses read-modify-write approach.
|
|
136
|
+
|
|
137
|
+
:param flag: The flag to set
|
|
138
|
+
:param value: The value to set
|
|
139
|
+
"""
|
|
140
|
+
# Retrieve current flags
|
|
141
|
+
current_flags = await self.flags
|
|
142
|
+
# Skip updating the flag if it has the desired value
|
|
143
|
+
if (flag in current_flags) == value:
|
|
144
|
+
_LOGGER.debug(
|
|
145
|
+
'Flag %s already set to %s, skipping update',
|
|
146
|
+
repr(flag), value
|
|
147
|
+
)
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
# Set or reset corresponding user flag depending on desired value
|
|
151
|
+
if value:
|
|
152
|
+
current_flags |= flag
|
|
153
|
+
else:
|
|
154
|
+
current_flags &= ~flag
|
|
155
|
+
|
|
156
|
+
# Set the updated flags
|
|
157
|
+
await self._set(current_flags)
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
async def flags(self) -> G90AlertConfigFlags:
|
|
161
|
+
"""
|
|
162
|
+
:return: Symbolic names for corresponding flag bits
|
|
163
|
+
"""
|
|
164
|
+
return (await self._get()).flags
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
async def flags_with_fallback(self) -> Optional[G90AlertConfigFlags]:
|
|
168
|
+
"""
|
|
169
|
+
:return: Symbolic names for corresponding flag bits, falling back to
|
|
170
|
+
cached data if device communication fails
|
|
171
|
+
"""
|
|
172
|
+
result = None
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
result = (await self._get()).flags
|
|
176
|
+
except G90Error as exc:
|
|
177
|
+
_LOGGER.debug(
|
|
178
|
+
'Retrieving alert config flags resulted in error %s',
|
|
179
|
+
repr(exc)
|
|
180
|
+
)
|
|
181
|
+
if self._cached_data is not None:
|
|
182
|
+
_LOGGER.debug(
|
|
183
|
+
'Falling back to cached alert configuration flags: %s',
|
|
184
|
+
repr(self._cached_data.flags)
|
|
185
|
+
)
|
|
186
|
+
result = self._cached_data.flags
|
|
187
|
+
else:
|
|
188
|
+
_LOGGER.debug('No cached alert configuration flags available')
|
|
189
|
+
|
|
190
|
+
return result
|
pyg90alarm/local/config.py
CHANGED
|
@@ -17,141 +17,16 @@
|
|
|
17
17
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
19
|
# SOFTWARE.
|
|
20
|
-
|
|
21
20
|
"""
|
|
22
|
-
|
|
21
|
+
Compatibility module for the alert configuration, which should be imported
|
|
22
|
+
from `local.alert_config` instead.
|
|
23
23
|
"""
|
|
24
|
-
from
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class G90AlertConfigFlags(IntFlag):
|
|
35
|
-
"""
|
|
36
|
-
Alert configuration flags, used bitwise
|
|
37
|
-
"""
|
|
38
|
-
AC_POWER_FAILURE = 1
|
|
39
|
-
AC_POWER_RECOVER = 2
|
|
40
|
-
ARM_DISARM = 4
|
|
41
|
-
HOST_LOW_VOLTAGE = 8
|
|
42
|
-
SENSOR_LOW_VOLTAGE = 16
|
|
43
|
-
WIFI_AVAILABLE = 32
|
|
44
|
-
WIFI_UNAVAILABLE = 64
|
|
45
|
-
DOOR_OPEN = 128
|
|
46
|
-
DOOR_CLOSE = 256
|
|
47
|
-
SMS_PUSH = 512
|
|
48
|
-
UNKNOWN1 = 2048
|
|
49
|
-
UNKNOWN2 = 8192
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
_LOGGER = logging.getLogger(__name__)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@dataclass
|
|
56
|
-
class G90AlertConfigData:
|
|
57
|
-
"""
|
|
58
|
-
Represents alert configuration data as received from the alarm panel.
|
|
59
|
-
"""
|
|
60
|
-
flags_data: int
|
|
61
|
-
|
|
62
|
-
@property
|
|
63
|
-
def flags(self) -> G90AlertConfigFlags:
|
|
64
|
-
"""
|
|
65
|
-
:return: The alert configuration flags
|
|
66
|
-
"""
|
|
67
|
-
return G90AlertConfigFlags(self.flags_data)
|
|
68
|
-
|
|
69
|
-
@flags.setter
|
|
70
|
-
def flags(self, value: G90AlertConfigFlags) -> None:
|
|
71
|
-
"""
|
|
72
|
-
:param value: The alert configuration flags
|
|
73
|
-
"""
|
|
74
|
-
self.flags_data = value.value
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
class G90AlertConfig:
|
|
78
|
-
"""
|
|
79
|
-
Represents alert configuration as received from the alarm panel.
|
|
80
|
-
"""
|
|
81
|
-
def __init__(self, parent: G90Alarm) -> None:
|
|
82
|
-
self.parent = parent
|
|
83
|
-
|
|
84
|
-
async def _get(self) -> G90AlertConfigData:
|
|
85
|
-
"""
|
|
86
|
-
Retrieves the alert configuration flags directly from the device.
|
|
87
|
-
|
|
88
|
-
:return: The alerts configured
|
|
89
|
-
"""
|
|
90
|
-
_LOGGER.debug('Retrieving alert configuration from the device')
|
|
91
|
-
res = await self.parent.command(G90Commands.GETNOTICEFLAG)
|
|
92
|
-
data = G90AlertConfigData(*res)
|
|
93
|
-
_LOGGER.debug(
|
|
94
|
-
'Alert configuration: %s, flags: %s', data,
|
|
95
|
-
repr(data.flags)
|
|
96
|
-
)
|
|
97
|
-
return data
|
|
98
|
-
|
|
99
|
-
async def set(self, flags: G90AlertConfigFlags) -> None:
|
|
100
|
-
"""
|
|
101
|
-
.. deprecated:: 2.3.0
|
|
102
|
-
|
|
103
|
-
This method is deprecated and will always raise a RuntimeError.
|
|
104
|
-
Please use :meth:`set_flag` to set individual flags.
|
|
105
|
-
"""
|
|
106
|
-
raise RuntimeError(
|
|
107
|
-
'The set() method is deprecated. Please use set_flag() to set'
|
|
108
|
-
' individual flags instead.'
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
async def _set(self, flags: G90AlertConfigFlags) -> None:
|
|
112
|
-
"""
|
|
113
|
-
Sets the alert configuration flags on the device.
|
|
114
|
-
"""
|
|
115
|
-
_LOGGER.debug('Setting alert configuration to %s', repr(flags))
|
|
116
|
-
await self.parent.command(G90Commands.SETNOTICEFLAG, [flags.value])
|
|
117
|
-
|
|
118
|
-
async def get_flag(self, flag: G90AlertConfigFlags) -> bool:
|
|
119
|
-
"""
|
|
120
|
-
:param flag: The flag to check
|
|
121
|
-
"""
|
|
122
|
-
return flag in await self.flags
|
|
123
|
-
|
|
124
|
-
async def set_flag(self, flag: G90AlertConfigFlags, value: bool) -> None:
|
|
125
|
-
"""
|
|
126
|
-
Sets the given flag to the desired value.
|
|
127
|
-
|
|
128
|
-
Uses read-modify-write approach.
|
|
129
|
-
|
|
130
|
-
:param flag: The flag to set
|
|
131
|
-
:param value: The value to set
|
|
132
|
-
"""
|
|
133
|
-
# Retrieve current flags
|
|
134
|
-
current_flags = await self.flags
|
|
135
|
-
# Skip updating the flag if it has the desired value
|
|
136
|
-
if (flag in current_flags) == value:
|
|
137
|
-
_LOGGER.debug(
|
|
138
|
-
'Flag %s already set to %s, skipping update',
|
|
139
|
-
repr(flag), value
|
|
140
|
-
)
|
|
141
|
-
return
|
|
142
|
-
|
|
143
|
-
# Set or reset corresponding user flag depending on desired value
|
|
144
|
-
if value:
|
|
145
|
-
current_flags |= flag
|
|
146
|
-
else:
|
|
147
|
-
current_flags &= ~flag
|
|
148
|
-
|
|
149
|
-
# Set the updated flags
|
|
150
|
-
await self._set(current_flags)
|
|
151
|
-
|
|
152
|
-
@property
|
|
153
|
-
async def flags(self) -> G90AlertConfigFlags:
|
|
154
|
-
"""
|
|
155
|
-
:return: Symbolic names for corresponding flag bits
|
|
156
|
-
"""
|
|
157
|
-
return (await self._get()).flags
|
|
24
|
+
from .alert_config import (
|
|
25
|
+
G90AlertConfig, G90AlertConfigData, G90AlertConfigFlags
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
'G90AlertConfig',
|
|
30
|
+
'G90AlertConfigData',
|
|
31
|
+
'G90AlertConfigFlags',
|
|
32
|
+
]
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
|
|
2
|
+
# Copyright (c) 2026 Ilia Sotnikov
|
|
3
|
+
#
|
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
# furnished to do so, subject to the following conditions:
|
|
10
|
+
#
|
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
|
12
|
+
# all copies or substantial portions of the Software.
|
|
13
|
+
#
|
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
# SOFTWARE.
|
|
21
|
+
"""
|
|
22
|
+
Base class for loading/saving dataclasses to a device.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
from typing import TYPE_CHECKING, Type, TypeVar, Optional, ClassVar, Any, Dict
|
|
26
|
+
import logging
|
|
27
|
+
from dataclasses import dataclass, astuple, asdict
|
|
28
|
+
from ..const import G90Commands
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from ..alarm import G90Alarm
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
_LOGGER = logging.getLogger(__name__)
|
|
34
|
+
S = TypeVar('S', bound='DataclassLoadSave')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class DataclassLoadSave:
|
|
39
|
+
"""
|
|
40
|
+
Base class for loading/saving dataclasses to a device.
|
|
41
|
+
|
|
42
|
+
There are multiple ways to implement the functionality:
|
|
43
|
+
- Encapsulate the dataclass inside another class that handles
|
|
44
|
+
loading/saving and exposes dataclass fields as properties. The latter
|
|
45
|
+
part gets complex as properties need to be asynchronous, as well as
|
|
46
|
+
added dynamically at runtime to improve maintainability.
|
|
47
|
+
- Inherit from this class, which provides `load` and `save` methods on top
|
|
48
|
+
of standard dataclasses. This is believed to be more concise and easier
|
|
49
|
+
to understand.
|
|
50
|
+
|
|
51
|
+
Implementing classes must define `LOAD_COMMAND` and `SAVE_COMMAND` class
|
|
52
|
+
variables to specify which commands to use for loading and saving data.
|
|
53
|
+
|
|
54
|
+
Example usage:
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class G90ExampleConfig(DataclassLoadSave):
|
|
58
|
+
LOAD_COMMAND = G90Commands.GETEXAMPLECONFIG
|
|
59
|
+
SAVE_COMMAND = G90Commands.SETEXAMPLECONFIG
|
|
60
|
+
field1: int
|
|
61
|
+
field2: str
|
|
62
|
+
|
|
63
|
+
# Loading data
|
|
64
|
+
config = await G90ExampleConfig.load(G90_alarm_instance)
|
|
65
|
+
print(config.field1, config.field2)
|
|
66
|
+
|
|
67
|
+
# Modifying and saving data
|
|
68
|
+
config.field1 = 42
|
|
69
|
+
await config.save()
|
|
70
|
+
"""
|
|
71
|
+
LOAD_COMMAND: ClassVar[Optional[G90Commands]] = None
|
|
72
|
+
SAVE_COMMAND: ClassVar[Optional[G90Commands]] = None
|
|
73
|
+
|
|
74
|
+
def __post_init__(self) -> None:
|
|
75
|
+
"""
|
|
76
|
+
Post-initialization processing.
|
|
77
|
+
"""
|
|
78
|
+
# Instance variable to hold reference to parent G90Alarm instance,
|
|
79
|
+
# declared here to avoid being part of dataclass fields
|
|
80
|
+
self._parent: Optional[G90Alarm] = None
|
|
81
|
+
|
|
82
|
+
async def save(self) -> None:
|
|
83
|
+
"""
|
|
84
|
+
Save the current data to the device.
|
|
85
|
+
"""
|
|
86
|
+
assert self.SAVE_COMMAND is not None, '`SAVE_COMMAND` must be defined'
|
|
87
|
+
assert self._parent is not None, 'Please call `load()` first'
|
|
88
|
+
|
|
89
|
+
_LOGGER.debug('Setting data to the device: %s', str(self))
|
|
90
|
+
await self._parent.command(
|
|
91
|
+
self.SAVE_COMMAND,
|
|
92
|
+
list(astuple(self))
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
async def load(cls: Type[S], parent: G90Alarm) -> S:
|
|
97
|
+
"""
|
|
98
|
+
Create an instance with values loaded from the device.
|
|
99
|
+
|
|
100
|
+
:return: An instance of the dataclass loaded from the device.
|
|
101
|
+
"""
|
|
102
|
+
assert cls.LOAD_COMMAND is not None, '`LOAD_COMMAND` must be defined'
|
|
103
|
+
assert parent is not None, '`parent` must be provided'
|
|
104
|
+
|
|
105
|
+
data = await parent.command(cls.LOAD_COMMAND)
|
|
106
|
+
obj = cls(*data)
|
|
107
|
+
_LOGGER.debug('Loaded data: %s', str(obj))
|
|
108
|
+
|
|
109
|
+
obj._parent = parent
|
|
110
|
+
|
|
111
|
+
return obj
|
|
112
|
+
|
|
113
|
+
def _asdict(self) -> Dict[str, Any]:
|
|
114
|
+
"""
|
|
115
|
+
Returns the dataclass fields as a dictionary.
|
|
116
|
+
|
|
117
|
+
:return: A dictionary representation.
|
|
118
|
+
"""
|
|
119
|
+
return asdict(self)
|
|
120
|
+
|
|
121
|
+
def __str__(self) -> str:
|
|
122
|
+
"""
|
|
123
|
+
Textual representation of the entry.
|
|
124
|
+
|
|
125
|
+
`str()` is used instead of `repr()` since dataclass provides `repr()`
|
|
126
|
+
by default, and it would be impractical to require each ancestor to
|
|
127
|
+
disable that.
|
|
128
|
+
|
|
129
|
+
:return: A textual representation.
|
|
130
|
+
"""
|
|
131
|
+
return super().__repr__() + f'({str(self._asdict())})'
|
pyg90alarm/local/history.py
CHANGED
|
@@ -33,7 +33,9 @@ from ..const import (
|
|
|
33
33
|
G90AlertStateChangeTypes,
|
|
34
34
|
G90HistoryStates,
|
|
35
35
|
G90RemoteButtonStates,
|
|
36
|
+
G90RFIDKeypadStates,
|
|
36
37
|
)
|
|
38
|
+
from ..event_mapping import map_alert_state
|
|
37
39
|
from ..notifications.base import G90DeviceAlert
|
|
38
40
|
|
|
39
41
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -54,6 +56,8 @@ states_mapping_alerts = {
|
|
|
54
56
|
G90HistoryStates.LOW_BATTERY,
|
|
55
57
|
G90AlertStates.ALARM:
|
|
56
58
|
G90HistoryStates.ALARM,
|
|
59
|
+
G90AlertStates.MOTION_DETECTED:
|
|
60
|
+
G90HistoryStates.MOTION_DETECTED,
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
states_mapping_state_changes = {
|
|
@@ -86,6 +90,27 @@ states_mapping_remote_buttons = {
|
|
|
86
90
|
G90HistoryStates.REMOTE_BUTTON_SOS,
|
|
87
91
|
}
|
|
88
92
|
|
|
93
|
+
states_mapping_rfid = {
|
|
94
|
+
G90RFIDKeypadStates.ARM_AWAY:
|
|
95
|
+
G90HistoryStates.RFID_KEY_ARM_AWAY,
|
|
96
|
+
G90RFIDKeypadStates.ARM_HOME:
|
|
97
|
+
G90HistoryStates.RFID_KEY_ARM_HOME,
|
|
98
|
+
G90RFIDKeypadStates.DISARM:
|
|
99
|
+
G90HistoryStates.RFID_KEY_DISARM,
|
|
100
|
+
G90RFIDKeypadStates.LOW_BATTERY:
|
|
101
|
+
G90HistoryStates.LOW_BATTERY,
|
|
102
|
+
G90RFIDKeypadStates.CARD_0:
|
|
103
|
+
G90HistoryStates.RFID_CARD_0,
|
|
104
|
+
G90RFIDKeypadStates.CARD_1:
|
|
105
|
+
G90HistoryStates.RFID_CARD_1,
|
|
106
|
+
G90RFIDKeypadStates.CARD_2:
|
|
107
|
+
G90HistoryStates.RFID_CARD_2,
|
|
108
|
+
G90RFIDKeypadStates.CARD_3:
|
|
109
|
+
G90HistoryStates.RFID_CARD_3,
|
|
110
|
+
G90RFIDKeypadStates.CARD_4:
|
|
111
|
+
G90HistoryStates.RFID_CARD_4,
|
|
112
|
+
}
|
|
113
|
+
|
|
89
114
|
|
|
90
115
|
@dataclass
|
|
91
116
|
class ProtocolData:
|
|
@@ -140,21 +165,28 @@ class G90History:
|
|
|
140
165
|
"""
|
|
141
166
|
State for the history entry.
|
|
142
167
|
"""
|
|
168
|
+
# pylint: disable=too-many-return-statements
|
|
143
169
|
# No meaningful state for SOS alerts initiated by the panel itself
|
|
144
170
|
# (host)
|
|
145
171
|
if self.type == G90AlertTypes.HOST_SOS:
|
|
146
172
|
return None
|
|
147
173
|
|
|
148
174
|
try:
|
|
149
|
-
#
|
|
150
|
-
if
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
175
|
+
# Remote button pressed or RFID keypad event occurred
|
|
176
|
+
if self.type in [
|
|
177
|
+
G90AlertTypes.SENSOR_ACTIVITY, G90AlertTypes.ALARM
|
|
178
|
+
]:
|
|
179
|
+
# State of the remote indicate which button has been pressed
|
|
180
|
+
if self.source == G90AlertSources.REMOTE:
|
|
181
|
+
return states_mapping_remote_buttons[
|
|
182
|
+
G90RemoteButtonStates(self._protocol_data.state)
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
# State of the RFID keypad indicate which action has occurred
|
|
186
|
+
if self.source == G90AlertSources.RFID:
|
|
187
|
+
return states_mapping_rfid[
|
|
188
|
+
G90RFIDKeypadStates(self._protocol_data.state)
|
|
189
|
+
]
|
|
158
190
|
|
|
159
191
|
# Door open/close or alert types, mapped against `G90AlertStates`
|
|
160
192
|
# using `state` incoming field
|
|
@@ -162,8 +194,14 @@ class G90History:
|
|
|
162
194
|
G90AlertTypes.SENSOR_ACTIVITY, G90AlertTypes.ALARM
|
|
163
195
|
]:
|
|
164
196
|
return G90HistoryStates(
|
|
197
|
+
# Map to history state via consolidated alert state
|
|
165
198
|
states_mapping_alerts[
|
|
166
|
-
|
|
199
|
+
# Map to consolidated alert state first
|
|
200
|
+
map_alert_state(
|
|
201
|
+
# Defaults to sensor source if none available
|
|
202
|
+
self.source or G90AlertSources.SENSOR,
|
|
203
|
+
self._protocol_data.state
|
|
204
|
+
)
|
|
167
205
|
]
|
|
168
206
|
)
|
|
169
207
|
except (ValueError, KeyError):
|
|
@@ -228,8 +266,12 @@ class G90History:
|
|
|
228
266
|
ID of the sensor related to the history entry, might be empty if none
|
|
229
267
|
associated.
|
|
230
268
|
"""
|
|
231
|
-
# Sensor ID will only be available if entry source is
|
|
232
|
-
|
|
269
|
+
# Sensor ID will only be available if entry source is an infrared, RFID
|
|
270
|
+
# keypad or other sensor
|
|
271
|
+
if self.source in [
|
|
272
|
+
G90AlertSources.SENSOR, G90AlertSources.RFID,
|
|
273
|
+
G90AlertSources.INFRARED
|
|
274
|
+
]:
|
|
233
275
|
return self._protocol_data.event_id
|
|
234
276
|
|
|
235
277
|
return None
|
|
@@ -267,6 +309,6 @@ class G90History:
|
|
|
267
309
|
|
|
268
310
|
def __repr__(self) -> str:
|
|
269
311
|
"""
|
|
270
|
-
|
|
312
|
+
Textual representation of the history entry.
|
|
271
313
|
"""
|
|
272
314
|
return super().__repr__() + f'({repr(self._asdict())})'
|