diff --git a/README.md b/README.md index 3c473cc..2e649e7 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ someipy is based on the specification version of R22-11: - [SOME/IP Protocol Specification](https://www.autosar.org/fileadmin/standards/R22-11/FO/AUTOSAR_PRS_SOMEIPProtocol.pdf) - [SOME/IP Service Discovery Protocol Specification](https://www.autosar.org/fileadmin/standards/R22-11/FO/AUTOSAR_PRS_SOMEIPServiceDiscoveryProtocol.pdf) -The library is currently developed and tested under Ubuntu 22.04 and Python 3.12. +The library is currently developed and tested under Ubuntu 22.04 and Python 3.8. ## Typical Use Cases @@ -35,7 +35,7 @@ someipy excels in scenarios where a full-scale Autosar (Adaptive or Classic) int The package can be installed from [PyPi](https://pypi.org/project/someipy/). ```bash -pip install someipy +pip3 install someipy ``` ## Example Applications diff --git a/example_apps/rosbags/Pose.msg b/example_apps/rosbags/Pose.msg new file mode 100644 index 0000000..5b3445b --- /dev/null +++ b/example_apps/rosbags/Pose.msg @@ -0,0 +1,8 @@ +# This is the original ROS message for topic "/turtle1/pose" contained in "test.bag" +# This ROS message is just added for demonstration purposes, but not needed for running replay_rosbags.py +float32 x +float32 y +float32 theta + +float32 linear_velocity +float32 angular_velocity \ No newline at end of file diff --git a/example_apps/rosbags/TurtlesimPose.py b/example_apps/rosbags/TurtlesimPose.py new file mode 100644 index 0000000..b05bc31 --- /dev/null +++ b/example_apps/rosbags/TurtlesimPose.py @@ -0,0 +1,17 @@ +from someipy.serialization import SomeIpPayload, Float32 + +# For defining the TurtleSimPose as a SOME/IP message, simply inherit from SomeIpPayload and use +# the provided datatypes such as Float32 for declaring the fields of the message +class TurtlesimPose(SomeIpPayload): + x: Float32 + y: Float32 + theta: Float32 + linear_velocity: Float32 + angular_velocity: Float32 + + def __init__(self): + self.x = Float32() + self.y = Float32() + self.theta = Float32() + self.linear_velocity = Float32() + self.angular_velocity = Float32() diff --git a/example_apps/rosbags/replay_rosbag.py b/example_apps/rosbags/replay_rosbag.py new file mode 100644 index 0000000..d46bd18 --- /dev/null +++ b/example_apps/rosbags/replay_rosbag.py @@ -0,0 +1,114 @@ +import asyncio +import ipaddress +import logging +import rosbag +import TurtlesimPose + +from someipy import TransportLayerProtocol, ServiceBuilder, EventGroup, construct_server_service_instance +from someipy.service_discovery import construct_service_discovery +from someipy.logging import set_someipy_log_level +from someipy.serialization import Float32 + +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_EVENTGROUP_ID = 0x0321 +SAMPLE_EVENT_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 + ) + + turtle_eventgroup = EventGroup( + id=SAMPLE_EVENTGROUP_ID, event_ids=[SAMPLE_EVENT_ID] + ) + turtle_pose_service = ( + ServiceBuilder() + .with_service_id(SAMPLE_SERVICE_ID) + .with_major_version(1) + .with_eventgroup(turtle_eventgroup) + .build() + ) + + # For sending events use a ServerServiceInstance + service_instance_turtle_pose = await construct_server_service_instance( + turtle_pose_service, + instance_id=SAMPLE_INSTANCE_ID, + endpoint=( + ipaddress.IPv4Address(INTERFACE_IP), + 3000, + ), # src IP and port of the service + ttl=5, + sd_sender=service_discovery, + cyclic_offer_delay_ms=2000, + 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 from other ECUs + service_discovery.attach(service_instance_turtle_pose) + + # ..it's also possible to construct another ServerServiceInstance and attach it to service_discovery as well + + # After constructing and attaching ServerServiceInstances to the ServiceDiscoveryProtocol object the + # start_offer method has to be called. This will start an internal timer, which will periodically send + # Offer service entries with a period of "cyclic_offer_delay_ms" which has been passed above + print("Start offering service..") + service_instance_turtle_pose.start_offer() + + bag = rosbag.Bag('test.bag') + + # Get the timestamp of the first message of /turtle1/pose in order to reproduce the timing of the recording + starting_timestamp = next(bag.read_messages(topics=['/turtle1/pose'])).timestamp + + for topic, msg, t in bag.read_messages(topics=['/turtle1/pose']): + + # Calculate the time difference between the current message and the message before + time_sleep = (t - starting_timestamp).to_sec() + + # Use asyncio.sleep to wait for the time difference between the current message and the message before + print(f"Sleeping for {(t - starting_timestamp).to_sec()} seconds") + await asyncio.sleep(time_sleep) + + # Create a SomeIpPayload object and fill it with the values from the rosbag message + someipPose = TurtlesimPose.TurtlesimPose() + someipPose.x = Float32(msg.x) + someipPose.y = Float32(msg.y) + someipPose.theta = Float32(msg.theta) + someipPose.linear_velocity = Float32(msg.linear_velocity) + someipPose.angular_velocity = Float32(msg.angular_velocity) + + # Serialize the SomeIpPayload object to a byte array + payload = someipPose.serialize() + + print(f"Sending event for message {msg}") + # Send the serialized byte array to all subscribers of the event group + service_instance_turtle_pose.send_event( + SAMPLE_EVENTGROUP_ID, SAMPLE_EVENT_ID, payload + ) + + starting_timestamp = t + + bag.close() + + await service_instance_turtle_pose.stop_offer() + print("Service Discovery close..") + service_discovery.close() + print("End main task..") + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + pass diff --git a/example_apps/rosbags/test.bag b/example_apps/rosbags/test.bag new file mode 100644 index 0000000..cb8cf55 Binary files /dev/null and b/example_apps/rosbags/test.bag differ diff --git a/setup.cfg b/setup.cfg index ba23167..1d8ac2c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = someipy -version = 0.0.1 +version = 0.0.2 author = Christian H. author_email = someipy.package@gmail.com description = A Python package implementing the SOME/IP protocol @@ -13,13 +13,13 @@ project_urls = classifiers = Intended Audience :: Developers Operating System :: POSIX :: Linux - Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.8 [options] package_dir = = src packages = find: -python_requires = >=3.12 +python_requires = >=3.8 [options.packages.find] where = src \ No newline at end of file diff --git a/src/someipy/_internal/someip_data_processor.py b/src/someipy/_internal/someip_data_processor.py index 9875fed..9fb2df6 100644 --- a/src/someipy/_internal/someip_data_processor.py +++ b/src/someipy/_internal/someip_data_processor.py @@ -16,11 +16,8 @@ def _reset(self): self._expected_bytes = 0 def process_data(self, new_data: bytes) -> bool: - received_length = len(new_data) - print(" ".join(f"{byte:02X}" for byte in new_data)) - # UDP case if self._datagram_mode: header = SomeIpHeader.from_buffer(new_data) diff --git a/src/someipy/_internal/someip_endpoint.py b/src/someipy/_internal/someip_endpoint.py index 015ba11..9136906 100644 --- a/src/someipy/_internal/someip_endpoint.py +++ b/src/someipy/_internal/someip_endpoint.py @@ -1,6 +1,6 @@ import asyncio from abc import ABC, abstractmethod -from typing import Callable, Tuple, Any +from typing import Callable, Tuple, Any, Union from someipy._internal.someip_message import SomeIpMessage from someipy._internal.tcp_client_manager import ( TcpClientManagerInterface, @@ -44,10 +44,10 @@ def set_someip_callback( def connection_made(self, transport: asyncio.DatagramTransport) -> None: self._transport = transport - def connection_lost(self, exc: Exception | None) -> None: + def connection_lost(self, exc: Exception) -> None: pass - def datagram_received(self, data: bytes, addr: Tuple[str | Any | int]) -> None: + def datagram_received(self, data: bytes, addr: Tuple[Union[str, Any, int]]) -> None: result = self._processor.process_data(data) if result and self._callback is not None: self._callback(self._processor.someip_message, addr) diff --git a/src/someipy/_internal/someip_sd_builder.py b/src/someipy/_internal/someip_sd_builder.py index 883c420..9b0e8f3 100644 --- a/src/someipy/_internal/someip_sd_builder.py +++ b/src/someipy/_internal/someip_sd_builder.py @@ -1,4 +1,21 @@ -from .someip_sd_header import * +import ipaddress +from typing import Tuple +from someipy._internal.someip_header import SomeIpHeader +from .someip_sd_header import ( + SD_BYTE_LENGTH_IP4ENDPOINT_OPTION, + SD_SINGLE_ENTRY_LENGTH_BYTES, + SdService, + SomeIpSdHeader, + SdEntry, + SdEntryType, + SdServiceEntry, + SdOptionCommon, + SD_IPV4ENDPOINT_OPTION_LENGTH_VALUE, + SdOptionType, + SdIPV4EndpointOption, + SdEventGroupEntry, +) +from someipy._internal.someip_sd_header import TransportLayerProtocol def build_offer_service_sd_header( diff --git a/src/someipy/_internal/someip_sd_extractors.py b/src/someipy/_internal/someip_sd_extractors.py index 03c8501..debc676 100644 --- a/src/someipy/_internal/someip_sd_extractors.py +++ b/src/someipy/_internal/someip_sd_extractors.py @@ -1,4 +1,5 @@ -from .someip_sd_header import * +from typing import List, Tuple +from .someip_sd_header import SomeIpSdHeader, SdService, SdEntryType, SdIPV4EndpointOption, SdEventGroupEntry def extract_offered_services(someip_sd_header: SomeIpSdHeader) -> List[SdService]: diff --git a/src/someipy/_internal/tcp_client_manager.py b/src/someipy/_internal/tcp_client_manager.py index 2fe7b2a..d457591 100644 --- a/src/someipy/_internal/tcp_client_manager.py +++ b/src/someipy/_internal/tcp_client_manager.py @@ -57,11 +57,11 @@ def _build_key(self, ip_addr: str, port: int) -> str: return f"{ip_addr}-{port}" def register_client(self, client: TcpClientProtocolInterface) -> None: - print(f"Register new client {client.ip_addr}, {client.port}") + #print(f"Register new client {client.ip_addr}, {client.port}") self._clients[self._build_key(client.ip_addr, client.port)] = client def unregister_client(self, client: TcpClientProtocolInterface) -> None: - print(f"Unregister client {client.ip_addr}, {client.port}") + #print(f"Unregister client {client.ip_addr}, {client.port}") if self._build_key(client.ip_addr, client.port) in self._clients.keys(): del self._clients[self._build_key(client.ip_addr, client.port)] diff --git a/src/someipy/client_service_instance.py b/src/someipy/client_service_instance.py index cf2ba67..331fbb3 100644 --- a/src/someipy/client_service_instance.py +++ b/src/someipy/client_service_instance.py @@ -146,7 +146,6 @@ async def call_method(self, method_id: int, payload: bytes) -> Tuple[MethodResul def someip_message_received( self, someip_message: SomeIpMessage, addr: Tuple[str, int] ) -> None: - print(someip_message.header) if ( someip_message.header.client_id == 0x00 and someip_message.header.message_type == MessageType.NOTIFICATION.value