11import contextlib
22from typing import *
3+ from typing import Any , Generator
34
45import attr
56import structlog
1617
1718
1819class DataResultError (Exception ):
19- """ Error retrieveing data"""
20+ """Error retrieveing data"""
2021
2122
2223class ActionError (Exception ):
@@ -41,6 +42,9 @@ class DlmsClient:
4142 client_initial_invocation_counter : int = attr .ib (default = 0 )
4243 meter_initial_invocation_counter : int = attr .ib (default = 0 )
4344 timeout : int = attr .ib (default = 10 )
45+ invoke_id : int = attr .ib (default = 0 )
46+ invoke_id_confirmed : bool = attr .ib (default = True )
47+ invoke_id_high_priority : bool = attr .ib (default = True )
4448 connection_settings : Optional [DlmsConnectionSettings ] = attr .ib (default = None )
4549
4650 dlms_connection : DlmsConnection = attr .ib (
@@ -63,21 +67,36 @@ class DlmsClient:
6367 )
6468
6569 @contextlib .contextmanager
66- def session (self ) -> "DlmsClient" :
70+ def session (self ) -> Generator [ "DlmsClient" , Any , None ] :
6771 self .connect ()
6872 self .associate ()
6973 yield self
7074 self .release_association ()
7175 self .disconnect ()
7276
77+ def next_invoke_id (self ) -> int :
78+ current = self .invoke_id
79+ self .invoke_id = (current + 1 ) % 16
80+ return current
81+
82+ def next_invoke_id_and_priority (self ) -> xdlms .InvokeIdAndPriority :
83+ return xdlms .InvokeIdAndPriority (
84+ invoke_id = self .next_invoke_id (),
85+ confirmed = self .invoke_id_confirmed ,
86+ high_priority = self .invoke_id_high_priority ,
87+ )
88+
7389 def get (
74- self ,
75- cosem_attribute : cosem .CosemAttribute ,
76- access_descriptor : Optional [RangeDescriptor ] = None ,
90+ self ,
91+ cosem_attribute : cosem .CosemAttribute ,
92+ access_descriptor : Optional [RangeDescriptor ] = None ,
7793 ) -> bytes :
94+ invoke = self .next_invoke_id_and_priority ()
7895 self .send (
7996 xdlms .GetRequestNormal (
80- cosem_attribute = cosem_attribute , access_selection = access_descriptor
97+ cosem_attribute = cosem_attribute ,
98+ access_selection = access_descriptor ,
99+ invoke_id_and_priority = invoke ,
81100 )
82101 )
83102 all_data_received = False
@@ -115,13 +134,15 @@ def get(
115134 return bytes (data )
116135
117136 def get_many (
118- self , cosem_attributes_with_selection : List [cosem .CosemAttributeWithSelection ]
137+ self , cosem_attributes_with_selection : List [cosem .CosemAttributeWithSelection ]
119138 ):
120139 """
121140 Make a GET.WITH_LIST call. Get many items in one request.
122141 """
142+ invoke = self .next_invoke_id_and_priority ()
123143 out = xdlms .GetRequestWithList (
124- cosem_attributes_with_selection = cosem_attributes_with_selection
144+ cosem_attributes_with_selection = cosem_attributes_with_selection ,
145+ invoke_id_and_priority = invoke ,
125146 )
126147 self .send (out )
127148 response = self .next_event ()
@@ -134,11 +155,25 @@ def get_many(
134155 return response
135156
136157 def set (self , cosem_attribute : cosem .CosemAttribute , data : bytes ):
137- self .send (xdlms .SetRequestNormal (cosem_attribute = cosem_attribute , data = data ))
158+ invoke = self .next_invoke_id_and_priority ()
159+ self .send (
160+ xdlms .SetRequestNormal (
161+ cosem_attribute = cosem_attribute ,
162+ data = data ,
163+ invoke_id_and_priority = invoke ,
164+ )
165+ )
138166 return self .next_event ()
139167
140168 def action (self , method : cosem .CosemMethod , data : bytes ):
141- self .send (xdlms .ActionRequestNormal (cosem_method = method , data = data ))
169+ invoke = self .next_invoke_id_and_priority ()
170+ self .send (
171+ xdlms .ActionRequestNormal (
172+ cosem_method = method ,
173+ data = data ,
174+ invoke_id_and_priority = invoke ,
175+ )
176+ )
142177 response = self .next_event ()
143178
144179 if isinstance (response , xdlms .ActionResponseNormalWithError ):
@@ -153,10 +188,9 @@ def action(self, method: cosem.CosemMethod, data: bytes):
153188 return
154189
155190 def associate (
156- self ,
157- association_request : Optional [acse .ApplicationAssociationRequest ] = None ,
191+ self ,
192+ association_request : Optional [acse .ApplicationAssociationRequest ] = None ,
158193 ) -> acse .ApplicationAssociationResponse :
159-
160194 # the aarq can be overridden or the standard one from the connection is used.
161195 aarq = association_request or self .dlms_connection .get_aarq ()
162196
@@ -174,7 +208,7 @@ def associate(
174208 extra_error = None
175209 if response .user_information :
176210 if isinstance (
177- response .user_information .content , ConfirmedServiceError
211+ response .user_information .content , ConfirmedServiceError
178212 ):
179213 extra_error = response .user_information .content .error
180214 raise exceptions .DlmsClientException (
@@ -187,7 +221,6 @@ def associate(
187221 )
188222
189223 if self .should_send_hls_reply ():
190-
191224 # TODO: wrap hls logic in method
192225 try :
193226 hls_response = self .send_hls_reply ()
@@ -203,7 +236,7 @@ def associate(
203236 raise HLSError ("Did not receive any HLS response data" )
204237
205238 if not self .dlms_connection .authentication .hls_meter_data_is_valid (
206- hls_data , self .dlms_connection
239+ hls_data , self .dlms_connection
207240 ):
208241 raise HLSError (
209242 f"Meter did not respond with correct challenge calculation"
@@ -213,8 +246,8 @@ def associate(
213246
214247 def should_send_hls_reply (self ) -> bool :
215248 return (
216- self .dlms_connection .state .current_state
217- == state .SHOULD_SEND_HLS_SEVER_CHALLENGE_RESULT
249+ self .dlms_connection .state .current_state
250+ == state .SHOULD_SEND_HLS_SEVER_CHALLENGE_RESULT
218251 )
219252
220253 def send_hls_reply (self ) -> Optional [bytes ]:
@@ -232,7 +265,6 @@ def send_hls_reply(self) -> Optional[bytes]:
232265 )
233266
234267 def release_association (self ) -> Optional [acse .ReleaseResponse ]:
235-
236268 rlrq = self .dlms_connection .get_rlrq ()
237269 try :
238270 self .send (rlrq )
0 commit comments