Skip to content

Commit 3ad6c3f

Browse files
authored
Merge branch 'develop' into develop
2 parents b2e1d75 + c7c1640 commit 3ad6c3f

7 files changed

Lines changed: 927 additions & 101 deletions

File tree

can/broadcastmanager.py

Lines changed: 98 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import threading
1313
import time
1414

15+
import can
16+
1517
log = logging.getLogger("can.bcm")
1618

1719

@@ -34,28 +36,65 @@ class CyclicSendTaskABC(CyclicTask):
3436
Message send task with defined period
3537
"""
3638

37-
def __init__(self, message, period):
39+
def __init__(self, messages, period):
3840
"""
39-
:param can.Message message: The message to be sent periodically.
40-
:param float period: The rate in seconds at which to send the message.
41+
:param Union[Sequence[can.Message], can.Message] messages:
42+
The messages to be sent periodically.
43+
:param float period: The rate in seconds at which to send the messages.
4144
"""
42-
self.message = message
43-
self.can_id = message.arbitration_id
44-
self.arbitration_id = message.arbitration_id
45+
messages = self._check_and_convert_messages(messages)
46+
47+
# Take the Arbitration ID of the first element
48+
self.arbitration_id = messages[0].arbitration_id
4549
self.period = period
46-
super().__init__()
50+
self.messages = messages
51+
52+
@staticmethod
53+
def _check_and_convert_messages(messages):
54+
"""Helper function to convert a Message or Sequence of messages into a
55+
tuple, and raises an error when the given value is invalid.
56+
57+
Performs error checking to ensure that all Messages have the same
58+
arbitration ID and channel.
59+
60+
Should be called when the cyclic task is initialized
61+
"""
62+
if not isinstance(messages, (list, tuple)):
63+
if isinstance(messages, can.Message):
64+
messages = [messages]
65+
else:
66+
raise ValueError("Must be either a list, tuple, or a Message")
67+
if not messages:
68+
raise ValueError("Must be at least a list or tuple of length 1")
69+
messages = tuple(messages)
70+
71+
all_same_id = all(
72+
message.arbitration_id == messages[0].arbitration_id for message in messages
73+
)
74+
if not all_same_id:
75+
raise ValueError("All Arbitration IDs should be the same")
76+
77+
all_same_channel = all(
78+
message.channel == messages[0].channel for message in messages
79+
)
80+
if not all_same_channel:
81+
raise ValueError("All Channel IDs should be the same")
82+
83+
return messages
4784

4885

4986
class LimitedDurationCyclicSendTaskABC(CyclicSendTaskABC):
50-
def __init__(self, message, period, duration):
87+
def __init__(self, messages, period, duration):
5188
"""Message send task with a defined duration and period.
5289
53-
:param can.Message message: The message to be sent periodically.
54-
:param float period: The rate in seconds at which to send the message.
90+
:param Union[Sequence[can.Message], can.Message] messages:
91+
The messages to be sent periodically.
92+
:param float period: The rate in seconds at which to send the messages.
5593
:param float duration:
56-
The duration to keep sending this message at given rate.
94+
Approximate duration in seconds to continue sending messages. If
95+
no duration is provided, the task will continue indefinitely.
5796
"""
58-
super().__init__(message, period)
97+
super().__init__(messages, period)
5998
self.duration = duration
6099

61100

@@ -71,44 +110,72 @@ def start(self):
71110
class ModifiableCyclicTaskABC(CyclicSendTaskABC):
72111
"""Adds support for modifying a periodic message"""
73112

74-
def modify_data(self, message):
75-
"""Update the contents of this periodically sent message without altering
76-
the timing.
113+
def _check_modified_messages(self, messages):
114+
"""Helper function to perform error checking when modifying the data in
115+
the cyclic task.
116+
117+
Performs error checking to ensure the arbitration ID and the number of
118+
cyclic messages hasn't changed.
77119
78-
:param can.Message message:
79-
The message with the new :attr:`can.Message.data`.
80-
Note: The arbitration ID cannot be changed.
120+
Should be called when modify_data is called in the cyclic task.
81121
"""
82-
self.message = message
122+
if len(self.messages) != len(messages):
123+
raise ValueError(
124+
"The number of new cyclic messages to be sent must be equal to "
125+
"the number of messages originally specified for this task"
126+
)
127+
if self.arbitration_id != messages[0].arbitration_id:
128+
raise ValueError(
129+
"The arbitration ID of new cyclic messages cannot be changed "
130+
"from when the task was created"
131+
)
132+
133+
def modify_data(self, messages):
134+
"""Update the contents of the periodically sent messages, without
135+
altering the timing.
136+
137+
:param Union[Sequence[can.Message], can.Message] messages:
138+
The messages with the new :attr:`can.Message.data`.
139+
140+
Note: The arbitration ID cannot be changed.
141+
142+
Note: The number of new cyclic messages to be sent must be equal
143+
to the original number of messages originally specified for this
144+
task.
145+
"""
146+
messages = self._check_and_convert_messages(messages)
147+
self._check_modified_messages(messages)
148+
149+
self.messages = messages
83150

