16
16
import asyncio
17
17
from enum import Enum
18
18
import struct
19
- from typing import Iterable , Tuple , Callable , Set , List
19
+ from typing import Dict , Iterable , Tuple , Callable , Set , List
20
20
21
21
from someipy import Service
22
22
from someipy ._internal .method_result import MethodResult
@@ -86,7 +86,10 @@ class ClientServiceInstance(ServiceDiscoveryObserver):
86
86
_callback : Callable [[bytes ], None ]
87
87
_found_services : Iterable [FoundService ]
88
88
_subscription_active : bool
89
- _method_call_future : asyncio .Future
89
+
90
+ _method_call_futures : Dict [int , asyncio .Future ]
91
+ _client_id : int
92
+ _session_id : int
90
93
91
94
def __init__ (
92
95
self ,
@@ -97,6 +100,7 @@ def __init__(
97
100
someip_endpoint : SomeipEndpoint ,
98
101
ttl : int = 0 ,
99
102
sd_sender = None ,
103
+ client_id : int = 0 ,
100
104
):
101
105
self ._service = service
102
106
self ._instance_id = instance_id
@@ -119,7 +123,10 @@ def __init__(
119
123
120
124
self ._found_services = []
121
125
self ._subscription_active = False
122
- self ._method_call_future = None
126
+ self ._method_call_futures : Dict [int , asyncio .Future ] = {}
127
+ self ._client_id = client_id
128
+
129
+ self ._session_id = 0 # Starts from 1 to 0xFFFF
123
130
124
131
def register_callback (self , callback : Callable [[SomeIpMessage ], None ]) -> None :
125
132
"""
@@ -157,7 +164,7 @@ async def call_method(self, method_id: int, payload: bytes) -> MethodResult:
157
164
payload (bytes): The payload to send with the method call.
158
165
159
166
Returns:
160
- MethodResult: The result of the method call which can contain an error or a successfull result including the response payload.
167
+ MethodResult: The result of the method call which can contain an error or a successful result including the response payload.
161
168
162
169
Raises:
163
170
RuntimeError: If the TCP connection to the server cannot be established or if the server service has not been found yet.
@@ -174,20 +181,25 @@ async def call_method(self, method_id: int, payload: bytes) -> MethodResult:
174
181
f"Method 0x{ method_id :04x} called, but service 0x{ self ._service .id :04X} with instance 0x{ self ._instance_id :04X} not found yet."
175
182
)
176
183
184
+ # Session ID is a 16-bit value and should be incremented for each method call starting from 1
185
+ self ._session_id = (self ._session_id + 1 ) % 0xFFFF
186
+ session_id = self ._session_id
187
+
177
188
header = SomeIpHeader (
178
189
service_id = self ._service .id ,
179
190
method_id = method_id ,
180
- client_id = 0x00 ,
181
- session_id = 0x00 ,
191
+ client_id = self . _client_id ,
192
+ session_id = session_id ,
182
193
protocol_version = 0x01 ,
183
- interface_version = 0x00 ,
194
+ interface_version = self . _service . major_version ,
184
195
message_type = MessageType .REQUEST .value ,
185
196
return_code = 0x00 ,
186
197
length = len (payload ) + 8 ,
187
198
)
188
199
someip_message = SomeIpMessage (header , payload )
189
200
190
- self ._method_call_future = asyncio .get_running_loop ().create_future ()
201
+ call_future = asyncio .get_running_loop ().create_future ()
202
+ self ._method_call_futures [session_id ] = call_future
191
203
192
204
dst_address = str (self ._found_services [0 ].service .endpoint [0 ])
193
205
dst_port = self ._found_services [0 ].service .endpoint [1 ]
@@ -234,20 +246,28 @@ async def call_method(self, method_id: int, payload: bytes) -> MethodResult:
234
246
endpoint_to_str_int_tuple (self ._found_services [0 ].service .endpoint ),
235
247
)
236
248
237
- # After sending the method call wait for maximum three seconds
249
+ # After sending the method call wait for maximum 10 seconds
238
250
try :
239
- await asyncio .wait_for (self . _method_call_future , 3 .0 )
251
+ await asyncio .wait_for (call_future , 10 .0 )
240
252
except asyncio .TimeoutError :
253
+
254
+ # Remove the call_future from self._method_call_futures
255
+ del self ._method_call_futures [session_id ]
256
+
241
257
get_logger (_logger_name ).error (
242
258
f"Waiting on response for method call 0x{ method_id :04X} timed out."
243
259
)
244
260
raise
245
261
246
- return self ._method_call_future .result ()
262
+ method_result = call_future .result ()
263
+ del self ._method_call_futures [session_id ]
264
+ return method_result
247
265
248
266
def someip_message_received (
249
267
self , someip_message : SomeIpMessage , addr : Tuple [str , int ]
250
268
) -> None :
269
+
270
+ # Handling a notification message
251
271
if (
252
272
someip_message .header .client_id == 0x00
253
273
and someip_message .header .message_type == MessageType .NOTIFICATION .value
@@ -257,16 +277,33 @@ def someip_message_received(
257
277
self ._callback (someip_message )
258
278
return
259
279
280
+ # Handling a response message
260
281
if (
261
282
someip_message .header .message_type == MessageType .RESPONSE .value
262
283
or someip_message .header .message_type == MessageType .ERROR .value
263
284
):
264
- if self ._method_call_future is not None :
285
+ if someip_message .header .session_id not in self ._method_call_futures .keys ():
286
+ return
287
+ if someip_message .header .client_id != self ._client_id :
288
+ return
289
+
290
+ call_future = None
291
+ try :
292
+ call_future = self ._method_call_futures [
293
+ someip_message .header .session_id
294
+ ]
295
+ except :
296
+ get_logger (_logger_name ).error (
297
+ f"Received response for unknown session ID { someip_message .header .session_id } "
298
+ )
299
+ return
300
+
301
+ if call_future is not None :
265
302
result = MethodResult ()
266
303
result .message_type = MessageType (someip_message .header .message_type )
267
304
result .return_code = ReturnCode (someip_message .header .return_code )
268
305
result .payload = someip_message .payload
269
- self . _method_call_future .set_result (result )
306
+ call_future .set_result (result )
270
307
return
271
308
272
309
def subscribe_eventgroup (self , eventgroup_id : int ):
@@ -314,64 +351,72 @@ def handle_find_service(self):
314
351
def handle_offer_service (self , offered_service : SdService ):
315
352
if self ._service .id != offered_service .service_id :
316
353
return
317
- if self ._instance_id != offered_service .instance_id :
354
+ if (
355
+ self ._instance_id != 0xFFFF
356
+ and offered_service .instance_id != 0xFFFF
357
+ and self ._instance_id != offered_service .instance_id
358
+ ):
359
+ # 0xFFFF allows to handle any instance ID
360
+ return
361
+ if self ._service .major_version != offered_service .major_version :
318
362
return
319
-
320
363
if (
321
- offered_service . service_id == self ._service .id
322
- and offered_service . instance_id == self . _instance_id
364
+ self ._service .minor_version != 0xFFFFFFFF
365
+ and self . _service . minor_version != offered_service . minor_version
323
366
):
324
- if FoundService ( offered_service ) not in self . _found_services :
325
- self . _found_services . append ( FoundService ( offered_service ))
367
+ # 0xFFFFFFFF allows to handle any minor version
368
+ return
326
369
327
- if len ( self . _eventgroups_to_subscribe ) == 0 :
328
- return
370
+ if FoundService ( offered_service ) not in self . _found_services :
371
+ self . _found_services . append ( FoundService ( offered_service ))
329
372
330
- # Try to subscribe to requested event groups
331
- for eventgroup_to_subscribe in self ._eventgroups_to_subscribe :
332
- (
333
- session_id ,
334
- reboot_flag ,
335
- ) = self ._sd_sender .get_unicast_session_handler ().update_session ()
336
-
337
- # Improvement: Pack all entries into a single SD message
338
- subscribe_sd_header = build_subscribe_eventgroup_sd_header (
339
- service_id = self ._service .id ,
340
- instance_id = self ._instance_id ,
341
- major_version = self ._service .major_version ,
342
- ttl = self ._ttl ,
343
- event_group_id = eventgroup_to_subscribe ,
344
- session_id = session_id ,
345
- reboot_flag = reboot_flag ,
346
- endpoint = self ._endpoint ,
347
- protocol = self ._protocol ,
348
- )
373
+ if len (self ._eventgroups_to_subscribe ) == 0 :
374
+ return
349
375
350
- get_logger (_logger_name ).debug (
351
- f"Send subscribe for instance 0x{ self ._instance_id :04X} , service: 0x{ self ._service .id :04X} , "
352
- f"eventgroup ID: { eventgroup_to_subscribe } TTL: { self ._ttl } , version: "
353
- f"session ID: { session_id } "
354
- )
376
+ # Try to subscribe to requested event groups
377
+ for eventgroup_to_subscribe in self ._eventgroups_to_subscribe :
378
+ (
379
+ session_id ,
380
+ reboot_flag ,
381
+ ) = self ._sd_sender .get_unicast_session_handler ().update_session ()
382
+ # Improvement: Pack all entries into a single SD message
383
+ subscribe_sd_header = build_subscribe_eventgroup_sd_header (
384
+ service_id = self ._service .id ,
385
+ instance_id = self ._instance_id ,
386
+ major_version = self ._service .major_version ,
387
+ ttl = self ._ttl ,
388
+ event_group_id = eventgroup_to_subscribe ,
389
+ session_id = session_id ,
390
+ reboot_flag = reboot_flag ,
391
+ endpoint = self ._endpoint ,
392
+ protocol = self ._protocol ,
393
+ )
355
394
356
- if self ._protocol == TransportLayerProtocol .TCP :
357
- if self ._tcp_task is None :
358
- get_logger (_logger_name ).debug (
359
- f"Create new TCP task for client of 0x{ self ._instance_id :04X} , 0x{ self ._service .id :04X} "
360
- )
361
- self ._tcp_task = asyncio .create_task (
362
- self .setup_tcp_connection (
363
- str (self ._endpoint [0 ]),
364
- self ._endpoint [1 ],
365
- str (offered_service .endpoint [0 ]),
366
- offered_service .endpoint [1 ],
367
- )
395
+ get_logger (_logger_name ).debug (
396
+ f"Send subscribe for instance 0x{ self ._instance_id :04X} , service: 0x{ self ._service .id :04X} , "
397
+ f"eventgroup ID: { eventgroup_to_subscribe } TTL: { self ._ttl } , version: "
398
+ f"session ID: { session_id } "
399
+ )
400
+
401
+ if self ._protocol == TransportLayerProtocol .TCP :
402
+ if self ._tcp_task is None :
403
+ get_logger (_logger_name ).debug (
404
+ f"Create new TCP task for client of 0x{ self ._instance_id :04X} , 0x{ self ._service .id :04X} "
405
+ )
406
+ self ._tcp_task = asyncio .create_task (
407
+ self .setup_tcp_connection (
408
+ str (self ._endpoint [0 ]),
409
+ self ._endpoint [1 ],
410
+ str (offered_service .endpoint [0 ]),
411
+ offered_service .endpoint [1 ],
368
412
)
413
+ )
369
414
370
- self ._expected_acks .append (ExpectedAck (eventgroup_to_subscribe ))
371
- self ._sd_sender .send_unicast (
372
- buffer = subscribe_sd_header .to_buffer (),
373
- dest_ip = offered_service .endpoint [0 ],
374
- )
415
+ self ._expected_acks .append (ExpectedAck (eventgroup_to_subscribe ))
416
+ self ._sd_sender .send_unicast (
417
+ buffer = subscribe_sd_header .to_buffer (),
418
+ dest_ip = offered_service .endpoint [0 ],
419
+ )
375
420
376
421
def handle_stop_offer_service (self , offered_service : SdService ) -> None :
377
422
if self ._service .id != offered_service .service_id :
@@ -489,6 +534,7 @@ async def construct_client_service_instance(
489
534
ttl : int = 0 ,
490
535
sd_sender = None ,
491
536
protocol = TransportLayerProtocol .UDP ,
537
+ client_id : int = 0 ,
492
538
) -> ClientServiceInstance :
493
539
"""
494
540
Asynchronously constructs a ClientServerInstance. Based on the given transport protocol, proper endpoints are setup before constructing the actual ServerServiceInstance.
@@ -523,6 +569,7 @@ async def construct_client_service_instance(
523
569
udp_endpoint ,
524
570
ttl ,
525
571
sd_sender ,
572
+ client_id ,
526
573
)
527
574
528
575
udp_endpoint .set_someip_callback (client_instance .someip_message_received )
0 commit comments