Source code for canlib.canlib.channeldata

import ctypes as ct

from .. import VersionNumber, deprecation
from ..ean import EAN
from .busparams import ClockInfo, BusParamTqLimits
from .enums import (BusTypeGroup, ChannelCap, ChannelCapEx, ChannelDataItem,
                    ChannelFlags, DriverCap, Error, HardwareType, LoggerType,
                    OperationalMode, RemoteType, TransceiverType)
from .exceptions import CanError, CanNotFound, CanNotImplementedError
from .structures import CanBusParamLimits
from .wrapper import dll, getNumberOfChannels

ATTRIBUTES = {
    'channel_cap': dict(
        item=ChannelDataItem.CHANNEL_CAP,
        ctype=ct.c_uint32,
        ptype=ChannelCap,
        __doc__="A `ChannelCap` object with the capabilities of the channel as flags. Also see `ChannelData.channel_cap_mask`.",
    ),
    'trans_cap': dict(
        item=ChannelDataItem.TRANS_CAP,
        ctype=ct.c_uint32,
        ptype=DriverCap,
        __doc__="A `DriverCap` object with the capabilities of the transceiver as flags. Not implemented in Linux.",
    ),
    'channel_flags': dict(
        item=ChannelDataItem.CHANNEL_FLAGS,
        ctype=ct.c_uint32,
        ptype=ChannelFlags,
        __doc__="A `ChannelFlags` object with the status of the channel as flags.",
    ),
    'card_type': dict(
        item=ChannelDataItem.CARD_TYPE,
        ctype=ct.c_uint32,
        ptype=HardwareType,
        __doc__="A member of the `HardwareType` enum representing the hardware type of the card.",
    ),
    'card_number': dict(
        item=ChannelDataItem.CARD_NUMBER,
        ctype=ct.c_uint32,
        ptype=int,
        __doc__="An `int` with the card's number in the computer. Each card type is numbered separately.",
    ),
    'chan_no_on_card': dict(
        item=ChannelDataItem.CHAN_NO_ON_CARD,
        ctype=ct.c_uint32,
        ptype=int,
        __doc__="An `int` of the channel number on the card.",
    ),
    'card_serial_no': dict(
        item=ChannelDataItem.CARD_SERIAL_NO,
        ctype=ct.c_uint64,
        ptype=int,
        __doc__="An `int` with the serial number of the card, or 0 if it doesn't have a serial number.",
    ),
    'trans_serial_no': dict(
        item=ChannelDataItem.TRANS_SERIAL_NO,
        ctype=ct.c_uint32,
        ptype=int,
        __doc__="An `int` with the serial number of the transceiver, or 0 if it doesn't have a serial number. Not implemented in Linux.",
    ),
    'card_firmware_rev': dict(
        item=ChannelDataItem.CARD_FIRMWARE_REV,
        ctype=ct.c_uint16 * 4,
        # little byteorder assumed:
        ptype=lambda t: VersionNumber(t[3], t[2], release=t[1], build=t[0]),
        __doc__="A `canlib.VersionNumber` object with the version of the card's firmware.",
    ),
    'card_hardware_rev': dict(
        item=ChannelDataItem.CARD_HARDWARE_REV,
        ctype=ct.c_uint16 * 4,
        ptype=lambda t: VersionNumber(t[1], t[0]),  # little byteorder assumed
        __doc__="A `canlib.VersionNumber` object with the version of the card's hardware.",
    ),
    'card_upc_no': dict(
        item=ChannelDataItem.CARD_UPC_NO,
        ctype=ct.c_ubyte * 8,
        ptype=EAN.from_bcd,
        __doc__="An `canlib.EAN` object with the EAN of the card, or `None` if it doesn't one.",
    ),
    'clock_info': dict(
        item=ChannelDataItem.CLOCK_INFO,
        ctype=ct.c_uint * 5,
        ptype=ClockInfo.from_list,
        __doc__="A `~canlib.canlib.busparams.ClockInfo` object with clock characteristics for the device (added in v1.16).",
    ),
    'trans_upc_no': dict(
        item=ChannelDataItem.TRANS_UPC_NO,
        ctype=ct.c_ubyte * 8,
        ptype=EAN.from_bcd,
        __doc__="An `canlib.EAN` object with the EAN of the transceiver, or `None` if it doesn't have one. Not implemented in Linux.",
    ),
    # 'channel_name': dict(  # ~deprecated
    #     item=ChannelDataItem.CHANNEL_NAME,
    # ),
    'dll_file_version': dict(
        item=ChannelDataItem.DLL_FILE_VERSION,
        ctype=ct.c_uint16 * 4,
        ptype=lambda t: VersionNumber(t[3], t[2], t[1]),  # little byteorder assumed,
        __doc__="A `canlib.VersionNumber` with the version of the dll file.",
    ),
    'dll_product_version': dict(
        item=ChannelDataItem.DLL_PRODUCT_VERSION,
        ctype=ct.c_uint16 * 4,
        ptype=lambda t: VersionNumber(t[3], t[2]),  # little byteorder assumed,
        __doc__="A `canlib.VersionNumber` with the product version of the dll.",
    ),
    'dll_filetype': dict(
        item=ChannelDataItem.DLL_FILETYPE,
        ctype=ct.c_uint32,
        ptype=int,
        __doc__="1 if \"kvalapw.dll\" is used, 2 if \"kvalapw2.dll\"",
    ),
    'trans_type': dict(
        item=ChannelDataItem.TRANS_TYPE,
        ctype=ct.c_uint32,
        ptype=TransceiverType,
        __doc__="A member of the `TransceiverType` enum.",
    ),
    'device_physical_position': dict(
        item=ChannelDataItem.DEVICE_PHYSICAL_POSITION,
        ctype=ct.c_uint32,
        ptype=int,
        __doc__="An `int` with the address of the device on its underlying bus. Not implemented in Linux.",
    ),
    'ui_number': dict(
        item=ChannelDataItem.UI_NUMBER,
        ctype=ct.c_uint32,
        ptype=int,
        __doc__="An `int` with the number associated with the device that can be displayed in the user interface. Not implemented in Linux.",
    ),
    'timesync_enabled': dict(
        item=ChannelDataItem.TIMESYNC_ENABLED,
        ctype=ct.c_uint32,
        ptype=bool,
        __doc__="A `bool` for whether legacy time synchronization is enabled. Not implemented in Linux.",
    ),
    'driver_file_version': dict(
        item=ChannelDataItem.DRIVER_FILE_VERSION,
        ctype=ct.c_uint16 * 4,
        ptype=lambda t: VersionNumber(t[3], t[2], t[1]),  # little byteorder assumed,
        __doc__="A `canlib.VersionNumber` with the version of the kernel-mode driver. Not implemented in Linux.",
    ),
    'driver_product_version': dict(
        item=ChannelDataItem.DRIVER_PRODUCT_VERSION,
        ctype=ct.c_uint16 * 4,
        ptype=lambda t: VersionNumber(t[3], t[2]),  # little byteorder assumed,
        __doc__="A `canlib.VersionNumber` with the product version of the kernel-mode driver. Not implemented in Linux.",
    ),
    'mfgname_unicode': dict(
        item=ChannelDataItem.MFGNAME_UNICODE,
        ctype=ct.c_wchar * 80,
        ptype=str,
        __doc__="A `str` with the manufacturer's name. Not implemented in Linux.",
    ),
    'mfgname_ascii': dict(
        item=ChannelDataItem.MFGNAME_ASCII,
        ctype=ct.c_char * 80,
        ptype=str,
        __doc__="A `str` with the manufacturer's name.",
    ),
    'devdescr_unicode': dict(
        item=ChannelDataItem.DEVDESCR_UNICODE,
        ctype=ct.c_wchar * 80,
        ptype=str,
        __doc__="A `str` with the product name of the device. Not implemented in Linux.",
    ),
    'devdescr_ascii': dict(
        item=ChannelDataItem.DEVDESCR_ASCII,
        ctype=ct.c_char * 80,
        ptype=str,
        __doc__="A `str` with the product name of the device.",
    ),
    'driver_name': dict(
        item=ChannelDataItem.DRIVER_NAME,
        ctype=ct.c_char * 80,
        ptype=str,
        __doc__="A `str` with the name of the device driver.",
    ),
    'channel_quality': dict(
        item=ChannelDataItem.CHANNEL_QUALITY,
        ctype=ct.c_uint32,
        ptype=int,
        __doc__="An `int` between 0 and 100 (inclusively) with the quality of the channel in percent of optimal quality. Not implemented in Linux.",
    ),
    'roundtrip_time': dict(
        item=ChannelDataItem.ROUNDTRIP_TIME,
        ctype=ct.c_uint32,
        ptype=int,
        __doc__="An `int` with the roundtrip time measured in milliseconds. Not implemented in Linux.",
    ),
    'bus_type': dict(
        item=ChannelDataItem.BUS_TYPE,
        ctype=ct.c_uint32,
        ptype=BusTypeGroup,
        __doc__="A member of the `BusTypeGroup` enum. Not implemented in Linux.",
    ),
    'devname_ascii': dict(
        item=ChannelDataItem.DEVNAME_ASCII,
        ctype=ct.c_char * 80,
        ptype=str,
        __doc__="A `str` with the current device name. Not implemented in Linux.",
    ),
    'time_since_last_seen': dict(
        item=ChannelDataItem.TIME_SINCE_LAST_SEEN,
        ctype=ct.c_uint32,
        ptype=int,
        __doc__="An `int` with the time in milliseconds since last communication occured. Not implemented in Linux.",
    ),
    'remote_operational_mode': dict(
        item=ChannelDataItem.REMOTE_OPERATIONAL_MODE,
        ctype=ct.c_uint32,
        ptype=OperationalMode,
        __doc__="A member of the `OperationalMode` enum. Not implemented in Linux.",
    ),
    'remote_profile_name': dict(
        item=ChannelDataItem.REMOTE_PROFILE_NAME,
        ctype=ct.c_char * 80,
        ptype=str,
        __doc__="A `str` with the remote profile name of the device. Not implemented in Linux.",
    ),
    'remote_host_name': dict(
        item=ChannelDataItem.REMOTE_HOST_NAME,
        ctype=ct.c_char * 80,
        ptype=str,
        __doc__="A `str` with the remote host name of the device. Not implemented in Linux.",
    ),
    'remote_mac': dict(
        item=ChannelDataItem.REMOTE_MAC,
        ctype=ct.c_char * 32,
        ptype=str,
        __doc__="A `str` with the remote mac address of the device. Not implemented in Linux.",
    ),
    'max_bitrate': dict(
        item=ChannelDataItem.MAX_BITRATE,
        ctype=ct.c_uint32,
        ptype=int,
        __doc__="An `int` with the maximum bitrate of the device. Zero means no limit on the bitrate.",
    ),
    'channel_cap_mask': dict(
        item=ChannelDataItem.CHANNEL_CAP_MASK,
        ctype=ct.c_uint32,
        ptype=ChannelCap,
        __doc__="A `ChannelCap` with which flags this device knows about.",
    ),
    'is_remote': dict(
        item=ChannelDataItem.IS_REMOTE,
        ctype=ct.c_uint32,
        ptype=bool,
        __doc__="A `bool` for whether the device is currently connected as a remote device. Not implemented in Linux.",
    ),
    'remote_type': dict(
        item=ChannelDataItem.REMOTE_TYPE,
        ctype=ct.c_uint32,
        ptype=RemoteType,
        __doc__="A member of the `RemoteType` enum. Not implemented in Linux.",
    ),
    'logger_type': dict(
        item=ChannelDataItem.LOGGER_TYPE,
        ctype=ct.c_uint32,
        ptype=LoggerType,
        __doc__="A member of the `LoggerType` enum. Not implemented in Linux.",
    ),
    'hw_status': dict(
        item=ChannelDataItem.HW_STATUS,
        ctype=ct.c_uint32 * 6,
        ptype=tuple,
        __doc__="Six `int` with hardware status codes. This is only intended for internal use.",
    ),
    'feature_ean': dict(
        item=ChannelDataItem.FEATURE_EAN,
        ctype=ct.c_ubyte * 8,
        ptype=EAN.from_bcd,
        __doc__="An `canlib.EAN` object with an internal EAN. This is only intended for internal use.",
    ),
    'channel_cap_ex': dict(
        item=ChannelDataItem.CHANNEL_CAP_EX,
        ctype=ct.c_uint64 * 2,
        ptype=lambda x: (ChannelCapEx(x[0]), ChannelCapEx(x[1])),
        __doc__="A tuple of `ChannelCapEx` with the extended capabilities of the channel (added in v1.17).",
    ),
}


