-
Notifications
You must be signed in to change notification settings - Fork 605
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
Add support for multiple Cyclic Messages in Task #610
Add support for multiple Cyclic Messages in Task #610
Conversation
can/broadcastmanager.py
Outdated
self.message = message | ||
self.can_id = message.arbitration_id | ||
self.arbitration_id = message.arbitration_id | ||
if not isinstance(messages, list): |
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.
TBH I'm not really a fan of keeping backwards compatibility and supporting both a List
and a Message
type. It seems kludgy to muddle the API like that. Plus, a single Message can still be supported by a list of length 1...
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.
No one has really requested this feature for years and neither one of IXXAT, Kvaser, NI-CAN, or Systec interfaces which provide cyclic message transmissions have seen the need for this and won't support it. Therefore I believe it is not justified to force everyone to change their implementations for the very few people that need this. At the very least we must have a deprecation period to allow implementations to adjust. A library that changes its API in backwards incompatible ways too often are not very popular among developers relying on it for critical applications.
Maybe it only has to be converted to a list at one place. Maybe in the send_periodic()
method?
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.
I appreciate how you've (mostly) kept backwards compatibility at the user facing API level - I'm strongly in favor of that.
I agree that it feels kludgy to support both List[Message]
and Message
throughout the stack and would prefer to try limit that to bus.send_periodic()
. The Task
returned will still have to adhere to the current api - e.g. ModifiableCyclicTaskABC.modify_data
must continue to take a single message (possibly a List[Message]
as well).
Codecov Report
@@ Coverage Diff @@
## develop #610 +/- ##
===========================================
- Coverage 63.65% 63.34% -0.31%
===========================================
Files 63 63
Lines 5544 5582 +38
===========================================
+ Hits 3529 3536 +7
- Misses 2015 2046 +31 |
Codecov Report
@@ Coverage Diff @@
## develop #610 +/- ##
===========================================
- Coverage 63.14% 60.37% -2.77%
===========================================
Files 66 66
Lines 5904 5908 +4
===========================================
- Hits 3728 3567 -161
- Misses 2176 2341 +165 |
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.
Perhaps you could refactor out some of the repeated checks to a utility function.
can/broadcastmanager.py
Outdated
|
||
# Take the Arbitration ID of the first element | ||
self.can_id = messages[0].arbitration_id | ||
self.arbitration_id = messages[0].arbitration_id |
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.
Could we perhaps remove one of these now?
can/broadcastmanager.py
Outdated
while not self.stopped: | ||
# Prevent calling bus.send from multiple threads | ||
with self.lock: | ||
started = time.time() | ||
try: | ||
self.bus.send(self.message) | ||
self.bus.send(self.messages[msg_index]) | ||
msg_index = (msg_index + 1) % len(self.messages) |
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.
You could move the msg_index increment outside the lock.
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.
Maybe I'm missing something, but why is this lock even necessary? What is this lock intended to protect?
From what I understand, there is a 1:1
mapping between cyclic tasks and threads, so _run
is essentially running in its own context for each thread. As such, the thread can get preempted, but nothing should be modifying its context state while it is context switched out.
According to git blame
, you were the one that added this locking, so I thought I'd take the opportunity to ask you 😂
If this lock is actually protecting against other threads poking into the context, then I believe the increment still needs to be protected by the lock, since Python doesn't provide atomics.
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.
I think the lock prevents Bus.send(...)
from being called concurrently, and not the update of msg_index
. This is required since Bus.send(...)
is not guaranteed to be thread safe.
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.
Ah, thanks!
can/interfaces/ixxat/canlib.py
Outdated
def __init__(self, scheduler, msgs, period, duration, resolution): | ||
super().__init__(msgs, period, duration) | ||
# Only supports 1 Message in the group? | ||
if len(self.messages) == 1: |
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.
I would probably just do a check in the beginning instead.
if len(self.messages) != 1:
raise ValueError("IXXAT Interface only supports 1 element")
can/interfaces/ixxat/canlib.py
Outdated
self._index = ctypes.c_uint32() | ||
_canlib.canSchedulerAddMessage(self._scheduler, self._msg, self._index) | ||
_canlib.canSchedulerStartMessage(self._scheduler, self._index, self._count) | ||
if len(self.messages) == 1: |
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.
Don't think we need to check self.messages again since it should not have changed.
body = bytearray() | ||
for message in messages: | ||
self.can_id_with_flags = _add_flags_to_can_id(message) | ||
self.flags = CAN_FD_FRAME if message.is_fd else 0 |
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.
These two can be set outside the loop to the value of the first message.
ival1 = 0 | ||
ival2 = self.period | ||
body += build_can_frame(message) | ||
log.debug("Sending BCM command") |
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.
Move outside loop.
) | ||
for message in messages: | ||
self.can_id_with_flags = _add_flags_to_can_id(message) | ||
self.flags = CAN_FD_FRAME if message.is_fd else 0 |
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.
Move outside loop using first entry.
self.can_id_with_flags = _add_flags_to_can_id(message) | ||
self.flags = CAN_FD_FRAME if message.is_fd else 0 | ||
body += build_can_frame(message) | ||
log.debug("Sending BCM command") |
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.
Move outside loop.
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.
Overall I think this is a good addition.
We should improve the docs and add a couple of tests similar to SimpleCyclicSendTaskTest
before merging.
can/bus.py
Outdated
@@ -174,7 +174,7 @@ def send_periodic(self, msg, period, duration=None, store_task=True): | |||
- :meth:`BusABC.stop_all_periodic_tasks()` is called | |||
- the task's :meth:`CyclicTask.stop()` method is called. | |||
|
|||
:param can.Message msg: | |||
:param List[can.Message] msgs: |
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.
This should be something like Union[List[can.Message], can.Message]
, the docstring should be updated too.
can/interfaces/ixxat/canlib.py
Outdated
def __init__(self, scheduler, msgs, period, duration, resolution): | ||
super().__init__(msgs, period, duration) | ||
if len(self.messages) != 1: | ||
raise ValueError("IXXAT Interface only supports 1 element") |
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.
raise ValueError("IXXAT Interface only supports 1 element") | |
raise ValueError("IXXAT Interface only supports periodic sending of 1 element") |
can/broadcastmanager.py
Outdated
self.message = message | ||
self.can_id = message.arbitration_id | ||
self.arbitration_id = message.arbitration_id | ||
if not isinstance(messages, list): |
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.
I appreciate how you've (mostly) kept backwards compatibility at the user facing API level - I'm strongly in favor of that.
I agree that it feels kludgy to support both List[Message]
and Message
throughout the stack and would prefer to try limit that to bus.send_periodic()
. The Task
returned will still have to adhere to the current api - e.g. ModifiableCyclicTaskABC.modify_data
must continue to take a single message (possibly a List[Message]
as well).
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.
Sorry, I don't have time for a full review but am okay with changing the API to use lists.
46fd898
to
21eec39
Compare
Note: The number of new cyclic messages to be sent must be equal to the | ||
original number of messages originally specified for this task. | ||
|
||
:param Union[List[can.Message], tuple(can.Message), can.Message] messages: |
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.
nit: I'd do this with typing.Sequence: Union[Sequence[can.Message], can.Message]
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.
Or typing.Iterable instead of Sequence
?
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.
@felixdivo correct me if I'm wrong, but I don't think you can get the length of an Iterable
without actually performing the iteration and storing the result.
If want to support an Iterable
, do we want to deal with buffering potentially unbounded input? Or implement specific bounded iterators for each interface that only iterates over the maximum number of elements the interface task can support, and errors otherwise?
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.
Ah I see. We could wrap it in a list if it's not already a sequence. Implementing something like that ourselves seems overkill. If you want, stay with what you have or a sequence.
can/broadcastmanager.py
Outdated
|
||
return messages | ||
|
||
def _check_modified_messages(self, messages): |
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.
Not sure this is the write location for this method since it's only ever use by ModifiableCyclicTaskABC.modify_data
. Maybe it should be in the ModifiableCyclicTaskABC
class or nested inside ModifiableCyclicTaskABC.modify_data
.
900ccca
to
b12ea74
Compare
can/broadcastmanager.py
Outdated
""" | ||
self.message = message | ||
messages = self._check_and_convert_messages(messages) | ||
messages = self._check_modified_messages(messages) |
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.
Minor, but self._check_modified_messages(messages)
do not make any modification to messages, so you do not really need to return messages
b12ea74
to
f4f2bc7
Compare
f4f2bc7
to
3ff4476
Compare
3ff4476
to
4f45fd5
Compare
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.
Haven’t checked in detail but seems good.
4f45fd5
to
7671687
Compare
This adds support for multiple Cyclic Messages in a Cyclic Task. The default implementation is also changed to provide support for this, by iterating over the list of Cyclic Messages. In order to maintain backwards compatibility, the periodic APIs now take a CAN Message as before, in addition to a Sequence of CAN Messages. The SocketCAN interface takes advantage of the Linux BCM APIs to do so, while the IXXAT interface maintains its original behaviour. This also introduces a new example that illustrates how to use Cyclic Messages, backed by the SocketCAN interface. We previously tracked the can_id and arbitration_id class members due to the ongoing deprecation of the can_id Message attribute. Now that can_id is replaced by arbitration_id, we no longer need this in CyclicSendTaskABC either. As such, this removes the deprecated can_id member from the Cyclic Task. Fixes hardbyte#606
7671687
to
c5cff12
Compare
This adds support for multiple Cyclic Messages in a Cyclic Task. The
default implementation is also changed to provide support for this, by
iterating over the list of Cyclic Messages.
The SocketCAN interface takes advantage of the Linux BCM APIs to do so,
while the IXXAT interface maintains its original behaviour.
This also introduces a new example that illustrates how to use Cyclic
Messages, backed by the SocketCAN interface.
Fixes #606