Skip to content

Commit

Permalink
Enable calling methods via UDP
Browse files Browse the repository at this point in the history
  • Loading branch information
chrizog committed Jun 1, 2024
1 parent 7824b51 commit efaa29e
Show file tree
Hide file tree
Showing 9 changed files with 398 additions and 9 deletions.
6 changes: 3 additions & 3 deletions example_apps/addition_method_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ class Addends(SomeIpPayload):
addend1: Sint16
addend2: Sint16

def __init__(self):
self.addend1 = Sint16()
self.addend2 = Sint16()
def __init__(self, addend1: int = 0, addend2: int = 0):
self.addend1 = Sint16(addend1)
self.addend2 = Sint16(addend2)

@dataclass
class Sum(SomeIpPayload):
Expand Down
84 changes: 84 additions & 0 deletions example_apps/call_method_udp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import asyncio
import ipaddress
import logging

from someipy import TransportLayerProtocol
from someipy.client_service_instance import MethodResult, construct_client_service_instance
from someipy.service import ServiceBuilder
from someipy.service_discovery import construct_service_discovery
from someipy.logging import set_someipy_log_level
from addition_method_parameters import Addends, Sum

SD_MULTICAST_GROUP = "224.224.224.245"
SD_PORT = 30490
INTERFACE_IP = "127.0.0.1"

SAMPLE_SERVICE_ID = 0x1234
SAMPLE_INSTANCE_ID = 0x5678
SAMPLE_METHOD_ID = 0x0123

async def main():

# It's possible to configure the logging level of the someipy library, e.g. logging.INFO, logging.DEBUG, logging.WARN, ..
set_someipy_log_level(logging.DEBUG)

# Since the construction of the class ServiceDiscoveryProtocol is not trivial and would require an async __init__ function
# use the construct_service_discovery function
# The local interface IP address needs to be passed so that the src-address of all SD UDP packets is correctly set
service_discovery = await construct_service_discovery(SD_MULTICAST_GROUP, SD_PORT, INTERFACE_IP)

addition_service = (
ServiceBuilder()
.with_service_id(SAMPLE_SERVICE_ID)
.with_major_version(1)
.build()
)

# For calling methods construct a ClientServiceInstance
client_instance_addition = await construct_client_service_instance(
service=addition_service,
instance_id=SAMPLE_INSTANCE_ID,
endpoint=(ipaddress.IPv4Address(INTERFACE_IP), 3002),
ttl=5,
sd_sender=service_discovery,
protocol=TransportLayerProtocol.UDP
)

# The service instance has to be attached always to the ServiceDiscoveryProtocol object, so that the service instance
# is notified by the ServiceDiscoveryProtocol about e.g. subscriptions or offers from other ECUs
service_discovery.attach(client_instance_addition)

try:
while True:

method_parameter = Addends(addend1=1, addend2=2)
method_success, method_result = await client_instance_addition.call_method(SAMPLE_METHOD_ID, method_parameter.serialize())
if method_success == MethodResult.SUCCESS:
print(f"Received result for method: {' '.join(f'0x{b:02x}' for b in method_result)}")
try:
sum = Sum().deserialize(method_result)
print(f"Sum: {sum.value.value}")
except e:
print(f"Error during deserialization: {e}")
elif method_success == MethodResult.ERROR:
print("Method call failed..")
elif method_success == MethodResult.TIMEOUT:
print("Method call timed out..")
elif method_success == MethodResult.SERVICE_NOT_FOUND:
print("Service not yet available..")

await asyncio.sleep(2)
except asyncio.CancelledError:
print("Shutdown..")
finally:
print("Service Discovery close..")
service_discovery.close()

print("End main task..")


if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
pass
4 changes: 3 additions & 1 deletion src/someipy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from ._internal.someip_sd_header import TransportLayerProtocol # noqa: F401
from .server_service_instance import ServerServiceInstance, construct_server_service_instance # noqa: F401
from .service import Service, ServiceBuilder, EventGroup # noqa: F401
from .server_service_instance import ServerServiceInstance, construct_server_service_instance # noqa: F401
from .client_service_instance import ClientServiceInstance, construct_client_service_instance, MethodResult # noqa: F401

from ._internal.someip_message import SomeIpMessage # noqa: F401
3 changes: 3 additions & 0 deletions src/someipy/_internal/someip_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
class SomeIpMessage:
header: SomeIpHeader
payload: bytes

def serialize(self) -> bytes:
return self.header.to_buffer() + self.payload
83 changes: 79 additions & 4 deletions src/someipy/client_service_instance.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import asyncio
from enum import Enum
import struct
from typing import Tuple, Callable, Set, List
from typing import Iterable, Tuple, Callable, Set, List

