Skip to content

Commit

Permalink
Improve method calls and method error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
chrizog committed Oct 22, 2024
1 parent 8cdd260 commit ec96e56
Show file tree
Hide file tree
Showing 15 changed files with 239 additions and 141 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pip3 install someipy

## Example Applications

In the directory [example_apps](./example_apps/), examples including explanations, can be found for using the someipy library. In [temperature_msg.py](./example_apps/temperature_msg.py), a payload interface "TemperatureMsg" is defined, which can be serialized and deserialized. In [send_events.py](./example_apps/send_events.py), the service discovery and two services are instantiated. The "TemperatureMsg" is serialized and used as the payload for sending events.
In the directory [example_apps](./example_apps/), examples including explanations, can be found for using the someipy library.

## Supported Features, Limitations and Deviations

Expand All @@ -64,7 +64,6 @@ The library is still under development. The current major limitations and deviat
- Configuration and load balancing options in SOME/IP SD messages are not supported.
- TTL of Service Discovery entries is not checked yet.
- The Initial Wait Phase and Repetition Phase of the Service Discovery specification are skipped. For simplification, the Main Phase is directly entered, i.e. SD Offer Entries are immediately sent cyclically.
- Multiple Service Discovery entries are not packed together in a single SD message, which is sent via UDP.

### De-/Serialization

Expand Down
58 changes: 34 additions & 24 deletions example_apps/call_method_tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
import logging
import sys

from someipy import TransportLayerProtocol
from someipy import TransportLayerProtocol, ReturnCode, MessageType
from someipy.client_service_instance import (
MethodResult,
construct_client_service_instance,
)
from someipy.service import ServiceBuilder
Expand Down Expand Up @@ -65,31 +64,42 @@ async def main():

try:
while True:

method_parameter = Addends(addend1=1, addend2=2)

