|
| 1 | +# |
| 2 | +# This kind of interface can be found for example on Neousys POC-551VTC |
| 3 | +# One needs to have correct drivers and DLL (Share object for Linux) from Neousys |
| 4 | +# |
| 5 | +# https://www.neousys-tech.com/en/support-service/resources/category/299-poc-551vtc-driver |
| 6 | +# |
| 7 | +# Beware this is only tested on Linux kernel higher thant 5.3. This should be drop in |
| 8 | +# with Windows but you have to replace with correct named DLL |
| 9 | +# |
| 10 | + |
| 11 | +import warnings |
| 12 | +from ctypes import * |
| 13 | +import threading |
| 14 | +import logging |
| 15 | +import platform |
| 16 | +import time |
| 17 | +from can import BusABC, Message |
| 18 | + |
| 19 | +logger = logging.getLogger(__name__) |
| 20 | + |
| 21 | + |
| 22 | +class WDT_CAN_SETUP(Structure): |
| 23 | + _fields_ = [ |
| 24 | + ("bitRate", c_uint), |
| 25 | + ("recvConfig", c_uint), |
| 26 | + ("recvId", c_uint), |
| 27 | + ("recvMask", c_uint), |
| 28 | + ] |
| 29 | + |
| 30 | + |
| 31 | +class WDT_CAN_MSG(Structure): |
| 32 | + _fields_ = [ |
| 33 | + ("id", c_uint), |
| 34 | + ("flags", c_ushort), |
| 35 | + ("extra", c_ubyte), |
| 36 | + ("len", c_ubyte), |
| 37 | + ("data", c_ubyte * 8), |
| 38 | + ] |
| 39 | + |
| 40 | + |
| 41 | +# valid:2~16, sum of the Synchronization, Propagation, and Phase Buffer 1 segments, measured in time quanta. |
| 42 | +# valid:1~8, the Phase Buffer 2 segment in time quanta. |
| 43 | +# valid:1~4, Resynchronization Jump Width in time quanta |
| 44 | +# valid:1~1023, CAN_CLK divider used to determine time quanta |
| 45 | +class WDT_CAN_BITCLK(Structure): |
| 46 | + _fields_ = [ |
| 47 | + ("syncPropPhase1Seg", c_ushort), |
| 48 | + ("phase2Seg", c_ushort), |
| 49 | + ("jumpWidth", c_ushort), |
| 50 | + ("quantumPrescaler", c_ushort), |
| 51 | + ] |
| 52 | + |
| 53 | + |
| 54 | +WDT_CAN_MSG_CALLBACK = CFUNCTYPE(None, POINTER(WDT_CAN_MSG), c_uint) |
| 55 | +WDT_CAN_STATUS_CALLBACK = CFUNCTYPE(None, c_uint) |
| 56 | + |
| 57 | +WDT_CAN_MSG_EXTENDED_ID = 0x0004 |
| 58 | +WDT_CAN_MSG_REMOTE_FRAME = 0x0040 |
| 59 | +WDT_CAN_MSG_DATA_NEW = 0x0080 |
| 60 | +WDT_CAN_MSG_DATA_LOST = 0x0100 |
| 61 | + |
| 62 | +WDT_CAN_MSG_USE_ID_FILTER = 0x00000008 |
| 63 | +WDT_CAN_MSG_USE_DIR_FILTER = ( |
| 64 | + 0x00000010 | WDT_CAN_MSG_USE_ID_FILTER |
| 65 | +) # only accept the direction specified in the message type |
| 66 | +WDT_CAN_MSG_USE_EXT_FILTER = ( |
| 67 | + 0x00000020 | WDT_CAN_MSG_USE_ID_FILTER |
| 68 | +) # filters on only extended identifiers |
| 69 | + |
| 70 | +WDT_CAN_STATUS_BUS_OFF = 0x00000080 |
| 71 | +WDT_CAN_STATUS_EWARN = ( |
| 72 | + 0x00000040 # can controller error level has reached warning level. |
| 73 | +) |
| 74 | +WDT_CAN_STATUS_EPASS = ( |
| 75 | + 0x00000020 # can controller error level has reached error passive level. |
| 76 | +) |
| 77 | +WDT_CAN_STATUS_LEC_STUFF = 0x00000001 # a bit stuffing error has occurred. |
| 78 | +WDT_CAN_STATUS_LEC_FORM = 0x00000002 # a formatting error has occurred. |
| 79 | +WDT_CAN_STATUS_LEC_ACK = 0x00000003 # an acknowledge error has occurred. |
| 80 | +WDT_CAN_STATUS_LEC_BIT1 = ( |
| 81 | + 0x00000004 # the bus remained a bit level of 1 for longer than is allowed. |
| 82 | +) |
| 83 | +WDT_CAN_STATUS_LEC_BIT0 = ( |
| 84 | + 0x00000005 # the bus remained a bit level of 0 for longer than is allowed. |
| 85 | +) |
| 86 | +WDT_CAN_STATUS_LEC_CRC = 0x00000006 # a crc error has occurred. |
| 87 | +WDT_CAN_STATUS_LEC_MASK = ( |
| 88 | + 0x00000007 # this is the mask for the can last error code (lec). |
| 89 | +) |
| 90 | + |
| 91 | + |
| 92 | +class NeousysWdtBus(BusABC): |
| 93 | + def __init__(self, channel, device=0, bitrate=500000, **kwargs): |
| 94 | + """ |
| 95 | + :param channel: channel number |
| 96 | + :param device: device number |
| 97 | + :param bitrate: bit rate. Renamed to bitrate in next release. |
| 98 | + """ |
| 99 | + super(NeousysWdtBus, self).__init__(channel, **kwargs) |
| 100 | + |
| 101 | + self.canlib = None |
| 102 | + |
| 103 | + try: |
| 104 | + if platform.system() == "Windows": |
| 105 | + self.canlib = WinDLL("./WDT_DIO.dll") |
| 106 | + else: |
| 107 | + self.canlib = CDLL("libwdt_dio.so") |
| 108 | + |
| 109 | + logger.info("Loaded Neousys WDT_DIO Can driver") |
| 110 | + |
| 111 | + self.channel = channel |
| 112 | + |
| 113 | + self.device = device |
| 114 | + |
| 115 | + self.channel_info = "Neousys WDT_DIO Can: device {}, channel {}".format( |
| 116 | + self.device, self.channel |
| 117 | + ) |
| 118 | + |
| 119 | + self.lock = threading.Lock() |
| 120 | + self.recv_msg_array = [] |
| 121 | + |
| 122 | + # Init with accept all and wanted bitrate |
| 123 | + self.init_config = WDT_CAN_SETUP(bitrate, WDT_CAN_MSG_USE_ID_FILTER, 0, 0) |
| 124 | + |
| 125 | + # These can be needed in some old 2.x consepts not needed in 3.6 though |
| 126 | + # self.canlib.CAN_RegisterReceived.argtypes = [c_uint, WDT_CAN_MSG_CALLBACK] |
| 127 | + # self.canlib.CAN_RegisterReceived.restype = c_int |
| 128 | + # self.canlib.CAN_RegisterStatus.argtypes = [c_uint, WDT_CAN_STATUS_CALLBACK] |
| 129 | + # self.canlib.CAN_RegisterStatus.restype = c_int |
| 130 | + |
| 131 | + self._WDTCAN_Received = WDT_CAN_MSG_CALLBACK(self._WDTCAN_Received) |
| 132 | + self._WDTCAN_Status = WDT_CAN_STATUS_CALLBACK(self._WDTCAN_Status) |
| 133 | + |
| 134 | + if self.canlib.CAN_RegisterReceived(0, self._WDTCAN_Received) == 0: |
| 135 | + logger.error("Neousys WDT_DIO CANBus Setup receive callback") |
| 136 | + |
| 137 | + if self.canlib.CAN_RegisterStatus(0, self._WDTCAN_Status) == 0: |
| 138 | + logger.error("Neousys WDT_DIO CANBus Setup status callback") |
| 139 | + |
| 140 | + if ( |
| 141 | + self.canlib.CAN_Setup( |
| 142 | + channel, byref(self.init_config), sizeof(self.init_config) |
| 143 | + ) |
| 144 | + == 0 |
| 145 | + ): |
| 146 | + logger.error("Neousys WDT_DIO CANBus Setup Error") |
| 147 | + |
| 148 | + if self.canlib.CAN_Start(channel) == 0: |
| 149 | + logger.error("Neousys WDT_DIO CANBus Start Error") |
| 150 | + |
| 151 | + except OSError as e: |
| 152 | + logger.info("Cannot Neousys WDT_DIO CANBus dll or share object") |
| 153 | + |
| 154 | + def send(self, msg, timeout=None): |
| 155 | + """ |
| 156 | + :param msg: message to send |
| 157 | + :param timeout: timeout is not used here |
| 158 | + :return: |
| 159 | + """ |
| 160 | + |
| 161 | + if self.canlib is None: |
| 162 | + print("Can't send msg as Neousys WDT_DIO DLL/SO is not loaded") |
| 163 | + else: |
| 164 | + tx_msg = WDT_CAN_MSG( |
| 165 | + msg.arbitration_id, 0, 0, msg.dlc, (c_ubyte * 8)(*msg.data) |
| 166 | + ) |
| 167 | + |
| 168 | + if self.canlib.CAN_Send(self.channel, byref(tx_msg), sizeof(tx_msg)) == 0: |
| 169 | + logger.error("Neousys WDT_DIO Can can't send message") |
| 170 | + |
| 171 | + def _recv_internal(self, timeout): |
| 172 | + msg = None |
| 173 | + |
| 174 | + # If there is message waiting in array |
| 175 | + # pass it as new message |
| 176 | + if len(self.recv_msg_array) > 0: |
| 177 | + self.lock.acquire() |
| 178 | + msg = self.recv_msg_array.pop(0) |
| 179 | + self.lock.release() |
| 180 | + |
| 181 | + return msg, False |
| 182 | + |
| 183 | + def _WDTCAN_Received(self, lpMsg, cbMsg): |
| 184 | + """ |
| 185 | + :param lpMsg struct CAN_MSG |
| 186 | + :param cbMsg message number |
| 187 | + :return: |
| 188 | + """ |
| 189 | + remote_frame = False |
| 190 | + extended_frame = False |
| 191 | + |
| 192 | + msg_bytes = bytearray(lpMsg.contents.data) |
| 193 | + |
| 194 | + if lpMsg.contents.flags & WDT_CAN_MSG_REMOTE_FRAME: |
| 195 | + remote_frame = True |
| 196 | + |
| 197 | + if lpMsg.contents.flags & WDT_CAN_MSG_EXTENDED_ID: |
| 198 | + extended_frame = True |
| 199 | + |
| 200 | + if lpMsg.contents.flags & WDT_CAN_MSG_DATA_LOST: |
| 201 | + print("_WDTCAN_Received flag CAN_MSG_DATA_LOST") |
| 202 | + |
| 203 | + msg = Message( |
| 204 | + timestamp=time.time(), |
| 205 | + arbitration_id=lpMsg.contents.id, |
| 206 | + is_remote_frame=remote_frame, |
| 207 | + is_extended_id=extended_frame, |
| 208 | + channel=self.channel, |
| 209 | + dlc=lpMsg.contents.len, |
| 210 | + data=msg_bytes[: lpMsg.contents.len], |
| 211 | + ) |
| 212 | + |
| 213 | + # Reading happens in Callback function and |
| 214 | + # with Python-CAN it happens polling |
| 215 | + # so cache stuff in array to for poll |
| 216 | + self.lock.acquire() |
| 217 | + self.recv_msg_array.append(msg) |
| 218 | + self.lock.release() |
| 219 | + |
| 220 | + def _WDTCAN_Status(status): |
| 221 | + """ |
| 222 | + :param status BUS Status |
| 223 | + :return: |
| 224 | + """ |
| 225 | + |
| 226 | + print("_WDTCAN_Status" + str(status)) |
| 227 | + |
| 228 | + def shutdown(self): |
| 229 | + WDTCan.CAN_Stop(self.channel) |
0 commit comments