pyg90alarm 2.3.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 +84 -0
- pyg90alarm/alarm.py +1274 -0
- pyg90alarm/callback.py +146 -0
- pyg90alarm/cloud/__init__.py +31 -0
- pyg90alarm/cloud/const.py +56 -0
- pyg90alarm/cloud/messages.py +593 -0
- pyg90alarm/cloud/notifications.py +410 -0
- pyg90alarm/cloud/protocol.py +518 -0
- pyg90alarm/const.py +273 -0
- pyg90alarm/definitions/__init__.py +3 -0
- pyg90alarm/definitions/base.py +247 -0
- pyg90alarm/definitions/devices.py +366 -0
- pyg90alarm/definitions/sensors.py +843 -0
- pyg90alarm/entities/__init__.py +3 -0
- pyg90alarm/entities/base_entity.py +93 -0
- pyg90alarm/entities/base_list.py +268 -0
- pyg90alarm/entities/device.py +97 -0
- pyg90alarm/entities/device_list.py +156 -0
- pyg90alarm/entities/sensor.py +891 -0
- pyg90alarm/entities/sensor_list.py +183 -0
- pyg90alarm/exceptions.py +63 -0
- pyg90alarm/local/__init__.py +0 -0
- pyg90alarm/local/base_cmd.py +293 -0
- pyg90alarm/local/config.py +157 -0
- pyg90alarm/local/discovery.py +103 -0
- pyg90alarm/local/history.py +272 -0
- pyg90alarm/local/host_info.py +89 -0
- pyg90alarm/local/host_status.py +52 -0
- pyg90alarm/local/notifications.py +117 -0
- pyg90alarm/local/paginated_cmd.py +132 -0
- pyg90alarm/local/paginated_result.py +135 -0
- pyg90alarm/local/targeted_discovery.py +162 -0
- pyg90alarm/local/user_data_crc.py +46 -0
- pyg90alarm/notifications/__init__.py +0 -0
- pyg90alarm/notifications/base.py +481 -0
- pyg90alarm/notifications/protocol.py +127 -0
- pyg90alarm/py.typed +0 -0
- pyg90alarm-2.3.0.dist-info/METADATA +277 -0
- pyg90alarm-2.3.0.dist-info/RECORD +42 -0
- pyg90alarm-2.3.0.dist-info/WHEEL +5 -0
- pyg90alarm-2.3.0.dist-info/licenses/LICENSE +21 -0
- pyg90alarm-2.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
Implements paginated command for G90 alarm panel protocol.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
import logging
|
|
26
|
+
from typing import Any, cast
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
from .base_cmd import G90BaseCommand, G90BaseCommandData
|
|
29
|
+
from ..exceptions import G90Error
|
|
30
|
+
from ..const import G90Commands
|
|
31
|
+
|
|
32
|
+
_LOGGER = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class G90PaginationFields:
|
|
37
|
+
"""
|
|
38
|
+
Represents structure of the pagination fields used by alarm panel.
|
|
39
|
+
|
|
40
|
+
:meta private:
|
|
41
|
+
"""
|
|
42
|
+
total: int
|
|
43
|
+
start: int
|
|
44
|
+
nelems: int
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class G90PaginatedCommand(G90BaseCommand):
|
|
48
|
+
"""
|
|
49
|
+
Implements paginated command for alarm panel protocol.
|
|
50
|
+
"""
|
|
51
|
+
# pylint: disable=too-many-positional-arguments,too-many-arguments
|
|
52
|
+
def __init__(
|
|
53
|
+
self, host: str, port: int, code: G90Commands, start: int, end: int,
|
|
54
|
+
**kwargs: Any
|
|
55
|
+
) -> None:
|
|
56
|
+
self._start = start
|
|
57
|
+
self._end = end
|
|
58
|
+
self._expected_nelems = end - start + 1
|
|
59
|
+
self._nelems = 0
|
|
60
|
+
self._total = 0
|
|
61
|
+
super().__init__(host, port, code, [self._start, self._end],
|
|
62
|
+
**kwargs)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def total(self) -> int:
|
|
66
|
+
"""
|
|
67
|
+
Total number of records available.
|
|
68
|
+
"""
|
|
69
|
+
return self._total
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def start(self) -> int:
|
|
73
|
+
"""
|
|
74
|
+
Index of the first record in the response.
|
|
75
|
+
"""
|
|
76
|
+
return self._start
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def count(self) -> int:
|
|
80
|
+
"""
|
|
81
|
+
Number of records in the response.
|
|
82
|
+
"""
|
|
83
|
+
return self._nelems
|
|
84
|
+
|
|
85
|
+
def _parse(self, data: str) -> None:
|
|
86
|
+
"""
|
|
87
|
+
Parses the response from the alarm panel.
|
|
88
|
+
"""
|
|
89
|
+
super()._parse(data)
|
|
90
|
+
resp_data: G90BaseCommandData = self._resp.data or []
|
|
91
|
+
try:
|
|
92
|
+
page_data = resp_data.pop(0)
|
|
93
|
+
page_info = G90PaginationFields(*page_data)
|
|
94
|
+
except TypeError as exc:
|
|
95
|
+
raise G90Error(f'Wrong pagination data {page_data} - {str(exc)}'
|
|
96
|
+
) from exc
|
|
97
|
+
except IndexError as exc:
|
|
98
|
+
raise G90Error(f"Missing pagination in response '{self._resp}'"
|
|
99
|
+
) from exc
|
|
100
|
+
|
|
101
|
+
self._total = page_info.total
|
|
102
|
+
self._start = page_info.start
|
|
103
|
+
self._nelems = page_info.nelems
|
|
104
|
+
|
|
105
|
+
errors = []
|
|
106
|
+
if self._nelems != len(resp_data):
|
|
107
|
+
qualifier = (
|
|
108
|
+
"Truncated" if self._nelems > len(resp_data) else "Extra"
|
|
109
|
+
)
|
|
110
|
+
errors.append(
|
|
111
|
+
f'{qualifier} data provided in paginated response -'
|
|
112
|
+
f' expected {self._nelems} entities as per response,'
|
|
113
|
+
f' received {len(resp_data)}')
|
|
114
|
+
|
|
115
|
+
if self._expected_nelems < len(resp_data):
|
|
116
|
+
errors.append(
|
|
117
|
+
f'Extra data provided in paginated response -'
|
|
118
|
+
f' expected {self._expected_nelems} entities as per request,'
|
|
119
|
+
f' received {len(resp_data)}')
|
|
120
|
+
|
|
121
|
+
if errors:
|
|
122
|
+
raise G90Error('. '.join(errors))
|
|
123
|
+
|
|
124
|
+
_LOGGER.debug('Paginated command response: '
|
|
125
|
+
'total records %s, start record %s, record count %s',
|
|
126
|
+
page_info.total, page_info.start, page_info.nelems)
|
|
127
|
+
|
|
128
|
+
async def process(self) -> G90PaginatedCommand:
|
|
129
|
+
"""
|
|
130
|
+
Initiates the command processing.
|
|
131
|
+
"""
|
|
132
|
+
return cast(G90PaginatedCommand, await super().process())
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
Extends paginated command for G90 alarm panel providing convenience interface
|
|
23
|
+
to work with results of paginated commands.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
from typing import Any, Optional, AsyncGenerator, Iterable, cast
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from .paginated_cmd import G90PaginatedCommand
|
|
30
|
+
from ..const import (
|
|
31
|
+
G90Commands,
|
|
32
|
+
CMD_PAGE_SIZE,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
_LOGGER = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class G90PaginatedResponse:
|
|
40
|
+
"""
|
|
41
|
+
Response yielded from the :meth:`.G90PaginatedResult.process` method
|
|
42
|
+
"""
|
|
43
|
+
proto_idx: int
|
|
44
|
+
data: str
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class G90PaginatedResult:
|
|
48
|
+
"""
|
|
49
|
+
Processes paginated response from G90 corresponding panel commands.
|
|
50
|
+
"""
|
|
51
|
+
# pylint: disable=too-few-public-methods
|
|
52
|
+
# pylint: disable=too-many-positional-arguments,too-many-arguments
|
|
53
|
+
def __init__(
|
|
54
|
+
self, host: str, port: int, code: G90Commands, start: int = 1,
|
|
55
|
+
end: Optional[int] = None, **kwargs: Any
|
|
56
|
+
):
|
|
57
|
+
self._host = host
|
|
58
|
+
self._port = port
|
|
59
|
+
self._code = code
|
|
60
|
+
self._start = start
|
|
61
|
+
self._end = end
|
|
62
|
+
self._kwargs = kwargs
|
|
63
|
+
|
|
64
|
+
async def process(self) -> AsyncGenerator[G90PaginatedResponse, None]:
|
|
65
|
+
"""
|
|
66
|
+
Process paginated response yielding :class:`.G90PaginatedResponse`
|
|
67
|
+
instance for each element.
|
|
68
|
+
"""
|
|
69
|
+
page = CMD_PAGE_SIZE
|
|
70
|
+
start = self._start
|
|
71
|
+
count = 0
|
|
72
|
+
while True:
|
|
73
|
+
# The start record number is one-based, so subtract one when
|
|
74
|
+
# calculating the number of the end record for the current
|
|
75
|
+
# iteration
|
|
76
|
+
end = start + page - 1
|
|
77
|
+
# Use the smallest of requested end record number and calculated
|
|
78
|
+
# one (based of page size), allows for number of records less than
|
|
79
|
+
# in page
|
|
80
|
+
if self._end:
|
|
81
|
+
end = min(end, self._end)
|
|
82
|
+
|
|
83
|
+
_LOGGER.debug('Invoking paginated command for %s..%s range',
|
|
84
|
+
start, end)
|
|
85
|
+
cmd = await G90PaginatedCommand(
|
|
86
|
+
host=self._host, port=self._port, code=self._code,
|
|
87
|
+
start=start, end=end,
|
|
88
|
+
**self._kwargs
|
|
89
|
+
).process()
|
|
90
|
+
|
|
91
|
+
# The caller didn't supply the end record number, use the records
|
|
92
|
+
# total since it is now known
|
|
93
|
+
if not self._end:
|
|
94
|
+
self._end = cmd.total
|
|
95
|
+
# The supplied end record number is higher than total records
|
|
96
|
+
# available, reset to the latter
|
|
97
|
+
if self._end > cmd.total:
|
|
98
|
+
_LOGGER.warning('Requested record range (%i) exceeds number of'
|
|
99
|
+
' available records (%i), setting to the'
|
|
100
|
+
' latter', self._end, cmd.total)
|
|
101
|
+
self._end = cmd.total
|
|
102
|
+
|
|
103
|
+
_LOGGER.debug('Retrieved %i records in the iteration,'
|
|
104
|
+
' %i available in total, target end'
|
|
105
|
+
' record number is %i',
|
|
106
|
+
cmd.count, cmd.total, self._end)
|
|
107
|
+
|
|
108
|
+
# Produce the resulting records for the consumer
|
|
109
|
+
for idx, data in enumerate(cast(Iterable[str], cmd.result)):
|
|
110
|
+
# Protocol uses one-based indexes, `start` implies that so no
|
|
111
|
+
# further additions to resulting value is needed.
|
|
112
|
+
# Note the index provided here is running one across multiple
|
|
113
|
+
# pages hence use of `start` variable
|
|
114
|
+
yield G90PaginatedResponse(start + idx, data)
|
|
115
|
+
|
|
116
|
+
# Count the number of processed records
|
|
117
|
+
count += cmd.count
|
|
118
|
+
|
|
119
|
+
# End the loop if we processed same number of sensors as in the
|
|
120
|
+
# pagination header (or attempted to process more than that by
|
|
121
|
+
# an error), or no records have been received
|
|
122
|
+
if not cmd.count:
|
|
123
|
+
break
|
|
124
|
+
if cmd.start + cmd.count - 1 >= self._end:
|
|
125
|
+
break
|
|
126
|
+
# Move to the next page for another iteration
|
|
127
|
+
start = start + page
|
|
128
|
+
|
|
129
|
+
_LOGGER.debug('Total number of paginated entries:'
|
|
130
|
+
' processed %s, expected %s',
|
|
131
|
+
count,
|
|
132
|
+
# Again, both end and start record numbers are one-based,
|
|
133
|
+
# so need to add one to calculate how many records have
|
|
134
|
+
# been requested
|
|
135
|
+
self._end - self._start + 1)
|
|
@@ -0,0 +1,162 @@
|
|
|
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
|
+
Discovers G90 alarm panel devices with specific ID.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
import logging
|
|
26
|
+
from typing import Tuple, Any, Optional, Dict, List
|
|
27
|
+
from dataclasses import dataclass, asdict
|
|
28
|
+
import asyncio
|
|
29
|
+
from asyncio.transports import BaseTransport
|
|
30
|
+
from .base_cmd import G90BaseCommand
|
|
31
|
+
from ..const import G90Commands
|
|
32
|
+
from ..exceptions import G90Error
|
|
33
|
+
|
|
34
|
+
_LOGGER = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
# pylint: disable=too-many-instance-attributes
|
|
39
|
+
class G90TargetedDiscoveryInfo:
|
|
40
|
+
"""
|
|
41
|
+
Wire representation of the information about discovered device.
|
|
42
|
+
"""
|
|
43
|
+
message: str
|
|
44
|
+
product_name: str
|
|
45
|
+
wifi_protocol_version: str
|
|
46
|
+
cloud_protocol_version: str
|
|
47
|
+
mcu_hw_version: str
|
|
48
|
+
fw_version: str
|
|
49
|
+
gsm_status: str
|
|
50
|
+
wifi_status: str
|
|
51
|
+
server_status: str
|
|
52
|
+
reserved1: str
|
|
53
|
+
reserved2: str
|
|
54
|
+
gsm_signal_level: str
|
|
55
|
+
wifi_signal_level: str
|
|
56
|
+
|
|
57
|
+
def _asdict(self) -> Dict[str, Any]:
|
|
58
|
+
"""
|
|
59
|
+
Returns the information about discovered device as dictionary.
|
|
60
|
+
"""
|
|
61
|
+
return asdict(self)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class G90DiscoveredDeviceTargeted(G90TargetedDiscoveryInfo):
|
|
66
|
+
"""
|
|
67
|
+
Discovered device with specific ID.
|
|
68
|
+
"""
|
|
69
|
+
host: str
|
|
70
|
+
port: int
|
|
71
|
+
guid: str
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class G90TargetedDiscovery(G90BaseCommand):
|
|
75
|
+
"""
|
|
76
|
+
Discovers alarm panel devices with specific ID.
|
|
77
|
+
"""
|
|
78
|
+
# pylint: disable=too-few-public-methods
|
|
79
|
+
def __init__(self, device_id: str, **kwargs: Any):
|
|
80
|
+
super().__init__(
|
|
81
|
+
# No actual command will be processed by base class, `NONE` is used
|
|
82
|
+
# for proper typing only
|
|
83
|
+
code=G90Commands.NONE, **kwargs
|
|
84
|
+
)
|
|
85
|
+
self._device_id = device_id
|
|
86
|
+
self._discovered_devices: List[G90DiscoveredDeviceTargeted] = []
|
|
87
|
+
|
|
88
|
+
def connection_made(self, transport: BaseTransport) -> None:
|
|
89
|
+
"""
|
|
90
|
+
Invoked when connection is established.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def connection_lost(self, exc: Optional[Exception]) -> None:
|
|
94
|
+
"""
|
|
95
|
+
Invoked when connection is lost.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
# Implementation of datagram protocol,
|
|
99
|
+
# https://docs.python.org/3/library/asyncio-protocol.html#datagram-protocols
|
|
100
|
+
def datagram_received(self, data: bytes, addr: Tuple[str, int]) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Invoked when datagram is received.
|
|
103
|
+
"""
|
|
104
|
+
try:
|
|
105
|
+
_LOGGER.debug('Received from %s:%s: %s', addr[0], addr[1], data)
|
|
106
|
+
try:
|
|
107
|
+
decoded = data.decode('utf-8')
|
|
108
|
+
except UnicodeDecodeError as exc:
|
|
109
|
+
raise G90Error(
|
|
110
|
+
'Unable to decode discovery response from UTF-8'
|
|
111
|
+
) from exc
|
|
112
|
+
if not decoded.endswith('\0'):
|
|
113
|
+
raise G90Error('Invalid discovery response')
|
|
114
|
+
host_info = G90TargetedDiscoveryInfo(*decoded[:-1].split(','))
|
|
115
|
+
if host_info.message != 'IWTAC_PROBE_DEVICE_ACK':
|
|
116
|
+
raise G90Error('Invalid discovery response')
|
|
117
|
+
res = G90DiscoveredDeviceTargeted(
|
|
118
|
+
host=addr[0],
|
|
119
|
+
port=addr[1],
|
|
120
|
+
guid=self._device_id,
|
|
121
|
+
**host_info._asdict()
|
|
122
|
+
)
|
|
123
|
+
_LOGGER.debug('Discovered device: %s', res)
|
|
124
|
+
self.add_device(res)
|
|
125
|
+
except Exception as exc: # pylint: disable=broad-except
|
|
126
|
+
_LOGGER.warning('Got exception, ignoring: %s', exc)
|
|
127
|
+
|
|
128
|
+
def error_received(self, exc: Exception) -> None:
|
|
129
|
+
"""
|
|
130
|
+
Invoked when error is received.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
def to_wire(self) -> bytes:
|
|
134
|
+
"""
|
|
135
|
+
Converts the command to wire representation.
|
|
136
|
+
"""
|
|
137
|
+
return bytes(f'IWTAC_PROBE_DEVICE,{self._device_id}\0', 'ascii')
|
|
138
|
+
|
|
139
|
+
async def process(self) -> G90TargetedDiscovery:
|
|
140
|
+
"""
|
|
141
|
+
Initiates the device discovery process.
|
|
142
|
+
"""
|
|
143
|
+
_LOGGER.debug('Attempting device discovery...')
|
|
144
|
+
transport, _ = await self._create_connection()
|
|
145
|
+
transport.sendto(self.to_wire())
|
|
146
|
+
await asyncio.sleep(self._timeout)
|
|
147
|
+
transport.close()
|
|
148
|
+
_LOGGER.debug('Discovered %s devices', len(self.devices))
|
|
149
|
+
return self
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def devices(self) -> List[G90DiscoveredDeviceTargeted]:
|
|
153
|
+
"""
|
|
154
|
+
The list of discovered devices.
|
|
155
|
+
"""
|
|
156
|
+
return self._discovered_devices
|
|
157
|
+
|
|
158
|
+
def add_device(self, value: G90DiscoveredDeviceTargeted) -> None:
|
|
159
|
+
"""
|
|
160
|
+
Adds discovered device to the list.
|
|
161
|
+
"""
|
|
162
|
+
self._discovered_devices.append(value)
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
Protocol entity for G90 alarm panel that provides checksums of different
|
|
23
|
+
on-device databases.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
from typing import Any, Dict
|
|
27
|
+
from dataclasses import dataclass, asdict
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class G90UserDataCRC:
|
|
32
|
+
"""
|
|
33
|
+
Represents structure of GETUSERDATACRC command response.
|
|
34
|
+
"""
|
|
35
|
+
sensor_list: str
|
|
36
|
+
device_list: str
|
|
37
|
+
history_list: str
|
|
38
|
+
scene_list: str
|
|
39
|
+
ifttt_list: str
|
|
40
|
+
fingerprint_list: str
|
|
41
|
+
|
|
42
|
+
def _asdict(self) -> Dict[str, Any]:
|
|
43
|
+
"""
|
|
44
|
+
Returns the host information as dictionary.
|
|
45
|
+
"""
|
|
46
|
+
return asdict(self)
|
|
File without changes
|