Skip to content

Commit

Permalink
Improve docstrings for logging and serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
chrizog committed May 27, 2024
1 parent 63f228b commit fedae37
Show file tree
Hide file tree
Showing 9 changed files with 772 additions and 177 deletions.
87 changes: 0 additions & 87 deletions example_apps/offer_method.py

This file was deleted.

1 change: 1 addition & 0 deletions example_apps/send_events_tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from someipy.serialization import Uint8, Uint64, Float32
from temperature_msg import TemparatureMsg


SD_MULTICAST_GROUP = "224.224.224.245"
SD_PORT = 30490
INTERFACE_IP = "127.0.0.1"
Expand Down
29 changes: 19 additions & 10 deletions example_apps/temperature_msg.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
from someipy.serialization import *
from dataclasses import dataclass
from someipy.serialization import (
SomeIpPayload,
SomeIpFixedSizeArray,
Uint8,
Uint64,

Float32,
)

# With someipy it's possible to either send and receive payloads unserialized simply as bytes-objects
# You can also define the payloads structure directly as Python classes and serialize your Python object
# With someipy it's possible to either send and receive payloads unserialized simply as bytes-objects. This is useful
# when you don't care about the content, e.g. reading binary data and simply sending it.
# However, you can also define the SOME/IP payload structure directly as Python classes and serialize your Python object
# to a bytes-payload which can be sent. You can also deserialize a received bytes-object into your defined
# Python object structure.

# In this example we'll define a "temperature message" that consists of another SOME/IP struct and of a fixed size
# In this example we define a "temperature message" that consists of another SOME/IP struct and of a fixed size
# SOME/IP array


@dataclass
class Version(SomeIpPayload):
major: Uint8
Expand All @@ -26,8 +36,8 @@ def __init__(self):
@dataclass
class TemparatureMsg(SomeIpPayload):
# Always define payloads with the @dataclass decorator. This leads to the __eq__ being
# generated which makes it easy to compare the content of two messages
# For defining a payload struct just derive from the SomeIpPayload class. This will ensure
# generated which makes it easy to compare the content of two messages.
# For defining a payload struct simply derive from the SomeIpPayload class. This will ensure
# the Python object can be serialized and deserialized and supports e.g. len() calls which
# will return the length of the payload in bytes

Expand All @@ -47,9 +57,8 @@ def __init__(self):

# Simple example how to instantiate a payload, change values, serialize and deserialize
if __name__ == "__main__":

tmp_msg = TemparatureMsg()

tmp_msg.version.major = Uint8(2)
tmp_msg.version.minor = Uint8(0)
# Reminder: Do NOT use "tmp_msg.version.major = 2". Always use the provided classes by someipy like Uint8,
Expand All @@ -69,8 +78,8 @@ def __init__(self):
output = tmp_msg.serialize()
print(output.hex())

# Create a new TemperatureMsg from the serialized bytes
# Create a new TemperatureMsg from the serialized bytes
tmp_msg_again = TemparatureMsg().deserialize(output)
print(tmp_msg_again)

