Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Web resources
Features
--------
* Sends and receives CAN frames.
* Handles parsing of CAN signals from CAN frames.
* Handles parsing of CAN signals from CAN frames (can optionally match labels).

..

Expand Down Expand Up @@ -58,7 +58,7 @@ Known limitations
-----------------
* Not all CAN functionality is implemented. 'Error frames' and 'remote request frames' are not
handled, and CAN multiplex signals are not supported.
* Not all features of the KCD file format are implemented, for example 'Labels'.
* Not all features of the KCD file format are implemented, for example 'LabelGroups'.
* It is assumed that each CAN signal name only is available in a single CAN frame ID.


Expand Down
24 changes: 12 additions & 12 deletions can4python/canbus.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
# 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
#
# 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
# 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
# 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"
Expand All @@ -23,7 +23,7 @@
# 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 logging

Expand Down Expand Up @@ -156,7 +156,7 @@ def init_reception(self):
frame_id_list = [x.frame_id for x in self._input_framedefinition_storage]
self.caninterface.set_receive_filters(frame_id_list)

def recv_next_signals(self):
def recv_next_signals(self, match_labels=False):
"""Receive one CAN frame, and unpack it to signal values.

Returns:
Expand All @@ -170,7 +170,7 @@ def recv_next_signals(self):

"""
frame = self.caninterface.recv_next_frame()
return frame.unpack(self._configuration.framedefinitions)
return frame.unpack(self._configuration.framedefinitions, match_labels=match_labels)

def recv_next_frame(self):
"""Receive one CAN frame. Returns a :class:`.CanFrame` object.
Expand All @@ -195,7 +195,7 @@ def stop_reception(self):

def send_signals(self, *args, **kwargs):
"""Send CAN signals in frames.

Args:
signals_to_send (dict): The signal values to send_frame. The keys are the signalnames (*str*),
and the items are the values (*numerical* or *None*). If the value is *None* the default value is used.
Expand All @@ -209,7 +209,7 @@ def send_signals(self, *args, **kwargs):

Raises:
CanException: When failing to set signal value etc. See :exc:`.CanException`.

"""
if args:
if isinstance(args[0], dict):
Expand Down Expand Up @@ -296,15 +296,15 @@ def stop(self):

def get_descriptive_ascii_art(self):
"""Display an overview of the :class:`.CanBus` object with frame definitions and signal definitions.

Returns:
A multi-line string.

"""
text = repr(self) + "\n"
text += " " + self._configuration.get_descriptive_ascii_art()
return text
return text

def write_configuration(self, filename):
"""Write configuration to file.

Expand Down
40 changes: 26 additions & 14 deletions can4python/canframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
# 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
#
# 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
# 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
# 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"
Expand All @@ -23,7 +23,7 @@
# 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

Expand Down Expand Up @@ -193,11 +193,11 @@ def get_signalvalue(self, signaldefinition):
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:
Expand Down Expand Up @@ -269,35 +269,47 @@ def set_signalvalue(self, signaldefinition, physical_value=None):

self.frame_data = utilities.int_to_can_bytes(dlc, dataint)

def unpack(self, frame_definitions):
def unpack(self, frame_definitions, match_labels=False):
"""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.
match_labels (bool): Whether labels in the :class:`.CanSignalDefinition`
should be matched to the signal values.

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).
A dictionary of signal values. The keys are the signalname (str) and the items are the values (numerical),
or - if match_labels is True and there is at least one label in the signal definion -
tuples of (value, label).
If there is a label for at least one value, but none for the current value, an empty string will be written
as label.

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
if match_labels and sigdef.labels:
try:
outputdict[sigdef.signalname] = (val, sigdef.labels[val])
except KeyError:
outputdict[sigdef.signalname] = (val, "")
else:
outputdict[sigdef.signalname] = val
return outputdict

def get_rawframe(self):
"""Returns a 16 bytes long 'bytes' object."""
dlc = len(self.frame_data)
Expand Down
59 changes: 40 additions & 19 deletions can4python/cansignal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
# 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
#
# 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
# 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
# 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"
Expand All @@ -23,7 +23,7 @@
# 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.
#
#

from . import constants
from . import utilities
Expand All @@ -32,11 +32,11 @@
SYMBOL_LEAST_SIGNIFICANT_BIT = "L"
SYMBOL_MOST_SIGNIFICANT_BIT = "M"
SYMBOL_OTHER_VALID_BIT = "X"


class CanSignalDefinition():
"""A class for describing a CAN signal definition (not the value of the signal).

Attributes:
signalname (str): Signal name
unit (str): Unit for the value. Defaults to ``''``.
Expand Down Expand Up @@ -66,9 +66,9 @@ class CanSignalDefinition():
* In the first byte the least significant bit (rightmost, value 1) is named ``0``,
and the most significant bit (leftmost, value 128) is named ``7``.
* In next byte, the least significant bit is named ``8`` etc.

This results in this bit numbering for the CAN frame::

7,6,5,4,3,2,1,0 15,14,13,12,11,10,9,8 23,22,21,20,19,18,17,16 31,30,29,28,27,26,25,24 etc.
Byte0 Byte1 Byte2 Byte3

