pypck 0.9.7__py3-none-any.whl → 0.9.9__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.
- pypck/device.py +64 -86
- pypck/inputs.py +6 -3
- pypck/pck_commands.py +4 -4
- pypck/status_requester.py +83 -88
- {pypck-0.9.7.dist-info → pypck-0.9.9.dist-info}/METADATA +1 -1
- pypck-0.9.9.dist-info/RECORD +15 -0
- pypck-0.9.7.dist-info/RECORD +0 -15
- {pypck-0.9.7.dist-info → pypck-0.9.9.dist-info}/WHEEL +0 -0
- {pypck-0.9.7.dist-info → pypck-0.9.9.dist-info}/licenses/LICENSE +0 -0
- {pypck-0.9.7.dist-info → pypck-0.9.9.dist-info}/top_level.txt +0 -0
pypck/device.py
CHANGED
|
@@ -50,8 +50,6 @@ class DeviceConnection:
|
|
|
50
50
|
self._serials_known = asyncio.Event()
|
|
51
51
|
|
|
52
52
|
self.input_callbacks: set[Callable[[inputs.Input], None]] = set()
|
|
53
|
-
self.last_requested_var_without_type_in_response = lcn_defs.Var.UNKNOWN
|
|
54
|
-
self.last_var_lock = asyncio.Lock()
|
|
55
53
|
|
|
56
54
|
# List of queued acknowledge codes from the LCN modules.
|
|
57
55
|
self.acknowledges: asyncio.Queue[lcn_defs.AcknowledgeErrorCode] = (
|
|
@@ -60,6 +58,7 @@ class DeviceConnection:
|
|
|
60
58
|
|
|
61
59
|
# StatusRequester
|
|
62
60
|
self.status_requester = StatusRequester(self)
|
|
61
|
+
self.request_lock = asyncio.Lock()
|
|
63
62
|
|
|
64
63
|
if self.addr.is_group:
|
|
65
64
|
self.wants_ack = False # groups do not send acks
|
|
@@ -773,29 +772,9 @@ class DeviceConnection:
|
|
|
773
772
|
await self.on_ack(inp.code)
|
|
774
773
|
return None
|
|
775
774
|
|
|
776
|
-
# handle typeless variable responses
|
|
777
|
-
if isinstance(inp, inputs.ModStatusVar):
|
|
778
|
-
inp = self.preprocess_modstatusvar(inp)
|
|
779
|
-
|
|
780
775
|
for input_callback in self.input_callbacks:
|
|
781
776
|
input_callback(inp)
|
|
782
777
|
|
|
783
|
-
def preprocess_modstatusvar(self, inp: inputs.ModStatusVar) -> inputs.Input:
|
|
784
|
-
"""Fill typeless response with last requested variable type."""
|
|
785
|
-
if inp.orig_var == lcn_defs.Var.UNKNOWN:
|
|
786
|
-
# Response without type (%Msssaaa.wwwww)
|
|
787
|
-
inp.var = self.last_requested_var_without_type_in_response
|
|
788
|
-
|
|
789
|
-
self.last_requested_var_without_type_in_response = lcn_defs.Var.UNKNOWN
|
|
790
|
-
|
|
791
|
-
if self.last_var_lock.locked():
|
|
792
|
-
self.last_var_lock.release()
|
|
793
|
-
else:
|
|
794
|
-
# Response with variable type (%Msssaaa.Avvvwww)
|
|
795
|
-
inp.var = inp.orig_var
|
|
796
|
-
|
|
797
|
-
return inp
|
|
798
|
-
|
|
799
778
|
async def dump_details(self) -> dict[str, Any]:
|
|
800
779
|
"""Dump detailed information about this module."""
|
|
801
780
|
is_local_segment = self.addr.seg_id in (0, self.conn.local_seg_id)
|
|
@@ -816,11 +795,15 @@ class DeviceConnection:
|
|
|
816
795
|
"groups": {
|
|
817
796
|
"static": sorted(
|
|
818
797
|
addr.addr_id
|
|
819
|
-
for addr in
|
|
798
|
+
for addr in (
|
|
799
|
+
await self.request_group_memberships(dynamic=False) or set()
|
|
800
|
+
)
|
|
820
801
|
),
|
|
821
802
|
"dynamic": sorted(
|
|
822
803
|
addr.addr_id
|
|
823
|
-
for addr in
|
|
804
|
+
for addr in (
|
|
805
|
+
await self.request_group_memberships(dynamic=True) or set()
|
|
806
|
+
)
|
|
824
807
|
),
|
|
825
808
|
},
|
|
826
809
|
}
|
|
@@ -844,15 +827,13 @@ class DeviceConnection:
|
|
|
844
827
|
_LOGGER.info("Status requests are not supported for groups.")
|
|
845
828
|
return None
|
|
846
829
|
|
|
847
|
-
|
|
830
|
+
return await self.status_requester.request(
|
|
848
831
|
response_type=inputs.ModStatusOutput,
|
|
849
832
|
request_pck=PckGenerator.request_output_status(output_id=output_port.value),
|
|
850
833
|
max_age=max_age,
|
|
851
834
|
output_id=output_port.value,
|
|
852
835
|
)
|
|
853
836
|
|
|
854
|
-
return cast(inputs.ModStatusOutput, result)
|
|
855
|
-
|
|
856
837
|
async def request_status_relays(
|
|
857
838
|
self, max_age: int = 0
|
|
858
839
|
) -> inputs.ModStatusRelays | None:
|
|
@@ -861,14 +842,12 @@ class DeviceConnection:
|
|
|
861
842
|
_LOGGER.info("Status requests are not supported for groups.")
|
|
862
843
|
return None
|
|
863
844
|
|
|
864
|
-
|
|
845
|
+
return await self.status_requester.request(
|
|
865
846
|
response_type=inputs.ModStatusRelays,
|
|
866
847
|
request_pck=PckGenerator.request_relays_status(),
|
|
867
848
|
max_age=max_age,
|
|
868
849
|
)
|
|
869
850
|
|
|
870
|
-
return cast(inputs.ModStatusRelays, result)
|
|
871
|
-
|
|
872
851
|
async def request_status_motor_position(
|
|
873
852
|
self,
|
|
874
853
|
motor: lcn_defs.MotorPort,
|
|
@@ -894,15 +873,13 @@ class DeviceConnection:
|
|
|
894
873
|
_LOGGER.debug("Only BS4 mode is supported for motor position requests.")
|
|
895
874
|
return None
|
|
896
875
|
|
|
897
|
-
|
|
876
|
+
return await self.status_requester.request(
|
|
898
877
|
response_type=inputs.ModStatusMotorPositionBS4,
|
|
899
878
|
request_pck=PckGenerator.request_motor_position_status(motor.value // 2),
|
|
900
879
|
max_age=max_age,
|
|
901
880
|
motor=motor.value,
|
|
902
881
|
)
|
|
903
882
|
|
|
904
|
-
return cast(inputs.ModStatusMotorPositionBS4, result)
|
|
905
|
-
|
|
906
883
|
async def request_status_binary_sensors(
|
|
907
884
|
self, max_age: int = 0
|
|
908
885
|
) -> inputs.ModStatusBinSensors | None:
|
|
@@ -911,14 +888,12 @@ class DeviceConnection:
|
|
|
911
888
|
_LOGGER.info("Status requests are not supported for groups.")
|
|
912
889
|
return None
|
|
913
890
|
|
|
914
|
-
|
|
891
|
+
return await self.status_requester.request(
|
|
915
892
|
response_type=inputs.ModStatusBinSensors,
|
|
916
893
|
request_pck=PckGenerator.request_bin_sensors_status(),
|
|
917
894
|
max_age=max_age,
|
|
918
895
|
)
|
|
919
896
|
|
|
920
|
-
return cast(inputs.ModStatusBinSensors, result)
|
|
921
|
-
|
|
922
897
|
async def request_status_variable(
|
|
923
898
|
self,
|
|
924
899
|
variable: lcn_defs.Var,
|
|
@@ -929,17 +904,17 @@ class DeviceConnection:
|
|
|
929
904
|
_LOGGER.info("Status requests are not supported for groups.")
|
|
930
905
|
return None
|
|
931
906
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
907
|
+
response_variable = variable
|
|
908
|
+
|
|
909
|
+
# for old modules the variable response is typeless
|
|
910
|
+
# - do not use concurrent requests
|
|
911
|
+
# - do not use buffered response
|
|
912
|
+
if has_typeless_response := not lcn_defs.Var.has_type_in_response(
|
|
913
|
+
variable, self.serials.software_serial
|
|
914
|
+
):
|
|
915
|
+
await self.request_lock.acquire()
|
|
916
|
+
max_age = 0
|
|
917
|
+
response_variable = lcn_defs.Var.UNKNOWN
|
|
943
918
|
|
|
944
919
|
result = await self.status_requester.request(
|
|
945
920
|
response_type=inputs.ModStatusVar,
|
|
@@ -947,10 +922,19 @@ class DeviceConnection:
|
|
|
947
922
|
variable, self.serials.software_serial
|
|
948
923
|
),
|
|
949
924
|
max_age=max_age,
|
|
950
|
-
var=
|
|
925
|
+
var=response_variable,
|
|
951
926
|
)
|
|
952
927
|
|
|
953
|
-
|
|
928
|
+
# for old modules (typeless response) we need to set the original variable
|
|
929
|
+
# - call input_callbacks with the original variable type
|
|
930
|
+
if result is not None and has_typeless_response:
|
|
931
|
+
result.var = variable
|
|
932
|
+
for input_callback in self.input_callbacks:
|
|
933
|
+
input_callback(result)
|
|
934
|
+
|
|
935
|
+
if self.request_lock.locked():
|
|
936
|
+
self.request_lock.release()
|
|
937
|
+
return result
|
|
954
938
|
|
|
955
939
|
async def request_status_led_and_logic_ops(
|
|
956
940
|
self, max_age: int = 0
|
|
@@ -960,14 +944,12 @@ class DeviceConnection:
|
|
|
960
944
|
_LOGGER.info("Status requests are not supported for groups.")
|
|
961
945
|
return None
|
|
962
946
|
|
|
963
|
-
|
|
947
|
+
return await self.status_requester.request(
|
|
964
948
|
response_type=inputs.ModStatusLedsAndLogicOps,
|
|
965
949
|
request_pck=PckGenerator.request_leds_and_logic_ops(),
|
|
966
950
|
max_age=max_age,
|
|
967
951
|
)
|
|
968
952
|
|
|
969
|
-
return cast(inputs.ModStatusLedsAndLogicOps, result)
|
|
970
|
-
|
|
971
953
|
async def request_status_locked_keys(
|
|
972
954
|
self, max_age: int = 0
|
|
973
955
|
) -> inputs.ModStatusKeyLocks | None:
|
|
@@ -976,14 +958,12 @@ class DeviceConnection:
|
|
|
976
958
|
_LOGGER.info("Status requests are not supported for groups.")
|
|
977
959
|
return None
|
|
978
960
|
|
|
979
|
-
|
|
961
|
+
return await self.status_requester.request(
|
|
980
962
|
response_type=inputs.ModStatusKeyLocks,
|
|
981
963
|
request_pck=PckGenerator.request_key_lock_status(),
|
|
982
964
|
max_age=max_age,
|
|
983
965
|
)
|
|
984
966
|
|
|
985
|
-
return cast(inputs.ModStatusKeyLocks, result)
|
|
986
|
-
|
|
987
967
|
# Request module properties
|
|
988
968
|
|
|
989
969
|
async def request_serials(self, max_age: int = 0) -> Serials:
|
|
@@ -992,23 +972,20 @@ class DeviceConnection:
|
|
|
992
972
|
_LOGGER.info("Status requests are not supported for groups.")
|
|
993
973
|
return Serials(-1, -1, -1, lcn_defs.HardwareType.UNKNOWN)
|
|
994
974
|
|
|
995
|
-
result =
|
|
996
|
-
inputs.ModSn
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
request_pck=PckGenerator.request_serial(),
|
|
1000
|
-
max_age=max_age,
|
|
1001
|
-
),
|
|
975
|
+
result = await self.status_requester.request(
|
|
976
|
+
response_type=inputs.ModSn,
|
|
977
|
+
request_pck=PckGenerator.request_serial(),
|
|
978
|
+
max_age=max_age,
|
|
1002
979
|
)
|
|
1003
980
|
|
|
1004
|
-
if result
|
|
1005
|
-
return Serials(
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
)
|
|
981
|
+
if isinstance(result, inputs.ModSn):
|
|
982
|
+
return Serials(
|
|
983
|
+
result.hardware_serial,
|
|
984
|
+
result.manu,
|
|
985
|
+
result.software_serial,
|
|
986
|
+
result.hardware_type,
|
|
987
|
+
)
|
|
988
|
+
return Serials(-1, -1, -1, lcn_defs.HardwareType.UNKNOWN)
|
|
1012
989
|
|
|
1013
990
|
async def request_name(self, max_age: int = 0) -> str | None:
|
|
1014
991
|
"""Request module name."""
|
|
@@ -1016,7 +993,7 @@ class DeviceConnection:
|
|
|
1016
993
|
_LOGGER.info("Status requests are not supported for groups.")
|
|
1017
994
|
return None
|
|
1018
995
|
|
|
1019
|
-
coros =
|
|
996
|
+
coros = (
|
|
1020
997
|
self.status_requester.request(
|
|
1021
998
|
response_type=inputs.ModNameComment,
|
|
1022
999
|
request_pck=PckGenerator.request_name(block_id),
|
|
@@ -1024,8 +1001,8 @@ class DeviceConnection:
|
|
|
1024
1001
|
command="N",
|
|
1025
1002
|
block_id=block_id,
|
|
1026
1003
|
)
|
|
1027
|
-
for block_id in
|
|
1028
|
-
|
|
1004
|
+
for block_id in (0, 1)
|
|
1005
|
+
)
|
|
1029
1006
|
|
|
1030
1007
|
coro_results = [await coro for coro in coros]
|
|
1031
1008
|
if not all(coro_results):
|
|
@@ -1040,7 +1017,7 @@ class DeviceConnection:
|
|
|
1040
1017
|
_LOGGER.info("Status requests are not supported for groups.")
|
|
1041
1018
|
return None
|
|
1042
1019
|
|
|
1043
|
-
coros =
|
|
1020
|
+
coros = (
|
|
1044
1021
|
self.status_requester.request(
|
|
1045
1022
|
response_type=inputs.ModNameComment,
|
|
1046
1023
|
request_pck=PckGenerator.request_comment(block_id),
|
|
@@ -1048,15 +1025,15 @@ class DeviceConnection:
|
|
|
1048
1025
|
command="K",
|
|
1049
1026
|
block_id=block_id,
|
|
1050
1027
|
)
|
|
1051
|
-
for block_id in
|
|
1052
|
-
|
|
1028
|
+
for block_id in (0, 1, 2)
|
|
1029
|
+
)
|
|
1053
1030
|
|
|
1054
1031
|
coro_results = [await coro for coro in coros]
|
|
1055
1032
|
if not all(coro_results):
|
|
1056
1033
|
return None
|
|
1057
1034
|
results = cast(list[inputs.ModNameComment], coro_results)
|
|
1058
|
-
|
|
1059
|
-
return
|
|
1035
|
+
comment = "".join([result.text for result in results if result])
|
|
1036
|
+
return comment
|
|
1060
1037
|
|
|
1061
1038
|
async def request_oem_text(self, max_age: int = 0) -> str | None:
|
|
1062
1039
|
"""Request module name."""
|
|
@@ -1064,7 +1041,7 @@ class DeviceConnection:
|
|
|
1064
1041
|
_LOGGER.info("Status requests are not supported for groups.")
|
|
1065
1042
|
return None
|
|
1066
1043
|
|
|
1067
|
-
coros =
|
|
1044
|
+
coros = (
|
|
1068
1045
|
self.status_requester.request(
|
|
1069
1046
|
response_type=inputs.ModNameComment,
|
|
1070
1047
|
request_pck=PckGenerator.request_oem_text(block_id),
|
|
@@ -1072,19 +1049,19 @@ class DeviceConnection:
|
|
|
1072
1049
|
command="O",
|
|
1073
1050
|
block_id=block_id,
|
|
1074
1051
|
)
|
|
1075
|
-
for block_id in
|
|
1076
|
-
|
|
1052
|
+
for block_id in (0, 1, 2, 3)
|
|
1053
|
+
)
|
|
1077
1054
|
|
|
1078
1055
|
coro_results = [await coro for coro in coros]
|
|
1079
1056
|
if not all(coro_results):
|
|
1080
1057
|
return None
|
|
1081
1058
|
results = cast(list[inputs.ModNameComment], coro_results)
|
|
1082
|
-
|
|
1083
|
-
return
|
|
1059
|
+
oem_text = "".join([result.text for result in results if result])
|
|
1060
|
+
return oem_text
|
|
1084
1061
|
|
|
1085
1062
|
async def request_group_memberships(
|
|
1086
1063
|
self, dynamic: bool = False, max_age: int = 0
|
|
1087
|
-
) -> set[LcnAddr]:
|
|
1064
|
+
) -> set[LcnAddr] | None:
|
|
1088
1065
|
"""Request module static/dynamic group memberships."""
|
|
1089
1066
|
if self.addr.is_group:
|
|
1090
1067
|
_LOGGER.info("Status requests are not supported for groups.")
|
|
@@ -1100,5 +1077,6 @@ class DeviceConnection:
|
|
|
1100
1077
|
max_age=max_age,
|
|
1101
1078
|
dynamic=dynamic,
|
|
1102
1079
|
)
|
|
1103
|
-
|
|
1104
|
-
|
|
1080
|
+
if result is not None:
|
|
1081
|
+
return set(result.groups)
|
|
1082
|
+
return None
|
pypck/inputs.py
CHANGED
|
@@ -26,6 +26,7 @@ class Input:
|
|
|
26
26
|
|
|
27
27
|
def __init__(self) -> None:
|
|
28
28
|
"""Construct Input object."""
|
|
29
|
+
self.pck = ""
|
|
29
30
|
|
|
30
31
|
@staticmethod
|
|
31
32
|
def try_parse(data: str) -> list[Input] | None:
|
|
@@ -1368,9 +1369,11 @@ class InputParser:
|
|
|
1368
1369
|
:rtype: List with instances of :class:`~pypck.input.Input`
|
|
1369
1370
|
"""
|
|
1370
1371
|
for parser in InputParser.parsers:
|
|
1371
|
-
|
|
1372
|
-
if
|
|
1373
|
-
|
|
1372
|
+
returns: list[Input] | None = parser.try_parse(data)
|
|
1373
|
+
if returns is not None:
|
|
1374
|
+
for ret in returns:
|
|
1375
|
+
ret.pck = data
|
|
1376
|
+
return returns
|
|
1374
1377
|
|
|
1375
1378
|
# We must never get to this point since the Unknown parser matches
|
|
1376
1379
|
# everything.
|
pypck/pck_commands.py
CHANGED
|
@@ -880,20 +880,20 @@ class PckGenerator:
|
|
|
880
880
|
if software_serial >= 0x170206:
|
|
881
881
|
var_id = lcn_defs.Var.to_var_id(var)
|
|
882
882
|
if var_id != -1:
|
|
883
|
-
return f"MWT{var_id + 1
|
|
883
|
+
return f"MWT{var_id + 1}"
|
|
884
884
|
|
|
885
885
|
set_point_id = lcn_defs.Var.to_set_point_id(var)
|
|
886
886
|
if set_point_id != -1:
|
|
887
|
-
return f"MWS{set_point_id + 1
|
|
887
|
+
return f"MWS{set_point_id + 1}"
|
|
888
888
|
|
|
889
889
|
thrs_register_id = lcn_defs.Var.to_thrs_register_id(var)
|
|
890
890
|
if thrs_register_id != -1:
|
|
891
891
|
# Whole register
|
|
892
|
-
return f"SE{thrs_register_id + 1
|
|
892
|
+
return f"SE{thrs_register_id + 1}"
|
|
893
893
|
|
|
894
894
|
s0_id = lcn_defs.Var.to_s0_id(var)
|
|
895
895
|
if s0_id != -1:
|
|
896
|
-
return f"MWC{s0_id + 1
|
|
896
|
+
return f"MWC{s0_id + 1}"
|
|
897
897
|
else:
|
|
898
898
|
if var == lcn_defs.Var.VAR1ORTVAR:
|
|
899
899
|
pck = "MWV"
|
pypck/status_requester.py
CHANGED
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import logging
|
|
7
7
|
from dataclasses import dataclass, field
|
|
8
|
-
from typing import TYPE_CHECKING, Any
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast
|
|
9
9
|
|
|
10
10
|
from pypck import inputs
|
|
11
11
|
|
|
@@ -14,147 +14,142 @@ if TYPE_CHECKING:
|
|
|
14
14
|
|
|
15
15
|
_LOGGER = logging.getLogger(__name__)
|
|
16
16
|
|
|
17
|
+
ResponseT = TypeVar("ResponseT", bound=inputs.Input)
|
|
18
|
+
|
|
17
19
|
|
|
18
20
|
@dataclass(unsafe_hash=True)
|
|
19
|
-
class StatusRequest:
|
|
21
|
+
class StatusRequest(Generic[ResponseT]):
|
|
20
22
|
"""Data class for status requests."""
|
|
21
23
|
|
|
22
|
-
type: type[
|
|
24
|
+
type: type[ResponseT] # Type of the input expected as response
|
|
23
25
|
parameters: frozenset[tuple[str, Any]] # {(parameter_name, parameter_value)}
|
|
24
26
|
timestamp: float = field(
|
|
25
27
|
compare=False
|
|
26
28
|
) # timestamp the response was received; -1=no timestamp
|
|
27
|
-
response: asyncio.Future[
|
|
29
|
+
response: asyncio.Future[ResponseT] = field(
|
|
28
30
|
compare=False
|
|
29
|
-
) #
|
|
31
|
+
) # the response input object
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
class StatusRequester:
|
|
33
35
|
"""Handling of status requests."""
|
|
34
36
|
|
|
37
|
+
current_request: StatusRequest[inputs.Input] | None
|
|
38
|
+
|
|
35
39
|
def __init__(
|
|
36
40
|
self,
|
|
37
41
|
device_connection: DeviceConnection,
|
|
38
42
|
) -> None:
|
|
39
43
|
"""Initialize the context."""
|
|
40
44
|
self.device_connection = device_connection
|
|
41
|
-
self.
|
|
45
|
+
self.current_request = None
|
|
46
|
+
self.request_cache: set[StatusRequest[inputs.Input]] = set()
|
|
47
|
+
self.max_response_age = self.device_connection.conn.settings["MAX_RESPONSE_AGE"]
|
|
48
|
+
self.request_lock = asyncio.Lock()
|
|
49
|
+
|
|
42
50
|
self.unregister_inputs = self.device_connection.register_for_inputs(
|
|
43
51
|
self.input_callback
|
|
44
52
|
)
|
|
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
53
|
|
|
66
54
|
def get_status_requests(
|
|
67
55
|
self,
|
|
68
|
-
request_type: type[
|
|
56
|
+
request_type: type[ResponseT],
|
|
69
57
|
parameters: frozenset[tuple[str, Any]] | None = None,
|
|
70
58
|
max_age: int = 0,
|
|
71
|
-
) -> list[StatusRequest]:
|
|
59
|
+
) -> list[StatusRequest[ResponseT]]:
|
|
72
60
|
"""Get the status requests for the given type and parameters."""
|
|
73
61
|
if parameters is None:
|
|
74
62
|
parameters = frozenset()
|
|
75
|
-
loop = asyncio.get_running_loop()
|
|
76
63
|
results = [
|
|
77
64
|
request
|
|
78
|
-
for request in self.
|
|
65
|
+
for request in self.request_cache
|
|
79
66
|
if request.type == request_type
|
|
80
67
|
and parameters.issubset(request.parameters)
|
|
81
68
|
and (
|
|
82
69
|
(request.timestamp == -1)
|
|
83
70
|
or (max_age == -1)
|
|
84
|
-
or (
|
|
71
|
+
or (asyncio.get_running_loop().time() - request.timestamp < max_age)
|
|
85
72
|
)
|
|
86
73
|
]
|
|
87
74
|
results.sort(key=lambda request: request.timestamp, reverse=True)
|
|
88
|
-
return results
|
|
75
|
+
return cast(list[StatusRequest[ResponseT]], results)
|
|
89
76
|
|
|
90
77
|
def input_callback(self, inp: inputs.Input) -> None:
|
|
91
78
|
"""Handle incoming inputs and set the result for the corresponding requests."""
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
79
|
+
# Update current request (if it exists)
|
|
80
|
+
if (
|
|
81
|
+
self.current_request is not None
|
|
82
|
+
and not self.current_request.response.done()
|
|
83
|
+
):
|
|
84
|
+
if isinstance(inp, self.current_request.type) and all(
|
|
85
|
+
getattr(inp, parameter_name) == parameter_value
|
|
86
|
+
for parameter_name, parameter_value in self.current_request.parameters
|
|
87
|
+
):
|
|
88
|
+
self.current_request.timestamp = asyncio.get_running_loop().time()
|
|
89
|
+
self.current_request.response.set_result(inp)
|
|
90
|
+
|
|
91
|
+
# Update cached requests
|
|
92
|
+
for request in self.get_status_requests(type(inp)):
|
|
95
93
|
if all(
|
|
96
94
|
getattr(inp, parameter_name) == parameter_value
|
|
97
95
|
for parameter_name, parameter_value in request.parameters
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
continue
|
|
103
|
-
request.timestamp = asyncio.get_running_loop().time()
|
|
104
|
-
request.response.set_result(inp)
|
|
96
|
+
):
|
|
97
|
+
request.timestamp = asyncio.get_running_loop().time()
|
|
98
|
+
request.response = asyncio.get_running_loop().create_future()
|
|
99
|
+
request.response.set_result(inp)
|
|
105
100
|
|
|
106
101
|
async def request(
|
|
107
102
|
self,
|
|
108
|
-
response_type: type[
|
|
103
|
+
response_type: type[ResponseT],
|
|
109
104
|
request_pck: str,
|
|
110
105
|
request_acknowledge: bool = False,
|
|
111
106
|
max_age: int = 0, # -1: no age limit / infinite age
|
|
112
107
|
**request_kwargs: Any,
|
|
113
|
-
) ->
|
|
108
|
+
) -> ResponseT | None:
|
|
114
109
|
"""Execute a status request and wait for the response."""
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
110
|
+
async with self.request_lock:
|
|
111
|
+
# check for matching request in cache
|
|
112
|
+
if requests := self.get_status_requests(
|
|
113
|
+
response_type,
|
|
114
|
+
frozenset(request_kwargs.items()),
|
|
115
|
+
max_age,
|
|
116
|
+
):
|
|
117
|
+
_LOGGER.debug(
|
|
118
|
+
"from %s: %s (cached)",
|
|
119
|
+
self.device_connection.conn.connection_id,
|
|
120
|
+
requests[0].response.result().pck,
|
|
121
|
+
)
|
|
122
|
+
return requests[0].response.result()
|
|
123
|
+
|
|
124
|
+
# no matching request in cache
|
|
125
|
+
self.current_request = StatusRequest(
|
|
126
|
+
response_type,
|
|
127
|
+
frozenset(request_kwargs.items()),
|
|
128
|
+
-1,
|
|
129
|
+
asyncio.get_running_loop().create_future(),
|
|
130
|
+
)
|
|
136
131
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
132
|
+
result = None
|
|
133
|
+
# send the request up to NUM_TRIES and wait for response future completion
|
|
134
|
+
for _ in range(self.device_connection.conn.settings["NUM_TRIES"]):
|
|
135
|
+
await self.device_connection.send_command(
|
|
136
|
+
request_acknowledge, request_pck
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
async with asyncio.timeout(
|
|
141
|
+
self.device_connection.conn.settings["DEFAULT_TIMEOUT"]
|
|
142
|
+
):
|
|
143
|
+
# Need to shield the future. Otherwise it would get cancelled.
|
|
144
|
+
result = await asyncio.shield(self.current_request.response)
|
|
145
|
+
break
|
|
146
|
+
except asyncio.TimeoutError:
|
|
147
|
+
continue
|
|
148
|
+
except asyncio.CancelledError:
|
|
150
149
|
break
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if result is None:
|
|
158
|
-
request.response.cancel()
|
|
159
|
-
self.last_requests.discard(request)
|
|
160
|
-
return result
|
|
150
|
+
|
|
151
|
+
if result is not None: # add request to cache
|
|
152
|
+
self.request_cache.discard(self.current_request)
|
|
153
|
+
self.request_cache.add(self.current_request)
|
|
154
|
+
|
|
155
|
+
return cast(ResponseT | None, result)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
pypck/__init__.py,sha256=jVx-aBsV_LmBf6jiivMrMcBUofC_AOseywDafgOzAS4,323
|
|
2
|
+
pypck/connection.py,sha256=n3itRe8oQtw64vyWGYhl6j4QJC6wgeeHitBSn-Cl2_4,23330
|
|
3
|
+
pypck/device.py,sha256=1R9C4Wra8llzJph4eher-KPF2iF7uGOspq3eWcqTLyM,39591
|
|
4
|
+
pypck/helpers.py,sha256=_5doqIsSRpqdQNPIUsjFh813xKGuMuEFY6sNGobJGIk,1280
|
|
5
|
+
pypck/inputs.py,sha256=dw9ebhP9RBM9d-lPSFqRrFH1TOETNsT4RNschNjAnA8,46056
|
|
6
|
+
pypck/lcn_addr.py,sha256=N2Od8KuANOglqKjf596hJVH1SRcG7MhESKA5YYlDnbw,1946
|
|
7
|
+
pypck/lcn_defs.py,sha256=wSceYBwM46NqPwvff1hi8RluqUECmNY1gNcm1kDKTaI,43356
|
|
8
|
+
pypck/pck_commands.py,sha256=SL9zpsswRGCHjAWm4uKWFTqtSBx2jURFHzq86QrtwuU,50442
|
|
9
|
+
pypck/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
pypck/status_requester.py,sha256=Ea9c9moA1Kxoh3U9tPVqHKAmnRwq-1vraP_cwqxGZt8,5774
|
|
11
|
+
pypck-0.9.9.dist-info/licenses/LICENSE,sha256=iYB6zyMJvShfAzQE7nhYFgLzzZuBmhasLw5fYP9KRz4,1023
|
|
12
|
+
pypck-0.9.9.dist-info/METADATA,sha256=HER_BuajbZll66fngbD2rQVTR6bcice7kOMW8bOLggk,2682
|
|
13
|
+
pypck-0.9.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
pypck-0.9.9.dist-info/top_level.txt,sha256=59ried49iFueDa5mQ_5BGVZcESjjzi4MZZKLcganvQA,6
|
|
15
|
+
pypck-0.9.9.dist-info/RECORD,,
|
pypck-0.9.7.dist-info/RECORD
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
pypck/__init__.py,sha256=jVx-aBsV_LmBf6jiivMrMcBUofC_AOseywDafgOzAS4,323
|
|
2
|
-
pypck/connection.py,sha256=n3itRe8oQtw64vyWGYhl6j4QJC6wgeeHitBSn-Cl2_4,23330
|
|
3
|
-
pypck/device.py,sha256=Ek-Zy4Id63vl43oW1pSafbDqRAfidhgoJI0F4EMWCXY,40375
|
|
4
|
-
pypck/helpers.py,sha256=_5doqIsSRpqdQNPIUsjFh813xKGuMuEFY6sNGobJGIk,1280
|
|
5
|
-
pypck/inputs.py,sha256=F7E8rprIhYzZnHARozt_hguYNgJaiNP3htrZ2E3Qa5I,45951
|
|
6
|
-
pypck/lcn_addr.py,sha256=N2Od8KuANOglqKjf596hJVH1SRcG7MhESKA5YYlDnbw,1946
|
|
7
|
-
pypck/lcn_defs.py,sha256=wSceYBwM46NqPwvff1hi8RluqUECmNY1gNcm1kDKTaI,43356
|
|
8
|
-
pypck/pck_commands.py,sha256=eJxmh2e8EbKGpek97L2961Kr_nVfT8rKgJCN3YgjIQM,50458
|
|
9
|
-
pypck/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
pypck/status_requester.py,sha256=10N5pbIBe_Ao-9ui_D7mCavk21BYZs9c-kxcTtmi-FI,5721
|
|
11
|
-
pypck-0.9.7.dist-info/licenses/LICENSE,sha256=iYB6zyMJvShfAzQE7nhYFgLzzZuBmhasLw5fYP9KRz4,1023
|
|
12
|
-
pypck-0.9.7.dist-info/METADATA,sha256=gooJ1CbK2mtAKuIEs8v3OKV_sDMHziMbmKHGPcEvsmE,2682
|
|
13
|
-
pypck-0.9.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
-
pypck-0.9.7.dist-info/top_level.txt,sha256=59ried49iFueDa5mQ_5BGVZcESjjzi4MZZKLcganvQA,6
|
|
15
|
-
pypck-0.9.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|