diff --git a/README.rst b/README.rst index a7943b6..c4649c7 100644 --- a/README.rst +++ b/README.rst @@ -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). .. @@ -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. diff --git a/can4python/canbus.py b/can4python/canbus.py index 739ce38..70e2b74 100644 --- a/can4python/canbus.py +++ b/can4python/canbus.py @@ -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" @@ -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 @@ -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: @@ -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. @@ -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. @@ -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): @@ -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. diff --git a/can4python/canframe.py b/can4python/canframe.py index 51ef1d7..fb1cad5 100644 --- a/can4python/canframe.py +++ b/can4python/canframe.py @@ -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" @@ -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 @@ -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: @@ -269,18 +269,24 @@ 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. """ @@ -288,16 +294,22 @@ def unpack(self, frame_definitions): 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) diff --git a/can4python/cansignal.py b/can4python/cansignal.py index faebc5c..c439847 100644 --- a/can4python/cansignal.py +++ b/can4python/cansignal.py @@ -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" @@ -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 @@ -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 ``''``. @@ -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 @@ -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 @@ -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 @@ -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, @@ -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. diff --git a/can4python/filehandler_kcd.py b/can4python/filehandler_kcd.py index 4d28c95..fe1915b 100644 --- a/can4python/filehandler_kcd.py +++ b/can4python/filehandler_kcd.py @@ -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)) @@ -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 @@ -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") diff --git a/tests/test_canbus.py b/tests/test_canbus.py index 525afb6..b783863 100755 --- a/tests/test_canbus.py +++ b/tests/test_canbus.py @@ -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() diff --git a/tests/test_canframe.py b/tests/test_canframe.py index 69b1e7c..f2620f0 100755 --- a/tests/test_canframe.py +++ b/tests/test_canframe.py @@ -20,11 +20,13 @@ class TestCanFrame(unittest.TestCase): - def setUp(self): self.frame = canframe.CanFrame(1, b'\x00\x02\x00\x08\x00\x00\x00\xff') self.testsig1 = cansignal.CanSignalDefinition('testsignal1', 56, 1) # Least significant bit in last byte + self.testsig1_with_one_label = cansignal.CanSignalDefinition('testsignal1', 56, 1, labels={0: "off"}) + self.testsig1_with_labels = cansignal.CanSignalDefinition('testsignal1', 56, 1, labels={0: "off", "1": "on"}) + self.testsig2 = cansignal.CanSignalDefinition('testsignal2', 8, 16, endianness='big') # Two leftmost bytes self.testsig3 = cansignal.CanSignalDefinition('testsignal3', 24, 16, endianness='little', maxvalue=1200) # Two center bytes @@ -46,20 +48,20 @@ def testConstructor(self): self.assertEqual(frame2.frame_id, 0x1FFFFFFF) self.assertEqual(frame2.frame_data, b'\x02\x03') self.assertEqual(frame2.frame_format, 'extended') - - def testConstructorNamedArguments(self): + + def testConstructorNamedArguments(self): frame = canframe.CanFrame(frame_id=3, frame_data=b'\x04', frame_format='extended') self.assertEqual(frame.frame_id, 3) self.assertEqual(frame.frame_data, b'\x04') self.assertEqual(frame.frame_format, 'extended') - - def testConstructorFromEmptyBytes(self): + + def testConstructorFromEmptyBytes(self): frame = canframe.CanFrame.from_empty_bytes(5, 6, 'extended') self.assertEqual(frame.frame_id, 5) self.assertEqual(frame.frame_data, b'\x00\x00\x00\x00\x00\x00') self.assertEqual(frame.frame_format, 'extended') - - def testConstructorFromRawframes(self): + + def testConstructorFromRawframes(self): frame1 = canframe.CanFrame.from_rawframe(b'\x07\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') self.assertEqual(frame1.frame_id, 7) self.assertEqual(frame1.frame_format, 'standard') @@ -146,7 +148,7 @@ def testFramedataSetWrongValue(self): self.assertRaises(exceptions.CanException, setattr, self.frame, 'frame_data', "7") self.assertRaises(exceptions.CanException, setattr, self.frame, 'frame_data', "\x01") - def testSignalvalueSet(self): + def testSignalvalueSet(self): self.frame.set_signalvalue(self.testsig1) self.frame.set_signalvalue(self.testsig1, 0) self.frame.set_signalvalue(self.testsig1, 1) @@ -157,7 +159,7 @@ def testSignalvalueSet(self): self.frame.set_signalvalue(self.testsig3, 1000) self.frame.set_signalvalue(self.testsig3, 0) self.assertEqual(self.frame.frame_data, b'\x00\x00\x00\x00\x00\x00\x00\xfe') - + self.frame.set_signalvalue(self.testsig1, 1) self.frame.set_signalvalue(self.testsig2, 16) self.frame.set_signalvalue(self.testsig3, 512) @@ -250,7 +252,6 @@ def testSignalvalueSetTooShortFrame(self): self.frame.frame_data = b'\00' self.assertRaises(exceptions.CanException, self.frame.set_signalvalue, self.testsig1) - def testSignalvalueGetSetMin(self): self.testsig3.minvalue = 0 self.frame.set_signalvalue(self.testsig3, 0) @@ -303,6 +304,32 @@ def testUnpack(self): self.assertEqual(result['testsignal3'], 8) self.assertEqual(result['testsignal4'], 0) + def testUnpackOneLabel(self): + # label only for one possible value + frame_def = self.frame_def + frame_def.signaldefinitions.remove(self.testsig1) + frame_def.signaldefinitions.append(self.testsig1_with_one_label) + + result = self.frame.unpack({self.frame_def.frame_id: frame_def}, match_labels=True) + self.assertEqual(len(result), 4) + self.assertEqual(result['testsignal1'], (1, "")) + self.assertEqual(result['testsignal2'], 2) + self.assertEqual(result['testsignal3'], 8) + self.assertEqual(result['testsignal4'], 0) + + def testUnpackTwoLabels(self): + # labels for all two possible values + frame_def = self.frame_def + frame_def.signaldefinitions.remove(self.testsig1) + frame_def.signaldefinitions.append(self.testsig1_with_labels) + + result = self.frame.unpack({self.frame_def.frame_id: frame_def}, match_labels=True) + self.assertEqual(len(result), 4) + self.assertEqual(result['testsignal1'], (1, "on")) + self.assertEqual(result['testsignal2'], 2) + self.assertEqual(result['testsignal3'], 8) + self.assertEqual(result['testsignal4'], 0) + def testUnpackWrongFrameId(self): self.frame.frame_id = 2 frame_defs = {self.frame_def.frame_id: self.frame_def} @@ -326,15 +353,13 @@ def testLen(self): def testGetDescriptiveAsciiArt(self): result = self.frame.get_descriptive_ascii_art() print('\n\n' + result) # Check the output manually - + if __name__ == '__main__': - - # Run all tests # + # Run all tests # unittest.main() - - # Run a single test # + + # Run a single test # # suite = unittest.TestSuite() # suite.addTest(TestCanFrame("testGetDescriptiveAsciiArt")) # unittest.TextTestRunner(verbosity=2).run(suite) - diff --git a/tests/test_caninterface_raw.py b/tests/test_caninterface_raw.py index a9b6ab5..7f8c823 100755 --- a/tests/test_caninterface_raw.py +++ b/tests/test_caninterface_raw.py @@ -39,14 +39,14 @@ def enable_virtual_can_bus(): except subprocess.CalledProcessError: pass try: - subprocess.check_output(["ifconfig", VIRTUAL_CAN_BUS_NAME, "up"]) + subprocess.check_output(["ip", "link", "set", "up", VIRTUAL_CAN_BUS_NAME]) except subprocess.CalledProcessError: raise exceptions.CanException("Could not enable {}. Are you sure you are running as sudo?".format( VIRTUAL_CAN_BUS_NAME)) def disable_virtual_can_bus(): - subprocess.check_output(["ifconfig", VIRTUAL_CAN_BUS_NAME, "down"]) + subprocess.check_output(["ip", "link", "set", "down", VIRTUAL_CAN_BUS_NAME]) class TestSocketCanRawInterface(unittest.TestCase): @@ -89,15 +89,15 @@ def testConstructor(self): self.assertEqual(a.interfacename, VIRTUAL_CAN_BUS_NAME) a.close() self.assertEqual(a.interfacename, VIRTUAL_CAN_BUS_NAME) - + b = caninterface_raw.SocketCanRawInterface(VIRTUAL_CAN_BUS_NAME, timeout=1.0) self.assertEqual(b.interfacename, VIRTUAL_CAN_BUS_NAME) b.close() - + c = caninterface_raw.SocketCanRawInterface(VIRTUAL_CAN_BUS_NAME, timeout=1.0) self.assertEqual(c.interfacename, VIRTUAL_CAN_BUS_NAME) c.close() - + d = caninterface_raw.SocketCanRawInterface(VIRTUAL_CAN_BUS_NAME, timeout=1.0) self.assertEqual(d.interfacename, VIRTUAL_CAN_BUS_NAME) d.close() @@ -113,13 +113,13 @@ def testConstructorWrongType(self): def testConstructorSeveralInterfaces(self): a = caninterface_raw.SocketCanRawInterface(VIRTUAL_CAN_BUS_NAME, timeout=1.0) self.assertEqual(a.interfacename, VIRTUAL_CAN_BUS_NAME) - + b = caninterface_raw.SocketCanRawInterface(VIRTUAL_CAN_BUS_NAME, timeout=1.0) self.assertEqual(b.interfacename, VIRTUAL_CAN_BUS_NAME) - + c = caninterface_raw.SocketCanRawInterface(VIRTUAL_CAN_BUS_NAME, timeout=1.0) self.assertEqual(c.interfacename, VIRTUAL_CAN_BUS_NAME) - + a.close() b.close() c.close() @@ -202,11 +202,11 @@ def testTooFewTooManyFiltersDefined(self): self.interface.set_receive_filters([]) self.interface.set_receive_filters(list(range(200))) -if __name__ == '__main__': - - # Run all tests # +if __name__ == '__main__': + + # Run all tests # unittest.main() - + # Run a single test # # suite = unittest.TestSuite() # suite.addTest(TestSocketCanInterfaceRaw("testConstructor")) diff --git a/tests/test_cansignal.py b/tests/test_cansignal.py index 3b3384b..cd7f52f 100755 --- a/tests/test_cansignal.py +++ b/tests/test_cansignal.py @@ -35,7 +35,8 @@ def testConstructor(self): self.assertEqual(sig.minvalue, None) self.assertEqual(sig.maxvalue, None) self.assertEqual(sig.defaultvalue, 0) - + self.assertEqual(sig.labels, {}) + sig = cansignal.CanSignalDefinition('testsignal2', 7, 1, endianness='big') # Most significant bit in first byte self.assertEqual(sig.signalname, 'testsignal2') self.assertEqual(sig.startbit, 7) @@ -46,9 +47,10 @@ def testConstructor(self): self.assertEqual(sig.signaltype, 'unsigned') self.assertEqual(sig.minvalue, None) self.assertEqual(sig.maxvalue, None) - self.assertEqual(sig.defaultvalue, 0) - - sig = cansignal.CanSignalDefinition('testsignal3', 56, 1, defaultvalue=1) # Least significant bit in last byte + self.assertEqual(sig.defaultvalue, 0) + self.assertEqual(sig.labels, {}) + + sig = cansignal.CanSignalDefinition('testsignal3', 56, 1, defaultvalue=1, labels={0: "on", '1': "off"}) # Least significant bit in last byte self.assertEqual(sig.signalname, 'testsignal3') self.assertEqual(sig.startbit, 56) self.assertEqual(sig.numberofbits, 1) @@ -59,6 +61,7 @@ def testConstructor(self): self.assertEqual(sig.minvalue, None) self.assertEqual(sig.maxvalue, None) self.assertEqual(sig.defaultvalue, 1) + self.assertEqual(sig.labels, {0: "on", 1: "off"}) def testConstructorWrongValues(self): self.assertRaises(exceptions.CanException, cansignal.CanSignalDefinition, 'testsignal', 63, 1, scalingfactor=-1) @@ -86,6 +89,11 @@ def testConstructorWrongValues(self): self.assertRaises(exceptions.CanException, cansignal.CanSignalDefinition, 'testsignal', 56, 1, endianness=None) + self.assertRaises(exceptions.CanException, cansignal.CanSignalDefinition, + 'testsignal', 56, 1, labels=[]) + self.assertRaises(exceptions.CanException, cansignal.CanSignalDefinition, + 'testsignal', 56, 1, labels={1: "correct", "str": "wrong"}) + def testProperties(self): self.assertEqual(self.signal.signalname, 'testsignal') self.assertEqual(self.signal.startbit, 56) @@ -96,6 +104,7 @@ def testProperties(self): self.assertEqual(self.signal.signaltype, constants.CAN_SIGNALTYPE_UNSIGNED) self.assertEqual(self.signal.maxvalue, None) self.assertEqual(self.signal.minvalue, None) + self.assertEqual(self.signal.labels, {}) self.signal.signalname = 'testsignal2' self.signal.startbit = 55 @@ -109,6 +118,7 @@ def testProperties(self): self.signal.signaltype = constants.CAN_SIGNALTYPE_SIGNED self.signal.unit = 'm/s' self.signal.comment = "ABC" + self.signal.labels = {'3': "somename"} self.assertEqual(self.signal.signalname, 'testsignal2') self.assertEqual(self.signal.startbit, 55) @@ -122,6 +132,7 @@ def testProperties(self): self.assertEqual(self.signal.signaltype, constants.CAN_SIGNALTYPE_SIGNED) self.assertEqual(self.signal.unit, 'm/s') self.assertEqual(self.signal.comment, "ABC") + self.assertEqual(self.signal.labels, {3: "somename"}) def testPropertiesWrongValues(self): self.assertRaises(exceptions.CanException, setattr, self.signal, 'startbit', -1) @@ -149,6 +160,8 @@ def testPropertiesWrongValues(self): self.assertRaises(exceptions.CanException, setattr, self.signal, 'scalingfactor', None) self.assertRaises(exceptions.CanException, setattr, self.signal, 'valueoffset', 'ABC') self.assertRaises(exceptions.CanException, setattr, self.signal, 'valueoffset', None) + self.assertRaises(exceptions.CanException, setattr, self.signal, 'labels', []) + self.assertRaises(exceptions.CanException, setattr, self.signal, 'labels', {"str": 3}) sig = cansignal.CanSignalDefinition('testsignal', 56, 1, endianness='big') sig.signaltype = constants.CAN_SIGNALTYPE_SINGLE @@ -171,7 +184,7 @@ def testGetDescriptiveAsciiArt(self): sig = cansignal.CanSignalDefinition('testsignalA', 56, 1) print(sig.get_descriptive_ascii_art()) - sig = cansignal.CanSignalDefinition('testsignalB', 54, 4) + sig = cansignal.CanSignalDefinition('testsignalB', 54, 4, labels={0: "disabled"}) print(sig.get_descriptive_ascii_art()) sig = cansignal.CanSignalDefinition('testsignalC', 54, 2, endianness='big') diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 1918ced..e4fa8e3 100755 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -34,7 +34,8 @@ fr_def1.signaldefinitions.append(testsig2) fr_def1.signaldefinitions.append(testsig3) fr_def1.signaldefinitions.append(testsig4) -testsig11 = cansignal.CanSignalDefinition('testsignal11', 56, 1) # Least significant bit in last byte +testsig11 = cansignal.CanSignalDefinition('testsignal11', 56, 1, + labels={0: "no", 1: "yes"}) # Least significant bit in last byte testsig12 = cansignal.CanSignalDefinition('testsignal12', 8, 16, endianness='big') # Two leftmost bytes testsig13 = cansignal.CanSignalDefinition('testsignal13', 24, 16, endianness='little') # Two center bytes testsig14 = cansignal.CanSignalDefinition('testsignal14', 59, 4, endianness='big', signaltype='signed') @@ -49,10 +50,10 @@ TESTCONFIG1.add_framedefinition(fr_def1) TESTCONFIG1.add_framedefinition(fr_def2) TESTCONFIG1.busname = "bus1" -TESTCONFIG1.ego_node_ids = ["1", "33", "45A", "A",] +TESTCONFIG1.ego_node_ids = ["1", "33", "45A", "A", ] -class TestConfiguration(unittest.TestCase): +class TestConfiguration(unittest.TestCase): def setUp(self): self.config = copy.deepcopy(TESTCONFIG1) @@ -73,7 +74,7 @@ def testConstructor(self): fr_def = canframe_definition.CanFrameDefinition(1, 'testframedef') sig1 = cansignal.CanSignalDefinition('testsignal', 56, 1) # Least significant bit in last byte fr_def.signaldefinitions.append(sig1) - config = configuration.Configuration({1:fr_def}, "DEF") + config = configuration.Configuration({1: fr_def}, "DEF") self.assertEqual(config.framedefinitions[1], fr_def) self.assertEqual(config.busname, "DEF") @@ -136,7 +137,7 @@ def testSetReceiveOnChangeOnlyFromSignalnamesWrongValues(self): self.config.set_receive_on_change_only_from_signalnames, ["nonexistingsignal"]) self.assertRaises(exceptions.CanException, self.config.set_receive_on_change_only_from_signalnames, "ABC") self.assertRaises(exceptions.CanException, self.config.set_receive_on_change_only_from_signalnames, 123) - + def testGetDescriptiveAsciiArt(self): result = self.config.get_descriptive_ascii_art() print('\n\n' + result) # Check the output manually @@ -148,5 +149,6 @@ def testAddFramedefinition(self): self.assertEqual(config.framedefinitions[1], fr_def) self.assertEqual(config.busname, None) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_filehandler_kcd.py b/tests/test_filehandler_kcd.py index a535fde..6e2c04f 100755 --- a/tests/test_filehandler_kcd.py +++ b/tests/test_filehandler_kcd.py @@ -165,6 +165,10 @@ def testSaveLoadedConfigurationToFile(self): filehandler_kcd.FilehandlerKcd.write(config, self.OUTPUT_FILENAME_2) self.assertTrue(os.path.exists(self.OUTPUT_FILENAME_2)) + # Read rewritten file + config2 = filehandler_kcd.FilehandlerKcd.read(self.OUTPUT_FILENAME_2, None) + self.assertEqual(config.get_descriptive_ascii_art(), config2.get_descriptive_ascii_art()) + # TODO: Check manually that the input and output files are similar def testWriteKcdFileNoBusnameGiven(self): diff --git a/tests/testfile_input.kcd b/tests/testfile_input.kcd index 66dadb0..26b7ff0 100644 --- a/tests/testfile_input.kcd +++ b/tests/testfile_input.kcd @@ -26,7 +26,12 @@ - + + + +