Source code for can4python.canframe

# -*- coding: utf-8 -*-
#
# Author: Jonas Berg
# Copyright (c) 2015, Semcon Sweden AB
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without modification, are permitted 
# provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
#    following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,  this list of conditions and 
#    the following disclaimer in the documentation and/or other materials provided with the distribution.
# 3. Neither the name of the Semcon Sweden AB nor the names of its contributors may be used to endorse or 
#    promote products derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# 

import struct

from . import exceptions
from . import constants
from . import utilities


[docs]class CanFrame(): """ CAN frame with data. Does not know how the signals are laid out etc. Raises: CanException: For wrong frame ID. See :exc:`.CanException`. To find the DLC, use one of:: len(myframe) len(myframe.frame_data) """ def __init__(self, frame_id, frame_data, frame_format=constants.CAN_FRAMEFORMAT_STANDARD): # Properties # self.frame_format = frame_format # Must be set before frame_id self.frame_id = frame_id self.frame_data = frame_data def __repr__(self): datastring = " ".join(["{:02X}".format(y) for y in self.frame_data]) if self.frame_data else "" return "CAN frame ID: {0} (0x{0:03X}, {1}) data: {2} ({3} bytes)".format( self.frame_id, self.frame_format, datastring, len(self.frame_data)) def __len__(self): return len(self.frame_data) @classmethod
[docs] def from_empty_bytes(cls, frame_id, number_of_bytes, frame_format=constants.CAN_FRAMEFORMAT_STANDARD): """ Create a :class:`.CanFrame` with empty bytes. Args: frame_id (int): CAN frame ID number number_of_bytes (int): number of empty data bytes to initialize the frame with. frame_format (str): Frame format. Should be ``'standard'`` or ``'extended'``. Defaults to standard frame format. """ try: number_of_bytes = int(number_of_bytes) except (ValueError, TypeError) as _: raise exceptions.CanException("number_of_bytes should be an integer. Given: {!r}".format(number_of_bytes)) if (number_of_bytes > constants.MAX_NUMBER_OF_CAN_DATA_BYTES) or (number_of_bytes < 0): raise exceptions.CanException("Wrong number of number_of_bytes given: {!r}".format(number_of_bytes)) framedata = constants.NULL_BYTE * number_of_bytes return cls(frame_id, framedata, frame_format)
@classmethod
[docs] def from_rawframe(cls, rawframe): """ Create a :class:`.CanFrame` from a raw frame from the SocketCAN interface. Args: rawframe (bytes): 16 bytes long, includes frame ID, frame format etc """ try: first_part, dlc, framedata8bytes = struct.unpack(constants.FORMAT_CAN_RAWFRAME, rawframe) except struct.error as err: raise exceptions.CanException("rawframe is illegal. Given: {!r}. Error: {}".format(rawframe, err)) frame_id = first_part & constants.CAN_MASK_ID_ONLY frame_format = constants.CAN_FRAMEFORMAT_EXTENDED if first_part & constants.CAN_MASK_EXTENDED_FRAME_BIT \ else constants.CAN_FRAMEFORMAT_STANDARD framedata = framedata8bytes[:dlc] # is_remote_request = bool(first_part & constants.CAN_MASK_REMOTE_REQUEST_BIT) # is_error_frame = bool(first_part & constants.CAN_MASK_ERROR_BIT) return cls(frame_id, framedata, frame_format)
@property def frame_id(self): """ *int* CAN frame ID number """ return self._frame_id @frame_id.setter def frame_id(self, value): utilities.check_frame_id_and_format(value, self.frame_format) self._frame_id = value @property def frame_data(self): """ *bytes object* 0-8 bytes of CAN data """ return self._frame_data @frame_data.setter def frame_data(self, value): if value is None: raise exceptions.CanException("frame_data should not be None") try: value = bytes(value) except TypeError: raise exceptions.CanException("frame_data should be bytes. Given: {!r}".format(value)) if len(value) > constants.MAX_NUMBER_OF_CAN_DATA_BYTES: raise exceptions.CanException("The frame_data has wrong length: {!r}".format(value)) self._frame_data = value @property def frame_format(self): """ *str* Frame format. Should be ``'standard'`` or ``'extended'``. Defaults to standard frame format. """ return self._frame_format @frame_format.setter def frame_format(self, value): if value not in [constants.CAN_FRAMEFORMAT_STANDARD, constants.CAN_FRAMEFORMAT_EXTENDED]: raise exceptions.CanException("Wrong frame_format. Given: {!r}".format(value)) self._frame_format = value
[docs] def get_signalvalue(self, signaldefinition): """ Extract a signal value from the frame. Args: signaldefinition (:class:`.CanSignalDefinition` object): The definition of the signal Returns: The extracted signal physical value (numerical). """ if signaldefinition.signaltype == constants.CAN_SIGNALTYPE_DOUBLE: if signaldefinition.endianness == constants.LITTLE_ENDIAN: unpacked_value = struct.unpack(constants.FORMAT_FLOAT_DOUBLE_LITTLE_ENDIAN, self.frame_data)[0] else: unpacked_value = struct.unpack(constants.FORMAT_FLOAT_DOUBLE_BIG_ENDIAN, self.frame_data)[0] else: bus_value = utilities.get_busvalue_from_bytes(self.frame_data, signaldefinition.endianness, signaldefinition.numberofbits, signaldefinition.startbit) # Unpack from signal type if signaldefinition.signaltype == constants.CAN_SIGNALTYPE_UNSIGNED: unpacked_value = bus_value elif signaldefinition.signaltype == constants.CAN_SIGNALTYPE_SIGNED: unpacked_value = utilities.from_twos_complement(bus_value, signaldefinition.numberofbits) else: # CAN_SIGNALTYPE_SINGLE: useful_bytes = struct.pack(constants.FORMAT_DATA_4BYTES_INT, bus_value) # Create 'bytes' of length 4 unpacked_value = struct.unpack(constants.FORMAT_FLOAT_SINGLE_BIG_ENDIAN, useful_bytes)[0] physical_value = (unpacked_value * signaldefinition.scalingfactor) + signaldefinition.valueoffset # Limit to minvalue and maxvalue if signaldefinition.minvalue is not None: physical_value = max(signaldefinition.minvalue, physical_value) if signaldefinition.maxvalue is not None: physical_value = min(signaldefinition.maxvalue, physical_value) return physical_value
[docs] def set_signalvalue(self, signaldefinition, physical_value=None): """ Set a signal physical_value in the frame. Args: signaldefinition (:class:`.CanSignalDefinition` object): The definition of the signal physical_value (numerical): The physical_value (numerical) of the signal. If the physical_value not is given, the default physical_value for the *signaldefinition* is used. Raises: CanException: For wrong startbit or values. See :exc:`.CanException`. """ if signaldefinition.get_minimum_dlc() > len(self): raise exceptions.CanException('The frame is too short to send_frame this signal. Frame: {}, signal: {}'. format(self, signaldefinition)) if physical_value is None: physical_value = signaldefinition.defaultvalue if physical_value < signaldefinition.get_minimum_possible_value() or \ physical_value > signaldefinition.get_maximum_possible_value(): raise exceptions.CanException('The physical value is out of range. Value: {}, range {} to {}'.format( physical_value, signaldefinition.get_minimum_possible_value(), signaldefinition.get_maximum_possible_value())) # Limit to minvalue and maxvalue if signaldefinition.minvalue is not None: physical_value = max(signaldefinition.minvalue, physical_value) if signaldefinition.maxvalue is not None: physical_value = min(signaldefinition.maxvalue, physical_value) # Scale according to valueoffset and scalingfactor scaled_value = float((physical_value - signaldefinition.valueoffset) / signaldefinition.scalingfactor) # Shortcut for double precision floats (occupies full frame) if signaldefinition.signaltype == constants.CAN_SIGNALTYPE_DOUBLE: if signaldefinition.endianness == constants.LITTLE_ENDIAN: self.frame_data = struct.pack(constants.FORMAT_FLOAT_DOUBLE_LITTLE_ENDIAN, scaled_value) else: self.frame_data = struct.pack(constants.FORMAT_FLOAT_DOUBLE_BIG_ENDIAN, scaled_value) return # Encode to correct signaltype if signaldefinition.signaltype == constants.CAN_SIGNALTYPE_UNSIGNED: bus_value = int(scaled_value) elif signaldefinition.signaltype == constants.CAN_SIGNALTYPE_SIGNED: bus_value = utilities.twos_complement(int(scaled_value), signaldefinition.numberofbits) else: # CAN_SIGNALTYPE_SINGLE: bus_value = utilities.can_bytes_to_int( struct.pack(constants.FORMAT_FLOAT_SINGLE_BIG_ENDIAN, scaled_value). rjust(constants.MAX_NUMBER_OF_CAN_DATA_BYTES, constants.NULL_BYTE)) # Limit the size of the field to be written assert bus_value <= (2 ** signaldefinition.numberofbits - 1), "Trying to set too large signal value to frame." assert bus_value >= 0, "Trying to set too small signal value to the frame." bitvalues = utilities.get_shiftedvalue_from_busvalue(bus_value, signaldefinition.endianness, signaldefinition.numberofbits, signaldefinition.startbit) raw_mask = ((1 << signaldefinition.numberofbits) - 1) # Mask with ones in 'numberofbits' positions mask = utilities.get_shiftedvalue_from_busvalue(raw_mask, signaldefinition.endianness, signaldefinition.numberofbits, signaldefinition.startbit) # Parse existing frame_data dataint = utilities.can_bytes_to_int(self.frame_data) dlc = len(self.frame_data) # Modify the frame_data by writing zeros to the appropriate field (using bitwise AND), # then writing in the relevant data (by using bitwise OR) dataint = (dataint & ~mask) | bitvalues self.frame_data = utilities.int_to_can_bytes(dlc, dataint)
[docs] def unpack(self, frame_definitions): """Unpack the CAN frame, and return all signal values. Args: frame_definitions (dict): The keys are frame_id (int) and the items are :class:`.CanFrameDefinition` objects. Raises: CanException: For wrong DLC. See :exc:`.CanException`. Returns: A dictionary of signal values. The keys are the signalname (str) and the items are the values (numerical). If the frame not is described in the 'frame_definitions', an empty dictionary is returned. """ try: fr_def = frame_definitions[self.frame_id] except KeyError: return {} if len(self.frame_data) != fr_def.dlc: raise exceptions.CanException('The received frame has wrong length: {}, Def: {}'.format(self, fr_def)) outputdict = {} for sigdef in fr_def.signaldefinitions: val = self.get_signalvalue(sigdef) outputdict[sigdef.signalname] = val return outputdict
[docs] def get_rawframe(self): """Returns a 16 bytes long 'bytes' object.""" dlc = len(self.frame_data) framedata8bytes = self.frame_data.ljust(constants.MAX_NUMBER_OF_CAN_DATA_BYTES, constants.NULL_BYTE) # Set flag for extended frame format, if necessary first_part = self.frame_id if self.frame_format == constants.CAN_FRAMEFORMAT_EXTENDED: first_part |= constants.CAN_MASK_EXTENDED_FRAME_BIT return struct.pack(constants.FORMAT_CAN_RAWFRAME, first_part, dlc, framedata8bytes)
[docs] def get_descriptive_ascii_art(self): """Create a visual indication of the frame data Returns: A multi-line string. """ text = "{!r} \n".format(self) text += utilities.generate_can_integer_overview(utilities.can_bytes_to_int(self.frame_data)) return text