# The call method function returns a tuple with the first element being a MethodResult enum
method_success, method_result = await client_instance_addition.call_method(
SAMPLE_METHOD_ID, method_parameter.serialize()
)
# Check the result of the method call and handle it accordingly
if method_success == MethodResult.SUCCESS:
print(
f"Received result for method: {' '.join(f'0x{b:02x}' for b in method_result)}"
try:
# You can query if the service offering the method was already found via SOME/IP service discovery
print(f"Service found: {client_instance_addition.service_found()}")

while not client_instance_addition.service_found():
print("Waiting for service..")
await asyncio.sleep(0.5)

# The call_method function can raise an error, e.g. if no TCP connection to the server can be established
# In case there is an application specific error in the server, the server still returns a response and the
# message_type and return_code are evaluated.
method_result = await client_instance_addition.call_method(
SAMPLE_METHOD_ID, method_parameter.serialize()
)
try:
sum = Sum().deserialize(method_result)
print(f"Sum: {sum.value.value}")
except Exception as e:
print(f"Error during deserialization of method's result: {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)

if method_result.message_type == MessageType.RESPONSE:
print(
f"Received result for method: {' '.join(f'0x{b:02x}' for b in method_result.payload)}"
)
if method_result.return_code == ReturnCode.E_OK:
sum = Sum().deserialize(method_result.payload)
print(f"Sum: {sum.value.value}")
else:
print(
f"Method call returned an error: {method_result.return_code}"
)
elif method_result.message_type == MessageType.ERROR:
print("Server returned an error..")
# In case the server includes an error message in the payload, it can be deserialized and printed

except Exception as e:
print(f"Error during method call: {e}")

await asyncio.sleep(1)

# When the application is canceled by the user, the asyncio.CancelledError is raised
except asyncio.CancelledError:
Expand Down
54 changes: 34 additions & 20 deletions example_apps/call_method_udp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import logging
import sys

from someipy import TransportLayerProtocol
from someipy import TransportLayerProtocol, MessageType, ReturnCode
from someipy.client_service_instance import (
MethodResult,
construct_client_service_instance,
Expand Down Expand Up @@ -36,8 +36,6 @@ async def main():
interface_ip = sys.argv[i + 1]
break

print(interface_ip)

# 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
Expand Down Expand Up @@ -69,26 +67,42 @@ async def main():
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:
# You can query if the service offering the method was already found via SOME/IP service discovery
print(f"Service found: {client_instance_addition.service_found()}")

while not client_instance_addition.service_found():
print("Waiting for service..")
await asyncio.sleep(0.5)

# The call_method function can raise an error, e.g. if no TCP connection to the server can be established
# In case there is an application specific error in the server, the server still returns a response and the
# message_type and return_code are evaluated.
method_result = await client_instance_addition.call_method(
SAMPLE_METHOD_ID, method_parameter.serialize()
)
try:
sum = Sum().deserialize(method_result)
print(f"Sum: {sum.value.value}")
except Exception as e:
print(f"Error during deserialization of method's result: {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..")

if method_result.message_type == MessageType.RESPONSE:
print(
f"Received result for method: {' '.join(f'0x{b:02x}' for b in method_result.payload)}"
)
if method_result.return_code == ReturnCode.E_OK:
sum = Sum().deserialize(method_result.payload)
print(f"Sum: {sum.value.value}")
else:
print(
f"Method call returned an error: {method_result.return_code}"
)
elif method_result.message_type == MessageType.ERROR:
print("Server returned an error..")
# In case the server includes an error message in the payload, it can be deserialized and printed

except Exception as e:
print(f"Error during method call: {e}")

await asyncio.sleep(1)

except asyncio.CancelledError:
print("Shutdown..")
finally:
Expand Down
23 changes: 14 additions & 9 deletions example_apps/offer_method_tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import sys
from typing import Tuple

from someipy import TransportLayerProtocol
from someipy import TransportLayerProtocol, MethodResult, MessageType, ReturnCode
from someipy.service import ServiceBuilder, Method
from someipy.service_discovery import construct_service_discovery
from someipy.server_service_instance import construct_server_service_instance
Expand All @@ -21,29 +21,34 @@
SAMPLE_METHOD_ID = 0x0123


def add_method_handler(input_data: bytes, addr: Tuple[str, int]) -> Tuple[bool, bytes]:
# Process the data and return True/False indicating the success of the operation
# and the result of the method call in serialized form (bytes object)
# If False is returned an error message will be sent back to the client. In that case
# the payload can be an empty bytes-object, e.g. return False, b""

def add_method_handler(input_data: bytes, addr: Tuple[str, int]) -> MethodResult:
print(
f"Received data: {' '.join(f'0x{b:02x}' for b in input_data)} from IP: {addr[0]} Port: {addr[1]}"
)

result = MethodResult()

try:
# Deserialize the input data
addends = Addends()
addends.deserialize(input_data)
except Exception as e:
print(f"Error during deserialization: {e}")
return False, b""

# Set the return code to E_MALFORMED_MESSAGE and return
result.message_type = MessageType.RESPONSE
result.return_code = ReturnCode.E_MALFORMED_MESSAGE
return result

# Perform the addition
sum = Sum()
sum.value = Sint32(addends.addend1.value + addends.addend2.value)
print(f"Send back: {' '.join(f'0x{b:02x}' for b in sum.serialize())}")
return True, sum.serialize()

result.message_type = MessageType.RESPONSE
result.return_code = ReturnCode.E_OK
result.payload = sum.serialize()
return result


async def main():
Expand Down
23 changes: 14 additions & 9 deletions example_apps/offer_method_udp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import sys
from typing import Tuple

from someipy import TransportLayerProtocol
from someipy import TransportLayerProtocol, MethodResult, ReturnCode, MessageType
from someipy.service import ServiceBuilder, Method
from someipy.service_discovery import construct_service_discovery
from someipy.server_service_instance import construct_server_service_instance
Expand All @@ -21,29 +21,34 @@
SAMPLE_METHOD_ID = 0x0123


def add_method_handler(input_data: bytes, addr: Tuple[str, int]) -> Tuple[bool, bytes]:
# Process the data and return True/False indicating the success of the operation
# and the result of the method call in serialized form (bytes object)
# If False is returned an error message will be sent back to the client. In that case
# the payload can be an empty bytes-object, e.g. return False, b""

def add_method_handler(input_data: bytes, addr: Tuple[str, int]) -> MethodResult:
print(
f"Received data: {' '.join(f'0x{b:02x}' for b in input_data)} from IP: {addr[0]} Port: {addr[1]}"
)

result = MethodResult()

try:
# Deserialize the input data
addends = Addends()
addends.deserialize(input_data)
except Exception as e:
print(f"Error during deserialization: {e}")
return False, b""

# Set the return code to E_MALFORMED_MESSAGE and return
result.message_type = MessageType.RESPONSE
result.return_code = ReturnCode.E_MALFORMED_MESSAGE
return result

# Perform the addition
sum = Sum()
sum.value = Sint32(addends.addend1.value + addends.addend2.value)
print(f"Send back: {' '.join(f'0x{b:02x}' for b in sum.serialize())}")
return True, sum.serialize()

result.message_type = MessageType.RESPONSE
result.return_code = ReturnCode.E_OK
result.payload = sum.serialize()
return result


async def main():
Expand Down
5 changes: 3 additions & 2 deletions example_apps/receive_events_udp.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ def temperature_callback(someip_message: SomeIpMessage) -> None:
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 for event {someip_message.header.method_id}. Try to deserialize.."
)
temperature_msg = TemparatureMsg().deserialize(someip_message.payload)
print(temperature_msg)
except Exception as e:
Expand Down Expand Up @@ -69,7 +71,6 @@ async def main():
ServiceBuilder()
.with_service_id(SAMPLE_SERVICE_ID)
.with_major_version(1)
.with_eventgroup(temperature_eventgroup)
.build()
)

Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = someipy
version = 0.0.7
version = 0.0.8
author = Christian H.
author_email = [email protected]
description = A Python package implementing the SOME/IP protocol
Expand Down
3 changes: 3 additions & 0 deletions src/someipy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@
) # 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
from ._internal.message_types import MessageType # noqa: F401
25 changes: 6 additions & 19 deletions src/someipy/_internal/message_types.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
# Copyright (C) 2024 Christian H.
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from enum import Enum


