1212import threading
1313import time
1414
15+ import can
16+
1517log = 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
4986class 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):
71110class 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
85152class 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
103170class 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 ))
0 commit comments