diff --git a/docs/api/client_service_instance.rst b/docs/api/client_service_instance.rst new file mode 100644 index 0000000..d4b7b2b --- /dev/null +++ b/docs/api/client_service_instance.rst @@ -0,0 +1,5 @@ +ClientServiceInstance +======================= + +.. autoclass:: someipy::ClientServiceInstance + :exclude-members: __init__ \ No newline at end of file diff --git a/docs/api/connect_to_someipy_daemon.rst b/docs/api/connect_to_someipy_daemon.rst new file mode 100644 index 0000000..5da5c8b --- /dev/null +++ b/docs/api/connect_to_someipy_daemon.rst @@ -0,0 +1,4 @@ +connect_to_someipy_daemon +=========================== + +.. autofunction:: someipy::connect_to_someipy_daemon \ No newline at end of file diff --git a/docs/api/event.rst b/docs/api/event.rst new file mode 100644 index 0000000..54ea600 --- /dev/null +++ b/docs/api/event.rst @@ -0,0 +1,5 @@ +Event +=========================== + +.. autoclass:: someipy::Event + :exclude-members: __init__ \ No newline at end of file diff --git a/docs/api/eventgroup.rst b/docs/api/eventgroup.rst new file mode 100644 index 0000000..a7ef91c --- /dev/null +++ b/docs/api/eventgroup.rst @@ -0,0 +1,5 @@ +EventGroup +======================= + +.. autoclass:: someipy::EventGroup + :exclude-members: __init__ \ No newline at end of file diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 0000000..1d5368f --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,32 @@ +API Reference +======================= + +Classes +----------------------- + +.. toctree:: + :maxdepth: 1 + + service + service_builder + method + method_result + eventgroup + event + server_service_instance + client_service_instance + serialization + transport_layer_protocol + message_type + return_code + + + +Functions +----------------------- + +.. toctree:: + :maxdepth: 1 + + connect_to_someipy_daemon + someipy_logging \ No newline at end of file diff --git a/docs/api/message_type.rst b/docs/api/message_type.rst new file mode 100644 index 0000000..210d71e --- /dev/null +++ b/docs/api/message_type.rst @@ -0,0 +1,5 @@ +MessageType +========================== + +.. autoclass:: someipy::MessageType + :exclude-members: __init__ \ No newline at end of file diff --git a/docs/api/method.rst b/docs/api/method.rst new file mode 100644 index 0000000..382c0c8 --- /dev/null +++ b/docs/api/method.rst @@ -0,0 +1,5 @@ +Method +=============== + +.. autoclass:: someipy::Method + :exclude-members: __init__ \ No newline at end of file diff --git a/docs/api/method_result.rst b/docs/api/method_result.rst new file mode 100644 index 0000000..f992ea2 --- /dev/null +++ b/docs/api/method_result.rst @@ -0,0 +1,5 @@ +MethodResult +========================== + +.. autoclass:: someipy::MethodResult + :exclude-members: __init__ \ No newline at end of file diff --git a/docs/api/return_code.rst b/docs/api/return_code.rst new file mode 100644 index 0000000..1ed81a0 --- /dev/null +++ b/docs/api/return_code.rst @@ -0,0 +1,5 @@ +ReturnCode +========================== + +.. autoclass:: someipy::ReturnCode + :exclude-members: __init__ \ No newline at end of file diff --git a/docs/api/serialization.rst b/docs/api/serialization.rst new file mode 100644 index 0000000..d2a00b8 --- /dev/null +++ b/docs/api/serialization.rst @@ -0,0 +1,6 @@ +Serialization +================= + +.. automodule:: someipy.serialization + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/api/server_service_instance.rst b/docs/api/server_service_instance.rst new file mode 100644 index 0000000..28384d5 --- /dev/null +++ b/docs/api/server_service_instance.rst @@ -0,0 +1,5 @@ +ServerServiceInstance +======================= + +.. autoclass:: someipy::ServerServiceInstance + :exclude-members: __init__ \ No newline at end of file diff --git a/docs/api/service.rst b/docs/api/service.rst new file mode 100644 index 0000000..e74c333 --- /dev/null +++ b/docs/api/service.rst @@ -0,0 +1,5 @@ +Service +======================= + +.. autoclass:: someipy::Service + :exclude-members: __init__ diff --git a/docs/api/service_builder.rst b/docs/api/service_builder.rst new file mode 100644 index 0000000..3113c6c --- /dev/null +++ b/docs/api/service_builder.rst @@ -0,0 +1,5 @@ +ServiceBuilder +======================= + +.. autoclass:: someipy::ServiceBuilder + :exclude-members: __init__ \ No newline at end of file diff --git a/docs/api/someipy_logging.rst b/docs/api/someipy_logging.rst new file mode 100644 index 0000000..857c3d1 --- /dev/null +++ b/docs/api/someipy_logging.rst @@ -0,0 +1,4 @@ +someipy_logging +======================= + +.. automodule:: someipy.someipy_logging diff --git a/docs/api/transport_layer_protocol.rst b/docs/api/transport_layer_protocol.rst new file mode 100644 index 0000000..0e447f4 --- /dev/null +++ b/docs/api/transport_layer_protocol.rst @@ -0,0 +1,5 @@ +TransportLayerProtocol +=========================== + +.. autoclass:: someipy::TransportLayerProtocol + :exclude-members: __init__ \ No newline at end of file diff --git a/docs/calling_methods.rst b/docs/calling_methods.rst index 8012068..c87978b 100644 --- a/docs/calling_methods.rst +++ b/docs/calling_methods.rst @@ -36,7 +36,7 @@ In this example, we will call a SOME/IP method that calculates the sum of two si Details on defining data types can be found :doc:`here `. Step 2: Connect to the someipy Daemon ----------------------------------- +-------------------------------------- The first step is to connect to the someipy daemon. The daemon is a separate process communicating with the application using someipy via a Unix Domain Socket (UDS). The daemon is responsible for handling all communication with the SOME/IP network, including service discovery and message sending/receiving. @@ -125,7 +125,7 @@ To avoid the ``RuntimeError`` it is possible to test whether the service was alr print(f"Error during method call: {e}") Step 6: Shutdown the Application ----------------------------- +-------------------------------- At the end of your application, make sure to stop offering the service instance and disconnect from the someipy daemon to ensure a clean shutdown of the application. diff --git a/docs/conf.py b/docs/conf.py index 9bfafe6..fb46934 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -6,15 +6,36 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +import sys +from pathlib import Path + project = "someipy" copyright = "2025, Christian H." author = "Christian H." release = "1.0.0" +sys.path.insert(0, str(Path("..", "src").resolve())) + # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = [] +extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon"] + +# Napoleon settings +napoleon_google_docstring = True +napoleon_numpy_docstring = False +napoleon_include_init_with_doc = False +napoleon_include_private_with_doc = False +napoleon_include_special_with_doc = True + +# Autodoc settings +autodoc_default_options = { + "members": True, + "undoc-members": True, + "private-members": False, +} + +autodoc_typehints = "description" # Show type hints in description templates_path = ["_templates"] exclude_patterns = [] diff --git a/docs/cookbook.rst b/docs/cookbook.rst new file mode 100644 index 0000000..1e68806 --- /dev/null +++ b/docs/cookbook.rst @@ -0,0 +1,10 @@ +Cookbook +========================== + +.. toctree:: + :maxdepth: 2 + + calling_methods + offering_methods + subscribing_events + offering_events \ No newline at end of file diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 3be045b..7c7f064 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -1,10 +1,10 @@ Getting Started -=============== +================= -This guide demonstrates how to create a SOME/IP service using someipy that broadcasts temperature measurements every second. The complete example is available in the `example applications on GitHub `_. +This guide demonstrates how to create a SOME/IP service using someipy that broadcasts temperature measurements every second using SOME/IP events. The complete example is available in the `example applications on GitHub `_. Prerequisites ------------- +---------------- - Linux (Ubuntu 22.04 or equivalent) - Python 3.8+ @@ -19,12 +19,12 @@ Install someipy from PyPI: pip3 install someipy -Service Datatype Definition +Service Data Type Definition --------------------------- Define the temperature data structure using Python dataclasses. No IDL files required. -Create **temperature_msg.py**: +Create ``temperature_msg.py``: .. code-block:: python @@ -64,7 +64,7 @@ someipy is an `asyncio `_ based First we will set up our application's structure. Typically in an asyncio application, a main coroutine-function is added which is executed using `asyncio.run `_. Our application's logic will be added inside the main coroutine-function *async def main*. -Create **send_events_udp.py** with the following structure: +Create ``send_events_udp.py`` with the following structure: .. code-block:: python @@ -265,4 +265,4 @@ Run the application using Python 3: .. code-block:: bash - python3 send_events_udp.py \ No newline at end of file + python3 send_events_udp.py diff --git a/docs/index.rst b/docs/index.rst index 5c835bc..03057ff 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,5 @@ someipy - An Open-Source Python library for SOME/IP -==================================== +===================================================== This is the documentation for someipy, an open-source Python library implementing the SOME/IP protocol, including SOME/IP Service Discovery. It is designed for fast prototyping of applications making SOME/IP more accessible to developers. @@ -30,15 +30,13 @@ Table of Contents ================= .. toctree:: - :maxdepth: 1 + :maxdepth: 2 whatsnew getting_started + cookbook someipy_daemon - calling_methods - offering_events - offering_methods - subscribing_events service_discovery service_interface changelog + api/index diff --git a/docs/offering_events.rst b/docs/offering_events.rst index 7b83132..521ef25 100644 --- a/docs/offering_events.rst +++ b/docs/offering_events.rst @@ -72,7 +72,7 @@ The constructor of the ``ServerServiceInstance`` class requires several paramete - endpoint_ip: The IP address of the network interface on which the service is offered (127.0.0.1 in this example) - endpoint_port: The port on which the service is offered (3000 in this example) - ttl: The time-to-live for the service discovery entries (5 seconds in this example) -- cyclic_offer_delay_ms: The period of the cylic offer service SD messages (2000 ms in this example) +- cyclic_offer_delay_ms: The period of the cyclic offer service SD messages (2000 ms in this example) .. code-block:: python @@ -115,11 +115,11 @@ Now that the service is offered, clients can subscribe to the eventgroup with ID Typical sending strategies for SOME/IP events are **cyclic updates** or **update on change**. Update on change means that an event is sent whenever the contained value changes. In a cyclic update, the event would be sent even if the contained data has not changed since the last publish. Step 6: Shutdown the Application ----------------------------- +------------------------------------------------ At the end of your application, make sure to stop offering the service instance and disconnect from the someipy daemon to ensure a clean shutdown of the application. .. code-block:: python await service_instance_temperature.stop_offer() - await someipy_daemon.disconnect_from_daemon() \ No newline at end of file + await someipy_daemon.disconnect_from_daemon() diff --git a/docs/offering_methods.rst b/docs/offering_methods.rst index eee315c..f597f20 100644 --- a/docs/offering_methods.rst +++ b/docs/offering_methods.rst @@ -129,7 +129,7 @@ The constructor of the ``ServerServiceInstance`` class requires several paramete - endpoint_ip: The IP address of the network interface on which the service is offered (127.0.0.1 in this example) - endpoint_port: The port on which the service is offered (3000 in this example) - ttl: The time-to-live for the service discovery entries (5 seconds in this example) -- cyclic_offer_delay_ms: The period of the cylic offer service SD messages (2000 ms in this example) +- cyclic_offer_delay_ms: The period of the cyclic offer service SD messages (2000 ms in this example) .. code-block:: python @@ -155,11 +155,11 @@ Step 6: Announce the Service via Service Discovery await service_instance_addition.start_offer() Step 7: Shutdown the Application ----------------------------- +--------------------------------- At the end of your application, make sure to stop offering the service instance and disconnect from the someipy daemon to ensure a clean shutdown of the application. .. code-block:: python await service_instance_temperature.stop_offer() - await someipy_daemon.disconnect_from_daemon() \ No newline at end of file + await someipy_daemon.disconnect_from_daemon() diff --git a/docs/service_discovery.rst b/docs/service_discovery.rst index be2039b..0d1e9fd 100644 --- a/docs/service_discovery.rst +++ b/docs/service_discovery.rst @@ -4,51 +4,48 @@ Service Discovery Configuration Parameters What is SOME/IP Service Discovery (SD)? --------------------------------------- -If a client needs to use a SOME/IP service, e.g., to call a method of a service, it needs to know the location of the service, i.e., on which IP address and on which port the service can be accessed. Vice versa, a server providing a service has to know to which receivers event messages have to be sent. Events only have to be sent to receivers that subscribed to the event beforehand (publish-subscribe pattern). +To use a SOME/IP service, a client must know the service location—the IP address and port where the service can be accessed. A server providing a service must know the recipients of event messages. Events are delivered only to subscribers (publish-subscribe). -SOME/IP service discovery primarily offers exactly these two explained features: +SOME/IP SD provides these capabilities: -- **Locating service instances** -- **Handling the publish-subscribe behavior** +- Locating service instances +- Handling publish-subscribe behavior -SOME/IP service discovery consists of specific messages that are sent via UDP to a multicast group. By joining this specific multicast group, all applications are able to listen to service discovery messages or send service discovery messages to all participants by themselves. - -The following image shows the difference between usual SOME/IP messages (right-hand side) and the SOME/IP service discovery messages sent via IP multicast. +SD uses UDP multicast. By joining the multicast group, applications can listen to or send SD messages. .. image:: images/service_discovery/multicast_service_discovery.png :align: center -The application offering the service instance cyclically sends a SOME/IP SD message with a so-called *offer entry* to all other participants in the multicast group. The *offer entry* contains the service instance location (IP address and port), the instance ID, service ID, major and minor version, and a time-to-live (TTL) of the offer. Participants that want to subscribe to the eventgroup of this offered service instance can send back a SOME/IP SD *subscribe eventgroup entry*. The subscribe eventgroup entry includes the service instance ID, the service ID, the major version, the eventgroup ID, the lifetime of the subscription (time-to-live), and most importantly the endpoint (IP address and port) of the client. The client will receive the SOME/IP events on this endpoint from the server. The server offering the service will answer this with a message containing a *subscribe acknowledge* entry. By using this type of handshake, the publish-subscribe pattern is implemented in SOME/IP. On the right-hand side of the image, you can see that the actual SOME/IP messages containing events are only delivered to two subscribers. +The offering application periodically sends a SOME/IP SD message containing an offer entry to all multicast participants. The offer entry includes the service location (IP and port), instance ID, service ID, major and minor versions, and a TTL for the offer. Subscribers can send back a SOME/IP SD subscribe entry for the eventgroup, including the instance ID, service ID, major version, eventgroup ID, the subscription TTL, and the client endpoint (IP and port). The server responds with a subscribe_acknowledge entry. This handshake implements publish-subscribe. The actual event messages are delivered to the subscribers. -Another way of locating a service is to send a *find service* entry to actively ask if another participant in the network offers a specific service. If a participant offers the requested service, it will answer with an *offer service* entry and the subscription happens as described above afterwards. Participants can also send *stop offer* entries to indicate to all participants that the service is shutdown. +An alternative is to send a find_service entry to query whether a participant offers a specific service. If offered, the participant responds with an offer_service entry, after which subscription proceeds as above. Participants can also send stop_offer entries to indicate shutdown. -Service discovery messages are actually SOME/IP messages, but filled with specific magic numbers in order to mark the messages as service discovery messages. E.g. service discovery messages have the message ID set to 0xFFFF81000 and protocol and interface version set to 0x01. +SD messages are SOME/IP messages marked with specific magic numbers. For example, the message ID is 0xFFFF81000 and the protocol and interface version are 0x01. -The specification of the SOME/IP service discovery protocol can be found `here `_. +The SOME/IP SD protocol specification is available `here `_. Service Discovery Parameters in the someipy Daemon ------------------------------------------------------- -As shown in the :doc:`someipy Daemon guide `, the daemon is responsible for handling all communication with the SOME/IP network, including service discovery and message sending/receiving. The daemon uses a specific configuration for the service discovery messages, which can be set in the daemon's configuration file. +---------------------------------------------------- +As shown in the someipy Daemon guide, the daemon handles all communication with the SOME/IP network, including service discovery, and uses a specific configuration for SD messages. -The configuration file is a JSON file that contains the following optional parameters: +The configuration is a JSON file with optional parameters: .. code-block:: json { - "sd_address": "224.224.224.245", - "sd_port": 30490, - "interface": "127.0.0.1", - } - + "sd_address": "224.224.224.245", + "sd_port": 30490, + "interface": "127.0.0.1", + } -- ``sd_address``: The IP address of the multicast group used for SOME/IP service discovery. The default value is "224.224.224.245". -- ``sd_port``: The port used for SOME/IP service discovery. The default value is 30490. -- ``interface``: The IP address of the network interface on which the daemon listens for service discovery messages. The default value is "127.0.0.1". +- ``sd_address``: Multicast address for SD. Default: "224.224.224.245". +- ``sd_port``: SD port. Default: 30490. +- ``interface``: Network interface IP the daemon listens on. Default: "127.0.0.1". Server-Side Parameters ---------------------- -For offering a service instance, a ``ServerServiceInstance`` is used in someipy. The constructor of this class allows to specify two service discovery related parameters: +Offering a service uses ``ServerServiceInstance``. The constructor accepts SD-related parameters: .. code-block:: python @@ -62,16 +59,15 @@ For offering a service instance, a ``ServerServiceInstance`` is used in someipy. cyclic_offer_delay_ms=2000, ) -- ``ttl``: The lifetime of the SD offer entries in seconds. The offer entries are sent cyclically in SD messages by the application offering a service. The offer entries are only valid for ``ttl`` seconds. -- ``cyclic_offer_delay_ms``: The period in milliseconds with which the cyclic SD offer entries are sent. This is the delay between two consecutive service discovery messages that contain the offer entries. - -Make sure that the ``ttl``is configured larger than the ``cyclic_offer_delay_ms``. If the ``ttl`` is smaller than the ``cyclic_offer_delay_ms``, your service appears to be not available for a while. E.g., if ``ttl`` is configured as one second and ``cyclic_offer_delay_ms`` with 2000ms, the service will not be available for a second until the next cyclic offer entry is sent. +- ``ttl``: Lifetime of the SD offer entries in seconds. The offer entries are repeated while valid. +- ``cyclic_offer_delay_ms``: Interval in milliseconds between consecutive SD offer messages. +Ensure ``ttl`` > ``cyclic_offer_delay_ms``; otherwise the service may appear unavailable briefly (for example, ttl=1 s and cyclic_offer_delay_ms=2000 ms). Client-Side Parameters ---------------------- -When subscribing to eventgroups as a client, the lifetime of the subscription has to be specified. This is done by the ``ttl_subscription_seconds`` parameter of the ``subscribe_eventgroup`` function of the ``ClientServiceInstance`` class. In case the subscription is not renewed, the subscription will be removed after this time. Ensure that the ``ttl_subscription_seconds`` is larger than the ``cyclic_offer_delay_ms`` of the server offering the service. If the ``ttl_subscription_seconds`` is smaller than the ``cyclic_offer_delay_ms``, the subscription will be removed before the next cyclic offer entry is sent, and the client will not receive any events. +When subscribing to eventgroups as a client, the subscription lifetime is set with ``ttl_subscription_seconds`` in ``subscribe_eventgroup``. If not renewed, the subscription will be removed after this period. Ensure ``ttl_subscription_seconds`` > ``cyclic_offer_delay_ms``; otherwise, the subscription may be removed before the next offer and events will not be delivered. .. code-block:: python diff --git a/docs/service_interface.rst b/docs/service_interface.rst index f3b2f8a..5afc1bc 100644 --- a/docs/service_interface.rst +++ b/docs/service_interface.rst @@ -1,29 +1,32 @@ SOME/IP Service Interface Data Types ==================================== -A major task in setting up a service-oriented communication between ECUs via SOME/IP (and also in other middleware) is to define a common service interface datatype definition, so that all participants know how to interpret and parse received raw bytes into a meaningful data structure. Vice versa participants need to know how to serialize their data structures into bytes to be transmitted to other participants. +Defining a common SOME/IP service interface datatype is essential for service-oriented communication between ECUs (and other middleware). It ensures all participants interpret raw bytes consistently and can serialize data back for transmission. -The `SOME/IP protocol specification `_ defines the on-wire format of SOME/IP. The specification describes a header structure and also how different data types are serialized on-wire. As a programmer using someipy, you don't need to take care of the on-wire format. someipy provides data types as Python classes ready to use and functions to serialize a Python object describing a data structure into a Python ``bytes()`` object and deserializing a received ``bytes()`` object into a Python object. +The `SOME/IP protocol specification `_ defines the on-wire format. You do not need to manage it directly; someipy provides Python types and serializers to convert between Python objects and bytes. SOME/IP Interface Datatype Definition in Python ----------------------------------------------- -Many middlewares and their stacks like `ROS (Robot Operating System) `_ or `OpenDDS `_ use an interface description language independent of the programming language to define the data structures. E.g. ROS uses ROS message (.msg) files and OpenDDS uses IDL (.idl) files. Out of these separate interface files source code is generated containing the actual data types and (de)serializers to be used in Python or C++. - -With someipy you do not need an extra code generator tool. Service interface data types can be directly written in Python, either in the same file as your application or in separate Python files which are imported in your application. It's recommended to use a separate Python file for each datatype defined. +Many middlewares, such as `ROS (Robot Operating System) `_ or `OpenDDS `_, use language-agnostic interface descriptions to define data structures. ROS uses .msg files; OpenDDS uses .idl files. Source code is typically generated from these definitions. With someipy, data types can be defined directly in Python, either in the application file or in separate modules imported by the application. Prefer a separate file per datatype. Defining Datatypes in someipy ----------------------------- -As described above, custom SOME/IP data types can be directly defined and used in Python. All datatype-related classes and functions can be imported from the ``someipy.serialization`` module. If you want to use an unsigned 8-bit datatype use: +Custom SOME/IP data types can be defined directly in Python. All datatype-related classes and functions can be imported from the ``someipy.serialization`` module. To use an unsigned 8-bit datatype: .. code-block:: python from someipy.serialization import Uint8 -All supported data types are listed in the table :ref:`at the end of the article `. +All supported data types are listed in the table at the end of this article. + +If you need a single value, you can use the basic datatypes directly (e.g., Uint8). For most scenarios, you will define structured types (dataclasses) by subclassing SomeIpPayload. + +Defining Datatypes using SomeIpPayload +------------------------------------- -If you want to send only a single value like an 8-bit datatype, you could directly use the basic datatypes like ``Uint8``. However, in most real world use-cases you want to use structured data types (struct). For that purpose, someipy provides the ``SomeIpPayload`` class. By creating a class and inheriting from ``SomeIpPayload`` you can easily define your own structured type. The following example defines a ``TemperatureMsg`` to demonstrate the approach. +As described above, you can define custom SOME/IP data types in Python. All datatype-related classes and functions reside in the someipy.serialization module. To define a structured type, subclass SomeIpPayload. For example: .. code-block:: python @@ -35,7 +38,7 @@ If you want to send only a single value like an 8-bit datatype, you could direct ) @dataclass - class TemparatureMsg(SomeIpPayload): + class TemperatureMsg(SomeIpPayload): timestamp: Uint64 temperature: Float32 @@ -43,31 +46,28 @@ If you want to send only a single value like an 8-bit datatype, you could direct self.timestamp = Uint64() self.temperature = Float32() -First, the needed classes are imported. We also import ``dataclass``. By using the ``@dataclass`` decorator, the ``__repr__`` and a ``__eq__`` are automatically generated so that we can easily print or log an object and compare two different ``TemperatureMsg`` objects using the equality operator ``==``. +Note: Using @dataclass provides auto-generated __repr__ and __eq__. -The class ``TemperatureMsg`` derives from ``SomeIpPayload``. Deriving from ``SomeIpPayload`` allows us to send the message via SOME/IP since the base class introduces the two important methods: +The TemperatureMsg class can be serialized and deserialized with the base class methods: -- ``def serialize(self) -> bytes`` -- ``def deserialize(self, payload: bytes) -> T`` +- serialize(self) -> bytes +- deserialize(self, payload: bytes) -> TemperatureMsg -``serialize`` is used when data shall be sent, e.g., sending SOME/IP events or calling a method and passing parameters. Here is an example for sending events: +Serialize is used to send data (e.g., events or method calls). Example: .. code-block:: python # Create a new instance of TemperatureMsg and fill some dummy values - tmp_msg = TemparatureMsg() + tmp_msg = TemperatureMsg() tmp_msg.timestamp = Uint64(10) tmp_msg.temperature = Float32(20.0) - # serialize returns a bytes object... payload = tmp_msg.serialize() - # .. than can be used in the send_event method of the service instance - # service_instance_temperature was initialised beforehand service_instance_temperature.send_event( SAMPLE_EVENTGROUP_ID, SAMPLE_EVENT_ID, payload ) -``deserialize`` is to be used whenever you receive a ``bytes`` object that shall be interpreted as the desired data structure ``TemperatureMsg``. Here is an example of a SOME/IP event callback. A ``SomeIpMessage`` is passed into the callback function by someipy. The ``SomeIpMessage`` consists of a ``header`` containing metadata like the instance ID and the sender and the ``payload`` which is a ``bytes`` object. +Deserialize is used when receiving a bytes payload to interpret as TemperatureMsg. Example callback: .. code-block:: python @@ -83,28 +83,27 @@ The class ``TemperatureMsg`` derives from ``SomeIpPayload``. Deriving from ``Som None: This function does not return anything. """ try: - print(f"Received {len(someip_message.payload)} bytes. Try to deserialize...") + print(f"Received {len(someip_message.payload)} bytes. Deserializing...") temperature_msg = TemperatureMsg().deserialize(someip_message.payload) print(temperature_msg) except Exception as e: print(f"Error in deserialization: {e}") -Notice that the ``deserialize`` call is packed into a try-except block in case malicious or wrong data was sent and received leading to a crash of the application. This increases the robustness of your application. +Note: The ``deserialize`` call is wrapped in a try-except block to prevent crashes from malformed data. This improves robustness. Updating Values --------------- -In the example above, the ``timestamp`` and ``temperature`` fields have been filled with the values ``10`` and ``20.0``. However, the numeric literals are not directly assigned like ``tmp_msg.timestamp = 10``. This shall never be done. The data types in someipy like ``Uint64`` support SOME/IP serialization while usual numeric literals like ``10`` do not. This would lead to an exception when calling the ``serialize`` method afterwards. +In the example above, the timestamp and temperature fields are initialized with Uint64(10) and Float32(20.0). Do not assign plain integers directly (e.g., tmp_msg.timestamp = 10); these literals are not compatible with the typed fields. Use the typed constructors and then serialize. .. code-block:: python - # Create a new instance of TemperatureMsg and fill some dummy values - tmp_msg = TemparatureMsg() + tmp_msg = TemperatureMsg() tmp_msg.timestamp = Uint64(10) tmp_msg.temperature = Float32(20.0) + payload = tmp_msg.serialize() -Another possibility to update the fields is to access the ``value`` field directly: - +Directly modifying the value field is also supported: .. code-block:: python tmp_msg.timestamp.value = 10 @@ -112,9 +111,9 @@ Another possibility to update the fields is to access the ``value`` field direct When accessing ``value``, the numeric literal can be directly assigned. Nesting Structured Data Types ------------------------------- +----------------------------- -someipy also allows nesting structured data types that derive from ``SomeIpPayload``. We will add another struct ``Version`` containing a ``major`` and ``minor`` version field: +You can nest structured types that derive from SomeIpPayload. For example, define a Version datatype and nest it inside TemperatureMsg: .. code-block:: python @@ -127,10 +126,6 @@ someipy also allows nesting structured data types that derive from ``SomeIpPaylo self.major = Uint8() self.minor = Uint8() -The ``Version`` class can now be nested into ``TemperatureMsg``: - -.. code-block:: python - @dataclass class TemperatureMsg(SomeIpPayload): version: Version @@ -153,8 +148,8 @@ The following table lists all supported SOME/IP data types and their respective For inquiries about support for additional data types, contact us at: -| someipy.package@gmail.com -| `LinkedIn `_ +- someipy.package@gmail.com +- `LinkedIn `_ .. list-table:: SOME/IP Data Types :header-rows: 1 diff --git a/example_apps/call_method_tcp.py b/example_apps/call_method_tcp.py index df9c94f..5e32db5 100644 --- a/example_apps/call_method_tcp.py +++ b/example_apps/call_method_tcp.py @@ -7,11 +7,10 @@ MessageType, ReturnCode, connect_to_someipy_daemon, -) -from someipy.client_service_instance import ( ClientServiceInstance, + Method, + ServiceBuilder, ) -from someipy.service import Method, ServiceBuilder from someipy.someipy_logging import set_someipy_log_level from addition_method_parameters import Addends, Sum diff --git a/example_apps/call_method_udp.py b/example_apps/call_method_udp.py index 8831440..195f9ce 100644 --- a/example_apps/call_method_udp.py +++ b/example_apps/call_method_udp.py @@ -7,11 +7,10 @@ MessageType, ReturnCode, connect_to_someipy_daemon, -) -from someipy.client_service_instance import ( ClientServiceInstance, + Method, + ServiceBuilder, ) -from someipy.service import Method, ServiceBuilder from someipy.someipy_logging import set_someipy_log_level from addition_method_parameters import Addends, Sum diff --git a/example_apps/offer_method_tcp.py b/example_apps/offer_method_tcp.py index e3fc815..83821cc 100644 --- a/example_apps/offer_method_tcp.py +++ b/example_apps/offer_method_tcp.py @@ -4,10 +4,16 @@ import sys from typing import Tuple -from someipy import TransportLayerProtocol, MethodResult, ReturnCode, MessageType -from someipy import connect_to_someipy_daemon -from someipy.server_service_instance import ServerServiceInstance -from someipy.service import ServiceBuilder, Method +from someipy import ( + TransportLayerProtocol, + MethodResult, + ReturnCode, + MessageType, + ServerServiceInstance, + connect_to_someipy_daemon, + ServiceBuilder, + Method, +) from someipy.someipy_logging import set_someipy_log_level from someipy.serialization import Sint32 from addition_method_parameters import Addends, Sum diff --git a/example_apps/offer_method_udp.py b/example_apps/offer_method_udp.py index 0b1ca5f..d01fe0e 100644 --- a/example_apps/offer_method_udp.py +++ b/example_apps/offer_method_udp.py @@ -4,10 +4,16 @@ import sys from typing import Tuple -from someipy import TransportLayerProtocol, MethodResult, ReturnCode, MessageType -from someipy import connect_to_someipy_daemon -from someipy.server_service_instance import ServerServiceInstance -from someipy.service import ServiceBuilder, Method +from someipy import ( + TransportLayerProtocol, + MethodResult, + ReturnCode, + MessageType, + connect_to_someipy_daemon, + ServerServiceInstance, + ServiceBuilder, + Method, +) from someipy.someipy_logging import set_someipy_log_level from someipy.serialization import Sint32 from addition_method_parameters import Addends, Sum diff --git a/example_apps/offer_multiple_services.py b/example_apps/offer_multiple_services.py index 31421c0..6387d0c 100644 --- a/example_apps/offer_multiple_services.py +++ b/example_apps/offer_multiple_services.py @@ -1,5 +1,4 @@ import asyncio -import ipaddress import logging import sys @@ -7,10 +6,10 @@ TransportLayerProtocol, ServiceBuilder, EventGroup, + connect_to_someipy_daemon, + ServerServiceInstance, + Event, ) -from someipy._internal.someipy_daemon_client import connect_to_someipy_daemon -from someipy.server_service_instance import ServerServiceInstance -from someipy.service import Event from someipy.someipy_logging import set_someipy_log_level from someipy.serialization import Uint8, Uint64, Float32 from temperature_msg import TemparatureMsg diff --git a/example_apps/receive_events_tcp.py b/example_apps/receive_events_tcp.py index a1414cf..d4c670f 100644 --- a/example_apps/receive_events_tcp.py +++ b/example_apps/receive_events_tcp.py @@ -2,12 +2,15 @@ import logging import sys -from someipy import ServiceBuilder, EventGroup, SomeIpMessage, TransportLayerProtocol -from someipy import connect_to_someipy_daemon -from someipy.client_service_instance import ( +from someipy import ( + ServiceBuilder, + EventGroup, + SomeIpMessage, + TransportLayerProtocol, ClientServiceInstance, + connect_to_someipy_daemon, + Event, ) -from someipy.service import Event from someipy.someipy_logging import set_someipy_log_level from temperature_msg import TemparatureMsg diff --git a/example_apps/receive_events_udp.py b/example_apps/receive_events_udp.py index 5607954..7c133ad 100644 --- a/example_apps/receive_events_udp.py +++ b/example_apps/receive_events_udp.py @@ -2,12 +2,14 @@ import logging import sys -from someipy import ServiceBuilder, EventGroup, SomeIpMessage, TransportLayerProtocol -from someipy import connect_to_someipy_daemon -from someipy.client_service_instance import ( +from someipy import ( + ServiceBuilder, + EventGroup, + TransportLayerProtocol, + connect_to_someipy_daemon, ClientServiceInstance, + Event, ) -from someipy.service import Event from someipy.someipy_logging import set_someipy_log_level from temperature_msg import TemparatureMsg diff --git a/example_apps/send_events_tcp.py b/example_apps/send_events_tcp.py index 54d300f..1ac0674 100644 --- a/example_apps/send_events_tcp.py +++ b/example_apps/send_events_tcp.py @@ -7,9 +7,9 @@ ServiceBuilder, EventGroup, connect_to_someipy_daemon, + ServerServiceInstance, + Event, ) -from someipy.server_service_instance import ServerServiceInstance -from someipy.service import Event from someipy.someipy_logging import set_someipy_log_level from someipy.serialization import Uint8, Uint64, Float32 from temperature_msg import TemparatureMsg diff --git a/example_apps/send_events_udp.py b/example_apps/send_events_udp.py index 76705a9..c2b6212 100644 --- a/example_apps/send_events_udp.py +++ b/example_apps/send_events_udp.py @@ -7,9 +7,9 @@ ServiceBuilder, EventGroup, connect_to_someipy_daemon, + ServerServiceInstance, + Event, ) -from someipy.server_service_instance import ServerServiceInstance -from someipy.service import Event from someipy.someipy_logging import set_someipy_log_level from someipy.serialization import Uint8, Uint64, Float32 from temperature_msg import TemparatureMsg diff --git a/setup.cfg b/setup.cfg index d78da38..23a72f7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,4 +24,8 @@ packages = find: python_requires = >=3.8 [options.packages.find] -where = src \ No newline at end of file +where = src + +[options.entry_points] +console_scripts = + someipyd = someipy.someipyd:main \ No newline at end of file diff --git a/src/someipy/__init__.py b/src/someipy/__init__.py index 2e42fd6..846ba37 100644 --- a/src/someipy/__init__.py +++ b/src/someipy/__init__.py @@ -1,11 +1,7 @@ from ._internal.transport_layer_protocol import TransportLayerProtocol # noqa: F401 -from .service import Service, ServiceBuilder, EventGroup # noqa: F401 +from .service import Service, ServiceBuilder, EventGroup, Method, Event # noqa: F401 from .server_service_instance import ServerServiceInstance # noqa: F401 -from .client_service_instance import ( - ClientServiceInstance, - MethodResult, -) # noqa: F401 - +from .client_service_instance import ClientServiceInstance # noqa: F401 from ._internal.someip_message import SomeIpMessage # noqa: F401 from ._internal.method_result import MethodResult # noqa: F401 from ._internal.return_codes import ReturnCode # noqa: F401 diff --git a/src/someipy/_internal/method_result.py b/src/someipy/_internal/method_result.py index 44741af..324e24b 100644 --- a/src/someipy/_internal/method_result.py +++ b/src/someipy/_internal/method_result.py @@ -18,6 +18,8 @@ class MethodResult: + """Class representing the result of a method call.""" + message_type: MessageType return_code: ReturnCode payload: bytes diff --git a/src/someipy/_internal/someipy_daemon_client.py b/src/someipy/_internal/someipy_daemon_client.py index 87e9afa..c25194c 100644 --- a/src/someipy/_internal/someipy_daemon_client.py +++ b/src/someipy/_internal/someipy_daemon_client.py @@ -45,6 +45,21 @@ async def connect_to_someipy_daemon(config: dict = None): + """Connect to the someipy daemon. + + Parameters + ---------- + config : dict, optional + Configuration dictionary. The following keys are supported: + + - socket_path: Path to the Unix domain socket. Default is /tmp/someipyd.sock + + Returns + ------- + SomeIpDaemonClient + An instance of the SomeIpDaemonClient class. + """ + daemon_client = SomeIpDaemonClient(config) await daemon_client._connect_to_daemon() return daemon_client @@ -264,7 +279,7 @@ async def _handle_message(self, message: BaseMessage): method = service_instance.service.methods.get(method_id, None) if method: get_logger(_logger_name).debug( - f"Calling method {method_id} on service {service_id}" + f"Calling method handler for {method_id:04x} on service {service_id:04x}" ) # Call the method handler, eventually call it in a separate task # to avoid blocking the event loop diff --git a/src/someipy/client_service_instance.py b/src/someipy/client_service_instance.py index 242a9e5..92169eb 100644 --- a/src/someipy/client_service_instance.py +++ b/src/someipy/client_service_instance.py @@ -62,6 +62,10 @@ def __hash__(self): class ClientServiceInstance(ClientInstanceInterface): + """Client-side representation of a SOME/IP service instance. + + This class provides methods to call SOME/IP methods and to subscribe to SOME/IP events. + """ def __init__( self, @@ -72,6 +76,23 @@ def __init__( endpoint_port: int, client_id: int = 0, ): + """Initialize a ClientServiceInstance. + + Parameters + ---------- + daemon : SomeIpDaemonClient + Daemon client used to communicate with the someipy daemon. + service : Service + Service configuration for this instance. + instance_id : int + Instance ID for this service instance. + endpoint_ip : str + IP address of the endpoint on which the service instance is reachable (method responses or reception of events). + endpoint_port : int + Port of the endpoint on which the service instance is reachable. + client_id : int, optional + Client ID used for this client service instance. Default: 0. + """ self._daemon: SomeIpDaemonClient = daemon self._service: Service = service self._instance_id: int = instance_id @@ -90,26 +111,28 @@ def __init__( @property def service(self) -> Service: + """Return the associated Service object for this client instance.""" return self._service @property def instance_id(self) -> int: + """Return the instance ID for this client service instance.""" return self._instance_id @property def endpoint(self) -> Tuple[str, int]: + """Return the (IP, port) endpoint for this client instance.""" return (self._endpoint_ip, self._endpoint_port) def register_callback(self, callback: Callable[[int, bytes], None]) -> None: - """ - Register a callback function to be called when a SOME/IP event is received. + """Register a callback for received SOME/IP events. - Args: - callback (Callable[[SomeIpMessage], None]): The callback function to be registered. - This function should take a SomeIpMessage object as its only argument and return None. + The registered callback will be invoked with (event_id, payload) when an event is received. - Returns: - None + Parameters + ---------- + callback : Callable[[int, bytes], None] + Callback function invoked when an event is received. """ self._event_callback = callback diff --git a/src/someipy/server_service_instance.py b/src/someipy/server_service_instance.py index 00ba1da..ed64eb1 100644 --- a/src/someipy/server_service_instance.py +++ b/src/someipy/server_service_instance.py @@ -26,6 +26,29 @@ class ServerServiceInstance(ServerInstanceInterface): + """Server-side representation of a service instance used for offering SOME/IP services. + + This class manages the lifecycle of offering a service instance via + the Some/IP-SD mechanism and enables sending events to subscribed + clients. + + Parameters + ---------- + daemon : SomeIpDaemonClient + Daemon client used to communicate with the someipy daemon. + service : Service + Service configuration for this instance. + instance_id : int + Instance ID for this service instance. + endpoint_ip : str + IP address of the endpoint on which the service instance is reachable. + endpoint_port : int + Port of the endpoint on which the service instance is reachable. + ttl : int, optional + TTL used for Service Discovery offers. Default: 0. + cyclic_offer_delay_ms : int, optional + Delay between cyclic offers in milliseconds. Default: 2000. + """ def __init__( self, @@ -37,6 +60,25 @@ def __init__( ttl: int = 0, # TTL used for SD Offer entries cyclic_offer_delay_ms=2000, ): + """Initialize a ServerServiceInstance. + + Parameters + ---------- + daemon : SomeIpDaemonClient + Daemon client used to communicate with the someipy daemon. + service : Service + Service configuration for this instance. + instance_id : int + Instance ID for this service instance. + endpoint_ip : str + IP address of the endpoint on which the service instance is reachable. + endpoint_port : int + Port of the endpoint on which the service instance is reachable. + ttl : int, optional + TTL used for Service Discovery offers. Default: 0. + cyclic_offer_delay_ms : int, optional + Delay between cyclic offers in milliseconds. Default: 2000. + """ self._daemon = daemon self._service = service self._instance_id = instance_id @@ -48,6 +90,16 @@ def __init__( self._daemon._server_service_instances.append(self) async def start_offer(self): + """Start offering the service instance via the service discovery offer entries. + + This method gathers the current service methods and event groups + and invokes the daemon to advertise the service with the + configured endpoint and version information. + + Returns + ------- + None + """ methods: List[Method] = self._service.methods.values() eventgroups: List[EventGroup] = self._service.eventgroups.values() @@ -65,6 +117,14 @@ async def start_offer(self): ) async def stop_offer(self): + """Stop offering the service instance and send out a stop offer entry. + + This stops advertising the service instance to clients via the daemon. + + Returns + ------- + None + """ methods: List[Method] = self._service.methods.values() eventgroups: List[EventGroup] = self._service.eventgroups.values() @@ -82,6 +142,31 @@ async def stop_offer(self): ) def send_event(self, eventgroup_id: int, event_id: int, payload: bytes): + """Send an event to clients subscribed to the event. + + This method validates the specified event group and event within the + service definition, encodes the payload in Base64, constructs a + UDS message and dispatches it through the daemon. + + Parameters + ---------- + eventgroup_id : int + ID of the event group the event belongs to. + event_id : int + ID of the event to send. + payload : bytes + Payload of the event to transmit to clients. + + Returns + ------- + None + + Raises + ------ + ValueError + If event group ID or event ID is not found within the service. + + """ if not eventgroup_id in self._service.eventgroupids: raise ValueError( f"Event group ID {eventgroup_id} not found in service 0x{self._service.id:04X}" @@ -119,16 +204,44 @@ def send_event(self, eventgroup_id: int, event_id: int, payload: bytes): @property def service(self) -> Service: + """Service associated with this server service instance. + + Returns + ------- + Service + The service configuration that this instance exposes. + """ return self._service @property def instance_id(self) -> int: + """Instance ID of this server service instance. + + Returns + ------- + int + The unique identifier for this service instance. + """ return self._instance_id @property def endpoint_ip(self) -> str: + """IP address of the endpoint this instance offers services on. + + Returns + ------- + str + Endpoint IP address. + """ return self._endpoint_ip @property def endpoint_port(self) -> int: + """Port of the endpoint this instance offers services on. + + Returns + ------- + int + Endpoint port number. + """ return self._endpoint_port diff --git a/src/someipy/service.py b/src/someipy/service.py index d94d304..94beade 100644 --- a/src/someipy/service.py +++ b/src/someipy/service.py @@ -29,6 +29,15 @@ class Method: """ Class representing a SOME/IP method with a method id and a method handler. + + Parameters + ---------- + id : int + Method identifier. + protocol : TransportLayerProtocol + Transport protocol for the method. + method_handler : MethodHandler, optional + Optional method which is called on server side when an offered service is invoked. The handler shall return a MethodResult. """ id: int @@ -41,14 +50,44 @@ def __init__( protocol: TransportLayerProtocol, method_handler: MethodHandler = None, ): + """Initialize a Method. + + Parameters + ---------- + id : int + Method identifier. + protocol : TransportLayerProtocol + Transport protocol for the method. + method_handler : MethodHandler, optional + Optional method which is called on server side when an offered service is invoked. The handler shall return a MethodResult. + """ self.id = id self.protocol = protocol self.method_handler = method_handler def __eq__(self, __value: object) -> bool: + """Check equality with another Method based on id and protocol. + + Parameters + ---------- + __value : object + Another Method instance. + + Returns + ------- + bool + True if ids and protocols match, False otherwise. + """ return self.id == __value.id and self.protocol == __value.protocol def to_json(self) -> str: + """Serialize this Method to a JSON string. + + Returns + ------- + str + JSON representation of the method. + """ as_dict = { "id": self.id, "protocol": self.protocol.value, @@ -57,6 +96,18 @@ def to_json(self) -> str: @classmethod def from_json(cls: _T, json_str: str) -> _T: + """Create a Method from a JSON string. + + Parameters + ---------- + json_str : str + JSON representation of a Method. + + Returns + ------- + Method + The constructed Method instance. + """ json_dict = json.loads(json_str) id = int(json_dict["id"]) @@ -76,6 +127,13 @@ class Event: protocol: TransportLayerProtocol def to_json(self) -> str: + """Serialize this Event to JSON. + + Returns + ------- + str + JSON representation of the event. + """ as_dict = { "id": self.id, "protocol": self.protocol.value, @@ -84,6 +142,17 @@ def to_json(self) -> str: @classmethod def from_json(cls: _T, json_str: str) -> _T: + """Create an Event from a JSON string. + + Parameters + ---------- + json_str : str + JSON representation of an Event. + + Returns + ------- + Event + """ json_dict = json.loads(json_str) o = cls(int(json_dict["id"]), TransportLayerProtocol(json_dict["protocol"])) return o @@ -99,6 +168,13 @@ class EventGroup: events: List[Event] def to_json(self) -> str: + """Serialize this EventGroup to JSON. + + Returns + ------- + str + JSON representation of the event group. + """ as_dict = { "id": self.id, "events": [event.to_json() for event in self.events], @@ -107,6 +183,17 @@ def to_json(self) -> str: @classmethod def from_json(cls: _T, json_str: str) -> _T: + """Create an EventGroup from a JSON string. + + Parameters + ---------- + json_str : str + JSON representation of an EventGroup. + + Returns + ------- + EventGroup + """ json_dict = json.loads( json_str ) # Contains a dictionary with "id" (int) and "events" (serialized Event) @@ -116,6 +203,7 @@ def from_json(cls: _T, json_str: str) -> _T: return o def __hash__(self): + """Return a hash based on the group id and contained event ids.""" return hash((self.id, tuple(event.id for event in self.events))) @property @@ -123,7 +211,10 @@ def has_udp(self) -> bool: """ Checks if the event group contains any events with UDP protocol. - :return: True if at least one event uses UDP, False otherwise. + Returns + ------- + bool + True if at least one event uses UDP, False otherwise. """ return any( event.protocol == TransportLayerProtocol.UDP for event in self.events @@ -134,7 +225,10 @@ def has_tcp(self) -> bool: """ Checks if the event group contains any events with TCP protocol. - :return: True if at least one event uses TCP, False otherwise. + Returns + ------- + bool + True if at least one event uses TCP, False otherwise. """ return any( event.protocol == TransportLayerProtocol.TCP for event in self.events @@ -145,6 +239,19 @@ def has_tcp(self) -> bool: class Service: """ Class representing a SOME/IP service. A service has an id, major and minor version and 0 or more methods and/or eventgroups. + + Attributes + ---------- + id : int + Service identifier. + major_version : int + Major version of the service. + minor_version : int + Minor version of the service. + methods : Dict[int, Method] + Mapping of method IDs to Method instances. + eventgroups : Dict[int, EventGroup] + Mapping of event group IDs to EventGroup instances. """ id: int @@ -155,6 +262,7 @@ class Service: eventgroups: Dict[int, EventGroup] def __init__(self): + """Initialize a new Service instance with defaults.""" self.id = 0 self.major_version = 1 self.minor_version = 0 @@ -166,7 +274,10 @@ def eventgroupids(self) -> List[int]: """ Returns a list of event group IDs associated with the service. - :return: A list of integers representing the event group IDs. + Returns + ------- + List[int] + List of event group IDs. """ return list(self.eventgroups.keys()) @@ -175,7 +286,10 @@ def events(self) -> List[Event]: """ Returns a list of events associated with the service. - :return: A list of Event objects. + Returns + ------- + List[Event] + All events across event groups for this service. """ return [ event @@ -188,31 +302,35 @@ def methodids(self) -> List[int]: """ Returns a list of method IDs associated with the object. - :return: A list of integers representing the method IDs. - :rtype: List[int] + Returns + ------- + List[int] + List of method IDs. """ return list(self.methods.keys()) class ServiceBuilder: """ - Class used to build a Service using a fluent API. Call the "with_" methods to add methods and event groups to the service. Call the build method to create the service. + Class used to build a Service using a fluent API. Call the 'with' methods to add methods and event groups to the service. Call the build method to create the service. """ def __init__(self): - """ - Initializes a new ServiceBuilder instance. - """ + """Initializes a new ServiceBuilder instance.""" self._service = Service() def with_service_id(self, id: int) -> "ServiceBuilder": """ Sets the service id for the service to be built. - :param id: The ID of the service. - :type id: int - :return: The ServiceBuilder instance. - :rtype: ServiceBuilder + Parameters + ---------- + id : int + The ID of the service. + + Returns + ------- + ServiceBuilder """ self._service.id = id return self @@ -221,10 +339,14 @@ def with_major_version(self, major_version: int) -> "ServiceBuilder": """ Sets the major version for the service to be built. - :param major_version: The major version of the service. - :type major_version: int - :return: The ServiceBuilder instance. - :rtype: ServiceBuilder + Parameters + ---------- + major_version : int + The major version of the service. + + Returns + ------- + ServiceBuilder """ self._service.major_version = major_version return self @@ -233,10 +355,14 @@ def with_minor_version(self, minor_version: int) -> "ServiceBuilder": """ Sets the minor version for the service to be built. - :param minor_version: The minor version of the service. - :type minor_version: int - :return: The ServiceBuilder instance. - :rtype: ServiceBuilder + Parameters + ---------- + minor_version : int + The minor version of the service. + + Returns + ------- + ServiceBuilder """ self._service.minor_version = minor_version return self @@ -245,10 +371,14 @@ def with_method(self, method: Method) -> "ServiceBuilder": """ Adds a method to the service to be built. - :param method: The method to be added. - :type method: Method - :return: The ServiceBuilder instance. - :rtype: ServiceBuilder + Parameters + ---------- + method : Method + The method to be added. + + Returns + ------- + ServiceBuilder """ if self._service.methods.get(method.id) is None: self._service.methods[method.id] = method @@ -258,20 +388,25 @@ def with_eventgroup(self, eventgroup: EventGroup) -> "ServiceBuilder": """ Adds an event group to the service to be built. - :param eventgroup: The event group to be added. - :type eventgroup: EventGroup - :return: The ServiceBuilder instance. - :rtype: ServiceBuilder + Parameters + ---------- + eventgroup : EventGroup + The event group to be added. + + Returns + ------- + ServiceBuilder """ if self._service.eventgroups.get(eventgroup.id) is None: self._service.eventgroups[eventgroup.id] = eventgroup return self def build(self) -> Service: - """ - Builds and returns a Service instance. + """Builds and returns a Service. - :return: The built Service instance. - :rtype: Service + Returns + ------- + Service + The built Service. """ return self._service diff --git a/src/someipy/someipy_logging.py b/src/someipy/someipy_logging.py index bfef7c4..612a664 100644 --- a/src/someipy/someipy_logging.py +++ b/src/someipy/someipy_logging.py @@ -22,9 +22,14 @@ def set_someipy_log_level(logging_level: int): """ Set the log level for the someipy module. - Args: + Parameters + ---------- logging_level (int): The log level to set. Must be one of the constants defined in the logging module, such as logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, or logging.CRITICAL. + + Returns + ------- + None """ global _log_level _log_level = logging_level @@ -34,7 +39,12 @@ def get_someipy_log_level() -> int: """ Get the current log level for the someipy library. - Returns: + Parameters + ---------- + int: The current log level. + + Returns + ------- int: The current log level. """ global _log_level diff --git a/src/someipy/someipyd.py b/src/someipy/someipyd.py index e48eba6..27ee852 100644 --- a/src/someipy/someipyd.py +++ b/src/someipy/someipyd.py @@ -1362,8 +1362,8 @@ def _handle_send_event_request(self, message: SendEventRequest, writer_id: int): and requested_subscription.major_version == message["major_version"] ): self.logger.debug( - f"Found internal subscription for service {message['service_id']}, " - f"instance {message['instance_id']}, eventgroup {message['eventgroup_id']}" + f"Internal subscription for service {message['service_id']:04x}, " + f"instance {message['instance_id']:04x}, eventgroup {message['eventgroup_id']:04x}" ) event_msg = create_uds_message(