|
1 |
| -from ctypes import * |
| 1 | +import collections |
| 2 | +from ctypes import c_ubyte |
2 | 3 | import logging
|
3 |
| -import platform |
4 |
| -from can import BusABC, Message |
| 4 | +import canalystii as driver |
| 5 | +import time |
| 6 | +import warnings |
| 7 | +from typing import Any, Dict, Optional, Deque, Sequence, Tuple, Union |
| 8 | +from can import BitTiming, BusABC, Message |
| 9 | +from can.exceptions import CanTimeoutError |
| 10 | +from can.typechecking import CanFilters |
5 | 11 |
|
6 | 12 | logger = logging.getLogger(__name__)
|
7 | 13 |
|
8 | 14 |
|
9 |
| -class VCI_INIT_CONFIG(Structure): |
10 |
| - _fields_ = [ |
11 |
| - ("AccCode", c_int32), |
12 |
| - ("AccMask", c_int32), |
13 |
| - ("Reserved", c_int32), |
14 |
| - ("Filter", c_ubyte), |
15 |
| - ("Timing0", c_ubyte), |
16 |
| - ("Timing1", c_ubyte), |
17 |
| - ("Mode", c_ubyte), |
18 |
| - ] |
19 |
| - |
20 |
| - |
21 |
| -class VCI_CAN_OBJ(Structure): |
22 |
| - _fields_ = [ |
23 |
| - ("ID", c_uint), |
24 |
| - ("TimeStamp", c_int), |
25 |
| - ("TimeFlag", c_byte), |
26 |
| - ("SendType", c_byte), |
27 |
| - ("RemoteFlag", c_byte), |
28 |
| - ("ExternFlag", c_byte), |
29 |
| - ("DataLen", c_byte), |
30 |
| - ("Data", c_ubyte * 8), |
31 |
| - ("Reserved", c_byte * 3), |
32 |
| - ] |
33 |
| - |
34 |
| - |
35 |
| -SENDTYPE_ALLOW_RETRY = 0 # Retry send if there is bus contention |
36 |
| -SENDTYPE_ONE_SHOT = 1 # Drop the message if transmission fails first time |
37 |
| - |
38 |
| -VCI_USBCAN2 = 4 |
39 |
| - |
40 |
| -STATUS_OK = 0x01 |
41 |
| -STATUS_ERR = 0x00 |
42 |
| - |
43 |
| -TIMING_DICT = { |
44 |
| - 5000: (0xBF, 0xFF), |
45 |
| - 10000: (0x31, 0x1C), |
46 |
| - 20000: (0x18, 0x1C), |
47 |
| - 33330: (0x09, 0x6F), |
48 |
| - 40000: (0x87, 0xFF), |
49 |
| - 50000: (0x09, 0x1C), |
50 |
| - 66660: (0x04, 0x6F), |
51 |
| - 80000: (0x83, 0xFF), |
52 |
| - 83330: (0x03, 0x6F), |
53 |
| - 100000: (0x04, 0x1C), |
54 |
| - 125000: (0x03, 0x1C), |
55 |
| - 200000: (0x81, 0xFA), |
56 |
| - 250000: (0x01, 0x1C), |
57 |
| - 400000: (0x80, 0xFA), |
58 |
| - 500000: (0x00, 0x1C), |
59 |
| - 666000: (0x80, 0xB6), |
60 |
| - 800000: (0x00, 0x16), |
61 |
| - 1000000: (0x00, 0x14), |
62 |
| -} |
63 |
| - |
64 |
| -try: |
65 |
| - if platform.system() == "Windows": |
66 |
| - CANalystII = WinDLL("./ControlCAN.dll") |
67 |
| - else: |
68 |
| - CANalystII = CDLL("./libcontrolcan.so") |
69 |
| - logger.info("Loaded CANalystII library") |
70 |
| -except OSError as e: |
71 |
| - CANalystII = None |
72 |
| - logger.info("Cannot load CANalystII library") |
73 |
| - |
74 |
| - |
75 | 15 | class CANalystIIBus(BusABC):
|
76 | 16 | def __init__(
|
77 | 17 | self,
|
78 |
| - channel, |
79 |
| - device=0, |
80 |
| - bitrate=None, |
81 |
| - Timing0=None, |
82 |
| - Timing1=None, |
83 |
| - can_filters=None, |
84 |
| - **kwargs, |
| 18 | + channel: Union[int, Sequence[int], str] = (0, 1), |
| 19 | + device: int = 0, |
| 20 | + bitrate: Optional[int] = None, |
| 21 | + bit_timing: Optional[BitTiming] = None, |
| 22 | + can_filters: Optional[CanFilters] = None, |
| 23 | + rx_queue_size: Optional[int] = None, |
| 24 | + **kwargs: Dict[str, Any], |
85 | 25 | ):
|
86 | 26 | """
|
87 | 27 |
|
88 |
| - :param channel: channel number |
89 |
| - :param device: device number |
90 |
| - :param bitrate: CAN network bandwidth (bits/s) |
91 |
| - :param Timing0: customize the timing register if bitrate is not specified |
92 |
| - :param Timing1: |
93 |
| - :param can_filters: filters for packet |
| 28 | + :param channel: |
| 29 | + Optional channel number, list/tuple of multiple channels, or comma |
| 30 | + separated string of channels. Default is to configure both |
| 31 | + channels. |
| 32 | + :param device: |
| 33 | + Optional USB device number. Default is 0 (first device found). |
| 34 | + :param bitrate: |
| 35 | + CAN bitrate in bits/second. Required unless the bit_timing argument is set. |
| 36 | + :param bit_timing: |
| 37 | + Optional BitTiming instance to use for custom bit timing setting. |
| 38 | + If this argument is set then it overrides the bitrate argument. |
| 39 | + :param can_filters: |
| 40 | + Optional filters for received CAN messages. |
| 41 | + :param rx_queue_size: |
| 42 | + If set, software received message queue can only grow to this many |
| 43 | + messages (for all channels) before older messages are dropped |
94 | 44 | """
|
95 | 45 | super().__init__(channel=channel, can_filters=can_filters, **kwargs)
|
96 | 46 |
|
97 |
| - if isinstance(channel, (list, tuple)): |
98 |
| - self.channels = channel |
99 |
| - elif isinstance(channel, int): |
100 |
| - self.channels = [channel] |
101 |
| - else: |
| 47 | + if not (bitrate or bit_timing): |
| 48 | + raise ValueError("Either bitrate or bit_timing argument is required") |
| 49 | + |
| 50 | + if isinstance(channel, str): |
102 | 51 | # Assume comma separated string of channels
|
103 | 52 | self.channels = [int(ch.strip()) for ch in channel.split(",")]
|
| 53 | + elif isinstance(channel, int): |
| 54 | + self.channels = [channel] |
| 55 | + else: # Sequence[int] |
| 56 | + self.channels = [c for c in channel] |
104 | 57 |
|
105 |
| - self.device = device |
106 |
| - |
107 |
| - self.channel_info = "CANalyst-II: device {}, channels {}".format( |
108 |
| - self.device, self.channels |
109 |
| - ) |
110 |
| - |
111 |
| - if bitrate is not None: |
112 |
| - try: |
113 |
| - Timing0, Timing1 = TIMING_DICT[bitrate] |
114 |
| - except KeyError: |
115 |
| - raise ValueError("Bitrate is not supported") |
116 |
| - |
117 |
| - if Timing0 is None or Timing1 is None: |
118 |
| - raise ValueError("Timing registers are not set") |
119 |
| - |
120 |
| - self.init_config = VCI_INIT_CONFIG(0, 0xFFFFFFFF, 0, 1, Timing0, Timing1, 0) |
| 58 | + self.rx_queue = collections.deque( |
| 59 | + maxlen=rx_queue_size |
| 60 | + ) # type: Deque[Tuple[int, driver.Message]] |
121 | 61 |
|
122 |
| - if CANalystII.VCI_OpenDevice(VCI_USBCAN2, self.device, 0) == STATUS_ERR: |
123 |
| - logger.error("VCI_OpenDevice Error") |
| 62 | + self.channel_info = f"CANalyst-II: device {device}, channels {self.channels}" |
124 | 63 |
|
| 64 | + self.device = driver.CanalystDevice(device_index=device) |
125 | 65 | for channel in self.channels:
|
126 |
| - status = CANalystII.VCI_InitCAN( |
127 |
| - VCI_USBCAN2, self.device, channel, byref(self.init_config) |
128 |
| - ) |
129 |
| - if status == STATUS_ERR: |
130 |
| - logger.error("VCI_InitCAN Error") |
131 |
| - self.shutdown() |
132 |
| - return |
133 |
| - |
134 |
| - if CANalystII.VCI_StartCAN(VCI_USBCAN2, self.device, channel) == STATUS_ERR: |
135 |
| - logger.error("VCI_StartCAN Error") |
136 |
| - self.shutdown() |
137 |
| - return |
138 |
| - |
139 |
| - def send(self, msg, timeout=None): |
140 |
| - """ |
| 66 | + if bit_timing: |
| 67 | + try: |
| 68 | + if bit_timing.f_clock != 8_000_000: |
| 69 | + warnings.warn( |
| 70 | + f"bit_timing.f_clock value {bit_timing.f_clock} " |
| 71 | + "doesn't match expected device f_clock 8MHz." |
| 72 | + ) |
| 73 | + except ValueError: |
| 74 | + pass # f_clock not specified |
| 75 | + self.device.init( |
| 76 | + channel, timing0=bit_timing.btr0, timing1=bit_timing.btr1 |
| 77 | + ) |
| 78 | + else: |
| 79 | + self.device.init(channel, bitrate=bitrate) |
| 80 | +
10000
div> |
| 81 | + # Delay to use between each poll for new messages |
| 82 | + # |
| 83 | + # The timeout is deliberately kept low to avoid the possibility of |
| 84 | + # a hardware buffer overflow. This value was determined |
| 85 | + # experimentally, but the ideal value will depend on the specific |
| 86 | + # system. |
| 87 | + RX_POLL_DELAY = 0.020 |
| 88 | + |
| 89 | + def send(self, msg: Message, timeout: Optional[float] = None) -> None: |
| 90 | + """Send a CAN message to the bus |
141 | 91 |
|
142 | 92 | :param msg: message to send
|
143 |
| - :param timeout: timeout is not used here |
144 |
| - :return: |
| 93 | + :param timeout: timeout (in seconds) to wait for the TX queue to clear. |
| 94 | + If set to ``None`` (default) the function returns immediately. |
| 95 | +
|
| 96 | + Note: Due to limitations in the device firmware and protocol, the |
| 97 | + timeout will not trigger if there are problems with CAN arbitration, |
| 98 | + but only if the device is overloaded with a backlog of too many |
| 99 | + messages to send. |
145 | 100 | """
|
146 |
| - extern_flag = 1 if msg.is_extended_id else 0 |
147 |
| - raw_message = VCI_CAN_OBJ( |
| 101 | + raw_message = driver.Message( |
148 | 102 | msg.arbitration_id,
|
149 |
| - 0, |
150 |
| - 0, |
151 |
| - SENDTYPE_ALLOW_RETRY, |
| 103 | + 0, # timestamp |
| 104 | + 1, # time_flag |
| 105 | + 0, # send_type |
152 | 106 | msg.is_remote_frame,
|
153 |
| - extern_flag, |
| 107 | + msg.is_extended_id, |
154 | 108 | msg.dlc,
|
155 | 109 | (c_ubyte * 8)(*msg.data),
|
156 |
| - (c_byte * 3)(*[0, 0, 0]), |
157 | 110 | )
|
158 | 111 |
|
159 | 112 | if msg.channel is not None:
|
160 | 113 | channel = msg.channel
|
161 | 114 | elif len(self.channels) == 1:
|
162 | 115 | channel = self.channels[0]
|
163 | 116 | else:
|
164 |
| - raise ValueError("msg.channel must be set when using multiple channels.") |
| 117 | + raise ValueError( |
| 118 | + "Message channel must be set when using multiple channels." |
| 119 | + ) |
165 | 120 |
|
166 |
| - CANalystII.VCI_Transmit( |
167 |
| - VCI_USBCAN2, self.device, channel, byref(raw_message), 1 |
| 121 | + send_result = self.device.send(channel, [raw_message], timeout) |
| 122 | + if timeout is not None and not send_result: |
| 123 | + raise CanTimeoutError(f"Send timed out after {timeout} seconds") |
| 124 | + |
| 125 | + def _recv_from_queue(self) -> Tuple[Message, bool]: |
| 126 | + """Return a message from the internal receive queue""" |
| 127 | + channel, raw_msg = self.rx_queue.popleft() |
| 128 | + |
| 129 | + # Protocol timestamps are in units of 100us, convert to seconds as |
| 130 | + # float |
| 131 | + timestamp = raw_msg.timestamp * 100e-6 |
| 132 | + |
| 133 | + return ( |
| 134 | + Message( |
| 135 | + channel=channel, |
| 136 | + timestamp=timestamp, |
| 137 | + arbitration_id=raw_msg.can_id, |
| 138 | + is_extended_id=raw_msg.extended, |
| 139 | + is_remote_frame=raw_msg.remote, |
| 140 | + dlc=raw_msg.data_len, |
| 141 | + data=bytes(raw_msg.data), |
| 142 | + ), |
| 143 | + False, |
168 | 144 | )
|
169 | 145 |
|
170 |
| - def _recv_internal(self, timeout=None): |
| 146 | + def poll_received_messages(self) -> None: |
| 147 | + """Poll new messages from the device into the rx queue but don't |
| 148 | + return any message to the caller |
| 149 | +
|
| 150 | + Calling this function isn't necessary as polling the device is done |
| 151 | + automatically when calling recv(). This function is for the situation |
| 152 | + where an application needs to empty the hardware receive buffer without |
| 153 | + consuming any message. |
| 154 | + """ |
| 155 | + for channel in self.channels: |
| 156 | + self.rx_queue.extend( |
| 157 | + (channel, raw_msg) for raw_msg in self.device.receive(channel) |
| 158 | + ) |
| 159 | + |
| 160 | + def _recv_internal( |
| 161 | + self, timeout: Optional[float] = None |
| 162 | + ) -> Tuple[Optional[Message], bool]: |
171 | 163 | """
|
172 | 164 |
|
173 | 165 | :param timeout: float in seconds
|
174 | 166 | :return:
|
175 | 167 | """
|
176 |
| - raw_message = VCI_CAN_OBJ() |
177 | 168 |
|
178 |
| - timeout = -1 if timeout is None else int(timeout * 1000) |
| 169 | + if self.rx_queue: |
| 170 | + return self._recv_from_queue() |
179 | 171 |
|
180 |
| - status = CANalystII.VCI_Receive( |
181 |
| - VCI_USBCAN2, self.device, self.channels[0], byref(raw_message), 1, timeout |
182 |
| - ) |
183 |
| - if status <= STATUS_ERR: |
184 |
| - return None, False |
| 172 | + deadline = None |
| 173 | + while deadline is None or time.time() < deadline: |
| 174 | + if deadline is None and timeout is not None: |
| 175 | + deadline = time.time() + timeout |
| 176 | + |
| 177 | + self.poll_received_messages() |
| 178 | + |
| 179 | + if self.rx_queue: |
| 180 | + return self._recv_from_queue() |
| 181 | + |
| 182 | + # If blocking on a timeout, add a sleep before we loop again |
| 183 | + # to reduce CPU usage. |
| 184 | + if deadline is None or deadline - time.time() > 0.050: |
| 185 | + time.sleep(self.RX_POLL_DELAY) |
| 186 | + |
| 187 | + return (None, False) |
| 188 | + |
| 189 | + def flush_tx_buffer(self, channel: Optional[int] = None) -> None: |
| 190 | + """Flush the TX buffer of the device. |
| 191 | +
|
| 192 | + :param channel: |
| 193 | + Optional channel number to flush. If set to None, all initialized |
| 194 | + channels are flushed. |
| 195 | +
|
| 196 | + Note that because of protocol limitations t
E17C
his function returning |
| 197 | + doesn't mean that messages have been sent, it may also mean they |
| 198 | + failed to send. |
| 199 | + """ |
| 200 | + if channel: |
| 201 | + self.device.flush_tx_buffer(channel, float("infinity")) |
185 | 202 | else:
|
186 |
| - return ( |
187 |
| - Message( |
188 |
| - timestamp=raw_message.TimeStamp if raw_message.TimeFlag else 0.0, |
189 |
| - arbitration_id=raw_message.ID, |
190 |
| - is_remote_frame=raw_message.RemoteFlag, |
191 |
| - is_extended_id=raw_message.ExternFlag, |
192 |
| - channel=0, |
193 |
| - dlc=raw_message.DataLen, |
194 |
| - data=raw_message.Data, |
195 |
| - ), |
196 |
| - False, |
197 |
| - ) |
| 203 | + for ch in self.channels: |
| 204 | + self.device.flush_tx_buffer(ch, float("infinity")) |
198 | 205 |
|
199 |
| - def flush_tx_buffer(self): |
| 206 | + def shutdown(self) -> None: |
200 | 207 | for channel in self.channels:
|
201 |
| - CANalystII.VCI_ClearBuffer(VCI_USBCAN2, self.device, channel) |
202 |
| - |
203 |
| - def shutdown(self): |
204 |
| - CANalystII.VCI_CloseDevice(VCI_USBCAN2, self.device) |
| 208 | + self.device.stop(channel) |
| 209 | + self.device = None |
0 commit comments