assert(tmp_msg_again == tmp_msg)
assert tmp_msg_again == tmp_msg
69 changes: 55 additions & 14 deletions src/someipy/client_service_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ def __init__(
self._tcp_task = None

def register_callback(self, callback: Callable[[SomeIpMessage], None]) -> None:
"""
Register a callback function to be called when a SOME/IP event is received.
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.
Returns:
None
"""
self._callback = callback

def someip_message_received(
Expand All @@ -88,13 +98,40 @@ def someip_message_received(
self._callback(someip_message)

def subscribe_eventgroup(self, eventgroup_id: int):
"""
Adds an event group to the list of event groups to subscribe to.
Args:
eventgroup_id (int): The ID of the event group to subscribe to.
Returns:
None
Raises:
None
Notes:
- If the event group ID is already in the subscription list, a debug log message is printed.
"""
if eventgroup_id in self._eventgroups_to_subscribe:
get_logger(_logger_name).debug(
f"Eventgroup ID {eventgroup_id} is already in subscription list."
)
self._eventgroups_to_subscribe.add(eventgroup_id)

def stop_subscribe_eventgroup(self, eventgroup_id: int):
"""
Stops subscribing to an event group. Not implemented yet.
Args:
eventgroup_id (int): The ID of the event group to stop subscribing to.
Raises:
NotImplementedError: This method is not yet implemented.
Notes:
- This method is currently not implemented and raises a `NotImplementedError`.
"""
# TODO: Implement StopSubscribe
raise NotImplementedError

Expand Down Expand Up @@ -167,8 +204,7 @@ class State(Enum):

expected_bytes = 8 # 2x 32-bit for header
header_data = bytes()
data: bytes = bytes()
count = 0
data: bytes = bytes()
get_logger(_logger_name).debug(f"Start TCP read on port {src_port}")

while self._tcp_connection.is_open():
Expand All @@ -178,10 +214,6 @@ class State(Enum):
new_data = await asyncio.wait_for(self._tcp_connection.reader.read(8), 3.0)
data += new_data
service_id, method_id, length = struct.unpack(">HHI", data[0:8])

count += 1
# print(f"{count} Received {len(data)} bytes: Service ID: 0x{service_id:02x} Method ID: 0x{method_id:02x} Length: {length}")

header_data = data[0:8]

# The length bytes also covers 8 bytes header data without payload
Expand All @@ -194,19 +226,12 @@ class State(Enum):
new_data = await asyncio.wait_for(self._tcp_connection.reader.read(expected_bytes), 3.0)
data += new_data

# print(f"Received {len(data)} bytes from expected {expected_bytes}")

header_data = header_data + data[0:8]
payload_data = data[8:]

message_data = header_data + payload_data
# hex_representation = ' '.join(f'0x{byte:02x}' for byte in message_data)
# print(hex_representation)
someip_header = SomeIpHeader.from_buffer(buf=message_data)
# print(str(someip_header))
payload_data = get_payload_from_someip_message(someip_header, message_data)
# hex_representation = ' '.join(f'0x{byte:02x}' for byte in payload_data)
# print(hex_representation)

if self._callback is not None:
self._callback(SomeIpMessage(someip_header, payload_data))
Expand All @@ -225,7 +250,6 @@ class State(Enum):
except Exception as e:
get_logger(_logger_name).error(f"Exception in setup_tcp_connection: {e}")
finally:
# 3. If the connection is closed, try to reconnect at beginning of loop (1)
await self._tcp_connection.close()

# Sleep for a while before reconnect
Expand Down Expand Up @@ -265,6 +289,23 @@ async def construct_client_service_instance(
sd_sender=None,
protocol=TransportLayerProtocol.UDP,
) -> ClientServiceInstance:
"""
Asynchronously constructs a ClientServerInstance. Based on the given transport protocol, proper endpoints are setup before constructing the actual ServerServiceInstance.
Args:
service (Service): The service associated with the instance.
instance_id (int): The ID of the instance.
endpoint (EndpointType): The endpoint of the client instance containing IP address and port.
ttl (int, optional): The time-to-live for the instance used for service discovery subscribe entries. A value of 0 means that subscriptions are valid for infinite time.
sd_sender (Any, optional): The service discovery sender.
protocol (TransportLayerProtocol, optional): The transport layer protocol for the instance. Defaults to TransportLayerProtocol.UDP.
Returns:
ClientServerInstance: The constructed ClientServerInstance.
Raises:
None
"""
if protocol == TransportLayerProtocol.UDP:
loop = asyncio.get_running_loop()
rcv_socket = create_udp_socket(str(endpoint[0]), endpoint[1])
Expand Down
19 changes: 17 additions & 2 deletions src/someipy/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,25 @@

_log_level = logging.DEBUG

def set_someipy_log_level(log_level: int):

def set_someipy_log_level(logging_level: int):
"""
Set the log level for the someipy module.
Args:
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.
"""
global _log_level
_log_level = log_level
_log_level = logging_level


def get_someipy_log_level() -> int:
"""
Get the current log level for the someipy library.
Returns:
int: The current log level.
"""
global _log_level
return _log_level
Loading

0 comments on commit fedae37

Please sign in to comment.