84151

85152
class MultiRateCyclicSendTaskABC(CyclicSendTaskABC):
86153
"""A Cyclic send task that supports switches send frequency after a set time.
87154
"""
88155

89-
def __init__(self, channel, message, count, initial_period, subsequent_period):
156+
def __init__(self, channel, messages, count, initial_period, subsequent_period):
90157
"""
91158
Transmits a message `count` times at `initial_period` then continues to
92-
transmit message at `subsequent_period`.
159+
transmit messages at `subsequent_period`.
93160
94161
:param channel: See interface specific documentation.
95-
:param can.Message message:
162+
:param Union[Sequence[can.Message], can.Message] messages:
96163
:param int count:
97164
:param float initial_period:
98165
:param float subsequent_period:
99166
"""
100-
super().__init__(channel, message, subsequent_period)
167+
super().__init__(channel, messages, subsequent_period)
101168

102169

103170
class ThreadBasedCyclicSendTask(
104171
ModifiableCyclicTaskABC, LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC
105172
):
106173
"""Fallback cyclic send task using thread."""
107174

108-
def __init__(self, bus, lock, message, period, duration=None):
109-
super().__init__(message, period, duration)
175+
def __init__(self, bus, lock, messages, period, duration=None):
176+
super().__init__(messages, period, duration)
110177
self.bus = bus
111-
self.lock = lock
178+
self.send_lock = lock
112179
self.stopped = True
113180
self.thread = None
114181
self.end_time = time.time() + duration if duration else None
@@ -120,23 +187,25 @@ def stop(self):
120187
def start(self):
121188
self.stopped = False
122189
if self.thread is None or not self.thread.is_alive():
123-
name = "Cyclic send task for 0x%X" % (self.message.arbitration_id)
190+
name = "Cyclic send task for 0x%X" % (self.messages[0].arbitration_id)
124191
self.thread = threading.Thread(target=self._run, name=name)
125192
self.thread.daemon = True
126193
self.thread.start()
127194

128195
def _run(self):
196+
msg_index = 0
129197
while not self.stopped:
130198
# Prevent calling bus.send from multiple threads
131-
with self.lock:
199+
with self.send_lock:
132200
started = time.time()
133201
try:
134-
self.bus.send(self.message)
202+
self.bus.send(self.messages[msg_index])
135203
except Exception as exc:
136204
log.exception(exc)
137205
break
138206
if self.end_time is not None and time.time() >= self.end_time:
139207
break
208+
msg_index = (msg_index + 1) % len(self.messages)
140209
# Compensate for the time it takes to send the message
141210
delay = self.period - (time.time() - started)
142211
time.sleep(max(0.0, delay))

can/bus.py

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66

77
from abc import ABCMeta, abstractmethod
8+
import can
89
import logging
910
import threading
1011
from time import time
@@ -163,8 +164,8 @@ def send(self, msg, timeout=None):
163164
"""
164165
raise NotImplementedError("Trying to write to a readonly bus?")
165166

166-
def send_periodic(self, msg, period, duration=None, store_task=True):
167-
"""Start sending a message at a given period on this bus.
167+
def send_periodic(self, msgs, period, duration=None, store_task=True):
168+
"""Start sending messages at a given period on this bus.
168169
169170
The task will be active until one of the following conditions are met:
170171
@@ -174,12 +175,12 @@ def send_periodic(self, msg, period, duration=None, store_task=True):
174175
- :meth:`BusABC.stop_all_periodic_tasks()` is called
175176
- the task's :meth:`CyclicTask.stop()` method is called.
176177
177-
:param can.Message msg:
178-
Message to transmit
178+
:param Union[Sequence[can.Message], can.Message] msgs:
179+
Messages to transmit
179180
:param float period:
180181
Period in seconds between each message
181182
:param float duration:
182-
The duration to keep sending this message at given rate. If
183+
Approximate duration in seconds to continue sending messages. If
183184
no duration is provided, the task will continue indefinitely.
184185
:param bool store_task:
185186
If True (the default) the task will be attached to this Bus instance.
@@ -191,18 +192,26 @@ def send_periodic(self, msg, period, duration=None, store_task=True):
191192
192193
.. note::
193194
194-
Note the duration before the message stops being sent may not
195+
Note the duration before the messages stop being sent may not
195196
be exactly the same as the duration specified by the user. In
196197
general the message will be sent at the given rate until at
197198
least **duration** seconds.
198199
199200
.. note::
200201
201-
For extremely long running Bus instances with many short lived tasks the default
202-
api with ``store_task==True`` may not be appropriate as the stopped tasks are
203-
still taking up memory as they are associated with the Bus instance.
202+
For extremely long running Bus instances with many short lived
203+
tasks the default api with ``store_task==True`` may not be
204+
appropriate as the stopped tasks are still taking up memory as they
205+
are associated with the Bus instance.
204206
"""
205-
task = self._send_periodic_internal(msg, period, duration)
207+
if not isinstance(msgs, (list, tuple)):
208+
if isinstance(msgs, can.Message):
209+
msgs = [msgs]
210+
else:
211+
raise ValueError("Must be either a list, tuple, or a Message")
212+
if not msgs:
213+
raise ValueError("Must be at least a list or tuple of length 1")
214+
task = self._send_periodic_internal(msgs, period, duration)
206215
# we wrap the task's stop method to also remove it from the Bus's list of tasks
207216
original_stop_method = task.stop
208217

