1
1
"""
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
3
3
4
4
TODO: We could implement this interface such that setting other filters
5
5
could work when the initial filters were set to zero using the
16
16
from typing import Optional , Callable , Tuple
17
17
18
18
from can import BusABC , Message
19
+ from can .bus import BusState
19
20
from can .exceptions import CanInterfaceNotImplementedError , CanInitializationError
20
21
from can .broadcastmanager import (
21
22
LimitedDurationCyclicSendTaskABC ,
38
39
39
40
log = logging .getLogger ("can.ixxat" )
40
41
41
- from time import perf_counter
42
42
43
43
# Hack to have vciFormatError as a free function, see below
44
44
vciFormatError = None
@@ -225,6 +225,13 @@ def __check_status(result, function, args):
225
225
(HANDLE , ctypes .c_uint32 , structures .PCANMSG ),
226
226
__check_status ,
227
227
)
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
+ )
228
235
229
236
# EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl );
230
237
_canlib .map_symbol (
@@ -509,11 +516,11 @@ def __init__(
509
516
== bytes (unique_hardware_id , "ascii" )
510
517
):
511
518
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
+ )
517
524
_canlib .vciEnumDeviceClose (self ._device_handle )
518
525
519
526
try :
@@ -522,7 +529,9 @@ def __init__(
522
529
ctypes .byref (self ._device_handle ),
523
530
)
524
531
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
526
535
527
536
log .info ("Using unique HW ID %s" , self ._device_info .UniqueHardwareId .AsChar )
528
537
@@ -543,7 +552,7 @@ def __init__(
543
552
except Exception as exception :
544
553
raise CanInitializationError (
545
554
f"Could not open and initialize channel: { exception } "
546
- )
555
+ ) from exception
547
556
548
557
# Signal TX/RX events when at least one frame has been handled
549
558
_canlib .canChannelInitialize (
@@ -637,85 +646,88 @@ def flush_tx_buffer(self):
637
646
638
647
def _recv_internal (self , timeout ):
639
648
"""Read a message from IXXAT device."""
640
-
641
- # TODO: handling CAN error messages?
642
649
data_received = False
643
650
644
- if timeout == 0 :
651
+ if self . _inWaiting () or timeout == 0 :
645
652
# 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
+ )
655
658
else :
656
659
# 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 ]} " ,
669
686
)
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" )
673
691
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 ]} " ,
698
696
)
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" )
719
731
720
732
if not data_received :
721
733
# Timed out / can message type is not DATA
@@ -764,11 +776,12 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None:
764
776
_canlib .canChannelSendMessage (
765
777
self ._channel_handle , int (timeout * 1000 ), message
766
778
)
767
-
768
779
else :
769
780
_canlib .canChannelPostMessage (self ._channel_handle , message )
781
+ # Want to log outgoing messages?
782
+ # log.log(self.RECV_LOGGING_LEVEL, "Sent: %s", message)
770
783
771
- def _send_periodic_internal (self , msg , period , duration = None ):
784
+ def _send_periodic_internal (self , msgs , period , duration = None ):
772
785
"""Send a message using built-in cyclic transmit list functionality."""
773
786
if self ._scheduler is None :
774
787
self ._scheduler = HANDLE ()
@@ -778,17 +791,43 @@ def _send_periodic_internal(self, msg, period, duration=None):
778
791
self ._scheduler_resolution = caps .dwClockFreq / caps .dwCmsDivisor
779
792
_canlib .canSchedulerActivate (self ._scheduler , constants .TRUE )
780
793
return CyclicSendTask (
781
- self ._scheduler , msg , period , duration , self ._scheduler_resolution
794
+ self ._scheduler , msgs , period , duration , self ._scheduler_resolution
782
795
)
783
796
784
797
def shutdown (self ):
785
798
if self ._scheduler is not None :
786
799
_canlib .canSchedulerClose (self ._scheduler )
787
800
_canlib .canChannelClose (self ._channel_handle )
788
801
_canlib .canControlStart (self ._control_handle , constants .FALSE )
802
+ _canlib .canControlReset (self ._control_handle )
789
803
_canlib .canControlClose (self ._control_handle )
790
804
_canlib .vciDeviceClose (self ._device_handle )
791
805
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
+
792
831
793
832
class CyclicSendTask (LimitedDurationCyclicSendTaskABC , RestartableCyclicTaskABC ):
794
833
"""A message in the cyclic transmit list."""
0 commit comments