Expand Down Expand Up @@ -100,7 +100,7 @@ class CanSignalDefinition():
"""
def __init__(self, signalname, startbit, numberofbits, scalingfactor=1, valueoffset=0, defaultvalue=None,
unit="", comment="", minvalue=None, maxvalue=None,
endianness=constants.LITTLE_ENDIAN, signaltype=constants.CAN_SIGNALTYPE_UNSIGNED):
endianness=constants.LITTLE_ENDIAN, signaltype=constants.CAN_SIGNALTYPE_UNSIGNED, labels={}):

# Properties #
self.endianness = endianness
Expand All @@ -111,6 +111,7 @@ def __init__(self, signalname, startbit, numberofbits, scalingfactor=1, valueoff
self.valueoffset = valueoffset
self.minvalue = minvalue
self.maxvalue = maxvalue
self.labels = labels

if defaultvalue is None:
defaultvalue = valueoffset
Expand Down Expand Up @@ -286,9 +287,26 @@ def numberofbits(self, value):
value))
self._numberofbits = value

@property
def labels(self):
"""
*dict* Descriptive names for specific values.

"""
return self._labels

@labels.setter
def labels(self, value):
try:
value = {int(k): v for k,v in value.items()}
except (ValueError, TypeError, AttributeError) as _:
raise exceptions.CanException("Labels must be assigned as a dictionary with numeric keys. Given: {}".format(value))

self._labels = value

def __repr__(self):
text = "Signal {!r} Startbit {}, bits {} (min DLC {}) {} endian, {}, scalingfactor {:1.2g}, unit: {}\n".format(
self.signalname, self.startbit, self.numberofbits,
self.signalname, self.startbit, self.numberofbits,
self.get_minimum_dlc(), self.endianness, self.signaltype, self.scalingfactor, self.unit)
text += " valoffset {:3.1f} (range {:1.1g} to {:1.1g}) min {}, max {}, default {:3.1f}.\n".format(
self.valueoffset,
Expand All @@ -306,27 +324,30 @@ def __repr__(self):
commentstring = "{} ...".format(self.comment[0:MAX_COMMENT_LENGTH].replace('\n', ' ').replace('\r', ''))
text += " {} ".format(commentstring)
return text

def get_descriptive_ascii_art(self):
"""Create a visual indication how the signal is located in the frame_definition.

Returns:
A multi-line string.

"""
tempstring, stopbit = self._get_overview_string()

text = " {!r}\n".format(self)

text = " {!r}".format(self)
if len(self.labels) > 0:
text += " Labels: {}\n".format(self.labels)
text += "\n"
text += " Startbit normal bit numbering, least significant bit: {}\n".format(self.startbit)
text += " Startbit normal bit numbering, most significant bit: {}\n".format(stopbit)
text += " Startbit backward bit numbering, least significant bit: {}\n\n".format(
utilities.calculate_backward_bitnumber(self.startbit))
text += utilities.generate_bit_byte_overview(tempstring, number_of_indent_spaces=9, show_reverse_bitnumbering=True)
return text

def get_maximum_possible_value(self):
"""Get the largest value that technically could be sent with this signal.

The largest integer we can store is ``2**numberofbits - 1``.
Also the :attr:`scalingfactor`, :attr:`valueoffset` and the :attr:`signaltype` affect the result.

Expand Down
15 changes: 14 additions & 1 deletion can4python/filehandler_kcd.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ def read(filename, busname=None):
unit = ""
minvalue = maxvalue = None
signaltype = constants.CAN_SIGNALTYPE_UNSIGNED

labels = {}

for val in signal.findall('kayak:Value', constants.KCD_XML_NAMESPACE):
scalingfactor = float(val.get('slope', 1))
valueoffset = float(val.get('intercept', 0))
Expand All @@ -150,10 +153,14 @@ def read(filename, busname=None):
if notes.text is not None:
signalcomment += notes.text

for labelset in signal.findall('kayak:LabelSet', constants.KCD_XML_NAMESPACE):
for label in labelset.findall('kayak:Label', constants.KCD_XML_NAMESPACE):
labels[label.get('value')] = label.get('name')

s = cansignal.CanSignalDefinition(signalname, startbit, numberofbits, scalingfactor, valueoffset,
unit=unit, comment=signalcomment,
minvalue=minvalue, maxvalue=maxvalue,
endianness=endianness, signaltype=signaltype)
endianness=endianness, signaltype=signaltype, labels=labels)

f.signaldefinitions.append(s)
config.framedefinitions[f.frame_id] = f
Expand Down Expand Up @@ -224,6 +231,12 @@ def write(config, filename):
if len(valueattributes):
ElementTree.SubElement(s_subtree, "Value", attrib=valueattributes)

if len(s.labels):
labelset_subtree = ElementTree.SubElement(s_subtree, "LabelSet")
for k, v in s.labels.items():
ElementTree.SubElement(labelset_subtree, "Label", attrib={"value": str(k),
"name": v})

# Producers
if f.producer_ids:
p_subtree = ElementTree.SubElement(m_subtree, "Producer")
Expand Down
9 changes: 9 additions & 0 deletions tests/test_canbus.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,15 @@ def testReceiveRaw(self):
shell=False, stderr=subprocess.STDOUT)
result = self.canbus_raw.recv_next_signals()
self.assertEqual(len(result), 4)
self.assertEqual(result['testsignal11'], 0)

# with label matching
time.sleep(0.1)
self.simulated_can_process = subprocess.Popen(["cansend", VIRTUAL_CAN_BUS_NAME, canstring],
shell=False, stderr=subprocess.STDOUT)
result = self.canbus_raw.recv_next_signals(match_labels=True)
self.assertEqual(len(result), 4)
self.assertEqual(result['testsignal11'], (0, "no"))

def testReceiveBcmAndStop(self):
self.canbus_bcm.init_reception()
Expand Down
Loading