class MessageType(Enum):
REQUEST = 0x00
REQUEST_NO_RETURN = 0x01
Expand All @@ -24,19 +25,5 @@ class MessageType(Enum):
TP_REQUEST = 0x20
TP_REQUEST_NO_RETURN = 0x21
TP_NOTIFICATION = 0x22
TP_RESPONSE = 0xa0
TP_ERROR = 0xa1

# PRS_SOMEIP_00191
class ReturnCode(Enum):
E_OK = 0x00 # No error occurred
E_NOT_OK = 0x01 # An unspecified error occurred
E_UNKNOWN_SERVICE = 0x02 # The requested Service ID is unknown.
E_UNKNOWN_METHOD = 0x03 # The requested Method ID is unknown.
E_NOT_READY = 0x04 # Service ID and Method ID are known. Application not running.
E_NOT_REACHABLE = 0x05 # System running the service is not reachable (inter-nal error code only).
E_TIMEOUT = 0x06 # A timeout occurred (internal error code only).
E_WRONG_PROTOCOL_VERSION = 0x07 # Version of SOME/IP protocol not supported
E_WRONG_INTERFACE_VERSION = 0x08 # Interface version mismatch
E_MALFORMED_MESSAGE = 0x09 # Deserialization error, so that payload cannot be de-serialized.
E_WRONG_MESSAGE_TYPE = 0x0a # An unexpected message type was received (e.g. REQUEST_NO_RETURN for a method defined as REQUEST).
TP_RESPONSE = 0xA0
TP_ERROR = 0xA1
13 changes: 13 additions & 0 deletions src/someipy/_internal/method_result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from .message_types import MessageType
from .return_codes import ReturnCode


class MethodResult:
message_type: MessageType
return_code: ReturnCode
payload: bytes

def __init__(self):
self.message_type = MessageType.RESPONSE
self.return_code = ReturnCode.E_OK
self.payload = b""
Loading

0 comments on commit ec96e56

Please sign in to comment.