pypck 0.9.6__tar.gz → 0.9.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pypck-0.9.6/pypck.egg-info → pypck-0.9.8}/PKG-INFO +1 -1
- pypck-0.9.8/VERSION +1 -0
- {pypck-0.9.6 → pypck-0.9.8}/pypck/device.py +39 -23
- pypck-0.9.8/pypck/status_requester.py +107 -0
- {pypck-0.9.6 → pypck-0.9.8/pypck.egg-info}/PKG-INFO +1 -1
- {pypck-0.9.6 → pypck-0.9.8}/tests/test_module.py +9 -7
- {pypck-0.9.6 → pypck-0.9.8}/tests/test_status_requester.py +26 -27
- pypck-0.9.6/VERSION +0 -1
- pypck-0.9.6/pypck/status_requester.py +0 -160
- {pypck-0.9.6 → pypck-0.9.8}/LICENSE +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/README.md +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/pypck/__init__.py +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/pypck/connection.py +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/pypck/helpers.py +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/pypck/inputs.py +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/pypck/lcn_addr.py +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/pypck/lcn_defs.py +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/pypck/pck_commands.py +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/pypck/py.typed +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/pypck.egg-info/SOURCES.txt +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/pypck.egg-info/dependency_links.txt +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/pypck.egg-info/not-zip-safe +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/pypck.egg-info/top_level.txt +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/pyproject.toml +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/setup.cfg +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/tests/test_commands.py +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/tests/test_connection.py +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/tests/test_dyn_text.py +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/tests/test_input.py +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/tests/test_messages.py +0 -0
- {pypck-0.9.6 → pypck-0.9.8}/tests/test_vars.py +0 -0
pypck-0.9.8/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.9.8
|
|
@@ -58,6 +58,7 @@ class DeviceConnection:
|
|
|
58
58
|
|
|
59
59
|
# StatusRequester
|
|
60
60
|
self.status_requester = StatusRequester(self)
|
|
61
|
+
self.request_lock = asyncio.Lock()
|
|
61
62
|
|
|
62
63
|
if self.addr.is_group:
|
|
63
64
|
self.wants_ack = False # groups do not send acks
|
|
@@ -794,11 +795,15 @@ class DeviceConnection:
|
|
|
794
795
|
"groups": {
|
|
795
796
|
"static": sorted(
|
|
796
797
|
addr.addr_id
|
|
797
|
-
for addr in
|
|
798
|
+
for addr in (
|
|
799
|
+
await self.request_group_memberships(dynamic=False) or set()
|
|
800
|
+
)
|
|
798
801
|
),
|
|
799
802
|
"dynamic": sorted(
|
|
800
803
|
addr.addr_id
|
|
801
|
-
for addr in
|
|
804
|
+
for addr in (
|
|
805
|
+
await self.request_group_memberships(dynamic=True) or set()
|
|
806
|
+
)
|
|
802
807
|
),
|
|
803
808
|
},
|
|
804
809
|
}
|
|
@@ -829,7 +834,7 @@ class DeviceConnection:
|
|
|
829
834
|
output_id=output_port.value,
|
|
830
835
|
)
|
|
831
836
|
|
|
832
|
-
return cast(inputs.ModStatusOutput, result)
|
|
837
|
+
return cast(inputs.ModStatusOutput | None, result)
|
|
833
838
|
|
|
834
839
|
async def request_status_relays(
|
|
835
840
|
self, max_age: int = 0
|
|
@@ -845,7 +850,7 @@ class DeviceConnection:
|
|
|
845
850
|
max_age=max_age,
|
|
846
851
|
)
|
|
847
852
|
|
|
848
|
-
return cast(inputs.ModStatusRelays, result)
|
|
853
|
+
return cast(inputs.ModStatusRelays | None, result)
|
|
849
854
|
|
|
850
855
|
async def request_status_motor_position(
|
|
851
856
|
self,
|
|
@@ -879,7 +884,7 @@ class DeviceConnection:
|
|
|
879
884
|
motor=motor.value,
|
|
880
885
|
)
|
|
881
886
|
|
|
882
|
-
return cast(inputs.ModStatusMotorPositionBS4, result)
|
|
887
|
+
return cast(inputs.ModStatusMotorPositionBS4 | None, result)
|
|
883
888
|
|
|
884
889
|
async def request_status_binary_sensors(
|
|
885
890
|
self, max_age: int = 0
|
|
@@ -895,7 +900,7 @@ class DeviceConnection:
|
|
|
895
900
|
max_age=max_age,
|
|
896
901
|
)
|
|
897
902
|
|
|
898
|
-
return cast(inputs.ModStatusBinSensors, result)
|
|
903
|
+
return cast(inputs.ModStatusBinSensors | None, result)
|
|
899
904
|
|
|
900
905
|
async def request_status_variable(
|
|
901
906
|
self,
|
|
@@ -907,13 +912,17 @@ class DeviceConnection:
|
|
|
907
912
|
_LOGGER.info("Status requests are not supported for groups.")
|
|
908
913
|
return None
|
|
909
914
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
915
|
+
response_variable = variable
|
|
916
|
+
|
|
917
|
+
# for old modules the variable response is typeless
|
|
918
|
+
# - do not use concurrent requests
|
|
919
|
+
# - do not use buffered response
|
|
920
|
+
if has_typeless_response := not lcn_defs.Var.has_type_in_response(
|
|
921
|
+
variable, self.serials.software_serial
|
|
922
|
+
):
|
|
923
|
+
await self.request_lock.acquire()
|
|
913
924
|
max_age = 0
|
|
914
|
-
|
|
915
|
-
else:
|
|
916
|
-
variable_response = variable
|
|
925
|
+
response_variable = lcn_defs.Var.UNKNOWN
|
|
917
926
|
|
|
918
927
|
result = await self.status_requester.request(
|
|
919
928
|
response_type=inputs.ModStatusVar,
|
|
@@ -921,14 +930,20 @@ class DeviceConnection:
|
|
|
921
930
|
variable, self.serials.software_serial
|
|
922
931
|
),
|
|
923
932
|
max_age=max_age,
|
|
924
|
-
var=
|
|
933
|
+
var=response_variable,
|
|
925
934
|
)
|
|
926
935
|
|
|
927
|
-
result = cast(inputs.ModStatusVar, result)
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
936
|
+
result = cast(inputs.ModStatusVar | None, result)
|
|
937
|
+
|
|
938
|
+
# for old modules (typeless response) we need to set the original variable
|
|
939
|
+
# - call input_callbacks with the original variable type
|
|
940
|
+
if result is not None and has_typeless_response:
|
|
941
|
+
result.var = variable
|
|
942
|
+
for input_callback in self.input_callbacks:
|
|
943
|
+
input_callback(result)
|
|
944
|
+
|
|
945
|
+
if self.request_lock.locked():
|
|
946
|
+
self.request_lock.release()
|
|
932
947
|
return result
|
|
933
948
|
|
|
934
949
|
async def request_status_led_and_logic_ops(
|
|
@@ -945,7 +960,7 @@ class DeviceConnection:
|
|
|
945
960
|
max_age=max_age,
|
|
946
961
|
)
|
|
947
962
|
|
|
948
|
-
return cast(inputs.ModStatusLedsAndLogicOps, result)
|
|
963
|
+
return cast(inputs.ModStatusLedsAndLogicOps | None, result)
|
|
949
964
|
|
|
950
965
|
async def request_status_locked_keys(
|
|
951
966
|
self, max_age: int = 0
|
|
@@ -961,7 +976,7 @@ class DeviceConnection:
|
|
|
961
976
|
max_age=max_age,
|
|
962
977
|
)
|
|
963
978
|
|
|
964
|
-
return cast(inputs.ModStatusKeyLocks, result)
|
|
979
|
+
return cast(inputs.ModStatusKeyLocks | None, result)
|
|
965
980
|
|
|
966
981
|
# Request module properties
|
|
967
982
|
|
|
@@ -1063,7 +1078,7 @@ class DeviceConnection:
|
|
|
1063
1078
|
|
|
1064
1079
|
async def request_group_memberships(
|
|
1065
1080
|
self, dynamic: bool = False, max_age: int = 0
|
|
1066
|
-
) -> set[LcnAddr]:
|
|
1081
|
+
) -> set[LcnAddr] | None:
|
|
1067
1082
|
"""Request module static/dynamic group memberships."""
|
|
1068
1083
|
if self.addr.is_group:
|
|
1069
1084
|
_LOGGER.info("Status requests are not supported for groups.")
|
|
@@ -1079,5 +1094,6 @@ class DeviceConnection:
|
|
|
1079
1094
|
max_age=max_age,
|
|
1080
1095
|
dynamic=dynamic,
|
|
1081
1096
|
)
|
|
1082
|
-
|
|
1083
|
-
|
|
1097
|
+
if result is not None:
|
|
1098
|
+
return set(cast(inputs.ModStatusGroups, result).groups)
|
|
1099
|
+
return None
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Status requester."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
from pypck import inputs
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from pypck.device import DeviceConnection
|
|
14
|
+
|
|
15
|
+
_LOGGER = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(unsafe_hash=True)
|
|
19
|
+
class StatusRequest:
|
|
20
|
+
"""Data class for status requests."""
|
|
21
|
+
|
|
22
|
+
type: type[inputs.Input] # Type of the input expected as response
|
|
23
|
+
parameters: frozenset[tuple[str, Any]] # {(parameter_name, parameter_value)}
|
|
24
|
+
timestamp: float = field(
|
|
25
|
+
compare=False
|
|
26
|
+
) # timestamp the response was received; -1=no timestamp
|
|
27
|
+
response: asyncio.Future[inputs.Input] = field(
|
|
28
|
+
compare=False
|
|
29
|
+
) # Future to hold the response input object
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class StatusRequester:
|
|
33
|
+
"""Handling of status requests."""
|
|
34
|
+
|
|
35
|
+
current_request: StatusRequest
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
device_connection: DeviceConnection,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Initialize the context."""
|
|
42
|
+
self.device_connection = device_connection
|
|
43
|
+
self.last_requests: set[StatusRequest] = set()
|
|
44
|
+
self.max_response_age = self.device_connection.conn.settings["MAX_RESPONSE_AGE"]
|
|
45
|
+
self.request_lock = asyncio.Lock()
|
|
46
|
+
|
|
47
|
+
def input_callback(self, inp: inputs.Input) -> None:
|
|
48
|
+
"""Handle incoming inputs and set the result for the corresponding requests."""
|
|
49
|
+
if (
|
|
50
|
+
self.current_request.response.done()
|
|
51
|
+
or self.current_request.response.cancelled()
|
|
52
|
+
):
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
if isinstance(inp, self.current_request.type) and all(
|
|
56
|
+
getattr(inp, parameter_name) == parameter_value
|
|
57
|
+
for parameter_name, parameter_value in self.current_request.parameters
|
|
58
|
+
):
|
|
59
|
+
self.current_request.timestamp = asyncio.get_running_loop().time()
|
|
60
|
+
self.current_request.response.set_result(inp)
|
|
61
|
+
|
|
62
|
+
async def request(
|
|
63
|
+
self,
|
|
64
|
+
response_type: type[inputs.Input],
|
|
65
|
+
request_pck: str,
|
|
66
|
+
request_acknowledge: bool = False,
|
|
67
|
+
max_age: int = 0, # -1: no age limit / infinite age
|
|
68
|
+
**request_kwargs: Any,
|
|
69
|
+
) -> inputs.Input | None:
|
|
70
|
+
"""Execute a status request and wait for the response."""
|
|
71
|
+
async with self.request_lock:
|
|
72
|
+
self.current_request = StatusRequest(
|
|
73
|
+
response_type,
|
|
74
|
+
frozenset(request_kwargs.items()),
|
|
75
|
+
-1,
|
|
76
|
+
asyncio.get_running_loop().create_future(),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
unregister_inputs = self.device_connection.register_for_inputs(
|
|
80
|
+
self.input_callback
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
result = None
|
|
84
|
+
# send the request up to NUM_TRIES and wait for response future completion
|
|
85
|
+
for _ in range(self.device_connection.conn.settings["NUM_TRIES"]):
|
|
86
|
+
await self.device_connection.send_command(
|
|
87
|
+
request_acknowledge, request_pck
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
async with asyncio.timeout(
|
|
92
|
+
self.device_connection.conn.settings["DEFAULT_TIMEOUT"]
|
|
93
|
+
):
|
|
94
|
+
# Need to shield the future. Otherwise it would get cancelled.
|
|
95
|
+
result = await asyncio.shield(self.current_request.response)
|
|
96
|
+
break
|
|
97
|
+
except asyncio.TimeoutError:
|
|
98
|
+
continue
|
|
99
|
+
except asyncio.CancelledError:
|
|
100
|
+
break
|
|
101
|
+
|
|
102
|
+
# if we got no results, remove the request from the set
|
|
103
|
+
if result is None:
|
|
104
|
+
self.current_request.response.cancel()
|
|
105
|
+
|
|
106
|
+
unregister_inputs()
|
|
107
|
+
return result
|
|
@@ -140,17 +140,17 @@ async def test_request_status_binary_sensors(module10: MockDeviceConnection) ->
|
|
|
140
140
|
|
|
141
141
|
|
|
142
142
|
@pytest.mark.parametrize(
|
|
143
|
-
"variable,
|
|
143
|
+
"variable, software_serial",
|
|
144
144
|
[
|
|
145
145
|
*[
|
|
146
|
-
(variable,
|
|
146
|
+
(variable, 0x170206)
|
|
147
147
|
for variable in lcn_defs.Var.variables_new()
|
|
148
148
|
+ lcn_defs.Var.set_points()
|
|
149
149
|
+ list(chain(*lcn_defs.Var.thresholds_new()))
|
|
150
150
|
+ lcn_defs.Var.s0s()
|
|
151
151
|
],
|
|
152
152
|
*[
|
|
153
|
-
(variable,
|
|
153
|
+
(variable, 0x170000)
|
|
154
154
|
for variable in lcn_defs.Var.variables_old()
|
|
155
155
|
+ lcn_defs.Var.set_points()
|
|
156
156
|
+ list(chain(*lcn_defs.Var.thresholds_old()))
|
|
@@ -158,16 +158,18 @@ async def test_request_status_binary_sensors(module10: MockDeviceConnection) ->
|
|
|
158
158
|
],
|
|
159
159
|
)
|
|
160
160
|
async def test_request_status_variable(
|
|
161
|
-
module10: MockDeviceConnection,
|
|
162
|
-
variable: lcn_defs.Var,
|
|
163
|
-
response_variable: lcn_defs.Var,
|
|
164
|
-
software_serial: int,
|
|
161
|
+
module10: MockDeviceConnection, variable: lcn_defs.Var, software_serial: int
|
|
165
162
|
) -> None:
|
|
166
163
|
"""Test requesting the variable status of a module."""
|
|
167
164
|
module10.serials.software_serial = software_serial
|
|
168
165
|
request_task = asyncio.create_task(module10.request_status_variable(variable))
|
|
169
166
|
|
|
170
167
|
await wait_until_called(module10.send_command)
|
|
168
|
+
response_variable = (
|
|
169
|
+
variable
|
|
170
|
+
if lcn_defs.Var.has_type_in_response(variable, software_serial)
|
|
171
|
+
else lcn_defs.Var.UNKNOWN
|
|
172
|
+
)
|
|
171
173
|
await module10.async_process_input(
|
|
172
174
|
inputs.ModStatusVar(
|
|
173
175
|
module10.addr, response_variable, lcn_defs.VarValue.from_native(50)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Test the status requester of a module connection."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
from unittest.mock import call
|
|
5
4
|
|
|
6
5
|
from pypck.pck_commands import PckGenerator
|
|
7
6
|
from pypck.status_requester import StatusRequest
|
|
@@ -39,32 +38,32 @@ async def test_request_status(module10: MockDeviceConnection) -> None:
|
|
|
39
38
|
assert result.states == RELAY_STATES
|
|
40
39
|
|
|
41
40
|
|
|
42
|
-
async def test_request_status_stored(module10: MockDeviceConnection) -> None:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
41
|
+
# async def test_request_status_stored(module10: MockDeviceConnection) -> None:
|
|
42
|
+
# """Test requesting the status of a module with stored status request."""
|
|
43
|
+
# status_request = StatusRequest(
|
|
44
|
+
# type=inputs.ModStatusRelays,
|
|
45
|
+
# parameters=frozenset(),
|
|
46
|
+
# timestamp=asyncio.get_running_loop().time(),
|
|
47
|
+
# response=asyncio.get_running_loop().create_future(),
|
|
48
|
+
# )
|
|
49
|
+
# status_request.response.set_result(
|
|
50
|
+
# inputs.ModStatusRelays(module10.addr, RELAY_STATES)
|
|
51
|
+
# )
|
|
52
|
+
# module10.status_requester.last_requests.add(status_request)
|
|
53
|
+
|
|
54
|
+
# result = await module10.status_requester.request(
|
|
55
|
+
# response_type=inputs.ModStatusRelays,
|
|
56
|
+
# request_pck=PckGenerator.request_relays_status(),
|
|
57
|
+
# max_age=10,
|
|
58
|
+
# )
|
|
59
|
+
|
|
60
|
+
# assert isinstance(result, inputs.ModStatusRelays)
|
|
61
|
+
# assert result.physical_source_addr == module10.addr
|
|
62
|
+
# assert result.states == RELAY_STATES
|
|
63
|
+
# assert (
|
|
64
|
+
# call(False, PckGenerator.request_relays_status())
|
|
65
|
+
# not in module10.send_command.await_args_list
|
|
66
|
+
# )
|
|
68
67
|
|
|
69
68
|
|
|
70
69
|
async def test_request_status_expired(module10: MockDeviceConnection) -> None:
|
pypck-0.9.6/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.9.6
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
"""Status requester."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import asyncio
|
|
6
|
-
import logging
|
|
7
|
-
from dataclasses import dataclass, field
|
|
8
|
-
from typing import TYPE_CHECKING, Any
|
|
9
|
-
|
|
10
|
-
from pypck import inputs
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from pypck.device import DeviceConnection
|
|
14
|
-
|
|
15
|
-
_LOGGER = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@dataclass(unsafe_hash=True)
|
|
19
|
-
class StatusRequest:
|
|
20
|
-
"""Data class for status requests."""
|
|
21
|
-
|
|
22
|
-
type: type[inputs.Input] # Type of the input expected as response
|
|
23
|
-
parameters: frozenset[tuple[str, Any]] # {(parameter_name, parameter_value)}
|
|
24
|
-
timestamp: float = field(
|
|
25
|
-
compare=False
|
|
26
|
-
) # timestamp the response was received; -1=no timestamp
|
|
27
|
-
response: asyncio.Future[inputs.Input] = field(
|
|
28
|
-
compare=False
|
|
29
|
-
) # Future to hold the response input object
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class StatusRequester:
|
|
33
|
-
"""Handling of status requests."""
|
|
34
|
-
|
|
35
|
-
def __init__(
|
|
36
|
-
self,
|
|
37
|
-
device_connection: DeviceConnection,
|
|
38
|
-
) -> None:
|
|
39
|
-
"""Initialize the context."""
|
|
40
|
-
self.device_connection = device_connection
|
|
41
|
-
self.last_requests: set[StatusRequest] = set()
|
|
42
|
-
self.unregister_inputs = self.device_connection.register_for_inputs(
|
|
43
|
-
self.input_callback
|
|
44
|
-
)
|
|
45
|
-
self.max_response_age = self.device_connection.conn.settings["MAX_RESPONSE_AGE"]
|
|
46
|
-
# asyncio.get_running_loop().create_task(self.prune_loop())
|
|
47
|
-
|
|
48
|
-
async def prune_loop(self) -> None:
|
|
49
|
-
"""Periodically prune old status requests."""
|
|
50
|
-
while True:
|
|
51
|
-
await asyncio.sleep(self.max_response_age)
|
|
52
|
-
self.prune_status_requests()
|
|
53
|
-
|
|
54
|
-
def prune_status_requests(self) -> None:
|
|
55
|
-
"""Prune old status requests."""
|
|
56
|
-
entries_to_remove = {
|
|
57
|
-
request
|
|
58
|
-
for request in self.last_requests
|
|
59
|
-
if asyncio.get_running_loop().time() - request.timestamp
|
|
60
|
-
> self.max_response_age
|
|
61
|
-
}
|
|
62
|
-
for entry in entries_to_remove:
|
|
63
|
-
entry.response.cancel()
|
|
64
|
-
self.last_requests.difference_update(entries_to_remove)
|
|
65
|
-
|
|
66
|
-
def get_status_requests(
|
|
67
|
-
self,
|
|
68
|
-
request_type: type[inputs.Input],
|
|
69
|
-
parameters: frozenset[tuple[str, Any]] | None = None,
|
|
70
|
-
max_age: int = 0,
|
|
71
|
-
) -> list[StatusRequest]:
|
|
72
|
-
"""Get the status requests for the given type and parameters."""
|
|
73
|
-
if parameters is None:
|
|
74
|
-
parameters = frozenset()
|
|
75
|
-
loop = asyncio.get_running_loop()
|
|
76
|
-
results = [
|
|
77
|
-
request
|
|
78
|
-
for request in self.last_requests
|
|
79
|
-
if request.type == request_type
|
|
80
|
-
and parameters.issubset(request.parameters)
|
|
81
|
-
and (
|
|
82
|
-
(request.timestamp == -1)
|
|
83
|
-
or (max_age == -1)
|
|
84
|
-
or (loop.time() - request.timestamp < max_age)
|
|
85
|
-
)
|
|
86
|
-
]
|
|
87
|
-
results.sort(key=lambda request: request.timestamp, reverse=True)
|
|
88
|
-
return results
|
|
89
|
-
|
|
90
|
-
def input_callback(self, inp: inputs.Input) -> None:
|
|
91
|
-
"""Handle incoming inputs and set the result for the corresponding requests."""
|
|
92
|
-
requests = [
|
|
93
|
-
request
|
|
94
|
-
for request in self.get_status_requests(type(inp))
|
|
95
|
-
if all(
|
|
96
|
-
getattr(inp, parameter_name) == parameter_value
|
|
97
|
-
for parameter_name, parameter_value in request.parameters
|
|
98
|
-
)
|
|
99
|
-
]
|
|
100
|
-
for request in requests:
|
|
101
|
-
if request.response.done() or request.response.cancelled():
|
|
102
|
-
continue
|
|
103
|
-
request.timestamp = asyncio.get_running_loop().time()
|
|
104
|
-
request.response.set_result(inp)
|
|
105
|
-
|
|
106
|
-
async def request(
|
|
107
|
-
self,
|
|
108
|
-
response_type: type[inputs.Input],
|
|
109
|
-
request_pck: str,
|
|
110
|
-
request_acknowledge: bool = False,
|
|
111
|
-
max_age: int = 0, # -1: no age limit / infinite age
|
|
112
|
-
**request_kwargs: Any,
|
|
113
|
-
) -> inputs.Input | None:
|
|
114
|
-
"""Execute a status request and wait for the response."""
|
|
115
|
-
parameters = frozenset(request_kwargs.items())
|
|
116
|
-
|
|
117
|
-
# check if we already have a received response for the current request
|
|
118
|
-
if requests := self.get_status_requests(response_type, parameters, max_age):
|
|
119
|
-
try:
|
|
120
|
-
async with asyncio.timeout(
|
|
121
|
-
self.device_connection.conn.settings["DEFAULT_TIMEOUT"]
|
|
122
|
-
):
|
|
123
|
-
return await requests[0].response
|
|
124
|
-
except asyncio.TimeoutError:
|
|
125
|
-
return None
|
|
126
|
-
except asyncio.CancelledError:
|
|
127
|
-
return None
|
|
128
|
-
|
|
129
|
-
# no stored request or forced request: set up a new request
|
|
130
|
-
request = StatusRequest(
|
|
131
|
-
response_type,
|
|
132
|
-
frozenset(request_kwargs.items()),
|
|
133
|
-
-1,
|
|
134
|
-
asyncio.get_running_loop().create_future(),
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
self.last_requests.discard(request)
|
|
138
|
-
self.last_requests.add(request)
|
|
139
|
-
result = None
|
|
140
|
-
# send the request up to NUM_TRIES and wait for response future completion
|
|
141
|
-
for _ in range(self.device_connection.conn.settings["NUM_TRIES"]):
|
|
142
|
-
await self.device_connection.send_command(request_acknowledge, request_pck)
|
|
143
|
-
|
|
144
|
-
try:
|
|
145
|
-
async with asyncio.timeout(
|
|
146
|
-
self.device_connection.conn.settings["DEFAULT_TIMEOUT"]
|
|
147
|
-
):
|
|
148
|
-
# Need to shield the future. Otherwise it would get cancelled.
|
|
149
|
-
result = await asyncio.shield(request.response)
|
|
150
|
-
break
|
|
151
|
-
except asyncio.TimeoutError:
|
|
152
|
-
continue
|
|
153
|
-
except asyncio.CancelledError:
|
|
154
|
-
break
|
|
155
|
-
|
|
156
|
-
# if we got no results, remove the request from the set
|
|
157
|
-
if result is None:
|
|
158
|
-
request.response.cancel()
|
|
159
|
-
self.last_requests.discard(request)
|
|
160
|
-
return result
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|