Skip to content

Commit 4b1acde

Browse files
cowo78Giuseppe Corbelli
andauthored
Ixxat bus state and hardware errors detection (#1141)
* Added comment to CAN_MSGFLAGS_* and CAN_MSGFLAGS2_* constants * CANMSGINFO.bAddFlags has been renamed to bFlags2 in IXXAT VCI4 * Added a comment that CANMSGINFO.Bytes.bAddFlags is called bFlags2 in VCI v4. * Implementation is now tested against VCI v4 * Dropped manual timeout handling as it is a job done by BusABC.read(). Better handling of CAN error messages: - In case of HW errors (overrun, warning limit exceeded, bus coupling error) raise VCIError - In case of error log IXXAT-specific error codes * Fixed unbound variable usage. Use log.warning instead of deprecated log.warn. * Now wrapping IXXAT VCI v4 * Added CAN_OPMODE_AUTOBAUD controller operating mode. * Mapped symbol canChannelGetStatus, used to implement the 'state' property. Hardware error checking in _recv_internal() handles BUS OFF situation. * Use CANMSGINFO.bAddFlags instead of CANMSGINFO.bFlags2 * Call canControlClose() AFTER canControlReset() or it will always fail * Added CANMSG.__str__ * Removed conflict marker * Renamed parameter 'msg' to 'msgs' in _send_periodic_internal(), consistent with BusABC * Changed plain format() calls to f-strings as per review * Removed binascii module dependency using memoryview Co-authored-by: Giuseppe Corbelli <[email protected]>
1 parent 8c4ed5f commit 4b1acde

File tree

7 files changed

+183
-122
lines changed

7 files changed

+183
-122
lines changed

can/interfaces/ixxat/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
2-
Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems
2+
Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems
33
4-
Copyright (C) 2016 Giuseppe Corbelli <[email protected]>
4+
Copyright (C) 2016-2021 Giuseppe Corbelli <[email protected]>
55
"""
66

77
from can.interfaces.ixxat.canlib import IXXATBus

can/interfaces/ixxat/canlib.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import can.interfaces.ixxat.canlib_vcinpl2 as vcinpl2
33

44
from can import BusABC, Message
5+
from can.bus import BusState
6+
57
from typing import Optional
68

79

@@ -11,7 +13,8 @@ class IXXATBus(BusABC):
1113
Based on the C implementation of IXXAT, two different dlls are provided by IXXAT, one to work with CAN,
1214
the other with CAN-FD.
1315
14-
This class only delegates to related implementation (in calib_vcinpl or canlib_vcinpl2) class depending on fd user option.
16+
This class only delegates to related implementation (in calib_vcinpl or canlib_vcinpl2)
17+
class depending on fd user option.
1518
"""
1619

1720
def __init__(
@@ -140,8 +143,18 @@ def _recv_internal(self, timeout):
140143
def send(self, msg: Message, timeout: Optional[float] = None) -> None:
141144
return self.bus.send(msg, timeout)
142145

143-
def _send_periodic_internal(self, msg, period, duration=None):
144-
return self.bus._send_periodic_internal(msg, period, duration)
146+
def _send_periodic_internal(self, msgs, period, duration=None):
147+
return self.bus._send_periodic_internal(msgs, period, duration)
145148

146149
def shutdown(self):
147150
return self.bus.shutdown()
151+
152+
@property
153+
def state(self) -> BusState:
154+
"""
155+
Return the current state of the hardware
156+
"""
157+
return self.bus.state
158+
159+
160+
# ~class IXXATBus(BusABC): ---------------------------------------------------

can/interfaces/ixxat/canlib_vcinpl.py

Lines changed: 122 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems
2+
Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems
33
44
TODO: We could implement this interface such that setting other filters
55
could work when the initial filters were set to zero using the
@@ -16,6 +16,7 @@
1616
from typing import Optional, Callable, Tuple
1717

1818
from can import BusABC, Message
19+
from can.bus import BusState
1920
from can.exceptions import CanInterfaceNotImplementedError, CanInitializationError
2021
from can.broadcastmanager import (
2122
LimitedDurationCyclicSendTaskABC,
@@ -38,7 +39,6 @@
3839

3940
log = logging.getLogger("can.ixxat")
4041

41-
from time import perf_counter
4242

4343
# Hack to have vciFormatError as a free function, see below
4444
vciFormatError = None
@@ -225,6 +225,13 @@ def __check_status(result, function, args):
225225
(HANDLE, ctypes.c_uint32, structures.PCANMSG),
226226
__check_status,
227227
)
228+
# HRESULT canChannelGetStatus (HANDLE hCanChn, PCANCHANSTATUS pStatus );
229+
_canlib.map_symbol(
230+
"canChannelGetStatus",
231+
ctypes.c_long,
232+
(HANDLE, structures.PCANCHANSTATUS),
233+
__check_status,
234+
)
228235

229236
# EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl );
230237
_canlib.map_symbol(
@@ -509,11 +516,11 @@ def __init__(
509516
== bytes(unique_hardware_id, "ascii")
510517
):
511518
break
512-
else:
513-
log.debug(
514-
"Ignoring IXXAT with hardware id '%s'.",
515-
self._device_info.UniqueHardwareId.AsChar.decode("ascii"),
516-
)
519+
520+
log.debug(
521+
"Ignoring IXXAT with hardware id '%s'.",
522+
self._device_info.UniqueHardwareId.AsChar.decode("ascii"),
523+
)
517524
_canlib.vciEnumDeviceClose(self._device_handle)
518525

519526
try:
@@ -522,7 +529,9 @@ def __init__(
522529
ctypes.byref(self._device_handle),
523530
)
524531
except Exception as exception:
525-
raise CanInitializationError(f"Could not open device: {exception}")
532+
raise CanInitializationError(
533+
f"Could not open device: {exception}"
534+
) from exception
526535

527536
log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar)
528537

@@ -543,7 +552,7 @@ def __init__(
543552
except Exception as exception:
544553
raise CanInitializationError(
545554
f"Could not open and initialize channel: {exception}"
546-
)
555+
) from exception
547556

548557
# Signal TX/RX events when at least one frame has been handled
549558
_canlib.canChannelInitialize(
@@ -637,85 +646,88 @@ def flush_tx_buffer(self):
637646

638647
def _recv_internal(self, timeout):
639648
"""Read a message from IXXAT device."""
640-
641-
# TODO: handling CAN error messages?
642649
data_received = False
643650

644-
if timeout == 0:
651+
if self._inWaiting() or timeout == 0:
645652
# Peek without waiting
646-
try:
647-
_canlib.canChannelPeekMessage(
648-
self._channel_handle, ctypes.byref(self._message)
649-
)
650-
except (VCITimeout, VCIRxQueueEmptyError):
651-
return None, True
652-
else:
653-
if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA:
654-
data_received = True
653+
recv_function = functools.partial(
654+
_canlib.canChannelPeekMessage,
655+
self._channel_handle,
656+
ctypes.byref(self._message),
657+
)
655658
else:
656659
# Wait if no message available
657-
if timeout is None or timeout < 0:
658-
remaining_ms = constants.INFINITE
659-
t0 = None
660-
else:
661-
timeout_ms = int(timeout * 1000)
662-
remaining_ms = timeout_ms
663-
t0 = perf_counter()
664-
665-
while True:
666-
try:
667-
_canlib.canChannelReadMessage(
668-
self._channel_handle, remaining_ms, ctypes.byref(self._message)
660+
timeout = (
661+
constants.INFINITE
662+
if (timeout is None or timeout < 0)
663+
else int(timeout * 1000)
664+
)
665+
recv_function = functools.partial(
666+
_canlib.canChannelReadMessage,
667+
self._channel_handle,
668+
timeout,
669+
ctypes.byref(self._message),
670+
)
671+
672+
try:
673+
recv_function()
674+
except (VCITimeout, VCIRxQueueEmptyError):
675+
# Ignore the 2 errors, overall timeout is handled by BusABC.recv
676+
pass
677+
else:
678+
# See if we got a data or info/error messages
679+
if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA:
680+
data_received = True
681+
elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO:
682+
log.info(
683+
CAN_INFO_MESSAGES.get(
684+
self._message.abData[0],
685+
f"Unknown CAN info message code {self._message.abData[0]}",
669686
)
670-
except (VCITimeout, VCIRxQueueEmptyError):
671-
# Ignore the 2 errors, the timeout is handled manually with the perf_counter()
672-
pass
687+
)
688+
elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR:
689+
if self._message.uMsgInfo.Bytes.bFlags & constants.CAN_MSGFLAGS_OVR:
690+
log.warning("CAN error: data overrun")
673691
else:
674-
# See if we got a data or info/error messages
675-
if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA:
676-
data_received = True
677-
break
678-
elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO:
679-
log.info(
680-
CAN_INFO_MESSAGES.get(
681-
self._message.abData[0],
682-
"Unknown CAN info message code {}".format(
683-
self._message.abData[0]
684-
),
685-
)
686-
)
687-
688-
elif (
689-
self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR
690-
):
691-
log.warning(
692-
CAN_ERROR_MESSAGES.get(
693-
self._message.abData[0],
694-
"Unknown CAN error message code {}".format(
695-
self._message.abData[0]
696-
),
697-
)
692+
log.warning(
693+
CAN_ERROR_MESSAGES.get(
694+
self._message.abData[0],
695+
f"Unknown CAN error message code {self._message.abData[0]}",
698696
)
699-
700-
elif (
701-
self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_STATUS
702-
):
703-
log.info(_format_can_status(self._message.abData[0]))
704-
if self._message.abData[0] & constants.CAN_STATUS_BUSOFF:
705-
raise VCIBusOffError()
706-
707-
elif (
708-
self._message.uMsgInfo.Bits.type
709-
== constants.CAN_MSGTYPE_TIMEOVR
710-
):
711-
pass
712-
else:
713-
log.warning("Unexpected message info type")
714-
715-
if t0 is not None:
716-
remaining_ms = timeout_ms - int((perf_counter() - t0) * 1000)
717-
if remaining_ms < 0:
718-
break
697+
)
698+
log.warning(
699+
"CAN message flags bAddFlags/bFlags2 0x%02X bflags 0x%02X",
700+
self._message.uMsgInfo.Bytes.bAddFlags,
701+
self._message.uMsgInfo.Bytes.bFlags,
702+
)
703+
elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR:
704+
pass
705+
else:
706+
log.warning(
707+
"Unexpected message info type 0x%X",
708+
self._message.uMsgInfo.Bits.type,
709+
)
710+
finally:
711+
if not data_received:
712+
# Check hard errors
713+
status = structures.CANLINESTATUS()
714+
_canlib.canControlGetStatus(self._control_handle, ctypes.byref(status))
715+
error_byte_1 = status.dwStatus & 0x0F
716+
error_byte_2 = status.dwStatus & 0xF0
717+
if error_byte_1 > constants.CAN_STATUS_TXPEND:
718+
# CAN_STATUS_OVRRUN = 0x02 # data overrun occurred
719+
# CAN_STATUS_ERRLIM = 0x04 # error warning limit exceeded
720+
# CAN_STATUS_BUSOFF = 0x08 # bus off status
721+
if error_byte_1 & constants.CAN_STATUS_OVRRUN:
722+
raise VCIError("Data overrun occurred")
723+
elif error_byte_1 & constants.CAN_STATUS_ERRLIM:
724+
raise VCIError("Error warning limit exceeded")
725+
elif error_byte_1 & constants.CAN_STATUS_BUSOFF:
726+
raise VCIError("Bus off status")
727+
elif error_byte_2 > constants.CAN_STATUS_ININIT:
728+
# CAN_STATUS_BUSCERR = 0x20 # bus coupling error
729+
if error_byte_2 & constants.CAN_STATUS_BUSCERR:
730+
raise VCIError("Bus coupling error")
719731

720732
if not data_received:
721733
# Timed out / can message type is not DATA
@@ -764,11 +776,12 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None:
764776
_canlib.canChannelSendMessage(
765777
self._channel_handle, int(timeout * 1000), message
766778
)
767-
768779
else:
769780
_canlib.canChannelPostMessage(self._channel_handle, message)
781+
# Want to log outgoing messages?
782+
# log.log(self.RECV_LOGGING_LEVEL, "Sent: %s", message)
770783

771-
def _send_periodic_internal(self, msg, period, duration=None):
784+
def _send_periodic_internal(self, msgs, period, duration=None):
772785
"""Send a message using built-in cyclic transmit list functionality."""
773786
if self._scheduler is None:
774787
self._scheduler = HANDLE()
@@ -778,17 +791,43 @@ def _send_periodic_internal(self, msg, period, duration=None):
778791
self._scheduler_resolution = caps.dwClockFreq / caps.dwCmsDivisor
779792
_canlib.canSchedulerActivate(self._scheduler, constants.TRUE)
780793
return CyclicSendTask(
781-
self._scheduler, msg, period, duration, self._scheduler_resolution
794+
self._scheduler, msgs, period, duration, self._scheduler_resolution
782795
)
783796

784797
def shutdown(self):
785798
if self._scheduler is not None:
786799
_canlib.canSchedulerClose(self._scheduler)
787800
_canlib.canChannelClose(self._channel_handle)
788801
_canlib.canControlStart(self._control_handle, constants.FALSE)
802+
_canlib.canControlReset(self._control_handle)
789803
_canlib.canControlClose(self._control_handle)
790804
_canlib.vciDeviceClose(self._device_handle)
791805

806+
@property
807+
def state(self) -> BusState:
808+
"""
809+
Return the current state of the hardware
810+
"""
811+
status = structures.CANLINESTATUS()
812+
_canlib.canControlGetStatus(self._control_handle, ctypes.byref(status))
813+
if status.bOpMode == constants.CAN_OPMODE_LISTONLY:
814+
return BusState.PASSIVE
815+
816+
error_byte_1 = status.dwStatus & 0x0F
817+
# CAN_STATUS_BUSOFF = 0x08 # bus off status
818+
if error_byte_1 & constants.CAN_STATUS_BUSOFF:
819+
return BusState.ERROR
820+
821+
error_byte_2 = status.dwStatus & 0xF0
822+
# CAN_STATUS_BUSCERR = 0x20 # bus coupling error
823+
if error_byte_2 & constants.CAN_STATUS_BUSCERR:
824+
raise BusState.ERROR
825+
826+
return BusState.ACTIVE
827+
828+
829+
# ~class IXXATBus(BusABC): ---------------------------------------------------
830+
792831

793832
class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC):
794833
"""A message in the cyclic transmit list."""

can/interfaces/ixxat/canlib_vcinpl2.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -825,9 +825,7 @@ def _recv_internal(self, timeout):
825825
log.info(
826826
CAN_INFO_MESSAGES.get(
827827
self._message.abData[0],
828-
"Unknown CAN info message code {}".format(
829-
self._message.abData[0]
830-
),
828+
f"Unknown CAN info message code {self._message.abData[0]}",
831829
)
832830
)
833831

@@ -837,9 +835,7 @@ def _recv_internal(self, timeout):
837835
log.warning(
838836
CAN_ERROR_MESSAGES.get(
839837
self._message.abData[0],
840-
"Unknown CAN error message code {}".format(
841-
self._message.abData[0]
842-
),
838+
f"Unknown CAN error message code {self._message.abData[0]}",
843839
)
844840
)
845841

@@ -933,7 +929,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None:
933929
else:
934930
_canlib.canChannelPostMessage(self._channel_handle, message)
935931

936-
def _send_periodic_internal(self, msg, period, duration=None):
932+
def _send_periodic_internal(self, msgs, period, duration=None):
937933
"""Send a message using built-in cyclic transmit list functionality."""
938934
if self._scheduler is None:
939935
self._scheduler = HANDLE()
@@ -945,7 +941,7 @@ def _send_periodic_internal(self, msg, period, duration=None):
945941
) # TODO: confirm
946942
_canlib.canSchedulerActivate(self._scheduler, constants.TRUE)
947943
return CyclicSendTask(
948-
self._scheduler, msg, period, duration, self._scheduler_resolution
944+
self._scheduler, msgs, period, duration, self._scheduler_resolution
949945
)
950946

951947
def shutdown(self):

0 commit comments

Comments
 (0)