|
| 1 | +""" Neousys CAN bus driver """ |
| 2 | + |
| 3 | +# |
| 4 | +# This kind of interface can be found for example on Neousys POC-551VTC |
| 5 | +# One needs to have correct drivers and DLL (Share object for Linux) from Neousys |
| 6 | +# |
| 7 | +# https://www.neousys-tech.com/en/support-service/resources/category/299-poc-551vtc-driver |
| 8 | +# |
| 9 | +# Beware this is only tested on Linux kernel higher than v5.3. This should be drop in |
| 10 | +# with Windows but you have to replace with correct named DLL |
| 11 | +# |
| 12 | + |
| 13 | +# pylint: disable=too-few-public-methods |
| 14 | +# pylint: disable=too-many-instance-attributes |
| 15 | +# pylint: disable=wrong-import-position |
| 16 | +# pylint: disable=method-hidden |
| 17 | +# pylint: disable=unused-import |
| 18 | + |
| 19 | +import warnings |
| 20 | +import queue |
| 21 | +import logging |
| 22 | +import platform |
| 23 | +import time |
| 24 | + |
| 25 | +from ctypes import ( |
| 26 | + byref, |
| 27 | + CFUNCTYPE, |
| 28 | + c_ubyte, |
| 29 | + c_uint, |
| 30 | + c_ushort, |
| 31 | + POINTER, |
| 32 | + sizeof, |
| 33 | + Structure, |
| 34 | +) |
| 35 | + |
| 36 | +if platform.system() == "Windows": |
| 37 | + from ctypes import WinDLL |
| 38 | +else: |
| 39 | + from ctypes import CDLL |
| 40 | +from can import BusABC, Message |
| 41 | + |
| 42 | + |
| 43 | +logger = logging.getLogger(__name__) |
| 44 | + |
| 45 | + |
| 46 | +class NeousysCanSetup(Structure): |
| 47 | + """ C CAN Setup struct """ |
| 48 | + |
| 49 | + _fields_ = [ |
| 50 | + ("bitRate", c_uint), |
| 51 | + ("recvConfig", c_uint), |
| 52 | + ("recvId", c_uint), |
| 53 | + ("recvMask", c_uint), |
| 54 | + ] |
| 55 | + |
| 56 | + |
| 57 | +class NeousysCanMsg(Structure): |
| 58 | + """ C CAN Message struct """ |
| 59 | + |
| 60 | + _fields_ = [ |
| 61 | + ("id", c_uint), |
| 62 | + ("flags", c_ushort), |
| 63 | + ("extra", c_ubyte), |
| 64 | + ("len", c_ubyte), |
| 65 | + ("data", c_ubyte * 8), |
| 66 | + ] |
| 67 | + |
| 68 | + |
| 69 | +# valid:2~16, sum of the Synchronization, Propagation, and |
| 70 | +# Phase Buffer 1 segments, measured in time quanta. |
| 71 | +# valid:1~8, the Phase Buffer 2 segment in time quanta. |
| 72 | +# valid:1~4, Resynchronization Jump Width in time quanta |
| 73 | +# valid:1~1023, CAN_CLK divider used to determine time quanta |
| 74 | +class NeousysCanBitClk(Structure): |
| 75 | + """ C CAN BIT Clock struct """ |
| 76 | + |
| 77 | + _fields_ = [ |
| 78 | + ("syncPropPhase1Seg", c_ushort), |
| 79 | + ("phase2Seg", c_ushort), |
| 80 | + ("jumpWidth", c_ushort), |
| 81 | + ("quantumPrescaler", c_ushort), |
| 82 | + ] |
| 83 | + |
| 84 | + |
| 85 | +NEOUSYS_CAN_MSG_CALLBACK = CFUNCTYPE(None, POINTER(NeousysCanMsg), c_uint) |
| 86 | +NEOUSYS_CAN_STATUS_CALLBACK = CFUNCTYPE(None, c_uint) |
| 87 | + |
| 88 | +NEOUSYS_CAN_MSG_EXTENDED_ID = 0x0004 |
| 89 | +NEOUSYS_CAN_MSG_REMOTE_FRAME = 0x0040 |
| 90 | +NEOUSYS_CAN_MSG_DATA_NEW = 0x0080 |
| 91 | +NEOUSYS_CAN_MSG_DATA_LOST = 0x0100 |
| 92 | + |
| 93 | +NEOUSYS_CAN_MSG_USE_ID_FILTER = 0x00000008 |
| 94 | +NEOUSYS_CAN_MSG_USE_DIR_FILTER = ( |
| 95 | + 0x00000010 | NEOUSYS_CAN_MSG_USE_ID_FILTER |
| 96 | +) # only accept the direction specified in the message type |
| 97 | +NEOUSYS_CAN_MSG_USE_EXT_FILTER = ( |
| 98 | + 0x00000020 | NEOUSYS_CAN_MSG_USE_ID_FILTER |
| 99 | +) # filters on only extended identifiers |
| 100 | + |
| 101 | +NEOUSYS_CAN_STATUS_BUS_OFF = 0x00000080 |
| 102 | +NEOUSYS_CAN_STATUS_EWARN = ( |
| 103 | + 0x00000040 # can controller error level has reached warning level. |
| 104 | +) |
| 105 | +NEOUSYS_CAN_STATUS_EPASS = ( |
| 106 | + 0x00000020 # can controller error level has reached error passive level. |
| 107 | +) |
| 108 | +NEOUSYS_CAN_STATUS_LEC_STUFF = 0x00000001 # a bit stuffing error has occurred. |
| 109 | +NEOUSYS_CAN_STATUS_LEC_FORM = 0x00000002 # a formatting error has occurred. |
| 110 | +NEOUSYS_CAN_STATUS_LEC_ACK = 0x00000003 # an acknowledge error has occurred. |
| 111 | +NEOUSYS_CAN_STATUS_LEC_BIT1 = ( |
| 112 | + 0x00000004 # the bus remained a bit level of 1 for longer than is allowed. |
| 113 | +) |
| 114 | +NEOUSYS_CAN_STATUS_LEC_BIT0 = ( |
| 115 | + 0x00000005 # the bus remained a bit level of 0 for longer than is allowed. |
| 116 | +) |
| 117 | +NEOUSYS_CAN_STATUS_LEC_CRC = 0x00000006 # a crc error has occurred. |
| 118 | +NEOUSYS_CAN_STATUS_LEC_MASK = ( |
| 119 | + 0x00000007 # this is the mask for the can last error code (lec). |
| 120 | +) |
| 121 | + |
| 122 | +NEOUSYS_CANLIB = None |
| 123 | + |
| 124 | +try: |
| 125 | + if platform.system() == "Windows": |
| 126 | + NEOUSYS_CANLIB = WinDLL("./WDT_DIO.dll") |
| 127 | + else: |
| 128 | + NEOUSYS_CANLIB = CDLL("libwdt_dio.so") |
| 129 | + logger.info("Loaded Neousys WDT_DIO Can driver") |
| 130 | +except OSError as error: |
| 131 | + logger.info("Cannot Neousys CAN bus dll or share object: %d", format(error)) |
| 132 | + # NEOUSYS_CANLIB = None |
| 133 | + |
| 134 | + |
| 135 | +class NeousysBus(BusABC): |
| 136 | + """ Neousys CAN bus Class""" |
| 137 | + |
| 138 | + def __init__(self, channel, device=0, bitrate=500000, **kwargs): |
| 139 | + """ |
| 140 | + :param channel: channel number |
| 141 | + :param device: device number |
| 142 | + :param bitrate: bit rate. Renamed to bitrate in next release. |
| 143 | + """ |
| 144 | + super().__init__(channel, **kwargs) |
| 145 | + |
| 146 | + if NEOUSYS_CANLIB is not None: |
| 147 | + self.channel = channel |
| 148 | + |
| 149 | + self.device = device |
| 150 | + |
| 151 | + self.channel_info = "Neousys Can: device {}, channel {}".format( |
| 152 | + self.device, self.channel |
| 153 | + ) |
| 154 | + |
| 155 | + self.queue = queue.Queue() |
| 156 | + |
| 157 | + # Init with accept all and wanted bitrate |
| 158 | + self.init_config = NeousysCanSetup( |
| 159 | + bitrate, NEOUSYS_CAN_MSG_USE_ID_FILTER, 0, 0 |
| 160 | + ) |
| 161 | + |
| 162 | + self._neousys_recv_cb = NEOUSYS_CAN_MSG_CALLBACK(self._neousys_recv_cb) |
| 163 | + self._neousys_status_cb = NEOUSYS_CAN_STATUS_CALLBACK( |
| 164 | + self._neousys_status_cb |
| 165 | + ) |
| 166 | + |
| 167 | + if NEOUSYS_CANLIB.CAN_RegisterReceived(0, self._neousys_recv_cb) == 0: |
| 168 | + logger.error("Neousys CAN bus Setup receive callback") |
| 169 | + |
| 170 | + if NEOUSYS_CANLIB.CAN_RegisterStatus(0, self._neousys_status_cb) == 0: |
| 171 | + logger.error("Neousys CAN bus Setup status callback") |
| 172 | + |
| 173 | + if ( |
| 174 | + NEOUSYS_CANLIB.CAN_Setup( |
| 175 | + channel, byref(self.init_config), sizeof(self.init_config) |
| 176 | + ) |
| 177 | + == 0 |
| 178 | + ): |
| 179 | + logger.error("Neousys CAN bus Setup Error") |
| 180 | + |
| 181 | + if NEOUSYS_CANLIB.CAN_Start(channel) == 0: |
| 182 | + logger.error("Neousys CAN bus Start Error") |
| 183 | + |
| 184 | + def send(self, msg, timeout=None): |
| 185 | + """ |
| 186 | + :param msg: message to send |
| 187 | + :param timeout: timeout is not used here |
| 188 | + :return: |
| 189 | + """ |
| 190 | + |
| 191 | + if NEOUSYS_CANLIB is None: |
| 192 | + logger.error("Can't send msg as Neousys DLL/SO is not loaded") |
| 193 | + else: |
| 194 | + tx_msg = NeousysCanMsg( |
| 195 | + msg.arbitration_id, 0, 0, msg.dlc, (c_ubyte * 8)(*msg.data) |
| 196 | + ) |
| 197 | + |
| 198 | + if ( |
| 199 | + NEOUSYS_CANLIB.CAN_Send(self.channel, byref(tx_msg), sizeof(tx_msg)) |
| 200 | + == 0 |
| 201 | + ): |
| 202 | + logger.error("Neousys Can can't send message") |
| 203 | + |
| 204 | + def _recv_internal(self, timeout): |
| 205 | + msg = None |
| 206 | + |
| 207 | + if not self.queue.empty(): |
| 208 | + msg = self.queue.get() |
| 209 | + |
| 210 | + return msg, False |
| 211 | + |
| 212 | + def _neousys_recv_cb(self, msg, sizeof_msg): |
| 213 | + """ |
| 214 | + :param msg struct CAN_MSG |
| 215 | + :param sizeof_msg message number |
| 216 | + :return: |
| 217 | + """ |
| 218 | + remote_frame = False |
| 219 | + extended_frame = False |
| 220 | + |
| 221 | + msg_bytes = bytearray(msg.contents.data) |
| 222 | + |
| 223 | + if msg.contents.flags & NEOUSYS_CAN_MSG_REMOTE_FRAME: |
| 224 | + remote_frame = True |
| 225 | + |
| 226 | + if msg.contents.flags & NEOUSYS_CAN_MSG_EXTENDED_ID: |
| 227 | + extended_frame = True |
| 228 | + |
| 229 | + if msg.contents.flags & NEOUSYS_CAN_MSG_DATA_LOST: |
| 230 | + logger.error("_neousys_recv_cb flag CAN_MSG_DATA_LOST") |
| 231 | + |
| 232 | + msg = Message( |
| 233 | + timestamp=time.time(), |
| 234 | + arbitration_id=msg.contents.id, |
| 235 | + is_remote_frame=remote_frame, |
| 236 | + is_extended_id=extended_frame, |
| 237 | + channel=self.channel, |
| 238 | + dlc=msg.contents.len, |
| 239 | + data=msg_bytes[: msg.contents.len], |
| 240 | + ) |
| 241 | + |
| 242 | + # Reading happens in Callback function and |
| 243 | + # with Python-CAN it happens polling |
| 244 | + # so cache stuff in array to for poll |
| 245 | + if not self.queue.full(): |
| 246 | + self.queue.put(msg) |
| 247 | + else: |
| 248 | + logger.error("Neousys message Queue is full") |
| 249 | + |
| 250 | + def _neousys_status_cb(self, status): |
| 251 | + """ |
| 252 | + :param status BUS Status |
| 253 | + :return: |
| 254 | + """ |
| 255 | + logger.info("%s _neousys_status_cb: %d", self.init_config, status) |
| 256 | + |
| 257 | + def shutdown(self): |
| 258 | + if NEOUSYS_CANLIB is not None: |
| 259 | + NEOUSYS_CANLIB.CAN_Stop(self.channel) |
| 260 | + |
| 261 | + def fileno(self): |
| 262 | + # Return an invalid file descriptor as not used |
| 263 | + return -1 |
| 264 | + |
| 265 | + @staticmethod |
| 266 | + def _detect_available_configs(): |
| 267 | + channels = [] |
| 268 | + |
| 269 | + # There is only one channel |
| 270 | + channels.append({"interface": "neousys", "channel": 0}) |
| 271 | + return channels |
0 commit comments