11import time
2- from typing import Optional , Tuple , List , Union
2+ from typing import Optional , Tuple , List , Union , TypedDict
3+ from collections .abc import Sequence
34import can
5+ from can .util import len2dlc
46from can .typechecking import CanFilters , AutoDetectedConfig
57import candle_api as api
68
79
8- ISO_DLC = (0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 12 , 16 , 20 , 24 , 32 , 48 , 64 )
10+ class ChannelConfig (TypedDict ):
11+ bitrate : int
12+ sample_point : float
13+ data_bitrate : int
14+ data_sample_point : float
15+ fd : bool
16+ loop_back : bool
17+ listen_only : bool
18+ triple_sample : bool
19+ one_shot : bool
20+ bit_error_reporting : bool
21+ termination : Optional [bool ]
22+
23+
24+ def convert_frame (channel : int , frame : api .CandleCanFrame , hardware_timestamp : bool ) -> can .Message :
25+ return can .Message (
26+ timestamp = frame .timestamp if hardware_timestamp else time .monotonic (),
27+ arbitration_id = frame .can_id ,
28+ is_extended_id = frame .frame_type .extended_id ,
29+ is_remote_frame = frame .frame_type .remote_frame ,
30+ is_error_frame = frame .frame_type .error_frame ,
31+ channel = channel ,
32+ dlc = frame .size , # https://github.com/hardbyte/python-can/issues/749
33+ data = bytearray (frame ),
34+ is_fd = frame .frame_type .fd ,
35+ is_rx = frame .frame_type .rx ,
36+ bitrate_switch = frame .frame_type .bitrate_switch ,
37+ error_state_indicator = frame .frame_type .error_state_indicator
38+ )
939
1040
1141class CandleBus (can .bus .BusABC ):
12- def __init__ (self , channel : Union [int , str ], can_filters : Optional [CanFilters ] = None ,
42+ def __init__ (self , channel : Union [int , str , Sequence [ int ] ], can_filters : Optional [CanFilters ] = None ,
1343 bitrate : int = 1000000 , sample_point : float = 87.5 ,
1444 data_bitrate : int = 5000000 , data_sample_point : float = 87.5 ,
1545 fd : bool = False , loop_back : bool = False , listen_only : bool = False ,
1646 triple_sample : bool = False , one_shot : bool = False , bit_error_reporting : bool = False ,
1747 termination : Optional [bool ] = None , vid : Optional [int ] = None , pid : Optional [int ] = None ,
1848 manufacture : Optional [str ] = None , product : Optional [str ] = None ,
19- serial_number : Optional [str ] = None , ** kwargs ) -> None :
49+ serial_number : Optional [str ] = None , channel_configs : Optional [dict [int , ChannelConfig ]] = None ,
50+ ** kwargs ) -> None :
2051
2152 # If ignore_config is not set, can.util.cast_from_string may cause unexpected type conversions.
2253 if manufacture is not None :
@@ -29,11 +60,13 @@ def __init__(self, channel: Union[int, str], can_filters: Optional[CanFilters] =
2960 # Parse channel.
3061 if isinstance (channel , str ):
3162 serial_number , channel_number = channel .split (':' )
32- self ._channel_number = int (channel_number )
63+ self ._channel_numbers = ( int (channel_number ), )
3364 elif isinstance (channel , int ):
34- self ._channel_number = channel
65+ self ._channel_numbers = (channel ,)
66+ elif isinstance (channel , Sequence ):
67+ self ._channel_numbers = tuple (channel )
3568 else :
36- raise TypeError ("channel must be of type str or int" )
69+ raise TypeError ("Channel must be of type int, str or Sequence[ int] " )
3770
3871 # Find the device.
3972 self ._device = self ._find_device (vid , pid , manufacture , product , serial_number )
@@ -42,69 +75,82 @@ def __init__(self, channel: Union[int, str], can_filters: Optional[CanFilters] =
4275 self ._device .open ()
4376
4477 # Get the channel.
45- self ._channel = self ._device [self ._channel_number ]
46- self ._hardware_timestamp = self ._channel .feature .hardware_timestamp
47- self .channel_info = f'{ self ._device .serial_number } :{ self ._channel_number } '
78+ self ._channels = { i : self ._device [i ] for i in self ._channel_numbers }
79+ self ._hardware_timestamps = { i : self ._channels [ i ] .feature .hardware_timestamp for i in self . _channel_numbers }
80+ self .channel_info = f'{ self ._device .serial_number } :{ self ._channel_numbers } '
4881
4982 # Reset channel.
50- self ._channel .reset ()
51-
52- # Set termination.
53- if termination is not None :
54- self ._channel .set_termination (termination )
55-
56- # Set bit timing.
57- props_seg = 1
58- if fd and self ._channel .feature .fd :
59- bit_timing_fd = can .BitTimingFd .from_sample_point (
60- f_clock = self ._channel .clock_frequency ,
61- nom_bitrate = bitrate ,
62- nom_sample_point = sample_point ,
63- data_bitrate = data_bitrate ,
64- data_sample_point = data_sample_point
65- )
66-
67- self ._channel .set_bit_timing (
68- props_seg ,
69- bit_timing_fd .nom_tseg1 - props_seg ,
70- bit_timing_fd .nom_tseg2 ,
71- bit_timing_fd .nom_sjw ,
72- bit_timing_fd .nom_brp
73- )
83+ [ch .reset () for ch in self ._channels .values ()]
7484
75- self ._channel .set_data_bit_timing (
76- props_seg ,
77- bit_timing_fd .data_tseg1 - props_seg ,
78- bit_timing_fd .data_tseg2 ,
79- bit_timing_fd .data_sjw ,
80- bit_timing_fd .data_brp
81- )
82- else :
83- bit_timing = can .BitTiming .from_sample_point (
84- f_clock = self ._channel .clock_frequency ,
85- bitrate = bitrate ,
86- sample_point = sample_point ,
87- )
85+ # Configure channel.
86+ default_config = ChannelConfig (
87+ bitrate = bitrate , sample_point = sample_point , data_bitrate = data_bitrate , data_sample_point = data_sample_point ,
88+ fd = fd , loop_back = loop_back , listen_only = listen_only , triple_sample = triple_sample , one_shot = one_shot ,
89+ bit_error_reporting = bit_error_reporting , termination = termination
90+ )
8891
89- self ._channel .set_bit_timing (
90- props_seg ,
91- bit_timing .tseg1 - props_seg ,
92- bit_timing .tseg2 ,
93- bit_timing .sjw ,
94- bit_timing .brp
92+ for i , ch in self ._channels .items ():
93+ # Get channel configuration.
94+ cfg : ChannelConfig = default_config .copy ()
95+ if channel_configs is not None :
96+ cfg |= channel_configs .get (i , {})
97+
98+ # Set termination.
99+ if cfg ["termination" ] is not None :
100+ ch .set_termination (termination )
101+
102+ # Set bit timing.
103+ props_seg = 1
104+ if cfg ["fd" ] and ch .feature .fd :
105+ bit_timing_fd = can .BitTimingFd .from_sample_point (
106+ f_clock = ch .clock_frequency ,
107+ nom_bitrate = cfg ["bitrate" ],
108+ nom_sample_point = cfg ["sample_point" ],
109+ data_bitrate = cfg ["data_bitrate" ],
110+ data_sample_point = cfg ["data_sample_point" ]
111+ )
112+
113+ ch .set_bit_timing (
114+ props_seg ,
115+ bit_timing_fd .nom_tseg1 - props_seg ,
116+ bit_timing_fd .nom_tseg2 ,
117+ bit_timing_fd .nom_sjw ,
118+ bit_timing_fd .nom_brp
119+ )
120+
121+ ch .set_data_bit_timing (
122+ props_seg ,
123+ bit_timing_fd .data_tseg1 - props_seg ,
124+ bit_timing_fd .data_tseg2 ,
125+ bit_timing_fd .data_sjw ,
126+ bit_timing_fd .data_brp
127+ )
128+ else :
129+ bit_timing = can .BitTiming .from_sample_point (
130+ f_clock = ch .clock_frequency ,
131+ bitrate = cfg ["bitrate" ],
132+ sample_point = cfg ["sample_point" ],
133+ )
134+
135+ ch .set_bit_timing (
136+ props_seg ,
137+ bit_timing .tseg1 - props_seg ,
138+ bit_timing .tseg2 ,
139+ bit_timing .sjw ,
140+ bit_timing .brp
141+ )
142+
143+ # Open the channel.
144+ ch .start (
145+ hardware_timestamp = self ._hardware_timestamps [i ],
146+ fd = cfg ["fd" ],
147+ loop_back = cfg ["loop_back" ],
148+ listen_only = cfg ["listen_only" ],
149+ triple_sample = cfg ["triple_sample" ],
150+ one_shot = cfg ["one_shot" ],
151+ bit_error_reporting = cfg ["bit_error_reporting" ]
95152 )
96153
97- # Open the channel.
98- self ._channel .start (
99- hardware_timestamp = self ._channel .feature .hardware_timestamp ,
100- fd = fd ,
101- loop_back = loop_back ,
102- listen_only = listen_only ,
103- triple_sample = triple_sample ,
104- one_shot = one_shot ,
105- bit_error_reporting = bit_error_reporting
106- )
107-
108154 super ().__init__ (
109155 channel = channel ,
110156 can_filters = can_filters ,
@@ -129,37 +175,47 @@ def _find_device(vid: Optional[int] = None, pid: Optional[int] = None, manufactu
129175 else :
130176 raise can .exceptions .CanInitializationError ('Device not found!' )
131177
132- def _recv_internal (
133- self , timeout : Optional [float ]
134- ) -> Tuple [Optional [can .Message ], bool ]:
135- frame : Optional [api .CandleCanFrame ] = None
178+ def _recv_internal (self , timeout : Optional [float ]) -> Tuple [Optional [can .Message ], bool ]:
179+ # Check if there is a frame available.
180+ for i , ch in self ._channels .items ():
181+ frame = ch .receive_nowait ()
182+ if frame is not None :
183+ return convert_frame (i , frame , self ._hardware_timestamps [i ]), False
184+
185+ # Do not block if timeout is None.
136186 if timeout is None :
137- frame = self ._channel .receive_nowait ()
138- else :
139- try :
140- frame = self ._channel .receive (timeout )
141- except TimeoutError :
142- pass
143-
144- if frame is not None :
145- msg = can .Message (
146- timestamp = frame .timestamp if self ._hardware_timestamp else time .monotonic (),
147- arbitration_id = frame .can_id ,
148- is_extended_id = frame .frame_type .extended_id ,
149- is_remote_frame = frame .frame_type .remote_frame ,
150- is_error_frame = frame .frame_type .error_frame ,
151- channel = self ._channel_number ,
152- dlc = frame .size , # https://github.com/hardbyte/python-can/issues/749
153- data = bytearray (frame ),
154- is_fd = frame .frame_type .fd ,
155- is_rx = frame .frame_type .rx ,
156- bitrate_switch = frame .frame_type .bitrate_switch ,
157- error_state_indicator = frame .frame_type .error_state_indicator
158- )
159- return msg , False
187+ return None , False
188+
189+ # Block until a frame is available.
190+ if not self ._device .wait_for_frame (timeout ):
191+ return None , False
192+
193+ # Check if there is a frame available.
194+ for i , ch in self ._channels .items ():
195+ frame = ch .receive_nowait ()
196+ if frame is not None :
197+ return convert_frame (i , frame , self ._hardware_timestamps [i ]), False
198+
199+ # No frame available after timeout.
160200 return None , False
161201
162202 def send (self , msg : can .Message , timeout : Optional [float ] = None ) -> None :
203+ # Parse channel.
204+ target_channels : Tuple [api .CandleChannel , ...]
205+ if len (self ._channel_numbers ) == 1 :
206+ # There is only one channel.
207+ target_channels = (self ._channels [self ._channel_numbers [0 ]],)
208+ else :
209+ if isinstance (msg .channel , str ):
210+ serial_number , channel_number = msg .channel .split (':' )
211+ target_channels = (self ._channels [int (channel_number )],)
212+ elif isinstance (msg .channel , int ):
213+ target_channels = (self ._channels [msg .channel ],)
214+ elif isinstance (msg .channel , Sequence ):
215+ target_channels = tuple (self ._channels [i ] for i in msg .channel )
216+ else :
217+ raise TypeError ("Channel must be of type int, str or Sequence[int]" )
218+
163219 if timeout is None :
164220 timeout = 1.0
165221
@@ -174,17 +230,18 @@ def send(self, msg: can.Message, timeout: Optional[float] = None) -> None:
174230 error_state_indicator = msg .error_state_indicator
175231 ),
176232 msg .arbitration_id ,
177- ISO_DLC . index (msg .dlc ),
233+ len2dlc (msg .dlc ),
178234 msg .data
179235 )
180236
181237 try :
182- self ._channel .send (frame , timeout )
238+ for ch in target_channels :
239+ ch .send (frame , timeout )
183240 except TimeoutError as exc :
184241 raise can .CanOperationError ("The message could not be sent" ) from exc
185242
186243 def shutdown (self ):
187- self ._channel . reset ()
244+ [ ch . reset () for ch in self ._channels . values ()]
188245 super ().shutdown ()
189246
190247 @staticmethod
0 commit comments