@@ -221,21 +230,22 @@ def wrapped_stop_method(remove_task=True):
221230

222231
return task
223232

224-
def _send_periodic_internal(self, msg, period, duration=None):
233+
def _send_periodic_internal(self, msgs, period, duration=None):
225234
"""Default implementation of periodic message sending using threading.
226235
227236
Override this method to enable a more efficient backend specific approach.
228237
229-
:param can.Message msg:
230-
Message to transmit
238+
:param Union[Sequence[can.Message], can.Message] msgs:
239+
Messages to transmit
231240
:param float period:
232241
Period in seconds between each message
233242
:param float duration:
234-
The duration to keep sending this message at given rate. If
243+
The duration between sending each message at the given rate. If
235244
no duration is provided, the task will continue indefinitely.
236245
:return:
237-
A started task instance. Note the task can be stopped (and depending on
238-
the backend modified) by calling the :meth:`stop` method.
246+
A started task instance. Note the task can be stopped (and
247+
depending on the backend modified) by calling the :meth:`stop`
248+
method.
239249
:rtype: can.broadcastmanager.CyclicSendTaskABC
240250
"""
241251
if not hasattr(self, "_lock_send_periodic"):
@@ -244,7 +254,7 @@ def _send_periodic_internal(self, msg, period, duration=None):
244254
threading.Lock()
245255
) # pylint: disable=attribute-defined-outside-init
246256
task = ThreadBasedCyclicSendTask(
247-
self, self._lock_send_periodic, msg, period, duration
257+
self, self._lock_send_periodic, msgs, period, duration
248258
)
249259
return task
250260

can/interfaces/ixxat/canlib.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -716,20 +716,25 @@ def shutdown(self):
716716
class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC):
717717
"""A message in the cyclic transmit list."""
718718

719-
def __init__(self, scheduler, msg, period, duration, resolution):
720-
super().__init__(msg, period, duration)
719+
def __init__(self, scheduler, msgs, period, duration, resolution):
720+
super().__init__(msgs, period, duration)
721+
if len(self.messages) != 1:
722+
raise ValueError(
723+
"IXXAT Interface only supports periodic transmission of 1 element"
724+
)
725+
721726
self._scheduler = scheduler
722727
self._index = None
723728
self._count = int(duration / period) if duration else 0
724729

725730
self._msg = structures.CANCYCLICTXMSG()
726731
self._msg.wCycleTime = int(round(period * resolution))
727-
self._msg.dwMsgId = msg.arbitration_id
732+
self._msg.dwMsgId = self.messages[0].arbitration_id
728733
self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA
729-
self._msg.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0
730-
self._msg.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0
731-
self._msg.uMsgInfo.Bits.dlc = msg.dlc
732-
for i, b in enumerate(msg.data):
734+
self._msg.uMsgInfo.Bits.ext = 1 if self.messages[0].is_extended_id else 0
735+
self._msg.uMsgInfo.Bits.rtr = 1 if self.messages[0].is_remote_frame else 0
736+
self._msg.uMsgInfo.Bits.dlc = self.messages[0].dlc
737+
for i, b in enumerate(self.messages[0].data):
733738
self._msg.abData[i] = b
734739
self.start()
735740

can/interfaces/socketcan/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# BCM opcodes
1212
CAN_BCM_TX_SETUP = 1
1313
CAN_BCM_TX_DELETE = 2
14+
CAN_BCM_TX_READ = 3
1415

1516
# BCM flags
1617
SETTIMER = 0x0001

0 commit comments

Comments
 (0)