From b44ac8283f4b0dd93d74eed2e657b580c50506fd Mon Sep 17 00:00:00 2001 From: thaotran Date: Thu, 4 Mar 2021 16:24:20 -0800 Subject: [PATCH 01/44] removed redundant paratheses and chained comparisons in if/else --- canopen_monitor/can/message.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/canopen_monitor/can/message.py b/canopen_monitor/can/message.py index 82fcd6e..92b15f2 100644 --- a/canopen_monitor/can/message.py +++ b/canopen_monitor/can/message.py @@ -49,11 +49,9 @@ def supertype(self: MessageType) -> MessageType: :return: The supertype of this type :rtype: MessageType """ - if(self.value[0] >= self.PDO.value[0] - and self.value[0] <= self.PDO.value[1]): + if self.PDO.value[0] <= self.value[0] <= self.PDO.value[1]: return MessageType['PDO'] - elif(self.value[0] >= self.SDO.value[0] - and self.value[0] <= self.SDO.value[1]): + elif self.SDO.value[0] <= self.value[0] <= self.SDO.value[1]: return MessageType['SDO'] else: return MessageType['UKNOWN'] @@ -98,7 +96,7 @@ def cob_id_to_type(cob_id: int) -> MessageType: :rtype: MessageType """ for t in list(MessageType): - if(cob_id >= t.value[0] and cob_id <= t.value[1]): + if t.value[0] <= cob_id <= t.value[1]: return t return MessageType['UKNOWN'] @@ -160,9 +158,9 @@ def state(self: Message) -> MessageState: :return: State of the message :rtype: MessageState """ - if(self.age >= DEAD_TIME): + if self.age >= DEAD_TIME: return MessageState['DEAD'] - elif(self.age >= STALE_TIME): + elif self.age >= STALE_TIME: return MessageState['STALE'] else: return MessageState['ALIVE'] From 33e015a386a94c85636e867dcb7d2ddedff474ab Mon Sep 17 00:00:00 2001 From: thaotran Date: Thu, 4 Mar 2021 20:18:23 -0800 Subject: [PATCH 02/44] added __init__ to use better name to get start/end of a range, modified massage type variables --- canopen_monitor/can/message.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/canopen_monitor/can/message.py b/canopen_monitor/can/message.py index 92b15f2..263cf51 100644 --- a/canopen_monitor/can/message.py +++ b/canopen_monitor/can/message.py @@ -34,8 +34,12 @@ class MessageType(Enum): # Special Types UKNOWN = (-0x1, -0x1) # Pseudo type unknown - PDO = (0x180, 0x57F) # Super type PDO - SDO = (0x580, 0x680) # Super type SDO + PDO = (0x180, 0x57F) # Super type PDO + SDO = (0x580, 0x680) # Super type SDO + + def __init__(self, start, end): + self.start = start + self.end = end @property def supertype(self: MessageType) -> MessageType: @@ -49,15 +53,15 @@ def supertype(self: MessageType) -> MessageType: :return: The supertype of this type :rtype: MessageType """ - if self.PDO.value[0] <= self.value[0] <= self.PDO.value[1]: + if self.PDO.start <= self.start <= self.PDO.end: return MessageType['PDO'] - elif self.SDO.value[0] <= self.value[0] <= self.SDO.value[1]: + elif self.SDO.start <= self.start <= self.SDO.end: return MessageType['SDO'] else: return MessageType['UKNOWN'] @staticmethod - def cob_to_node(mtype: MessageType, cob_id: int) -> int: + def cob_to_node(msg_type: MessageType, cob_id: int) -> int: """Determines the Node ID based on the given COB ID The COB ID is the raw ID sent with the CAN message, and the node id is @@ -83,7 +87,7 @@ def cob_to_node(mtype: MessageType, cob_id: int) -> int: :return: The Node ID :rtype: int """ - return cob_id - mtype.value[0] + return cob_id - msg_type.start @staticmethod def cob_id_to_type(cob_id: int) -> MessageType: @@ -95,9 +99,9 @@ def cob_id_to_type(cob_id: int) -> MessageType: :return: The message type (range) the COB ID fits into :rtype: MessageType """ - for t in list(MessageType): - if t.value[0] <= cob_id <= t.value[1]: - return t + for msg_type in list(MessageType): + if msg_type.start <= cob_id <= msg_type.end: + return msg_type return MessageType['UKNOWN'] def __str__(self) -> str: @@ -122,8 +126,6 @@ class MessageState(Enum): DEAD = 'Dead' def __str__(self: MessageState) -> str: - """ Overloaded `str()` operator - """ return self.value + ' ' From 89525feb96e91dbc157dcc65e8451bc317cf3737 Mon Sep 17 00:00:00 2001 From: Jacob Crisan Date: Thu, 25 Feb 2021 14:14:18 -0800 Subject: [PATCH 03/44] Initial Comments --- canopen_monitor/parse/__init__.py | 13 +++++++++---- canopen_monitor/parse/canopen.py | 5 ++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/canopen_monitor/parse/__init__.py b/canopen_monitor/parse/__init__.py index e8027a3..e576fcc 100644 --- a/canopen_monitor/parse/__init__.py +++ b/canopen_monitor/parse/__init__.py @@ -13,6 +13,7 @@ 'load_eds_file', ] +# This is only referenced here and not used anywhere else. Why is it here? It's not even exported data_types = {0x01: "BOOLEAN", 0x02: "INTEGER8", 0x03: "INTEGER16", @@ -29,6 +30,7 @@ 0x15: "INTEGER64", 0x1B: "UNSIGNED64"} +# This is only referenced here and not used anywhere else. Why is it here? It's not even exported node_names = {0x01: "C3", 0x06: "Solar Panel", 0x11: "SDR GPS", @@ -40,7 +42,9 @@ 0x33: "Test Board 2", 0x40: "MDC"} - +# Redundant? +# Where are these used? +# This is only referenced here and not used anywhere else. Why is it here? It's not even exported class DataTypes(enum.Enum): BOOLEAN = 0x1 INTEGER8 = 0x2 @@ -58,7 +62,7 @@ class DataTypes(enum.Enum): INTEGER64 = 0x15 UNSIGNED64 = 0x1B - +# This is only referenced here and not used anywhere else. Why is it here? It's not even exported object_types = {0x00: "NULL", 0x02: "DOMAIN", 0x05: "DEFTYPE", @@ -67,14 +71,15 @@ class DataTypes(enum.Enum): 0x08: "ARRAY", 0x09: "RECORD"} - +# Needs comments. It's not clear what this is doing (to an uninformed user at least). +# If the goal is to simply lowercase a string, this seems a bit convoluted def camel_to_snake(old_name: str) -> str: new_name = '' for match in finditer('[A-Z0-9]+[a-z]*', old_name): span = match.span() substr = old_name[span[0]:span[1]] - # length = span[1] - span[0] + # length = span[1] - span[0] <- If it's not needed, get rid of it found_submatch = False for sub_match in finditer('[A-Z]+', substr): diff --git a/canopen_monitor/parse/canopen.py b/canopen_monitor/parse/canopen.py index 8d6177c..32bf234 100644 --- a/canopen_monitor/parse/canopen.py +++ b/canopen_monitor/parse/canopen.py @@ -1,3 +1,4 @@ +# Comments? from ..can import Message, MessageType from . import hb as HBParser, \ pdo as PDOParser, \ @@ -7,12 +8,14 @@ from .sdo import SDOParser from .utilities import FailedValidationError - +# Comments? class CANOpenParser: + # COmments def __init__(self, eds_configs: dict): self.sdo_parser = SDOParser() self.eds_configs = eds_configs + # Comments def parse(self, message: Message) -> str: node_id = message.node_id eds_config = self.eds_configs.get(hex(node_id)) \ From a25345f0811234c6317c77c851092be22513f393 Mon Sep 17 00:00:00 2001 From: Jacob Crisan Date: Thu, 25 Feb 2021 14:18:57 -0800 Subject: [PATCH 04/44] Made the initial comments a little more friendly --- canopen_monitor/parse/__init__.py | 13 ++++++------- canopen_monitor/parse/canopen.py | 5 ++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/canopen_monitor/parse/__init__.py b/canopen_monitor/parse/__init__.py index e576fcc..0092348 100644 --- a/canopen_monitor/parse/__init__.py +++ b/canopen_monitor/parse/__init__.py @@ -13,7 +13,7 @@ 'load_eds_file', ] -# This is only referenced here and not used anywhere else. Why is it here? It's not even exported +# This is only referenced here and not used anywhere else. It seems like it doesn't need to be here data_types = {0x01: "BOOLEAN", 0x02: "INTEGER8", 0x03: "INTEGER16", @@ -30,7 +30,7 @@ 0x15: "INTEGER64", 0x1B: "UNSIGNED64"} -# This is only referenced here and not used anywhere else. Why is it here? It's not even exported +# This is only referenced here and not used anywhere else. It seems like it doesn't need to be here node_names = {0x01: "C3", 0x06: "Solar Panel", 0x11: "SDR GPS", @@ -43,8 +43,7 @@ 0x40: "MDC"} # Redundant? -# Where are these used? -# This is only referenced here and not used anywhere else. Why is it here? It's not even exported +# This is only referenced here and not used anywhere else. It seems like it doesn't need to be here class DataTypes(enum.Enum): BOOLEAN = 0x1 INTEGER8 = 0x2 @@ -62,7 +61,7 @@ class DataTypes(enum.Enum): INTEGER64 = 0x15 UNSIGNED64 = 0x1B -# This is only referenced here and not used anywhere else. Why is it here? It's not even exported +# This is only referenced here and not used anywhere else. It seems like it doesn't need to be here object_types = {0x00: "NULL", 0x02: "DOMAIN", 0x05: "DEFTYPE", @@ -71,7 +70,7 @@ class DataTypes(enum.Enum): 0x08: "ARRAY", 0x09: "RECORD"} -# Needs comments. It's not clear what this is doing (to an uninformed user at least). +# Seems like it needs comments. It's not clear to me what this is doing (this may change after consulting the docs) # If the goal is to simply lowercase a string, this seems a bit convoluted def camel_to_snake(old_name: str) -> str: new_name = '' @@ -79,7 +78,7 @@ def camel_to_snake(old_name: str) -> str: for match in finditer('[A-Z0-9]+[a-z]*', old_name): span = match.span() substr = old_name[span[0]:span[1]] - # length = span[1] - span[0] <- If it's not needed, get rid of it + # length = span[1] - span[0] <- Not needed? found_submatch = False for sub_match in finditer('[A-Z]+', substr): diff --git a/canopen_monitor/parse/canopen.py b/canopen_monitor/parse/canopen.py index 32bf234..3ab828c 100644 --- a/canopen_monitor/parse/canopen.py +++ b/canopen_monitor/parse/canopen.py @@ -1,4 +1,3 @@ -# Comments? from ..can import Message, MessageType from . import hb as HBParser, \ pdo as PDOParser, \ @@ -10,12 +9,12 @@ # Comments? class CANOpenParser: - # COmments + # Comments? def __init__(self, eds_configs: dict): self.sdo_parser = SDOParser() self.eds_configs = eds_configs - # Comments + # Comments? def parse(self, message: Message) -> str: node_id = message.node_id eds_config = self.eds_configs.get(hex(node_id)) \ From cdf391a0262f29ae97ccdde3570ba657c51613f5 Mon Sep 17 00:00:00 2001 From: Jacob Crisan Date: Fri, 26 Feb 2021 13:29:28 -0800 Subject: [PATCH 05/44] Removed some unnecessary code --- canopen_monitor/parse/__init__.py | 58 +------------------------------ 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/canopen_monitor/parse/__init__.py b/canopen_monitor/parse/__init__.py index 0092348..f87de29 100644 --- a/canopen_monitor/parse/__init__.py +++ b/canopen_monitor/parse/__init__.py @@ -1,5 +1,5 @@ """This module is primarily responsible for providing a high-level interface -for parsing CANOpen messages according to Ojbect Definiton files or Electronic +for parsing CANOpen messages according to Object Definiton files or Electronic Data Sheet files, provided by the end user. """ import enum @@ -13,62 +13,6 @@ 'load_eds_file', ] -# This is only referenced here and not used anywhere else. It seems like it doesn't need to be here -data_types = {0x01: "BOOLEAN", - 0x02: "INTEGER8", - 0x03: "INTEGER16", - 0x04: "INTEGER32", - 0x05: "UNSIGNED8", - 0x06: "UNSIGNED16", - 0x07: "UNSIGNED32", - 0x08: "REAL32", - 0x09: "VISIBLE_STRING", - 0x0A: "OCTET_STRING", - 0x0B: "UNICODE_STRING", - 0x0F: "DOMAIN", - 0x11: "REAL64", - 0x15: "INTEGER64", - 0x1B: "UNSIGNED64"} - -# This is only referenced here and not used anywhere else. It seems like it doesn't need to be here -node_names = {0x01: "C3", - 0x06: "Solar Panel", - 0x11: "SDR GPS", - 0x12: "Star Tracker", - 0x21: "OreSat Live", - 0x22: "Cirrus Flux Cameras", - 0x31: "Battery", - 0x32: "Test Board 1", - 0x33: "Test Board 2", - 0x40: "MDC"} - -# Redundant? -# This is only referenced here and not used anywhere else. It seems like it doesn't need to be here -class DataTypes(enum.Enum): - BOOLEAN = 0x1 - INTEGER8 = 0x2 - INTEGER16 = 0x3 - INTEGER32 = 0x4 - UNSIGNED8 = 0x5 - UNSIGNED16 = 0x6 - UNSIGNED32 = 0x7 - REAL32 = 0x8 - VISIBLE_STRING = 0x9 - OCTET_STRING = 0xA - UNICODE_STRING = 0xB - DOMAIN = 0xF - REAL64 = 0x11 - INTEGER64 = 0x15 - UNSIGNED64 = 0x1B - -# This is only referenced here and not used anywhere else. It seems like it doesn't need to be here -object_types = {0x00: "NULL", - 0x02: "DOMAIN", - 0x05: "DEFTYPE", - 0x06: "DEFSTRUCT", - 0x07: "VAR", - 0x08: "ARRAY", - 0x09: "RECORD"} # Seems like it needs comments. It's not clear to me what this is doing (this may change after consulting the docs) # If the goal is to simply lowercase a string, this seems a bit convoluted From 6c3edbc0f740d8403ae76e80893ddf7f2766d3ea Mon Sep 17 00:00:00 2001 From: Jacob Crisan Date: Fri, 26 Feb 2021 13:48:10 -0800 Subject: [PATCH 06/44] Moved the camel_to_snake function to the .eds file, attempted to add a comment to the parse function in canopen.py --- canopen_monitor/parse/__init__.py | 39 +------------------------------ canopen_monitor/parse/canopen.py | 33 ++++++++++++++++++-------- canopen_monitor/parse/eds.py | 39 ++++++++++++++++++++++++++++--- 3 files changed, 60 insertions(+), 51 deletions(-) diff --git a/canopen_monitor/parse/__init__.py b/canopen_monitor/parse/__init__.py index f87de29..7c66ddd 100644 --- a/canopen_monitor/parse/__init__.py +++ b/canopen_monitor/parse/__init__.py @@ -3,7 +3,6 @@ Data Sheet files, provided by the end user. """ import enum -from re import finditer from .eds import EDS, load_eds_file from .canopen import CANOpenParser @@ -11,40 +10,4 @@ 'CANOpenParser', 'EDS', 'load_eds_file', -] - - -# Seems like it needs comments. It's not clear to me what this is doing (this may change after consulting the docs) -# If the goal is to simply lowercase a string, this seems a bit convoluted -def camel_to_snake(old_name: str) -> str: - new_name = '' - - for match in finditer('[A-Z0-9]+[a-z]*', old_name): - span = match.span() - substr = old_name[span[0]:span[1]] - # length = span[1] - span[0] <- Not needed? - found_submatch = False - - for sub_match in finditer('[A-Z]+', substr): - sub_span = sub_match.span() - sub_substr = old_name[sub_span[0]:sub_span[1]] - sub_length = sub_span[1] - sub_span[0] - - if (sub_length > 1): - found_submatch = True - - if (span[0] != 0): - new_name += '_' - - first = sub_substr[:-1] - second = substr.replace(first, '') - - new_name += '{}_{}'.format(first, second).lower() - - if (not found_submatch): - if (span[0] != 0): - new_name += '_' - - new_name += substr.lower() - - return new_name +] \ No newline at end of file diff --git a/canopen_monitor/parse/canopen.py b/canopen_monitor/parse/canopen.py index 3ab828c..0eb129c 100644 --- a/canopen_monitor/parse/canopen.py +++ b/canopen_monitor/parse/canopen.py @@ -9,36 +9,49 @@ # Comments? class CANOpenParser: - # Comments? def __init__(self, eds_configs: dict): self.sdo_parser = SDOParser() self.eds_configs = eds_configs - # Comments? def parse(self, message: Message) -> str: + """ + Detect the type of the given message and returns the parsed version + + Arguments + --------- + @:param: message: a Message object containing the message + + Returns + ------- + `str`: The parsed message + + """ node_id = message.node_id eds_config = self.eds_configs.get(hex(node_id)) \ if node_id is not None else None + # Detect message type and select the appropriate parse function if (message.type == MessageType.SYNC): - parse = SYNCParser.parse + parse_function = SYNCParser.parse elif (message.type == MessageType.EMER): - parse = EMCYParser.parse + parse_function = EMCYParser.parse elif (message.supertype == MessageType.PDO): - parse = PDOParser.parse + parse_function = PDOParser.parse elif (message.supertype == MessageType.SDO): if self.sdo_parser.is_complete: self.sdo_parser = SDOParser() - parse = self.sdo_parser.parse + parse_function = self.sdo_parser.parse elif (message.type == MessageType.HEARTBEAT): - parse = HBParser.parse + parse_function = HBParser.parse elif (message.type == MessageType.TIME): - parse = TIMEParser.parse + parse_function = TIMEParser.parse else: - parse = None + parse_function = None + # Call the parse function and save the result + # On error, return the message data try: - parsed_message = parse(message.arb_id, message.data, eds_config) + parsed_message = parse_function(message.arb_id, message.data, eds_config) except (FailedValidationError, TypeError): parsed_message = ' '.join(list(map(lambda x: hex(x)[2:] .upper() diff --git a/canopen_monitor/parse/eds.py b/canopen_monitor/parse/eds.py index 92dad30..8c68c6d 100644 --- a/canopen_monitor/parse/eds.py +++ b/canopen_monitor/parse/eds.py @@ -2,7 +2,40 @@ from typing import Union import canopen_monitor.parse as cmp from dateutil.parser import parse as dtparse +from re import finditer +def camel_to_snake(old_name: str) -> str: + new_name = '' + + for match in finditer('[A-Z0-9]+[a-z]*', old_name): + span = match.span() + substr = old_name[span[0]:span[1]] + # length = span[1] - span[0] <- Not needed? + found_submatch = False + + for sub_match in finditer('[A-Z]+', substr): + sub_span = sub_match.span() + sub_substr = old_name[sub_span[0]:sub_span[1]] + sub_length = sub_span[1] - sub_span[0] + + if (sub_length > 1): + found_submatch = True + + if (span[0] != 0): + new_name += '_' + + first = sub_substr[:-1] + second = substr.replace(first, '') + + new_name += '{}_{}'.format(first, second).lower() + + if (not found_submatch): + if (span[0] != 0): + new_name += '_' + + new_name += substr.lower() + + return new_name class Metadata: def __init__(self, data): @@ -16,7 +49,7 @@ def __init__(self, data): key, value = e.split('=') # Create the proper field name - key = cmp.camel_to_snake(key) + key = camel_to_snake(key) # Turn date-time-like objects into datetimes if ('time' in key): @@ -60,7 +93,7 @@ def __init__(self, data, sub_id=None): elif(all(c in string.hexdigits for c in value)): value = int(value, 16) - self.__setattr__(cmp.camel_to_snake(key), value) + self.__setattr__(camel_to_snake(key), value) def add(self, index) -> None: self.sub_indices.append(index) @@ -99,7 +132,7 @@ def __init__(self, eds_data: [str]): .add(Index(section[1:], sub_id=int(id[1], 16))) else: name = section[0][1:-1] - self.__setattr__(cmp.camel_to_snake(name), + self.__setattr__(camel_to_snake(name), Metadata(section[1:])) prev = i + 1 self.node_id = self[0x2101].default_value From cb07e3641e4178c912decc22da3dafc3e86b41a6 Mon Sep 17 00:00:00 2001 From: Jacob Crisan Date: Fri, 5 Mar 2021 16:02:16 -0800 Subject: [PATCH 07/44] Added a comment to the CANOpenParser class and tried simplifying the camel_to_snake function --- canopen_monitor/parse/canopen.py | 6 +++-- canopen_monitor/parse/eds.py | 40 +++++++------------------------- 2 files changed, 12 insertions(+), 34 deletions(-) diff --git a/canopen_monitor/parse/canopen.py b/canopen_monitor/parse/canopen.py index 0eb129c..841a180 100644 --- a/canopen_monitor/parse/canopen.py +++ b/canopen_monitor/parse/canopen.py @@ -7,15 +7,17 @@ from .sdo import SDOParser from .utilities import FailedValidationError -# Comments? class CANOpenParser: + """ + A convenience wrapper for the parse function + """ def __init__(self, eds_configs: dict): self.sdo_parser = SDOParser() self.eds_configs = eds_configs def parse(self, message: Message) -> str: """ - Detect the type of the given message and returns the parsed version + Detect the type of the given message and return the parsed version Arguments --------- diff --git a/canopen_monitor/parse/eds.py b/canopen_monitor/parse/eds.py index 8c68c6d..d6d2e5c 100644 --- a/canopen_monitor/parse/eds.py +++ b/canopen_monitor/parse/eds.py @@ -2,40 +2,16 @@ from typing import Union import canopen_monitor.parse as cmp from dateutil.parser import parse as dtparse -from re import finditer +from re import sub -def camel_to_snake(old_name: str) -> str: - new_name = '' - for match in finditer('[A-Z0-9]+[a-z]*', old_name): - span = match.span() - substr = old_name[span[0]:span[1]] - # length = span[1] - span[0] <- Not needed? - found_submatch = False - - for sub_match in finditer('[A-Z]+', substr): - sub_span = sub_match.span() - sub_substr = old_name[sub_span[0]:sub_span[1]] - sub_length = sub_span[1] - sub_span[0] - - if (sub_length > 1): - found_submatch = True - - if (span[0] != 0): - new_name += '_' - - first = sub_substr[:-1] - second = substr.replace(first, '') - - new_name += '{}_{}'.format(first, second).lower() - - if (not found_submatch): - if (span[0] != 0): - new_name += '_' - - new_name += substr.lower() - - return new_name +def camel_to_snake(old_str: str) -> str: + """ + Converts camel case to snake case + """ + # https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case + new_str = sub(r'[A-Z0-9]+[a-z]*', '_', old_str) + return new_str class Metadata: def __init__(self, data): From 5287335f3ac51e1235bf5612544db674706e0fbc Mon Sep 17 00:00:00 2001 From: Brian ONeill Date: Sat, 6 Mar 2021 10:30:16 -0800 Subject: [PATCH 08/44] Fix import on test eds test spec --- tests/spec_eds_parser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/spec_eds_parser.py b/tests/spec_eds_parser.py index 6d9ccd0..e277300 100644 --- a/tests/spec_eds_parser.py +++ b/tests/spec_eds_parser.py @@ -1,8 +1,10 @@ import unittest -import canopen_monitor.parse.eds as eds +from canopen_monitor import parse from unittest.mock import mock_open, patch from tests import TEST_EDS +eds = parse.eds + class TestEDS(unittest.TestCase): def setUp(self): From 8234a4e83a73e125913798f531708c6fc08f8228 Mon Sep 17 00:00:00 2001 From: Jacob Crisan Date: Sat, 6 Mar 2021 16:47:19 -0800 Subject: [PATCH 09/44] Brought back a commented version of the old camel_to_snake case function --- canopen_monitor/parse/eds.py | 72 ++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/canopen_monitor/parse/eds.py b/canopen_monitor/parse/eds.py index d6d2e5c..aa651f3 100644 --- a/canopen_monitor/parse/eds.py +++ b/canopen_monitor/parse/eds.py @@ -2,17 +2,81 @@ from typing import Union import canopen_monitor.parse as cmp from dateutil.parser import parse as dtparse -from re import sub +from re import sub, finditer +"""def camel_to_snake(old_name: str) -> str: + new_name = '' + + for match in finditer('[A-Z0-9]+[a-z]*', old_name): + span = match.span() + substr = old_name[span[0]:span[1]] + found_submatch = False + + for sub_match in finditer('[A-Z]+', substr): + sub_span = sub_match.span() + sub_substr = old_name[sub_span[0]:sub_span[1]] + sub_length = sub_span[1] - sub_span[0] + + if (sub_length > 1): + found_submatch = True + + if (span[0] != 0): + new_name += '_' + + first = sub_substr[:-1] + second = substr.replace(first, '') + + new_name += '{}_{}'.format(first, second).lower() + + if (not found_submatch): + if (span[0] != 0): + new_name += '_' + + new_name += substr.lower() + + return new_name""" + def camel_to_snake(old_str: str) -> str: """ - Converts camel case to snake case + Converts camel cased string to snake case, counting groups of repeated capital letters (such as "PDO") as one unit + That is, string like "PDO_group" become "pdo_group" instead of "p_d_o_group" """ - # https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case - new_str = sub(r'[A-Z0-9]+[a-z]*', '_', old_str) + # Find all groups that contains one or more capital letters followed by one or more lowercase letters + # The new, camel_cased string will be built up along the way + new_str = "" + for match in finditer('[A-Z0-9]+[a-z]*', old_str): + span = match.span() + substr = old_str[span[0]:span[1]] + found_submatch = False + + # Add a "_" to the newstring to separate the current match group from the previous + # It looks like we shouldn't need to worry about getting "_strings_like_this", because they don't seem to happen + if (span[0] != 0): + new_str += '_' + + # Find all sub-groups of *more than one* capital letters within the match group, and seperate them with "_" characters, + # Append the subgroups to the new_str as they are found + # If no subgroups are found, just append the match group to the new_str + for sub_match in finditer('[A-Z]+', substr): + sub_span = sub_match.span() + sub_substr = old_str[sub_span[0]:sub_span[1]] + sub_length = sub_span[1] - sub_span[0] + + if (sub_length > 1): + found_submatch = True + + first = sub_substr[:-1] + second = substr.replace(first, '') + + new_str += '{}_{}'.format(first, second).lower() + + if (not found_submatch): + new_str += substr.lower() + return new_str + class Metadata: def __init__(self, data): # Process all sub-data From 8a9eb7cbbbb54408b64e3aacd34ac70da8070019 Mon Sep 17 00:00:00 2001 From: Jacob Crisan Date: Mon, 8 Mar 2021 14:17:35 -0800 Subject: [PATCH 10/44] Removed commented out code --- canopen_monitor/parse/eds.py | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/canopen_monitor/parse/eds.py b/canopen_monitor/parse/eds.py index aa651f3..c0857d1 100644 --- a/canopen_monitor/parse/eds.py +++ b/canopen_monitor/parse/eds.py @@ -5,38 +5,6 @@ from re import sub, finditer -"""def camel_to_snake(old_name: str) -> str: - new_name = '' - - for match in finditer('[A-Z0-9]+[a-z]*', old_name): - span = match.span() - substr = old_name[span[0]:span[1]] - found_submatch = False - - for sub_match in finditer('[A-Z]+', substr): - sub_span = sub_match.span() - sub_substr = old_name[sub_span[0]:sub_span[1]] - sub_length = sub_span[1] - sub_span[0] - - if (sub_length > 1): - found_submatch = True - - if (span[0] != 0): - new_name += '_' - - first = sub_substr[:-1] - second = substr.replace(first, '') - - new_name += '{}_{}'.format(first, second).lower() - - if (not found_submatch): - if (span[0] != 0): - new_name += '_' - - new_name += substr.lower() - - return new_name""" - def camel_to_snake(old_str: str) -> str: """ Converts camel cased string to snake case, counting groups of repeated capital letters (such as "PDO") as one unit From f22a9425dd161c1aaf8322c38544c8ad08b5dd7a Mon Sep 17 00:00:00 2001 From: Sanlinlin Date: Sat, 6 Mar 2021 23:25:36 -0800 Subject: [PATCH 11/44] update and add some new comments for some functions --- canopen_monitor/ui/message_pane.py | 39 ++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/canopen_monitor/ui/message_pane.py b/canopen_monitor/ui/message_pane.py index 422082d..1ced972 100644 --- a/canopen_monitor/ui/message_pane.py +++ b/canopen_monitor/ui/message_pane.py @@ -5,7 +5,8 @@ class MessagePane(Pane): - """A derivative of Pane customized specifically to list miscellaneous CAN + """ + A derivative of Pane customized specifically to list miscellaneous CAN messages stored in a MessageTable :param name: The name of the pane (to be printed in the top left) @@ -58,7 +59,8 @@ def __init__(self: MessagePane, self.__reset_col_widths() def resize(self: MessagePane, height: int, width: int) -> None: - """A wrapper for `Pane.resize()`. This intercepts a call for a resize + """ + A wrapper for `Pane.resize()`. This intercepts a call for a resize in order to upate MessagePane-specific details that change on a resize event. The parent `resize()` gets called first and then MessagePane's details are updated. @@ -78,26 +80,34 @@ def resize(self: MessagePane, height: int, width: int) -> None: self.__top_max = occluded if occluded > 0 else 0 def _reset_scroll_positions(self: MessagePane) -> None: + """ + Reset the scroll positions. + Initialize the y position to be zero. + Initialize the x position to be zero. + """ self.cursor = self.cursor_max self.scroll_position_y = 0 self.scroll_position_x = 0 @property def scroll_limit_y(self: MessagePane) -> int: - """The maximim rows the pad is allowed to shift by when scrolling + """ + The maximim rows the pad is allowed to shift by when scrolling """ return self.d_height - 2 @property def scroll_limit_x(self: MessagePane) -> int: - """The maximim columns the pad is allowed to shift by when scrolling + """ + The maximim columns the pad is allowed to shift by when scrolling """ max_length = sum(list(map(lambda x: x[1], self.cols.values()))) occluded = max_length - self.d_width + 7 return occluded if(occluded > 0) else 0 def scroll_up(self: MessagePane, rate: int = 1) -> None: - """This overrides `Pane.scroll_up()`. Instead of shifting the + """ + This overrides `Pane.scroll_up()`. Instead of shifting the pad vertically, the slice of messages from the `MessageTable` is shifted. @@ -123,7 +133,8 @@ def scroll_up(self: MessagePane, rate: int = 1) -> None: self.__top = min if(self.__top < min) else self.__top def scroll_down(self: MessagePane, rate: int = 1) -> None: - """This overrides `Pane.scroll_up()`. Instead of shifting the + """ + This overrides `Pane.scroll_up()`. Instead of shifting the pad vertically, the slice of messages from the `MessageTable` is shifted. @@ -149,7 +160,8 @@ def scroll_down(self: MessagePane, rate: int = 1) -> None: self.__top = max if(self.__top > max) else self.__top def __draw_header(self: Pane) -> None: - """Draw the table header at the top of the Pane + """ + Draw the table header at the top of the Pane This uses the `cols` dictionary to determine what to write """ @@ -169,7 +181,8 @@ def __draw_header(self: Pane) -> None: pos += data[1] + self.__col_sep def draw(self: MessagePane) -> None: - """Draw all records from the MessageTable to the Pane + """ + Draw all records from the MessageTable to the Pane """ super().draw() self.resize(self.v_height, self.v_width) @@ -199,11 +212,21 @@ def draw(self: MessagePane) -> None: super().refresh() def __reset_col_widths(self: Message): + """ + Reset the width of Pane collumn. + Based on the length of data to change the width. + """ for name, data in self.cols.items(): self.cols[name] = (data[0], len(name), data[2]) \ if (len(data) == 3) else (data[0], len(name)) def __check_col_widths(self: MessagePane, messages: [Message]) -> None: + """ + Check the width of the message in Pane column. + + :param messages: The list of the messages + :type messages: list + """ for message in messages: for name, data in self.cols.items(): attr = getattr(message, data[0]) From 0e3518c323f965a5b28d71c1c799b7ca1767de4a Mon Sep 17 00:00:00 2001 From: Sanlinlin Date: Sun, 7 Mar 2021 13:20:43 -0800 Subject: [PATCH 12/44] add some comments for some functions --- canopen_monitor/ui/pane.py | 46 ++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/canopen_monitor/ui/pane.py b/canopen_monitor/ui/pane.py index 222d832..822da2b 100755 --- a/canopen_monitor/ui/pane.py +++ b/canopen_monitor/ui/pane.py @@ -4,7 +4,8 @@ class Pane(ABC): - """Abstract Pane Class, contains a PAD and a window + """ + Abstract Pane Class, contains a PAD and a window :param v_height: The virtual height of the embedded pad :type v_height: int @@ -30,7 +31,8 @@ def __init__(self: Pane, x: int = 0, border: bool = True, color_pair: int = 0): - """Abstract pane initialization + """ + Abstract pane initialization :param border: Toggiling whether or not to draw a border :type border: bool @@ -64,15 +66,22 @@ def __init__(self: Pane, @property def scroll_limit_y(self: Pane) -> int: + """ + Limit the scroll on the y axis + """ return 0 @property def scroll_limit_x(self: Pane) -> int: + """ + Limit the scroll on the x axis + """ return 0 @abstractmethod def draw(self: Pane) -> None: - """Abstract draw method, must be overwritten in child class + """ + Abstract draw method, must be overwritten in child class draw should first resize the pad using: `super().resize(w, h)` then add content using: self._pad.addstr() then refresh using: `super().refresh()` @@ -91,7 +100,8 @@ def draw(self: Pane) -> None: self._pad.box() def resize(self: Pane, height: int, width: int) -> None: - """Resize the virtual pad and change internal variables to reflect that + """ + Resize the virtual pad and change internal variables to reflect that :param height: New virtual height :type height: int @@ -105,12 +115,17 @@ def resize(self: Pane, height: int, width: int) -> None: self._pad.resize(self.v_height, self.v_width) def __reset_draw_dimensions(self: Pane) -> None: + """ + Reset the pane dimensions. + You can change the width and height of the pane. + """ p_height, p_width = self.parent.getmaxyx() self.d_height = min(self.v_height, p_height - 1) self.d_width = min(self.v_width, p_width - 1) def clear(self: Pane) -> None: - """Clear all contents of pad and parent window + """ + Clear all contents of pad and parent window .. warning:: @@ -123,7 +138,8 @@ def clear(self: Pane) -> None: # self.refresh() def clear_line(self: Pane, y: int, style: any = None) -> None: - """Clears a single line of the Pane + """ + Clears a single line of the Pane :param y: The line to clear :type y: int @@ -139,7 +155,8 @@ def clear_line(self: Pane, y: int, style: any = None) -> None: self._pad.attroff(line_style) def refresh(self: Pane) -> None: - """Refresh the pane based on configured draw dimensions + """ + Refresh the pane based on configured draw dimensions """ self._pad.refresh(self.scroll_position_y, self.scroll_position_x, @@ -150,7 +167,8 @@ def refresh(self: Pane) -> None: self.needs_refresh = False def scroll_up(self: Pane, rate: int = 1) -> bool: - """Scroll pad upwards + """ + Scroll pad upwards .. note:: @@ -171,7 +189,8 @@ def scroll_up(self: Pane, rate: int = 1) -> bool: return True def scroll_down(self: Pane, rate: int = 1) -> bool: - """Scroll pad downwards + """ + Scroll pad downwards .. note:: @@ -192,7 +211,8 @@ def scroll_down(self: Pane, rate: int = 1) -> bool: return True def scroll_left(self: Pane, rate: int = 1) -> bool: - """Scroll pad left + """ + Scroll pad left .. note:: @@ -213,7 +233,8 @@ def scroll_left(self: Pane, rate: int = 1) -> bool: return True def scroll_right(self: Pane, rate: int = 1) -> bool: - """Scroll pad right + """ + Scroll pad right .. note:: @@ -241,7 +262,8 @@ def add_line(self: Pane, underline: bool = False, highlight: bool = False, color: any = None) -> None: - """Adds a line of text to the Pane and if needed, it handles the + """ + Adds a line of text to the Pane and if needed, it handles the process of resizing the embedded pad :param y: Line's row position From 9da66ac9b329a881c77ffd0d0ecb6b2084d0789b Mon Sep 17 00:00:00 2001 From: thaotran Date: Sat, 6 Mar 2021 22:52:57 -0800 Subject: [PATCH 13/44] added magic number for state byte index for readability --- canopen_monitor/parse/hb.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/canopen_monitor/parse/hb.py b/canopen_monitor/parse/hb.py index 905c2ed..be0ff9e 100644 --- a/canopen_monitor/parse/hb.py +++ b/canopen_monitor/parse/hb.py @@ -9,20 +9,24 @@ def parse(cob_id: int, data: list, eds_config: EDS): Arguments --------- - @:param: data: a byte string containing the heartbeat message + @:param: data: a byte string containing the heartbeat message, + byte 0 is the heartbeat state info. Returns ------- `str`: The parsed message """ + STATE_BYTE_IDX = 0 states = { 0x00: "Boot-up", 0x04: "Stopped", 0x05: "Operational", 0x7F: "Pre-operational" } + node_id = MessageType.cob_to_node(MessageType.HEARTBEAT, cob_id) - if len(data) < 1 or data[0] not in states: + hb_state = data[STATE_BYTE_IDX] + if len(data) < 1 or hb_state not in states: raise FailedValidationError(data, node_id, cob_id, __name__, "Invalid heartbeat state detected") - return states.get(data[0]) + return states.get(hb_state) From 5409d742fbd4af11af538f9a8527ce5202af98fe Mon Sep 17 00:00:00 2001 From: nhanlt81 Date: Sat, 27 Feb 2021 17:44:52 -0800 Subject: [PATCH 14/44] Update windows.py --- canopen_monitor/ui/windows.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/canopen_monitor/ui/windows.py b/canopen_monitor/ui/windows.py index 6d6dc54..c21e6cf 100755 --- a/canopen_monitor/ui/windows.py +++ b/canopen_monitor/ui/windows.py @@ -90,3 +90,5 @@ def draw(self: PopupWindow) -> None: else: # super().clear() ... + + From 474d32ee995276cea09b5f1a6d0e524b1bdfb609 Mon Sep 17 00:00:00 2001 From: nhanlt81 Date: Wed, 3 Mar 2021 11:57:21 -0800 Subject: [PATCH 15/44] Update windows.py --- canopen_monitor/ui/windows.py | 43 ++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/canopen_monitor/ui/windows.py b/canopen_monitor/ui/windows.py index c21e6cf..3bdc3dc 100755 --- a/canopen_monitor/ui/windows.py +++ b/canopen_monitor/ui/windows.py @@ -4,6 +4,7 @@ class PopupWindow(Pane): + def __init__(self: PopupWindow, parent: any, header: str = 'Alert', @@ -16,10 +17,7 @@ def __init__(self: PopupWindow, y=10, x=10) # Pop-up window properties - self.header = header - self.content = content - self.footer = footer - self.enabled = False + self.setWindowProperties(header, content, footer) # Parent window dimensions (Usually should be STDOUT directly) p_height, p_width = self.parent.getmaxyx() @@ -28,19 +26,31 @@ def __init__(self: PopupWindow, self.content = self.break_lines(int(2 * p_width / 3), self.content) # UI dimensions - p_height, p_width = self.parent.getmaxyx() + p_height, p_width = self.parent.getmaxyx() # Would we consider to reuse it? + self.setUIDimension(p_height, p_width) + + # UI properties + self.style = (style or curses.color_pair(0)) + self._pad.attron(self.style) + + # Set UI Dimension (x,y) by giving parent height and width + def setUIDimension(self, p_height, p_width): self.v_height = (len(self.content)) + 2 width = len(self.header) + 2 - if(len(self.content) > 0): + if (len(self.content) > 0): width = max(width, max(list(map(lambda x: len(x), self.content)))) self.v_width = width + 4 self.y = int(((p_height + self.v_height) / 2) - self.v_height) self.x = int(((p_width + self.v_width) / 2) - self.v_width) - # UI properties - self.style = (style or curses.color_pair(0)) - self._pad.attron(self.style) + # Set default window properties + def setWindowProperties(self:PopupWindow, header, content, footer): + self.header = header + self.content = content + self.footer = footer + self.enabled = False + # Take a content and break it to the lines def break_lines(self: PopupWindow, max_width: int, content: [str]) -> [str]: @@ -62,13 +72,16 @@ def break_lines(self: PopupWindow, content.insert(i + 1, line[mid:]) return content + def toggle(self: PopupWindow) -> bool: self.enabled = not self.enabled return self.enabled + # Add the header to the window def __draw_header(self: PopupWindow) -> None: self.add_line(0, 1, self.header, underline=True) + # Add the footer to the window def __draw__footer(self: PopupWindow) -> None: f_width = len(self.footer) + 2 self.add_line(self.v_height - 1, @@ -76,15 +89,19 @@ def __draw__footer(self: PopupWindow) -> None: self.footer, underline=True) + # Read each line of the content and add to the window + def __draw_content(self): + for i, line in enumerate(self.content): + self.add_line(1 + i, 2, line) + + # Draw the Content to the Window def draw(self: PopupWindow) -> None: if(self.enabled): super().resize(self.v_height, self.v_width) super().draw() + # start to draw self.__draw_header() - - for i, line in enumerate(self.content): - self.add_line(1 + i, 2, line) - + self.__draw_content() self.__draw__footer() super().refresh() else: From b24ceb326a2688323a860c5c33ae11e00c5999c0 Mon Sep 17 00:00:00 2001 From: nhanlt81 Date: Thu, 4 Mar 2021 18:14:16 -0800 Subject: [PATCH 16/44] Update windows.py Update the comments for individual function to be within and use three " marks --- canopen_monitor/ui/windows.py | 50 +++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/canopen_monitor/ui/windows.py b/canopen_monitor/ui/windows.py index 3bdc3dc..7463f1c 100755 --- a/canopen_monitor/ui/windows.py +++ b/canopen_monitor/ui/windows.py @@ -16,6 +16,7 @@ def __init__(self: PopupWindow, width=1, y=10, x=10) + """Set an init to Popup window""" # Pop-up window properties self.setWindowProperties(header, content, footer) @@ -26,15 +27,16 @@ def __init__(self: PopupWindow, self.content = self.break_lines(int(2 * p_width / 3), self.content) # UI dimensions - p_height, p_width = self.parent.getmaxyx() # Would we consider to reuse it? self.setUIDimension(p_height, p_width) # UI properties self.style = (style or curses.color_pair(0)) self._pad.attron(self.style) - # Set UI Dimension (x,y) by giving parent height and width + def setUIDimension(self, p_height, p_width): + """Set UI Dimension (x,y) by giving parent + height and width""" self.v_height = (len(self.content)) + 2 width = len(self.header) + 2 if (len(self.content) > 0): @@ -43,14 +45,15 @@ def setUIDimension(self, p_height, p_width): self.y = int(((p_height + self.v_height) / 2) - self.v_height) self.x = int(((p_width + self.v_width) / 2) - self.v_width) - # Set default window properties + def setWindowProperties(self:PopupWindow, header, content, footer): + """Set default window properties""" self.header = header self.content = content self.footer = footer self.enabled = False - # Take a content and break it to the lines + def break_lines(self: PopupWindow, max_width: int, content: [str]) -> [str]: @@ -59,47 +62,54 @@ def break_lines(self: PopupWindow, length = len(line) mid = int(length / 2) - if(length >= max_width): - # Break the line at the next available space - for j, c in enumerate(line[mid - 1:]): - if(c == ' '): - mid += j - break - - # Apply the line break to the content array - content.pop(i) - content.insert(i, line[:mid - 1]) - content.insert(i + 1, line[mid:]) + self.determine_to_break_content(content, i, length, line, max_width, mid) return content + def determine_to_break_content(self, content, i, length, line, max_width, mid): + if (length >= max_width): + # Break the line at the next available space + for j, c in enumerate(line[mid - 1:]): + if (c == ' '): + mid += j + break + self.apply_line_to_content_array(content, i, line, mid) + + + def apply_line_to_content_array(self, content, i, line, mid): + """Apply the line break to the content array""" + content.pop(i) + content.insert(i, line[:mid - 1]) + content.insert(i + 1, line[mid:]) def toggle(self: PopupWindow) -> bool: self.enabled = not self.enabled return self.enabled - # Add the header to the window + def __draw_header(self: PopupWindow) -> None: + """Add the header line to the window""" self.add_line(0, 1, self.header, underline=True) - # Add the footer to the window + def __draw__footer(self: PopupWindow) -> None: + """Add the footer to the window""" f_width = len(self.footer) + 2 self.add_line(self.v_height - 1, self.v_width - f_width, self.footer, underline=True) - # Read each line of the content and add to the window + def __draw_content(self): + """Read each line of the content and add to the window""" for i, line in enumerate(self.content): self.add_line(1 + i, 2, line) - # Draw the Content to the Window + def draw(self: PopupWindow) -> None: if(self.enabled): super().resize(self.v_height, self.v_width) super().draw() - # start to draw self.__draw_header() self.__draw_content() self.__draw__footer() From 682151b2bea369479aec90ac728178c6dca4c1e2 Mon Sep 17 00:00:00 2001 From: Brian ONeill Date: Thu, 18 Feb 2021 15:10:38 -0800 Subject: [PATCH 17/44] Updating Readme.md --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 8b8567d..0641a3e 100755 --- a/README.md +++ b/README.md @@ -42,12 +42,32 @@ The default configurations provided by CANOpen Monitor can be found in [canopen_ Check out our [Read The Docs](https://canopen-monitor.readthedocs.io) pages for more info on the application sub-components and methods. +### Pre-Requisites +* Ubuntu/Debian Linux System + +* Python 3.8.5 or higher (may use pyenv, https://realpython.com/intro-to-pyenv/#build-dependencies) + ### Install Locally `$` `pip install -e .[dev]` *(Note: the `-e` flag creates a symbolic-link to your local development version. Set it once, and forget it)* +### Test + +#### How to setup a Virtual CAN Signal Generator +`$` `sudo apt-get install can-utils` + +#### How to start a Virtual CAN +`$` `sudo ip link add dev vcan0 type vcan` + +`$` `sudo ip link set up vcan0` + +#### Generate Random Messages with socketcan-dev +`$` `chmod 700 socketcan-dev` + +`$` `./socketcan-dev.py --random-id --random-message -r` + ### Create Documentation Locally `$` `make -C docs clean html` From 2a5dafeb123f5f51b98cd1810218aab13a104e82 Mon Sep 17 00:00:00 2001 From: thaotran Date: Mon, 1 Mar 2021 21:49:52 -0800 Subject: [PATCH 18/44] updated README file --- README.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 0641a3e..fe49c69 100755 --- a/README.md +++ b/README.md @@ -30,12 +30,6 @@ An NCurses-based TUI application for tracking activity over the CAN bus and deco *** -# Configuration - -The default configurations provided by CANOpen Monitor can be found in [canopen_monitor/assets](./canopen_monitor/assets). These are the default assets provided. At runtime these configs are copied to `~/.config/canopen-monitor` where they can be modified and the changes will persist. - -*** - # Development and Contribution ### Documentation @@ -45,30 +39,36 @@ Check out our [Read The Docs](https://canopen-monitor.readthedocs.io) pages for ### Pre-Requisites * Ubuntu/Debian Linux System -* Python 3.8.5 or higher (may use pyenv, https://realpython.com/intro-to-pyenv/#build-dependencies) +* Python 3.8.5 or higher (pyenv is recommended for managing different python versions, https://realpython.com/intro-to-pyenv/#build-dependencies) ### Install Locally -`$` `pip install -e .[dev]` - -*(Note: the `-e` flag creates a symbolic-link to your local development version. Set it once, and forget it)* - -### Test - -#### How to setup a Virtual CAN Signal Generator +#### Setup a virtual CAN signal generator `$` `sudo apt-get install can-utils` -#### How to start a Virtual CAN +#### Start a virtual CAN `$` `sudo ip link add dev vcan0 type vcan` `$` `sudo ip link set up vcan0` -#### Generate Random Messages with socketcan-dev +#### Clone the repo +`$` `git clone https://github.com/Boneill3/CANopen-monitor.git` + +`$` `cd CANopen-monitor` + +`$` `pip install -e .[dev]` + +*(Note: the `-e` flag creates a symbolic-link to your local development version. Set it once, and forget it)* + +#### Generate random messages with socketcan-dev `$` `chmod 700 socketcan-dev` `$` `./socketcan-dev.py --random-id --random-message -r` -### Create Documentation Locally +#### Start the monitor +`$` `canopen-monitor` + +### Create documentation locally `$` `make -C docs clean html` From 3b509849f3ef879f79961d02dd7a3a6cff261829 Mon Sep 17 00:00:00 2001 From: Jane Seigman Date: Mon, 1 Mar 2021 22:31:41 -0800 Subject: [PATCH 19/44] jseigman_utilities.py - review work in progress --- canopen_monitor/parse/utilities.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/canopen_monitor/parse/utilities.py b/canopen_monitor/parse/utilities.py index bdf5fe9..e3b248f 100644 --- a/canopen_monitor/parse/utilities.py +++ b/canopen_monitor/parse/utilities.py @@ -1,3 +1,5 @@ +# Under review by jseigman - in-progress + import array import datetime from struct import unpack From 7bce810811ead760380aacfa7495d641612e6a4b Mon Sep 17 00:00:00 2001 From: Jane Seigman Date: Mon, 8 Mar 2021 21:38:34 -0800 Subject: [PATCH 20/44] Just improve comment on decode function --- canopen_monitor/parse/utilities.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/canopen_monitor/parse/utilities.py b/canopen_monitor/parse/utilities.py index e3b248f..aa5d817 100644 --- a/canopen_monitor/parse/utilities.py +++ b/canopen_monitor/parse/utilities.py @@ -1,5 +1,3 @@ -# Under review by jseigman - in-progress - import array import datetime from struct import unpack @@ -83,16 +81,10 @@ def get_name(eds_config: EDS, index: List[int]) -> (str, str): def decode(defined_type: str, data: List[int]) -> str: """ - Does something? - - Arguments - --------- - defined_type `str`: The data type? - data `[int]`: The data? - - Returns - ------- - `str`: something + Decodes data by defined type + :param defined_type: Hex constant for type + :param data: list of ints to be decoded + :return: Decoded data as string """ if defined_type in (UNSIGNED8, UNSIGNED16, UNSIGNED32, UNSIGNED64): result = str(int.from_bytes(data, byteorder="little", signed=False)) From 8ae3be1a427f08eaf1d050254374174f8fa70d6b Mon Sep 17 00:00:00 2001 From: TheDragonMask Date: Fri, 12 Mar 2021 01:01:29 -0800 Subject: [PATCH 21/44] Recreating prior andniven_pdo.py branch, this time without compatibility issues. Restoring commit: Added informal constants to clarify parse function's conditional message processing --- canopen_monitor/parse/sdo.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/canopen_monitor/parse/sdo.py b/canopen_monitor/parse/sdo.py index 4acceec..a8037b6 100644 --- a/canopen_monitor/parse/sdo.py +++ b/canopen_monitor/parse/sdo.py @@ -880,6 +880,11 @@ def __init__(self): self.__last_sequence = 0 self.__awaiting_conf = False + # informal constants used in parse(cob_id, data, eds) + self.SDO_tx_min = 0x580 + self.SDO_rx_min = 0x600 + self.SDO_rx_max_plus_1 = 0x680 + @property def is_complete(self): return self.__is_complete @@ -887,12 +892,12 @@ def is_complete(self): def parse(self, cob_id: int, data: List[int], eds: EDS): node_id = None try: - if 0x580 <= cob_id < 0x600: + if self.SDO_tx_min <= cob_id < self.SDO_rx_min: sdo_type = SDO_TX - node_id = cob_id - 0x580 - elif 0x600 <= cob_id < 0x680: + node_id = cob_id - self.SDO_tx_min + elif self.SDO_rx_min <= cob_id < self.SDO_rx_max_plus_1: sdo_type = SDO_RX - node_id = cob_id - 0x600 + node_id = cob_id - self.SDO_rx_min else: raise ValueError(f"Provided COB-ID {str(cob_id)} " f"is outside of the range of SDO messages") From 49a335d34b4263d2fee10817dd9e3fd97f3f0c77 Mon Sep 17 00:00:00 2001 From: TheDragonMask Date: Fri, 12 Mar 2021 01:06:11 -0800 Subject: [PATCH 22/44] Restoring commit: Imported MessageType from ..can and used the SDO_TX and SDO_RX tuples instead of the informal constants I had made. --- canopen_monitor/parse/sdo.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/canopen_monitor/parse/sdo.py b/canopen_monitor/parse/sdo.py index a8037b6..03b0e98 100644 --- a/canopen_monitor/parse/sdo.py +++ b/canopen_monitor/parse/sdo.py @@ -2,6 +2,11 @@ from .eds import EDS from .utilities import FailedValidationError, get_name, decode from typing import List +# Using import method from here (and canopen.py): +# https://www.kite.com/python/answers/how-to-import-a-class-from-another-file-in-python +# This was also a useful resource: +# https://www.kite.com/python/answers/how-to-import-a-class-from-another-file-in-python +from ..can import MessageType SDO_TX = 'SDO_TX' SDO_RX = 'SDO_RX' @@ -880,10 +885,11 @@ def __init__(self): self.__last_sequence = 0 self.__awaiting_conf = False + # (no longer necessary, using range of values from message.py) # informal constants used in parse(cob_id, data, eds) - self.SDO_tx_min = 0x580 - self.SDO_rx_min = 0x600 - self.SDO_rx_max_plus_1 = 0x680 + # self.SDO_tx_min = 0x580 + # self.SDO_rx_min = 0x600 + # self.SDO_rx_max_plus_1 = 0x680 @property def is_complete(self): @@ -892,12 +898,13 @@ def is_complete(self): def parse(self, cob_id: int, data: List[int], eds: EDS): node_id = None try: - if self.SDO_tx_min <= cob_id < self.SDO_rx_min: + if MessageType.SDO_TX[0] <= cob_id < MessageType.SDO_TX[0]: sdo_type = SDO_TX - node_id = cob_id - self.SDO_tx_min - elif self.SDO_rx_min <= cob_id < self.SDO_rx_max_plus_1: + node_id = cob_id - MessageType.SDO_TX[0] + # unsure if MessageType.SDO_RX[1] is reaching the right value (0x680), but it seems to + elif MessageType.SDO_RX[0] <= cob_id < (MessageType.SDO_RX[1] + 1): sdo_type = SDO_RX - node_id = cob_id - self.SDO_rx_min + node_id = cob_id - MessageType.SDO_RX[0] else: raise ValueError(f"Provided COB-ID {str(cob_id)} " f"is outside of the range of SDO messages") From e8cee547d7ab0f651cf6d711c47f766c473da025 Mon Sep 17 00:00:00 2001 From: TheDragonMask Date: Fri, 12 Mar 2021 01:07:41 -0800 Subject: [PATCH 23/44] Fixed typo on line 901, had SDO_TX[0] when I meant SDO_RX[0]. --- canopen_monitor/parse/sdo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canopen_monitor/parse/sdo.py b/canopen_monitor/parse/sdo.py index 03b0e98..cd5f0eb 100644 --- a/canopen_monitor/parse/sdo.py +++ b/canopen_monitor/parse/sdo.py @@ -898,7 +898,7 @@ def is_complete(self): def parse(self, cob_id: int, data: List[int], eds: EDS): node_id = None try: - if MessageType.SDO_TX[0] <= cob_id < MessageType.SDO_TX[0]: + if MessageType.SDO_TX[0] <= cob_id < MessageType.SDO_RX[0]: sdo_type = SDO_TX node_id = cob_id - MessageType.SDO_TX[0] # unsure if MessageType.SDO_RX[1] is reaching the right value (0x680), but it seems to From b1dabde7009d77df3613affbad34b3833d9be787 Mon Sep 17 00:00:00 2001 From: TheDragonMask Date: Sat, 13 Mar 2021 17:46:28 -0800 Subject: [PATCH 24/44] Deleted unnecessary informal constants and switched to using ".value[0]" style of accessing the tuple values, as per Jacob Crisan's suggestions. Thanks Jacob! --- canopen_monitor/parse/sdo.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/canopen_monitor/parse/sdo.py b/canopen_monitor/parse/sdo.py index cd5f0eb..88c78dd 100644 --- a/canopen_monitor/parse/sdo.py +++ b/canopen_monitor/parse/sdo.py @@ -885,12 +885,6 @@ def __init__(self): self.__last_sequence = 0 self.__awaiting_conf = False - # (no longer necessary, using range of values from message.py) - # informal constants used in parse(cob_id, data, eds) - # self.SDO_tx_min = 0x580 - # self.SDO_rx_min = 0x600 - # self.SDO_rx_max_plus_1 = 0x680 - @property def is_complete(self): return self.__is_complete @@ -898,13 +892,13 @@ def is_complete(self): def parse(self, cob_id: int, data: List[int], eds: EDS): node_id = None try: - if MessageType.SDO_TX[0] <= cob_id < MessageType.SDO_RX[0]: + if MessageType.SDO_TX.value[0] <= cob_id < MessageType.SDO_RX.value[0]: sdo_type = SDO_TX - node_id = cob_id - MessageType.SDO_TX[0] + node_id = cob_id - MessageType.SDO_TX.value[0] # unsure if MessageType.SDO_RX[1] is reaching the right value (0x680), but it seems to - elif MessageType.SDO_RX[0] <= cob_id < (MessageType.SDO_RX[1] + 1): + elif MessageType.SDO_RX.value[0] <= cob_id < (MessageType.SDO_RX.value[1] + 1): sdo_type = SDO_RX - node_id = cob_id - MessageType.SDO_RX[0] + node_id = cob_id - MessageType.SDO_RX.value[0] else: raise ValueError(f"Provided COB-ID {str(cob_id)} " f"is outside of the range of SDO messages") From 070583b45a0855f3a279b813d9d1dcd6cc8afe4b Mon Sep 17 00:00:00 2001 From: Brian ONeill Date: Mon, 15 Mar 2021 15:40:36 -0700 Subject: [PATCH 25/44] Update SDO parse to use range. --- canopen_monitor/parse/sdo.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/canopen_monitor/parse/sdo.py b/canopen_monitor/parse/sdo.py index 88c78dd..dc66c04 100644 --- a/canopen_monitor/parse/sdo.py +++ b/canopen_monitor/parse/sdo.py @@ -2,10 +2,6 @@ from .eds import EDS from .utilities import FailedValidationError, get_name, decode from typing import List -# Using import method from here (and canopen.py): -# https://www.kite.com/python/answers/how-to-import-a-class-from-another-file-in-python -# This was also a useful resource: -# https://www.kite.com/python/answers/how-to-import-a-class-from-another-file-in-python from ..can import MessageType SDO_TX = 'SDO_TX' @@ -892,11 +888,10 @@ def is_complete(self): def parse(self, cob_id: int, data: List[int], eds: EDS): node_id = None try: - if MessageType.SDO_TX.value[0] <= cob_id < MessageType.SDO_RX.value[0]: + if cob_id in range(*MessageType.SDO_TX.value): sdo_type = SDO_TX node_id = cob_id - MessageType.SDO_TX.value[0] - # unsure if MessageType.SDO_RX[1] is reaching the right value (0x680), but it seems to - elif MessageType.SDO_RX.value[0] <= cob_id < (MessageType.SDO_RX.value[1] + 1): + elif cob_id in range(*MessageType.SDO_RX.value): sdo_type = SDO_RX node_id = cob_id - MessageType.SDO_RX.value[0] else: From bd19e0fb9840ef2f4f46e5a90d3432328f076f5d Mon Sep 17 00:00:00 2001 From: TheDragonMask Date: Thu, 25 Feb 2021 14:40:46 -0800 Subject: [PATCH 26/44] andniven_pdo.py - Started a new branch --- canopen_monitor/parse/pdo.py | 1 + 1 file changed, 1 insertion(+) diff --git a/canopen_monitor/parse/pdo.py b/canopen_monitor/parse/pdo.py index 9b24a95..c6729d3 100644 --- a/canopen_monitor/parse/pdo.py +++ b/canopen_monitor/parse/pdo.py @@ -1,3 +1,4 @@ +#Start of new branch import string from math import ceil, floor from .eds import EDS From c9e7fc21250da1ce6c8d1fe6a14d7f51ba082067 Mon Sep 17 00:00:00 2001 From: TheDragonMask Date: Thu, 25 Feb 2021 14:51:06 -0800 Subject: [PATCH 27/44] Testing 2nd commit --- canopen_monitor/parse/pdo.py | 1 + 1 file changed, 1 insertion(+) diff --git a/canopen_monitor/parse/pdo.py b/canopen_monitor/parse/pdo.py index c6729d3..f94c533 100644 --- a/canopen_monitor/parse/pdo.py +++ b/canopen_monitor/parse/pdo.py @@ -1,4 +1,5 @@ #Start of new branch +#test of 2nd commit import string from math import ceil, floor from .eds import EDS From e2ed63c00a3955fe70f0936ba644716784f80a39 Mon Sep 17 00:00:00 2001 From: TheDragonMask Date: Fri, 5 Mar 2021 17:50:07 -0800 Subject: [PATCH 28/44] added informal constants to clarify parse function's conditional message processing --- canopen_monitor/parse/pdo.py | 43 ++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/canopen_monitor/parse/pdo.py b/canopen_monitor/parse/pdo.py index f94c533..e12eaa2 100644 --- a/canopen_monitor/parse/pdo.py +++ b/canopen_monitor/parse/pdo.py @@ -5,6 +5,17 @@ from .eds import EDS from .utilities import FailedValidationError, get_name, decode +#informal constants used in parse(cob_id, data, eds) +PDO1_tx_min = 0x180 +PDO1_rx_min = 0x200 +PDO2_tx_min = 0x280 +PDO2_rx_min = 0x300 +PDO3_tx_min = 0x380 +PDO3_rx_min = 0x400 +PDO4_tx_min = 0x480 +PDO4_rx_min = 0x500 +PDO4_rx_max_plus_1 = 0x580 + PDO1_TX = 0x1A00 PDO1_RX = 0x1600 PDO2_TX = 0x1A01 @@ -24,36 +35,36 @@ def parse(cob_id: int, data: bytes, eds: EDS): The eds mapping is determined by the cob_id passed ot this function. That indicated which PDO record to look up in the EDS file. """ - if 0x180 <= cob_id < 0x200: # PDO1 tx + if PDO1_tx_min <= cob_id < PDO1_rx_min: # PDO1 tx pdo_type = PDO1_TX - elif 0x200 <= cob_id < 0x280: # PDO1 rx + elif PDO1_rx_min <= cob_id < PDO2_tx_min: # PDO1 rx pdo_type = PDO1_RX - elif 0x280 <= cob_id < 0x300: # PDO2 tx + elif PDO2_tx_min <= cob_id < PDO2_rx_min: # PDO2 tx pdo_type = PDO2_TX - elif 0x300 <= cob_id < 0x380: # PDO2 rx + elif PDO2_rx_min <= cob_id < PDO3_tx_min: # PDO2 rx pdo_type = PDO2_RX - elif 0x380 <= cob_id < 0x400: # PDO3 tx + elif PDO3_tx_min <= cob_id < PDO3_rx_min: # PDO3 tx pdo_type = PDO3_TX - elif 0x400 <= cob_id < 0x480: # PDO3 rx + elif PDO3_rx_min <= cob_id < PDO4_tx_min: # PDO3 rx pdo_type = PDO3_RX - elif 0x480 <= cob_id < 0x500: # PDO4 tx + elif PDO4_tx_min <= cob_id < PDO4_rx_min: # PDO4 tx pdo_type = PDO4_TX - elif 0x500 <= cob_id < 0x580: # PDO4 rx + elif PDO4_rx_min <= cob_id < PDO4_rx_max_plus_1: # PDO4 rx pdo_type = PDO4_RX else: - raise FailedValidationError(data, cob_id - 0x180, cob_id, __name__, + raise FailedValidationError(data, cob_id - PDO1_tx_min, cob_id, __name__, f"Unable to determine pdo type with given " f"cob_id {hex(cob_id)}, expected value " - f"between 0x180 and 0x580") + f"between PDO1_tx_min and PDO4_rx_max_plus_1") if len(data) > 8 or len(data) < 1: - raise FailedValidationError(data, cob_id - 0x180, cob_id, __name__, + raise FailedValidationError(data, cob_id - PDO1_tx_min, cob_id, __name__, f"Invalid payload length {len(data)} " f"expected between 1 and 8") try: eds_elements = eds[hex(pdo_type)][0] except TypeError: - raise FailedValidationError(data, cob_id - 0x180, cob_id, __name__, + raise FailedValidationError(data, cob_id - PDO1_tx_min, cob_id, __name__, f"Unable to find eds data for pdo type " f"{hex(pdo_type)}") @@ -68,12 +79,12 @@ def parse(cob_id: int, data: bytes, eds: EDS): if num_elements in (0xFE, 0xFF): if len(data) != 8: - raise FailedValidationError(data, cob_id - 0x180, cob_id, __name__, + raise FailedValidationError(data, cob_id - PDO1_tx_min, cob_id, __name__, f"Invalid payload length {len(data)} " f"expected 8") return parse_mpdo(num_elements, pdo_type, eds, data, cob_id) - raise FailedValidationError(data, cob_id - 0x180, cob_id, __name__, + raise FailedValidationError(data, cob_id - PDO1_tx_min, cob_id, __name__, f"Invalid pdo mapping detected in eds file at " f"[{pdo_type}sub0]") @@ -89,7 +100,7 @@ def parse_pdo(num_elements, pdo_type, cob_id, eds, data): try: eds_record = eds[hex(pdo_type)][i] except TypeError: - raise FailedValidationError(data, cob_id - 0x180, cob_id, __name__, + raise FailedValidationError(data, cob_id - PDO1_tx_min, cob_id, __name__, f"Unable to find eds data for pdo type " f"{hex(pdo_type)} index {i}") @@ -122,7 +133,7 @@ def parse_pdo(num_elements, pdo_type, cob_id, eds, data): def parse_mpdo(num_elements, pdo_type, eds, data, cob_id): mpdo = MPDO(data) if mpdo.is_source_addressing and num_elements != 0xFE: - raise FailedValidationError(data, cob_id - 0x180, cob_id, __name__, + raise FailedValidationError(data, cob_id - PDO1_tx_min, cob_id, __name__, f"MPDO type and definition do not match. " f"Check eds file at [{pdo_type}sub0]") From 0e249308c459d03d67d1b267eded464b6cb2191f Mon Sep 17 00:00:00 2001 From: TheDragonMask Date: Fri, 12 Mar 2021 01:52:12 -0800 Subject: [PATCH 29/44] Restoring commit: Imported MessageType from ..can and used the PDO_TX and PDO_RX tuples instead of the informal constants I had made. --- canopen_monitor/parse/pdo.py | 37 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/canopen_monitor/parse/pdo.py b/canopen_monitor/parse/pdo.py index e12eaa2..ccd8691 100644 --- a/canopen_monitor/parse/pdo.py +++ b/canopen_monitor/parse/pdo.py @@ -4,17 +4,18 @@ from math import ceil, floor from .eds import EDS from .utilities import FailedValidationError, get_name, decode +from ..can import MessageType #informal constants used in parse(cob_id, data, eds) -PDO1_tx_min = 0x180 -PDO1_rx_min = 0x200 -PDO2_tx_min = 0x280 -PDO2_rx_min = 0x300 -PDO3_tx_min = 0x380 -PDO3_rx_min = 0x400 -PDO4_tx_min = 0x480 -PDO4_rx_min = 0x500 -PDO4_rx_max_plus_1 = 0x580 +# PDO1_tx_min = 0x180 +# PDO1_rx_min = 0x200 +# PDO2_tx_min = 0x280 +# PDO2_rx_min = 0x300 +# PDO3_tx_min = 0x380 +# PDO3_rx_min = 0x400 +# PDO4_tx_min = 0x480 +# PDO4_rx_min = 0x500 +# PDO4_rx_max_plus_1 = 0x580 PDO1_TX = 0x1A00 PDO1_RX = 0x1600 @@ -35,24 +36,24 @@ def parse(cob_id: int, data: bytes, eds: EDS): The eds mapping is determined by the cob_id passed ot this function. That indicated which PDO record to look up in the EDS file. """ - if PDO1_tx_min <= cob_id < PDO1_rx_min: # PDO1 tx + if MessageType.PDO1_TX[0] <= cob_id < MessageType.PDO1_RX[0]: # PDO1 tx pdo_type = PDO1_TX - elif PDO1_rx_min <= cob_id < PDO2_tx_min: # PDO1 rx + elif MessageType.PDO1_RX[0] <= cob_id < MessageType.PDO2_TX[0]: # PDO1 rx pdo_type = PDO1_RX - elif PDO2_tx_min <= cob_id < PDO2_rx_min: # PDO2 tx + elif MessageType.PDO2_TX[0] <= cob_id < MessageType.PDO2_RX[0]: # PDO2 tx pdo_type = PDO2_TX - elif PDO2_rx_min <= cob_id < PDO3_tx_min: # PDO2 rx + elif MessageType.PDO2_RX[0] <= cob_id < MessageType.PDO3_TX[0]: # PDO2 rx pdo_type = PDO2_RX - elif PDO3_tx_min <= cob_id < PDO3_rx_min: # PDO3 tx + elif MessageType.PDO3_TX[0] <= cob_id < MessageType.PDO3_RX[0]: # PDO3 tx pdo_type = PDO3_TX - elif PDO3_rx_min <= cob_id < PDO4_tx_min: # PDO3 rx + elif MessageType.PDO3_RX[0] <= cob_id < MessageType.PDO4_TX[0]: # PDO3 rx pdo_type = PDO3_RX - elif PDO4_tx_min <= cob_id < PDO4_rx_min: # PDO4 tx + elif MessageType.PDO4_TX[0] <= cob_id < MessageType.PDO4_RX[0]: # PDO4 tx pdo_type = PDO4_TX - elif PDO4_rx_min <= cob_id < PDO4_rx_max_plus_1: # PDO4 rx + elif MessageType.PDO4_RX[0] <= cob_id < (MessageType.PDO4_RX[0] + 1): # PDO4 rx pdo_type = PDO4_RX else: - raise FailedValidationError(data, cob_id - PDO1_tx_min, cob_id, __name__, + raise FailedValidationError(data, cob_id - MessageType.PDO1_TX[0], cob_id, __name__, f"Unable to determine pdo type with given " f"cob_id {hex(cob_id)}, expected value " f"between PDO1_tx_min and PDO4_rx_max_plus_1") From f141aab481b1f139d8ca191b027d9a8b2f4ce0b5 Mon Sep 17 00:00:00 2001 From: TheDragonMask Date: Fri, 12 Mar 2021 01:57:41 -0800 Subject: [PATCH 30/44] Made minor comment change to reflect the use of message.py's tuples. --- canopen_monitor/parse/pdo.py | 1 + 1 file changed, 1 insertion(+) diff --git a/canopen_monitor/parse/pdo.py b/canopen_monitor/parse/pdo.py index ccd8691..fa657aa 100644 --- a/canopen_monitor/parse/pdo.py +++ b/canopen_monitor/parse/pdo.py @@ -6,6 +6,7 @@ from .utilities import FailedValidationError, get_name, decode from ..can import MessageType +# (no longer necessary, using the tuple values from message.py) #informal constants used in parse(cob_id, data, eds) # PDO1_tx_min = 0x180 # PDO1_rx_min = 0x200 From 5578c32337a2f8e11ef684015f1206f5d0f2ebb5 Mon Sep 17 00:00:00 2001 From: TheDragonMask Date: Fri, 12 Mar 2021 01:59:43 -0800 Subject: [PATCH 31/44] Fixed typo on line 54, meant to write MessageType.PD04_RX[1] (for the max), not "[0]" (which is the min). --- canopen_monitor/parse/pdo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canopen_monitor/parse/pdo.py b/canopen_monitor/parse/pdo.py index fa657aa..2107072 100644 --- a/canopen_monitor/parse/pdo.py +++ b/canopen_monitor/parse/pdo.py @@ -51,7 +51,7 @@ def parse(cob_id: int, data: bytes, eds: EDS): pdo_type = PDO3_RX elif MessageType.PDO4_TX[0] <= cob_id < MessageType.PDO4_RX[0]: # PDO4 tx pdo_type = PDO4_TX - elif MessageType.PDO4_RX[0] <= cob_id < (MessageType.PDO4_RX[0] + 1): # PDO4 rx + elif MessageType.PDO4_RX[0] <= cob_id < (MessageType.PDO4_RX[1] + 1): # PDO4 rx pdo_type = PDO4_RX else: raise FailedValidationError(data, cob_id - MessageType.PDO1_TX[0], cob_id, __name__, From aea675d41345a016827a5b39386680ce1b2a2a8b Mon Sep 17 00:00:00 2001 From: TheDragonMask Date: Fri, 12 Mar 2021 02:06:30 -0800 Subject: [PATCH 32/44] Fixed typo that left references to the old "constants" method, which were causing errors in the program. --- canopen_monitor/parse/pdo.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/canopen_monitor/parse/pdo.py b/canopen_monitor/parse/pdo.py index 2107072..059e946 100644 --- a/canopen_monitor/parse/pdo.py +++ b/canopen_monitor/parse/pdo.py @@ -8,7 +8,7 @@ # (no longer necessary, using the tuple values from message.py) #informal constants used in parse(cob_id, data, eds) -# PDO1_tx_min = 0x180 +# MessageType.PDO1_TX[0] = 0x180 # PDO1_rx_min = 0x200 # PDO2_tx_min = 0x280 # PDO2_rx_min = 0x300 @@ -57,16 +57,16 @@ def parse(cob_id: int, data: bytes, eds: EDS): raise FailedValidationError(data, cob_id - MessageType.PDO1_TX[0], cob_id, __name__, f"Unable to determine pdo type with given " f"cob_id {hex(cob_id)}, expected value " - f"between PDO1_tx_min and PDO4_rx_max_plus_1") + f"between MessageType.PDO1_TX[0] and MessageType.PDO4_RX[1] + 1") if len(data) > 8 or len(data) < 1: - raise FailedValidationError(data, cob_id - PDO1_tx_min, cob_id, __name__, + raise FailedValidationError(data, cob_id - MessageType.PDO1_TX[0], cob_id, __name__, f"Invalid payload length {len(data)} " f"expected between 1 and 8") try: eds_elements = eds[hex(pdo_type)][0] except TypeError: - raise FailedValidationError(data, cob_id - PDO1_tx_min, cob_id, __name__, + raise FailedValidationError(data, cob_id - MessageType.PDO1_TX[0], cob_id, __name__, f"Unable to find eds data for pdo type " f"{hex(pdo_type)}") @@ -81,12 +81,12 @@ def parse(cob_id: int, data: bytes, eds: EDS): if num_elements in (0xFE, 0xFF): if len(data) != 8: - raise FailedValidationError(data, cob_id - PDO1_tx_min, cob_id, __name__, + raise FailedValidationError(data, cob_id - MessageType.PDO1_TX[0], cob_id, __name__, f"Invalid payload length {len(data)} " f"expected 8") return parse_mpdo(num_elements, pdo_type, eds, data, cob_id) - raise FailedValidationError(data, cob_id - PDO1_tx_min, cob_id, __name__, + raise FailedValidationError(data, cob_id - MessageType.PDO1_TX[0], cob_id, __name__, f"Invalid pdo mapping detected in eds file at " f"[{pdo_type}sub0]") @@ -102,7 +102,7 @@ def parse_pdo(num_elements, pdo_type, cob_id, eds, data): try: eds_record = eds[hex(pdo_type)][i] except TypeError: - raise FailedValidationError(data, cob_id - PDO1_tx_min, cob_id, __name__, + raise FailedValidationError(data, cob_id - MessageType.PDO1_TX[0], cob_id, __name__, f"Unable to find eds data for pdo type " f"{hex(pdo_type)} index {i}") @@ -135,7 +135,7 @@ def parse_pdo(num_elements, pdo_type, cob_id, eds, data): def parse_mpdo(num_elements, pdo_type, eds, data, cob_id): mpdo = MPDO(data) if mpdo.is_source_addressing and num_elements != 0xFE: - raise FailedValidationError(data, cob_id - PDO1_tx_min, cob_id, __name__, + raise FailedValidationError(data, cob_id - MessageType.PDO1_TX[0], cob_id, __name__, f"MPDO type and definition do not match. " f"Check eds file at [{pdo_type}sub0]") From 0c90fa476fbee04d3d27c51276efe0172ffdac5c Mon Sep 17 00:00:00 2001 From: TheDragonMask Date: Fri, 12 Mar 2021 02:07:34 -0800 Subject: [PATCH 33/44] Fixed very minor typo in comments near top. --- canopen_monitor/parse/pdo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canopen_monitor/parse/pdo.py b/canopen_monitor/parse/pdo.py index 059e946..43a1353 100644 --- a/canopen_monitor/parse/pdo.py +++ b/canopen_monitor/parse/pdo.py @@ -8,7 +8,7 @@ # (no longer necessary, using the tuple values from message.py) #informal constants used in parse(cob_id, data, eds) -# MessageType.PDO1_TX[0] = 0x180 +# PDO1_tx_min = 0x180 # PDO1_rx_min = 0x200 # PDO2_tx_min = 0x280 # PDO2_rx_min = 0x300 From 7105b63d7d5022b4543ea96468050962a3dae640 Mon Sep 17 00:00:00 2001 From: TheDragonMask Date: Sat, 13 Mar 2021 17:53:45 -0800 Subject: [PATCH 34/44] Deleted unnecessary informal constants and switched to using ".value[0]" style of accessing the tuple values, as per Jacob Crisan's suggestions on the sdo.py branch. Thanks Jacob! --- canopen_monitor/parse/pdo.py | 44 +++++++++++++----------------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/canopen_monitor/parse/pdo.py b/canopen_monitor/parse/pdo.py index 43a1353..9e06eda 100644 --- a/canopen_monitor/parse/pdo.py +++ b/canopen_monitor/parse/pdo.py @@ -6,18 +6,6 @@ from .utilities import FailedValidationError, get_name, decode from ..can import MessageType -# (no longer necessary, using the tuple values from message.py) -#informal constants used in parse(cob_id, data, eds) -# PDO1_tx_min = 0x180 -# PDO1_rx_min = 0x200 -# PDO2_tx_min = 0x280 -# PDO2_rx_min = 0x300 -# PDO3_tx_min = 0x380 -# PDO3_rx_min = 0x400 -# PDO4_tx_min = 0x480 -# PDO4_rx_min = 0x500 -# PDO4_rx_max_plus_1 = 0x580 - PDO1_TX = 0x1A00 PDO1_RX = 0x1600 PDO2_TX = 0x1A01 @@ -37,36 +25,36 @@ def parse(cob_id: int, data: bytes, eds: EDS): The eds mapping is determined by the cob_id passed ot this function. That indicated which PDO record to look up in the EDS file. """ - if MessageType.PDO1_TX[0] <= cob_id < MessageType.PDO1_RX[0]: # PDO1 tx + if MessageType.PDO1_TX.value[0] <= cob_id < MessageType.PDO1_RX.value[0]: # PDO1 tx pdo_type = PDO1_TX - elif MessageType.PDO1_RX[0] <= cob_id < MessageType.PDO2_TX[0]: # PDO1 rx + elif MessageType.PDO1_RX.value[0] <= cob_id < MessageType.PDO2_TX.value[0]: # PDO1 rx pdo_type = PDO1_RX - elif MessageType.PDO2_TX[0] <= cob_id < MessageType.PDO2_RX[0]: # PDO2 tx + elif MessageType.PDO2_TX.value[0] <= cob_id < MessageType.PDO2_RX.value[0]: # PDO2 tx pdo_type = PDO2_TX - elif MessageType.PDO2_RX[0] <= cob_id < MessageType.PDO3_TX[0]: # PDO2 rx + elif MessageType.PDO2_RX.value[0] <= cob_id < MessageType.PDO3_TX.value[0]: # PDO2 rx pdo_type = PDO2_RX - elif MessageType.PDO3_TX[0] <= cob_id < MessageType.PDO3_RX[0]: # PDO3 tx + elif MessageType.PDO3_TX.value[0] <= cob_id < MessageType.PDO3_RX.value[0]: # PDO3 tx pdo_type = PDO3_TX - elif MessageType.PDO3_RX[0] <= cob_id < MessageType.PDO4_TX[0]: # PDO3 rx + elif MessageType.PDO3_RX.value[0] <= cob_id < MessageType.PDO4_TX.value[0]: # PDO3 rx pdo_type = PDO3_RX - elif MessageType.PDO4_TX[0] <= cob_id < MessageType.PDO4_RX[0]: # PDO4 tx + elif MessageType.PDO4_TX.value[0] <= cob_id < MessageType.PDO4_RX.value[0]: # PDO4 tx pdo_type = PDO4_TX - elif MessageType.PDO4_RX[0] <= cob_id < (MessageType.PDO4_RX[1] + 1): # PDO4 rx + elif MessageType.PDO4_RX.value[0] <= cob_id < (MessageType.PDO4_RX.value[1] + 1): # PDO4 rx pdo_type = PDO4_RX else: - raise FailedValidationError(data, cob_id - MessageType.PDO1_TX[0], cob_id, __name__, + raise FailedValidationError(data, cob_id - MessageType.PDO1_TX.value[0], cob_id, __name__, f"Unable to determine pdo type with given " f"cob_id {hex(cob_id)}, expected value " - f"between MessageType.PDO1_TX[0] and MessageType.PDO4_RX[1] + 1") + f"between MessageType.PDO1_TX.value[0] and MessageType.PDO4_RX.value[1] + 1") if len(data) > 8 or len(data) < 1: - raise FailedValidationError(data, cob_id - MessageType.PDO1_TX[0], cob_id, __name__, + raise FailedValidationError(data, cob_id - MessageType.PDO1_TX.value[0], cob_id, __name__, f"Invalid payload length {len(data)} " f"expected between 1 and 8") try: eds_elements = eds[hex(pdo_type)][0] except TypeError: - raise FailedValidationError(data, cob_id - MessageType.PDO1_TX[0], cob_id, __name__, + raise FailedValidationError(data, cob_id - MessageType.PDO1_TX.value[0], cob_id, __name__, f"Unable to find eds data for pdo type " f"{hex(pdo_type)}") @@ -81,12 +69,12 @@ def parse(cob_id: int, data: bytes, eds: EDS): if num_elements in (0xFE, 0xFF): if len(data) != 8: - raise FailedValidationError(data, cob_id - MessageType.PDO1_TX[0], cob_id, __name__, + raise FailedValidationError(data, cob_id - MessageType.PDO1_TX.value[0], cob_id, __name__, f"Invalid payload length {len(data)} " f"expected 8") return parse_mpdo(num_elements, pdo_type, eds, data, cob_id) - raise FailedValidationError(data, cob_id - MessageType.PDO1_TX[0], cob_id, __name__, + raise FailedValidationError(data, cob_id - MessageType.PDO1_TX.value[0], cob_id, __name__, f"Invalid pdo mapping detected in eds file at " f"[{pdo_type}sub0]") @@ -102,7 +90,7 @@ def parse_pdo(num_elements, pdo_type, cob_id, eds, data): try: eds_record = eds[hex(pdo_type)][i] except TypeError: - raise FailedValidationError(data, cob_id - MessageType.PDO1_TX[0], cob_id, __name__, + raise FailedValidationError(data, cob_id - MessageType.PDO1_TX.value[0], cob_id, __name__, f"Unable to find eds data for pdo type " f"{hex(pdo_type)} index {i}") @@ -135,7 +123,7 @@ def parse_pdo(num_elements, pdo_type, cob_id, eds, data): def parse_mpdo(num_elements, pdo_type, eds, data, cob_id): mpdo = MPDO(data) if mpdo.is_source_addressing and num_elements != 0xFE: - raise FailedValidationError(data, cob_id - MessageType.PDO1_TX[0], cob_id, __name__, + raise FailedValidationError(data, cob_id - MessageType.PDO1_TX.value[0], cob_id, __name__, f"MPDO type and definition do not match. " f"Check eds file at [{pdo_type}sub0]") From 5fef3e8e6da62d0e56a2a390d38fe594c11ab131 Mon Sep 17 00:00:00 2001 From: Brian ONeill Date: Mon, 15 Mar 2021 15:54:13 -0700 Subject: [PATCH 35/44] Remove unneeded comments in pdo.py --- canopen_monitor/parse/pdo.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/canopen_monitor/parse/pdo.py b/canopen_monitor/parse/pdo.py index 9e06eda..650868e 100644 --- a/canopen_monitor/parse/pdo.py +++ b/canopen_monitor/parse/pdo.py @@ -1,5 +1,3 @@ -#Start of new branch -#test of 2nd commit import string from math import ceil, floor from .eds import EDS From c4ba7db09fde18ea41e08801e5eda6546b10e4c2 Mon Sep 17 00:00:00 2001 From: Brian ONeill Date: Thu, 25 Feb 2021 19:35:37 -0800 Subject: [PATCH 36/44] Fix missing control input when using ubuntu --- canopen_monitor/app.py | 80 ++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/canopen_monitor/app.py b/canopen_monitor/app.py index e2d1a36..534f967 100644 --- a/canopen_monitor/app.py +++ b/canopen_monitor/app.py @@ -2,10 +2,19 @@ import curses import datetime as dt from enum import Enum -from . import APP_NAME, APP_VERSION, APP_LICENSE, APP_AUTHOR, APP_DESCRIPTION, APP_URL +from . import APP_NAME, APP_VERSION, APP_LICENSE, APP_AUTHOR, APP_DESCRIPTION, \ + APP_URL from .can import MessageTable, MessageType from .ui import MessagePane, PopupWindow +# Key Constants not defined in curses +# _UBUNTU key constants work in Ububtu +KEY_S_UP = 337 +KEY_S_DOWN = 337 +KEY_C_UP = 567 +KEY_C_UP_UBUNTU = 566 +KEY_C_DOWN = 526 +KEY_C_DOWN_UBUNTU = 525 def pad_hex(value: int) -> str: return f'0x{hex(value).upper()[2:].rjust(3, "0")}' @@ -18,10 +27,12 @@ class KeyMap(Enum): DOWN_ARR = ('Down Arrow', 'Scroll pane down 1 row', curses.KEY_DOWN) LEFT_ARR = ('Left Arrow', 'Scroll pane left 4 cols', curses.KEY_LEFT) RIGHT_ARR = ('Right Arrow', 'Scroll pane right 4 cols', curses.KEY_RIGHT) - S_UP_ARR = ('Shift + Up Arrow', 'Scroll pane up 16 rows', 337) - S_DOWN_ARR = ('Shift + Down Arrow', 'Scroll pane down 16 rows', 336) - C_UP_ARR = ('Ctrl + Up Arrow', 'Move pane selection up', 567) - C_DOWN_ARR = ('Ctrl + Down Arrow', 'Move pane selection down', 526) + S_UP_ARR = ('Shift + Up Arrow', 'Scroll pane up 16 rows', KEY_S_UP) + S_DOWN_ARR = ('Shift + Down Arrow', 'Scroll pane down 16 rows', KEY_S_DOWN) + C_UP_ARR = ('Ctrl + Up Arrow', 'Move pane selection up', + [KEY_C_UP, KEY_C_UP_UBUNTU]) + C_DOWN_ARR = ('Ctrl + Down Arrow', 'Move pane selection down', + [KEY_C_DOWN, KEY_C_UP_UBUNTU]) RESIZE = ('Resize Terminal', 'Reset the dimensions of the app', curses.KEY_RESIZE) @@ -35,15 +46,16 @@ def __init__(self: App, message_table: MessageTable): self.table = message_table self.selected_pane_pos = 0 self.selected_pane = None + self.current = None def __enter__(self: App): # Monitor setup, take a snapshot of the terminal state self.screen = curses.initscr() # Initialize standard out - self.screen.scrollok(True) # Enable window scroll - self.screen.keypad(True) # Enable special key input - self.screen.nodelay(True) # Disable user-input blocking - curses.curs_set(False) # Disable the cursor - self.__init_color_pairs() # Enable colors and create pairs + self.screen.scrollok(True) # Enable window scroll + self.screen.keypad(True) # Enable special key input + self.screen.nodelay(True) # Disable user-input blocking + curses.curs_set(False) # Disable the cursor + self.__init_color_pairs() # Enable colors and create pairs # Don't initialize any grids, sub-panes, or windows until standard io # screen has been initialized @@ -63,10 +75,10 @@ def __enter__(self: App): self.hotkeys_win = PopupWindow(self.screen, header='Hotkeys', content=list( - map(lambda x: - f'{x.value[0]}: {x.value[1]}' - f' ({x.value[2]})', - list(KeyMap))), + map(lambda x: + f'{x.value[0]}: {x.value[1]}' + f' ({x.value[2]})', + list(KeyMap))), footer='F2: exit window', style=curses.color_pair(1)) self.hb_pane = MessagePane(cols={'Node ID': ('node_name', 0, hex), @@ -102,11 +114,11 @@ def __enter__(self: App): def __exit__(self: App, type, value, traceback) -> None: # Monitor destruction, restore terminal state - curses.nocbreak() # Re-enable line-buffering - curses.noecho() # Enable user-input echo - curses.curs_set(True) # Enable the cursor - curses.resetty() # Restore the terminal state - curses.endwin() # Destroy the virtual screen + curses.nocbreak() # Re-enable line-buffering + curses.noecho() # Enable user-input echo + curses.curs_set(True) # Enable the cursor + curses.resetty() # Restore the terminal state + curses.endwin() # Destroy the virtual screen def _handle_keyboard_input(self: App) -> None: """This is only a temporary implementation @@ -119,33 +131,33 @@ def _handle_keyboard_input(self: App) -> None: input = self.screen.getch() curses.flushinp() - if(input == curses.KEY_UP): + if (input == curses.KEY_UP): self.selected_pane.scroll_up() - elif(input == curses.KEY_DOWN): + elif (input == curses.KEY_DOWN): self.selected_pane.scroll_down() - elif(input == 337): # Shift + Up + elif (input == KEY_S_UP): # Shift + Up self.selected_pane.scroll_up(rate=16) - elif(input == 336): # Shift + Down + elif (input == KEY_S_DOWN): # Shift + Down self.selected_pane.scroll_down(rate=16) - elif(input == curses.KEY_LEFT): + elif (input == curses.KEY_LEFT): self.selected_pane.scroll_left(rate=4) - elif(input == curses.KEY_RIGHT): + elif (input == curses.KEY_RIGHT): self.selected_pane.scroll_right(rate=4) - elif(input == curses.KEY_RESIZE): + elif (input == curses.KEY_RESIZE): self.hb_pane._reset_scroll_positions() self.misc_pane._reset_scroll_positions() self.screen.clear() - elif(input == 567): # Ctrl + Up + elif (input in [KEY_C_UP, KEY_C_UP_UBUNTU]): # Ctrl + Up self.__select_pane(self.hb_pane, 0) - elif(input == 526): # Ctrl + Down + elif (input in [KEY_C_DOWN, KEY_C_DOWN_UBUNTU]): # Ctrl + Down self.__select_pane(self.misc_pane, 1) - elif(input == curses.KEY_F1): - if(self.hotkeys_win.enabled): + elif (input == curses.KEY_F1): + if (self.hotkeys_win.enabled): self.hotkeys_win.toggle() self.hotkeys_win.clear() self.info_win.toggle() - elif(input == curses.KEY_F2): - if(self.info_win.enabled): + elif (input == curses.KEY_F2): + if (self.info_win.enabled): self.info_win.toggle() self.info_win.clear() self.hotkeys_win.toggle() @@ -161,7 +173,7 @@ def __init_color_pairs(self: App) -> None: def __select_pane(self: App, pane: MessagePane, pos: int) -> None: # Only undo previous selection if there was any - if(self.selected_pane is not None): + if (self.selected_pane is not None): self.selected_pane.selected = False # Select the new pane and change internal Pane state to indicate it @@ -192,7 +204,7 @@ def draw(self: App, ifaces: [tuple]): self.__draw_header(ifaces) # Draw header info # Draw panes - if(not window_active): + if (not window_active): self.hb_pane.draw() self.misc_pane.draw() From e3e68a851a25ac31e9ac62b6f50fc83d3aec1757 Mon Sep 17 00:00:00 2001 From: Brian ONeill Date: Thu, 25 Feb 2021 20:28:26 -0800 Subject: [PATCH 37/44] Minor cleanup to avoid defining the same thing multiple places --- canopen_monitor/app.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/canopen_monitor/app.py b/canopen_monitor/app.py index 534f967..ab1adf6 100644 --- a/canopen_monitor/app.py +++ b/canopen_monitor/app.py @@ -16,6 +16,11 @@ KEY_C_DOWN = 526 KEY_C_DOWN_UBUNTU = 525 +# Additional User Interface Related Constants +VERTICAL_SCROLL_RATE = 16 +HORIZONTAL_SCROLL_RATE = 4 + + def pad_hex(value: int) -> str: return f'0x{hex(value).upper()[2:].rjust(3, "0")}' @@ -32,7 +37,7 @@ class KeyMap(Enum): C_UP_ARR = ('Ctrl + Up Arrow', 'Move pane selection up', [KEY_C_UP, KEY_C_UP_UBUNTU]) C_DOWN_ARR = ('Ctrl + Down Arrow', 'Move pane selection down', - [KEY_C_DOWN, KEY_C_UP_UBUNTU]) + [KEY_C_DOWN, KEY_C_DOWN_UBUNTU]) RESIZE = ('Resize Terminal', 'Reset the dimensions of the app', curses.KEY_RESIZE) @@ -131,32 +136,32 @@ def _handle_keyboard_input(self: App) -> None: input = self.screen.getch() curses.flushinp() - if (input == curses.KEY_UP): + if (input == KeyMap.UP_ARR.value[2]): self.selected_pane.scroll_up() - elif (input == curses.KEY_DOWN): + elif (input == KeyMap.DOWN_ARR.value[2]): self.selected_pane.scroll_down() - elif (input == KEY_S_UP): # Shift + Up - self.selected_pane.scroll_up(rate=16) - elif (input == KEY_S_DOWN): # Shift + Down - self.selected_pane.scroll_down(rate=16) - elif (input == curses.KEY_LEFT): - self.selected_pane.scroll_left(rate=4) - elif (input == curses.KEY_RIGHT): - self.selected_pane.scroll_right(rate=4) - elif (input == curses.KEY_RESIZE): + elif (input == KeyMap.S_UP_ARR.value[2]): # Shift + Up + self.selected_pane.scroll_up(rate=VERTICAL_SCROLL_RATE) + elif (input == KeyMap.S_DOWN_ARR.value[2]): # Shift + Down + self.selected_pane.scroll_down(rate=VERTICAL_SCROLL_RATE) + elif (input == KeyMap.LEFT_ARR.value[2]): + self.selected_pane.scroll_left(rate=HORIZONTAL_SCROLL_RATE) + elif (input == KeyMap.RIGHT_ARR.value[2]): + self.selected_pane.scroll_right(rate=HORIZONTAL_SCROLL_RATE) + elif (input == KeyMap.RESIZE.value[2]): self.hb_pane._reset_scroll_positions() self.misc_pane._reset_scroll_positions() self.screen.clear() - elif (input in [KEY_C_UP, KEY_C_UP_UBUNTU]): # Ctrl + Up + elif (input in KeyMap.C_UP_ARR.value[2]): # Ctrl + Up self.__select_pane(self.hb_pane, 0) - elif (input in [KEY_C_DOWN, KEY_C_DOWN_UBUNTU]): # Ctrl + Down + elif (input in KeyMap.C_DOWN_ARR.value[2]): # Ctrl + Down self.__select_pane(self.misc_pane, 1) - elif (input == curses.KEY_F1): + elif (input == KeyMap.F1.value[2]): if (self.hotkeys_win.enabled): self.hotkeys_win.toggle() self.hotkeys_win.clear() self.info_win.toggle() - elif (input == curses.KEY_F2): + elif (input == KeyMap.F2.value[2]): if (self.info_win.enabled): self.info_win.toggle() self.info_win.clear() From f5af3e92492bc33382cf32c9ace05afc127cb668 Mon Sep 17 00:00:00 2001 From: Brian ONeill Date: Thu, 25 Feb 2021 21:25:48 -0800 Subject: [PATCH 38/44] Additional function comments --- canopen_monitor/app.py | 85 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 11 deletions(-) diff --git a/canopen_monitor/app.py b/canopen_monitor/app.py index ab1adf6..1f92685 100644 --- a/canopen_monitor/app.py +++ b/canopen_monitor/app.py @@ -8,7 +8,7 @@ from .ui import MessagePane, PopupWindow # Key Constants not defined in curses -# _UBUNTU key constants work in Ububtu +# _UBUNTU key constants work in Ubuntu KEY_S_UP = 337 KEY_S_DOWN = 337 KEY_C_UP = 567 @@ -22,10 +22,19 @@ def pad_hex(value: int) -> str: + """Convert integer value to a hex string with padding + :param value: number of spaces to pad hex value + :return: padded string + """ return f'0x{hex(value).upper()[2:].rjust(3, "0")}' class KeyMap(Enum): + """Enumerator of valid keyboard input + value[0]: input name + value[1]: input description + value[2]: curses input value + """ F1 = ('F1', 'Toggle app info menu', curses.KEY_F1) F2 = ('F2', 'Toggle this menu', curses.KEY_F2) UP_ARR = ('Up Arrow', 'Scroll pane up 1 row', curses.KEY_UP) @@ -44,16 +53,34 @@ class KeyMap(Enum): class App: - """The User Interface + """The User Interface Container + :param table + :type MessageTable + + :param selected_pane_pos index of currently selected pane + :type int + + :param selected_pane reference to currently selected Pane + :type MessagePane """ def __init__(self: App, message_table: MessageTable): + """App Initialization function + :param message_table: Reference to shared message table object + :type MessageTable + """ self.table = message_table self.selected_pane_pos = 0 self.selected_pane = None - self.current = None - def __enter__(self: App): + def __enter__(self: App) -> App: + """Enter the runtime context related to this object + Create the user interface layout. Any changes to the layout should + be done here. + + :return: self + :type App + """ # Monitor setup, take a snapshot of the terminal state self.screen = curses.initscr() # Initialize standard out self.screen.scrollok(True) # Enable window scroll @@ -118,6 +145,14 @@ def __enter__(self: App): return self def __exit__(self: App, type, value, traceback) -> None: + """Exit the runtime context related to this object. + Cleanup any curses settings to allow the terminal + to retrun to normal + :param type: exception type or None + :param value: exception value or None + :param traceback: exception traceback or None + :return: None + """ # Monitor destruction, restore terminal state curses.nocbreak() # Re-enable line-buffering curses.noecho() # Enable user-input echo @@ -140,9 +175,9 @@ def _handle_keyboard_input(self: App) -> None: self.selected_pane.scroll_up() elif (input == KeyMap.DOWN_ARR.value[2]): self.selected_pane.scroll_down() - elif (input == KeyMap.S_UP_ARR.value[2]): # Shift + Up + elif (input == KeyMap.S_UP_ARR.value[2]): self.selected_pane.scroll_up(rate=VERTICAL_SCROLL_RATE) - elif (input == KeyMap.S_DOWN_ARR.value[2]): # Shift + Down + elif (input == KeyMap.S_DOWN_ARR.value[2]): self.selected_pane.scroll_down(rate=VERTICAL_SCROLL_RATE) elif (input == KeyMap.LEFT_ARR.value[2]): self.selected_pane.scroll_left(rate=HORIZONTAL_SCROLL_RATE) @@ -152,9 +187,9 @@ def _handle_keyboard_input(self: App) -> None: self.hb_pane._reset_scroll_positions() self.misc_pane._reset_scroll_positions() self.screen.clear() - elif (input in KeyMap.C_UP_ARR.value[2]): # Ctrl + Up + elif (input in KeyMap.C_UP_ARR.value[2]): self.__select_pane(self.hb_pane, 0) - elif (input in KeyMap.C_DOWN_ARR.value[2]): # Ctrl + Down + elif (input in KeyMap.C_DOWN_ARR.value[2]): self.__select_pane(self.misc_pane, 1) elif (input == KeyMap.F1.value[2]): if (self.hotkeys_win.enabled): @@ -168,6 +203,10 @@ def _handle_keyboard_input(self: App) -> None: self.hotkeys_win.toggle() def __init_color_pairs(self: App) -> None: + """Initialize color options used by curses + + :return: None + """ curses.start_color() # Implied: color pair 0 is standard black and white curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK) @@ -177,6 +216,12 @@ def __init_color_pairs(self: App) -> None: curses.init_pair(5, curses.COLOR_MAGENTA, curses.COLOR_BLACK) def __select_pane(self: App, pane: MessagePane, pos: int) -> None: + """Set Pane as Selected + + :param pane: Reference to selected Pane + :param pos: Index of Selected Pane + :return: None + """ # Only undo previous selection if there was any if (self.selected_pane is not None): self.selected_pane.selected = False @@ -187,6 +232,11 @@ def __select_pane(self: App, pane: MessagePane, pos: int) -> None: self.selected_pane.selected = True def __draw_header(self: App, ifaces: [tuple]) -> None: + """Draw the header at the top of the interface + + :param ifaces: CAN Bus Interfaces + :return: None + """ # Draw the timestamp date_str = f'{dt.datetime.now().ctime()},' self.screen.addstr(0, 0, date_str) @@ -200,11 +250,20 @@ def __draw_header(self: App, ifaces: [tuple]) -> None: pos += sl + 1 def __draw__footer(self: App) -> None: + """Draw the footer at the bottom of the interface + + :return: None + """ height, width = self.screen.getmaxyx() footer = ': Info, : Hotkeys' self.screen.addstr(height - 1, 1, footer) - def draw(self: App, ifaces: [tuple]): + def draw(self: App, ifaces: [tuple]) -> None: + """Draw the entire interface + + :param ifaces: CAN Bus Interfaces + :return: None + """ window_active = self.info_win.enabled or self.hotkeys_win.enabled self.__draw_header(ifaces) # Draw header info @@ -217,7 +276,11 @@ def draw(self: App, ifaces: [tuple]): self.info_win.draw() self.hotkeys_win.draw() - self.__draw__footer() # Draw footer info + self.__draw__footer() - def refresh(self: App): + def refresh(self: App) -> None: + """Refresh entire screen + + :return: None + """ self.screen.refresh() From c1def73fac5adf42c0494508a34979bb78ee3ec6 Mon Sep 17 00:00:00 2001 From: nhanlt81 Date: Sun, 7 Mar 2021 22:15:06 -0800 Subject: [PATCH 39/44] Update KeyMap and key input to use dictionary --- canopen_monitor/app.py | 55 ++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/canopen_monitor/app.py b/canopen_monitor/app.py index 1f92685..fbdbfa1 100644 --- a/canopen_monitor/app.py +++ b/canopen_monitor/app.py @@ -33,23 +33,20 @@ class KeyMap(Enum): """Enumerator of valid keyboard input value[0]: input name value[1]: input description - value[2]: curses input value + value[2]: curses input value key """ - F1 = ('F1', 'Toggle app info menu', curses.KEY_F1) - F2 = ('F2', 'Toggle this menu', curses.KEY_F2) - UP_ARR = ('Up Arrow', 'Scroll pane up 1 row', curses.KEY_UP) - DOWN_ARR = ('Down Arrow', 'Scroll pane down 1 row', curses.KEY_DOWN) - LEFT_ARR = ('Left Arrow', 'Scroll pane left 4 cols', curses.KEY_LEFT) - RIGHT_ARR = ('Right Arrow', 'Scroll pane right 4 cols', curses.KEY_RIGHT) - S_UP_ARR = ('Shift + Up Arrow', 'Scroll pane up 16 rows', KEY_S_UP) - S_DOWN_ARR = ('Shift + Down Arrow', 'Scroll pane down 16 rows', KEY_S_DOWN) - C_UP_ARR = ('Ctrl + Up Arrow', 'Move pane selection up', - [KEY_C_UP, KEY_C_UP_UBUNTU]) - C_DOWN_ARR = ('Ctrl + Down Arrow', 'Move pane selection down', - [KEY_C_DOWN, KEY_C_DOWN_UBUNTU]) - RESIZE = ('Resize Terminal', - 'Reset the dimensions of the app', - curses.KEY_RESIZE) + + F1 = {'name':'F1','description':'Toggle app info menu','key' : curses.KEY_F1} + F2 = {'name':'F2', 'description':'Toggle this menu', 'key': curses.KEY_F2} + UP_ARR = {'name':'Up Arrow', 'description':'Scroll pane up 1 row', 'key':curses.KEY_UP} + DOWN_ARR = {'name':'Down Arrow', 'description':'Scroll pane down 1 row', 'key':curses.KEY_DOWN} + LEFT_ARR = {'name':'Left Arrow', 'description':'Scroll pane left 4 cols', 'key':curses.KEY_LEFT} + RIGHT_ARR = {'name':'Right Arrow', 'description':'Scroll pane right 4 cols', 'key':curses.KEY_RIGHT} + S_UP_ARR = {'name':'Shift + Up Arrow', 'description':'Scroll pane up 16 rows', 'key':KEY_S_UP} + S_DOWN_ARR ={'name':'Shift + Down Arrow', 'description':'Scroll pane down 16 rows', 'key':KEY_S_DOWN} + C_UP_ARR ={'name':'Ctrl + Up Arrow', 'description':'Move pane selection up', 'key':[KEY_C_UP, KEY_C_UP_UBUNTU]} + C_DOWN_ARR ={'name':'Ctrl + Down Arrow', 'description':'Move pane selection down', 'key':[KEY_C_DOWN, KEY_C_DOWN_UBUNTU]} + RESIZE ={'name':'Resize Terminal', 'description':'Reset the dimensions of the app', 'key':curses.KEY_RESIZE} class App: @@ -108,8 +105,8 @@ def __enter__(self: App) -> App: header='Hotkeys', content=list( map(lambda x: - f'{x.value[0]}: {x.value[1]}' - f' ({x.value[2]})', + f'{x.value["name"]}: {x.value["description"]}' + f' ({x.value["key"]})', list(KeyMap))), footer='F2: exit window', style=curses.color_pair(1)) @@ -171,32 +168,32 @@ def _handle_keyboard_input(self: App) -> None: input = self.screen.getch() curses.flushinp() - if (input == KeyMap.UP_ARR.value[2]): + if (input == KeyMap.UP_ARR.value['key']): # KEY_UP self.selected_pane.scroll_up() - elif (input == KeyMap.DOWN_ARR.value[2]): + elif (input == KeyMap.DOWN_ARR.value['key']): # KEY_DOWN self.selected_pane.scroll_down() - elif (input == KeyMap.S_UP_ARR.value[2]): + elif (input == KeyMap.S_UP_ARR.value['key']): # KEY_S_UP self.selected_pane.scroll_up(rate=VERTICAL_SCROLL_RATE) - elif (input == KeyMap.S_DOWN_ARR.value[2]): + elif (input == KeyMap.S_DOWN_ARR.value['key']): # KEY_S_DOWN self.selected_pane.scroll_down(rate=VERTICAL_SCROLL_RATE) - elif (input == KeyMap.LEFT_ARR.value[2]): + elif (input == KeyMap.LEFT_ARR.value['key']): # KEY_LEFT self.selected_pane.scroll_left(rate=HORIZONTAL_SCROLL_RATE) - elif (input == KeyMap.RIGHT_ARR.value[2]): + elif (input == KeyMap.RIGHT_ARR.value['key']): # KEY_RIGHT self.selected_pane.scroll_right(rate=HORIZONTAL_SCROLL_RATE) - elif (input == KeyMap.RESIZE.value[2]): + elif (input == KeyMap.RESIZE.value['key']): # KEY_RESIZE self.hb_pane._reset_scroll_positions() self.misc_pane._reset_scroll_positions() self.screen.clear() - elif (input in KeyMap.C_UP_ARR.value[2]): + elif (input in KeyMap.C_UP_ARR.value['key']): # KEY_C_UP self.__select_pane(self.hb_pane, 0) - elif (input in KeyMap.C_DOWN_ARR.value[2]): + elif (input in KeyMap.C_DOWN_ARR.value['key']): # KEY_C_DOWN self.__select_pane(self.misc_pane, 1) - elif (input == KeyMap.F1.value[2]): + elif (input == KeyMap.F1.value['key']): # KEY_F1 if (self.hotkeys_win.enabled): self.hotkeys_win.toggle() self.hotkeys_win.clear() self.info_win.toggle() - elif (input == KeyMap.F2.value[2]): + elif (input == KeyMap.F2.value['key']): # KEY_F2 if (self.info_win.enabled): self.info_win.toggle() self.info_win.clear() From 176bcd69c6b50abf26806cc13f3627821f736695 Mon Sep 17 00:00:00 2001 From: Jane Seigman Date: Mon, 8 Mar 2021 21:24:22 -0800 Subject: [PATCH 40/44] Created key input dictionary and functions to handle keyboard input instead of if/else statements --- canopen_monitor/app.py | 187 +++++++++++++++++++++++++++++------------ 1 file changed, 131 insertions(+), 56 deletions(-) diff --git a/canopen_monitor/app.py b/canopen_monitor/app.py index fbdbfa1..5c13ebe 100644 --- a/canopen_monitor/app.py +++ b/canopen_monitor/app.py @@ -22,7 +22,8 @@ def pad_hex(value: int) -> str: - """Convert integer value to a hex string with padding + """ + Convert integer value to a hex string with padding :param value: number of spaces to pad hex value :return: padded string """ @@ -30,7 +31,8 @@ def pad_hex(value: int) -> str: class KeyMap(Enum): - """Enumerator of valid keyboard input + """ + Enumerator of valid keyboard input value[0]: input name value[1]: input description value[2]: curses input value key @@ -50,7 +52,8 @@ class KeyMap(Enum): class App: - """The User Interface Container + """ + The User Interface Container :param table :type MessageTable @@ -62,19 +65,35 @@ class App: """ def __init__(self: App, message_table: MessageTable): - """App Initialization function + """ + App Initialization function :param message_table: Reference to shared message table object :type MessageTable """ self.table = message_table self.selected_pane_pos = 0 self.selected_pane = None + self.key_dict = { + KeyMap.UP_ARR.value['key']: self.up, + KeyMap.S_UP_ARR.value['key']: self.shift_up, + KeyMap.C_UP_ARR.value['key'][0]: self.ctrl_up, + KeyMap.C_UP_ARR.value['key'][1]: self.ctrl_up, # Ubuntu key + KeyMap.DOWN_ARR.value['key']: self.down, + KeyMap.S_DOWN_ARR.value['key']: self.shift_down, + KeyMap.C_DOWN_ARR.value['key'][0]: self.ctrl_down, + KeyMap.C_DOWN_ARR.value['key'][1]: self.ctrl_down, # Ubuntu key + KeyMap.LEFT_ARR.value['key']: self.left, + KeyMap.RIGHT_ARR.value['key']: self.right, + KeyMap.RESIZE.value['key']: self.resize, + KeyMap.F1.value['key']: self.f1, + KeyMap.F2.value['key']: self.f2 + } def __enter__(self: App) -> App: - """Enter the runtime context related to this object + """ + Enter the runtime context related to this object Create the user interface layout. Any changes to the layout should be done here. - :return: self :type App """ @@ -142,9 +161,10 @@ def __enter__(self: App) -> App: return self def __exit__(self: App, type, value, traceback) -> None: - """Exit the runtime context related to this object. + """ + Exit the runtime context related to this object. Cleanup any curses settings to allow the terminal - to retrun to normal + to return to normal :param type: exception type or None :param value: exception value or None :param traceback: exception traceback or None @@ -157,51 +177,106 @@ def __exit__(self: App, type, value, traceback) -> None: curses.resetty() # Restore the terminal state curses.endwin() # Destroy the virtual screen - def _handle_keyboard_input(self: App) -> None: - """This is only a temporary implementation + def up(self): + """ + Up arrow key scrolls pane up 1 row + :return: None + """ + self.selected_pane.scroll_up() - .. deprecated:: + def shift_up(self): + """ + Shift + Up arrow key scrolls pane up 16 rows + :return: None + """ + self.selected_pane.scroll_up(rate=VERTICAL_SCROLL_RATE) - Soon to be removed + def ctrl_up(self): """ - # Grab user input - input = self.screen.getch() - curses.flushinp() + Ctrl + Up arrow key moves pane selection up + :return: None + """ + self.__select_pane(self.hb_pane, 0) - if (input == KeyMap.UP_ARR.value['key']): # KEY_UP - self.selected_pane.scroll_up() - elif (input == KeyMap.DOWN_ARR.value['key']): # KEY_DOWN - self.selected_pane.scroll_down() - elif (input == KeyMap.S_UP_ARR.value['key']): # KEY_S_UP - self.selected_pane.scroll_up(rate=VERTICAL_SCROLL_RATE) - elif (input == KeyMap.S_DOWN_ARR.value['key']): # KEY_S_DOWN - self.selected_pane.scroll_down(rate=VERTICAL_SCROLL_RATE) - elif (input == KeyMap.LEFT_ARR.value['key']): # KEY_LEFT - self.selected_pane.scroll_left(rate=HORIZONTAL_SCROLL_RATE) - elif (input == KeyMap.RIGHT_ARR.value['key']): # KEY_RIGHT - self.selected_pane.scroll_right(rate=HORIZONTAL_SCROLL_RATE) - elif (input == KeyMap.RESIZE.value['key']): # KEY_RESIZE - self.hb_pane._reset_scroll_positions() - self.misc_pane._reset_scroll_positions() - self.screen.clear() - elif (input in KeyMap.C_UP_ARR.value['key']): # KEY_C_UP - self.__select_pane(self.hb_pane, 0) - elif (input in KeyMap.C_DOWN_ARR.value['key']): # KEY_C_DOWN - self.__select_pane(self.misc_pane, 1) - elif (input == KeyMap.F1.value['key']): # KEY_F1 - if (self.hotkeys_win.enabled): - self.hotkeys_win.toggle() - self.hotkeys_win.clear() - self.info_win.toggle() - elif (input == KeyMap.F2.value['key']): # KEY_F2 - if (self.info_win.enabled): - self.info_win.toggle() - self.info_win.clear() + def down(self): + """ + Down arrow key scrolls pane down 1 row + :return: None + """ + self.selected_pane.scroll_down() + + def shift_down(self): + """ + Shift + Down arrow key scrolls down pane 16 rows + :return: + """ + self.selected_pane.scroll_down(rate=VERTICAL_SCROLL_RATE) + + def ctrl_down(self): + """ + Ctrl + Down arrow key moves pane selection down + :return: None + """ + self.__select_pane(self.misc_pane, 1) + + def left(self): + """ + Left arrow key scrolls pane left 4 cols + :return: None + """ + self.selected_pane.scroll_left(rate=HORIZONTAL_SCROLL_RATE) + + def right(self): + """ + Right arrow key scrolls pane right 4 cols + :return: None + """ + self.selected_pane.scroll_right(rate=HORIZONTAL_SCROLL_RATE) + + def resize(self): + """ + Resets the dimensions of the app + :return: None + """ + self.hb_pane._reset_scroll_positions() + self.misc_pane._reset_scroll_positions() + self.screen.clear() + + def f1(self): + """ + Toggle app info menu + :return: None + """ + if self.hotkeys_win.enabled: self.hotkeys_win.toggle() + self.hotkeys_win.clear() + self.info_win.toggle() - def __init_color_pairs(self: App) -> None: - """Initialize color options used by curses + def f2(self): + """ + Toggles KeyMap + :return: None + """ + if self.info_win.enabled: + self.info_win.toggle() + self.info_win.clear() + self.hotkeys_win.toggle() + + def _handle_keyboard_input(self: App) -> None: + """ + Retrieves keyboard input and calls the associated key function + """ + keyboard_input = self.screen.getch() + curses.flushinp() + + try: + self.key_dict[keyboard_input]() + except KeyError: + ... + def __init_color_pairs(self: App) -> None: + """ + Initialize color options used by curses :return: None """ curses.start_color() @@ -213,8 +288,8 @@ def __init_color_pairs(self: App) -> None: curses.init_pair(5, curses.COLOR_MAGENTA, curses.COLOR_BLACK) def __select_pane(self: App, pane: MessagePane, pos: int) -> None: - """Set Pane as Selected - + """ + Set Pane as Selected :param pane: Reference to selected Pane :param pos: Index of Selected Pane :return: None @@ -229,8 +304,8 @@ def __select_pane(self: App, pane: MessagePane, pos: int) -> None: self.selected_pane.selected = True def __draw_header(self: App, ifaces: [tuple]) -> None: - """Draw the header at the top of the interface - + """ + Draw the header at the top of the interface :param ifaces: CAN Bus Interfaces :return: None """ @@ -247,8 +322,8 @@ def __draw_header(self: App, ifaces: [tuple]) -> None: pos += sl + 1 def __draw__footer(self: App) -> None: - """Draw the footer at the bottom of the interface - + """ + Draw the footer at the bottom of the interface :return: None """ height, width = self.screen.getmaxyx() @@ -256,8 +331,8 @@ def __draw__footer(self: App) -> None: self.screen.addstr(height - 1, 1, footer) def draw(self: App, ifaces: [tuple]) -> None: - """Draw the entire interface - + """ + Draw the entire interface :param ifaces: CAN Bus Interfaces :return: None """ @@ -276,8 +351,8 @@ def draw(self: App, ifaces: [tuple]) -> None: self.__draw__footer() def refresh(self: App) -> None: - """Refresh entire screen - + """ + Refresh entire screen :return: None """ self.screen.refresh() From 3ad4c7e9c2efb8811291d2c105a0dba6f941998c Mon Sep 17 00:00:00 2001 From: Brian ONeill Date: Sat, 13 Mar 2021 17:53:24 -0800 Subject: [PATCH 41/44] Correct Shift Down key --- canopen_monitor/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canopen_monitor/app.py b/canopen_monitor/app.py index 5c13ebe..9112601 100644 --- a/canopen_monitor/app.py +++ b/canopen_monitor/app.py @@ -10,7 +10,7 @@ # Key Constants not defined in curses # _UBUNTU key constants work in Ubuntu KEY_S_UP = 337 -KEY_S_DOWN = 337 +KEY_S_DOWN = 336 KEY_C_UP = 567 KEY_C_UP_UBUNTU = 566 KEY_C_DOWN = 526 From ce3f4ad6aa739912ce8dac636973423640d0481b Mon Sep 17 00:00:00 2001 From: Brian ONeill Date: Mon, 15 Mar 2021 17:28:21 -0700 Subject: [PATCH 42/44] Update hb parser to allow for empty payload --- canopen_monitor/parse/hb.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/canopen_monitor/parse/hb.py b/canopen_monitor/parse/hb.py index be0ff9e..4993e14 100644 --- a/canopen_monitor/parse/hb.py +++ b/canopen_monitor/parse/hb.py @@ -2,6 +2,7 @@ from .utilities import FailedValidationError from ..can import MessageType +STATE_BYTE_IDX = 0 def parse(cob_id: int, data: list, eds_config: EDS): """ @@ -16,7 +17,6 @@ def parse(cob_id: int, data: list, eds_config: EDS): ------- `str`: The parsed message """ - STATE_BYTE_IDX = 0 states = { 0x00: "Boot-up", 0x04: "Stopped", @@ -25,8 +25,7 @@ def parse(cob_id: int, data: list, eds_config: EDS): } node_id = MessageType.cob_to_node(MessageType.HEARTBEAT, cob_id) - hb_state = data[STATE_BYTE_IDX] - if len(data) < 1 or hb_state not in states: + if len(data) < 1 or data[STATE_BYTE_IDX] not in states: raise FailedValidationError(data, node_id, cob_id, __name__, "Invalid heartbeat state detected") - return states.get(hb_state) + return states.get(data[STATE_BYTE_IDX]) From 972e5a0c0559ada2213698b3219cb9788aad5a62 Mon Sep 17 00:00:00 2001 From: Brian ONeill Date: Tue, 23 Mar 2021 10:40:49 -0700 Subject: [PATCH 43/44] Updating configuration --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index fe49c69..b759c4c 100755 --- a/README.md +++ b/README.md @@ -30,6 +30,16 @@ An NCurses-based TUI application for tracking activity over the CAN bus and deco *** +# Configuration +The default configurations provided by CANOpen Monitor can be found in +[canopen_monitor/assets](./canopen_monitor/assets). These are the default +assets provided. At runtime these configs are copied to +`~/.config/canopen-monitor` where they can be modified and the changes +will persist. + +EDS files are loaded from `~/.cache/canopen-monitor` +*** + # Development and Contribution ### Documentation From 9b6ec91af81881b9f7774d0bc0c77f70fc41c7ca Mon Sep 17 00:00:00 2001 From: Brian ONeill Date: Tue, 23 Mar 2021 10:41:35 -0700 Subject: [PATCH 44/44] Version Bump --- canopen_monitor/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canopen_monitor/__init__.py b/canopen_monitor/__init__.py index a19f6fa..2a5e141 100755 --- a/canopen_monitor/__init__.py +++ b/canopen_monitor/__init__.py @@ -2,7 +2,7 @@ MAJOR = 3 MINOR = 2 -PATCH = 2 +PATCH = 3 APP_NAME = 'canopen-monitor' APP_DESCRIPTION = 'An NCurses-based TUI application for tracking activity' \