from someipy import Service
from someipy._internal.someip_sd_header import (
Expand All @@ -15,7 +15,7 @@
ServiceDiscoveryObserver,
ServiceDiscoverySender,
)
from someipy._internal.utils import create_udp_socket, EndpointType
from someipy._internal.utils import create_udp_socket, EndpointType, endpoint_to_str_int_tuple
from someipy._internal.logging import get_logger
from someipy._internal.message_types import MessageType
from someipy._internal.someip_endpoint import (
Expand All @@ -27,10 +27,25 @@

_logger_name = "client_service_instance"

class MethodResult(Enum):
SUCCESS = 0
TIMEOUT = 1
SERVICE_NOT_FOUND = 2
ERROR = 3

class ExpectedAck:
def __init__(self, eventgroup_id: int) -> None:
self.eventgroup_id = eventgroup_id

class FoundService:
service: SdService

def __init__(self, service: SdService) -> None:
self.service = service

def __eq__(self, __value: object) -> bool:
if isinstance(__value, FoundService):
return self.service == __value.service

class ClientServiceInstance(ServiceDiscoveryObserver):
_service: Service
Expand All @@ -44,6 +59,8 @@ class ClientServiceInstance(ServiceDiscoveryObserver):
_eventgroups_to_subscribe: Set[int]
_expected_acks: List[ExpectedAck]
_callback: Callable[[bytes], None]
_found_services: Iterable[FoundService]
_method_call_future: asyncio.Future

def __init__(
self,
Expand Down Expand Up @@ -72,6 +89,10 @@ def __init__(
self._tcp_connect_lock = asyncio.Lock()
self._tcp_task = None

self._found_services = []
self._method_call_future = None


def register_callback(self, callback: Callable[[SomeIpMessage], None]) -> None:
"""
Register a callback function to be called when a SOME/IP event is received.
Expand All @@ -85,6 +106,43 @@ def register_callback(self, callback: Callable[[SomeIpMessage], None]) -> None:
"""
self._callback = callback

async def call_method(self, method_id: int, payload: bytes) -> Tuple[MethodResult, bytes]:

has_service = False
for s in self._found_services:
if s.service.service_id == self._service.id and s.service.instance_id == self._instance_id:
has_service = True
break

if not has_service:
get_logger(_logger_name).warn(f"Do not execute method call. Service 0x{self._service.id:04X} with instance 0x{self._instance_id:04X} not found.")
return MethodResult.SERVICE_NOT_FOUND, b""

header = SomeIpHeader(
service_id=self._service.id,
method_id=method_id,
client_id=0x00,
session_id=0x00,
protocol_version=0x01,
interface_version=0x00,
message_type=MessageType.REQUEST.value,
return_code=0x00,
length=len(payload) + 8,
)
someip_message = SomeIpMessage(header, payload)

self._method_call_future = asyncio.get_running_loop().create_future()
self._someip_endpoint.sendto(someip_message.serialize(), endpoint_to_str_int_tuple(self._found_services[0].service.endpoint))

try:
await asyncio.wait_for(self._method_call_future, 1.0)
except asyncio.TimeoutError:
get_logger(_logger_name).error(f"Waiting on response for method call 0x{method_id:04X} timed out.")
return MethodResult.TIMEOUT, b""

return MethodResult.SUCCESS, self._method_call_future.result()


def someip_message_received(
self, someip_message: SomeIpMessage, addr: Tuple[str, int]
) -> None:
Expand All @@ -96,6 +154,18 @@ def someip_message_received(
):
if self._callback is not None:
self._callback(someip_message)

if (someip_message.header.message_type == MessageType.RESPONSE.value
and someip_message.header.return_code == 0x00): # E_OK
if self._method_call_future is not None:
self._method_call_future.set_result(someip_message.payload)
return

if (someip_message.header.message_type == MessageType.ERROR.value
and someip_message.header.return_code == 0x01): # E_NOT_OK
if self._method_call_future is not None:
self._method_call_future.set_result(b"")
return

def subscribe_eventgroup(self, eventgroup_id: int):
"""
Expand Down Expand Up @@ -140,8 +210,8 @@ def find_service_update(self):
pass

def offer_service_update(self, offered_service: SdService):
if len(self._eventgroups_to_subscribe) == 0:
return
#if len(self._eventgroups_to_subscribe) == 0:
# return

if self._service.id != offered_service.service_id:
return
Expand All @@ -152,6 +222,11 @@ def offer_service_update(self, offered_service: SdService):
offered_service.service_id == self._service.id
and offered_service.instance_id == self._instance_id
):
# TODO: Check does not work, fix
if FoundService(offered_service) not in self._found_services:
self._found_services.append(FoundService(offered_service))

# Try to subscribe to requested event groups
for eventgroup_to_subscribe in self._eventgroups_to_subscribe:
(
session_id,
Expand Down
2 changes: 1 addition & 1 deletion test_apps/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ create_test_target("send_events")

create_test_target("receive_events_udp")
create_test_target("receive_events_tcp")

create_test_target("offer_method_udp")
create_test_target("offer_method_tcp")
create_test_target("call_method_udp")
Loading

0 comments on commit efaa29e

Please sign in to comment.