-
Notifications
You must be signed in to change notification settings - Fork 1
feat: add J1939Message for subscriber callbacks with destination address #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,21 +6,9 @@ | |
| logging.getLogger('j1939').setLevel(logging.DEBUG) | ||
| logging.getLogger('can').setLevel(logging.DEBUG) | ||
|
|
||
| def on_message(priority, pgn, sa, timestamp, data): | ||
| """Receive incoming messages from the bus | ||
|
|
||
| :param int priority: | ||
| Priority of the message | ||
| :param int pgn: | ||
| Parameter Group Number of the message | ||
| :param int sa: | ||
| Source Address of the message | ||
| :param int timestamp: | ||
| Timestamp of the message | ||
| :param bytearray data: | ||
| Data of the PDU | ||
| """ | ||
| print(f"PGN {hex(pgn)} length {len(data)}") | ||
| def on_message(msg: j1939.J1939Message): | ||
| """Receive incoming messages from the bus""" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should probably still document the param here |
||
| print(f"PGN {hex(msg.pgn)} to {hex(msg.dest_address)} length {len(msg.data)}") | ||
|
|
||
| def main(): | ||
| print("Initializing") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import heapq | ||
| import inspect | ||
| import logging | ||
| import queue | ||
| import threading | ||
|
|
@@ -13,6 +14,7 @@ | |
| from .controller_application import ControllerApplication | ||
| from .j1939_21 import J1939_21 | ||
| from .j1939_22 import J1939_22 | ||
| from .message import J1939Message | ||
| from .message_id import FrameFormat | ||
| from .parameter_group_number import ParameterGroupNumber | ||
|
|
||
|
|
@@ -324,11 +326,44 @@ def disconnect(self): | |
| self._bus_created = False | ||
| self._bus = None | ||
|
|
||
| @staticmethod | ||
| def _callback_takes_message(callback): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think for this one it may be worth documenting that it returns a bool, and that it's true if it does take the new single arg format. Since it's not the most straight forward on it's evaluation. |
||
| """Detect whether ``callback`` expects the new single-argument | ||
| ``J1939Message`` calling convention rather than the legacy | ||
| ``(priority, pgn, sa, timestamp, data)`` positional arguments. | ||
|
|
||
| A callback is treated as new-style if it accepts exactly one | ||
| positional parameter (besides ``self`` for bound methods), or if its | ||
| signature cannot be inspected (e.g. some C-implemented callables) — | ||
| in which case it falls back to the legacy calling convention. | ||
| """ | ||
| try: | ||
| sig = inspect.signature(callback) | ||
| except (TypeError, ValueError): | ||
| return False | ||
| positional = [ | ||
| p | ||
| for p in sig.parameters.values() | ||
| if p.kind | ||
| in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD) | ||
| ] | ||
| has_var_positional = any( | ||
| p.kind == inspect.Parameter.VAR_POSITIONAL for p in sig.parameters.values() | ||
| ) | ||
| if has_var_positional: | ||
| return False | ||
| return len(positional) == 1 | ||
|
|
||
| def subscribe(self, callback, device_address=None, owner=None): | ||
| """Add the given callback to the message notification stream. | ||
|
|
||
| :param callback: | ||
| Function to call when message is received. | ||
| Function to call when message is received. Either the legacy | ||
| 5-argument form ``callback(priority, pgn, sa, timestamp, data)`` | ||
| or the new single-argument form ``callback(msg: J1939Message)``, | ||
| which also carries the destination address. The calling | ||
| convention is detected once from the callback's signature at | ||
| subscribe time. | ||
| :param int device_address: | ||
| Device address of the application. | ||
| This is a simple way for peer-to-peer reception without adding a controller-application. | ||
|
|
@@ -345,7 +380,12 @@ def subscribe(self, callback, device_address=None, owner=None): | |
| """ | ||
| with self._subscribers_lock: | ||
| self._subscribers.append( | ||
| {"cb": callback, "dev_adr": device_address, "owner": owner} | ||
| { | ||
| "cb": callback, | ||
| "dev_adr": device_address, | ||
| "owner": owner, | ||
| "takes_message": self._callback_takes_message(callback), | ||
| } | ||
| ) | ||
|
|
||
| def unsubscribe(self, callback, owner=None): | ||
|
|
@@ -728,7 +768,10 @@ def _notify_subscribers(self, priority, pgn, sa, dest, timestamp, data): | |
| or (callable(dic["dev_adr"]) and dic["dev_adr"](dest)) | ||
| or (dest == dic["dev_adr"]) | ||
| ): | ||
| dic["cb"](priority, pgn, sa, timestamp, data) | ||
| if dic["takes_message"]: | ||
| dic["cb"](J1939Message(priority, pgn, sa, timestamp, data, dest)) | ||
| else: | ||
| dic["cb"](priority, pgn, sa, timestamp, data) | ||
|
|
||
| def _is_message_acceptable(self, dest): | ||
| # Ownership / active-participation check only: does a subscriber own this | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| from typing import NamedTuple | ||
|
|
||
|
|
||
| class J1939Message(NamedTuple): | ||
| """A received J1939 message, passed to single-argument subscriber callbacks. | ||
|
|
||
| Also unpacks positionally like the legacy 5-tuple | ||
| ``(priority, pgn, source_address, timestamp, data)`` for callbacks that | ||
| don't need ``dest_address``, since it's appended last. | ||
|
|
||
| :ivar int priority: | ||
| Priority of the message. | ||
| :ivar int pgn: | ||
| Parameter Group Number of the message. | ||
| :ivar int source_address: | ||
| Source address of the message. | ||
| :ivar int timestamp: | ||
| Timestamp of the CAN message. | ||
| :ivar bytearray data: | ||
| Data of the PDU. | ||
| :ivar int dest_address: | ||
| Destination address of the message. ``ParameterGroupNumber.Address.GLOBAL`` | ||
| for broadcast (PDU2) messages. | ||
| """ | ||
|
|
||
| priority: int | ||
| pgn: int | ||
| source_address: int | ||
|
Comment on lines
+26
to
+28
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be better to have the arbitration id here. If we care about the other fields we can get them from the Arbitration Id. This J1939Message would then comprise of: arbitration_id, timestamp, data, and dest_address |
||
| timestamp: int | ||
| data: bytearray | ||
| dest_address: int | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should probably also document the param here still