[docs]class ChannelData: __doc__ = """Object for querying various information about a channel After instantiating a `ChannelData` object with a channel number, a variety of information is available as attributes. Most attributes are named after the C constant used to retrieve the information and are found in the list below. Other information does not follow the C implementation completely, and are documented as separate properties further down. There is also the `raw` function, that is used internally to get all information and can also be used to interact more directly with the dll. Attributes: {dyn_attrs} """.format( dyn_attrs='\n'.join( ' ' + name + ': ' + d['__doc__'] for name, d in sorted(ATTRIBUTES.items()) ) ) def __init__(self, channel_number): self.channel_number = channel_number def __dir__(self): # Support autocompletion in IDE by adding our attributes attrs = set(super().__dir__()) | set(ATTRIBUTES.keys()) return attrs def __getattr__(self, name): try: attr = ATTRIBUTES[name] except KeyError: raise AttributeError( f"{self.__class__.__name__} object has no attribute {name}" ) item = attr['item'] ctype = attr['ctype'] ptype = attr['ptype'] ret = self.raw(item=item, ctype=ctype) if ptype: if ptype is str and isinstance(ret, bytes): ret = ret.decode('utf8') else: # In python 2, we get `long` and not `int` resulting in ValueError :( try: ret = ptype(ret) except ValueError as orig_e: try: ret = ptype(int(ret)) except Exception: raise orig_e return ret
[docs] def raw(self, item, ctype=ct.c_uint32): """A raw call to `canGetChannelData` Args: item (`ChannelDataItem`): The information to be retrieved. ctype: The `ctypes` type that the information should be interpreted as. """ buf = ctype() try: dll.canGetChannelData( self.channel_number, item, ct.byref(buf), ct.sizeof(buf), ) except CanError as e: # On windows and CANlib < 5.23 Error.PARAM is returned instead of CanNotFound if e.status == Error.PARAM and self.channel_number >= getNumberOfChannels(): raise CanNotFound else: raise e try: return buf.value except AttributeError: if item == ChannelDataItem.BUS_PARAM_LIMITS: return buf else: return tuple(buf)
@property def bus_param_limits(self): """Get device's bus parameter limits Example usage: >>> chd = canlib.ChannelData(channel_number=2) >>> limits = chd.bus_param_limits >>> limits.arbitration_min._asdict() {'tq': 0, 'phase1': 1, 'phase2': 1, 'sjw': 1, 'prescaler': 1, 'prop': 0} >>> limits.arbitration_max._asdict() {'tq': 0, 'phase1': 512, 'phase2': 32, 'sjw': 16, 'prescaler': 8192, 'prop': 0} >>> limits.data_min._asdict() {'tq': 0, 'phase1': 1, 'phase2': 1, 'sjw': 1, 'prescaler': 1, 'prop': 0} >>> limits.data_max._asdict() {'tq': 0, 'phase1': 512, 'phase2': 32, 'sjw': 16, 'prescaler': 8192, 'prop': 0} The ``tq`` field is always zero, and is reserved for possible other uses in future releases. If ``prop`` is zero for both ``min`` and ``max`` values, that means that the device does not distinguish between phase segment one and the propagation segment, i.e. the ``phase1`` limit applies to (``phase1`` + ``prop``). Returns: `.busparams.BusParamTqLimits` .. versionadded:: 1.20 """ limits = self.raw(item=ChannelDataItem.BUS_PARAM_LIMITS, ctype=CanBusParamLimits) if limits.version != 2: raise NotImplementedError( f"kvBusParamLimits Struct version {limits.version} is not supported." ) return BusParamTqLimits( limits.arbitration_min, limits.arbitration_max, limits.data_min, limits.data_max) @property def channel_name(self): """`str`: The product channel name. Retrieves a clear text name of the channel. The name is returned as a string. """ name = self.devdescr_ascii no_on_card = self.chan_no_on_card return f"{name} (channel {no_on_card})" @property @deprecation.deprecated.favour("ChannelData.channel_name") def device_name(self): """.. deprecated:: 1.7""" return self.channel_name @property def custom_name(self): """`str`: The custom channel name if set, or an empty string otherwise""" try: ret = self.raw(item=ChannelDataItem.CUST_CHANNEL_NAME, ctype=ct.c_char * 64).decode( 'utf8' ) except CanNotImplementedError: ret = "" return ret
[docs]class HandleData(ChannelData): __doc__ = """Object for querying various information about a handle This is identical to the `ChannelData` object but it's constructor takes a `canlib.Channel` instead of a channel number. .. versionadded:: 1.13 Attributes: {dyn_attrs} """.format( dyn_attrs='\n'.join( ' ' + name + ': ' + d['__doc__'] for name, d in sorted(ATTRIBUTES.items()) ) ) def __init__(self, channel): self.channel = channel
[docs] def raw(self, item, ctype=ct.c_uint32): """A raw call to `canGetHandleData` Args: item (`ChannelDataItem`): The information to be retrieved. ctype: The `ctypes` type that the information should be interpreted as. """ buf = ctype() dll.canGetHandleData( self.channel.handle, item, ct.byref(buf), ct.sizeof(buf), ) try: return buf.value except AttributeError: if item == ChannelDataItem.BUS_PARAM_LIMITS: return buf else: return tuple(buf)