Skip to content
This repository has been archived by the owner on Apr 1, 2024. It is now read-only.

Commit

Permalink
Update distro line in README
Browse files Browse the repository at this point in the history
Update missing docstring parameters
Update PDO parser to reduce cyclomatic complexity
Fix various PEP8 violations
  • Loading branch information
dmitri-mcguckin committed Mar 24, 2021
1 parent 4b6d7f3 commit dca4280
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 78 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ 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
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`
Expand All @@ -47,9 +47,9 @@ EDS files are loaded from `~/.cache/canopen-monitor`
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 (pyenv is recommended for managing different python versions, https://realpython.com/intro-to-pyenv/#build-dependencies)
* Linux 4.11 or greater (any distribution)

* Python 3.8.5 or higher *(pyenv is recommended for managing different python versions, see [pyenv homepage](https://realpython.com/intro-to-pyenv/#build-dependencies) for information)*

### Install Locally

Expand Down
23 changes: 15 additions & 8 deletions canopen_monitor/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,20 @@
HORIZONTAL_SCROLL_RATE = 4


def pad_hex(value: int) -> str:
def pad_hex(value: int, pad: int = 3) -> str:
"""
Convert integer value to a hex string with padding
:param value: number of spaces to pad hex value
:type value: int
:param pad: the ammount of padding to add
:type pad: int
:return: padded string
:rtype: str
"""
return f'0x{hex(value).upper()[2:].rjust(3, "0")}'
return f'0x{hex(value).upper()[2:].rjust(pad, "0")}'


class KeyMap(Enum):
Expand All @@ -54,14 +61,14 @@ class KeyMap(Enum):
class App:
"""
The User Interface Container
:param table
:type MessageTable
:param table: The table of CAN messages
:type table: MessageTable
:param selected_pane_pos index of currently selected pane
:type int
:param selected_pane_pos: index of currently selected pane
:type selected_pane_pos: int
:param selected_pane reference to currently selected Pane
:type MessagePane
:param selected_pane: A reference to the currently selected Pane
:type selected_pane: MessagePane
"""

def __init__(self: App, message_table: MessageTable):
Expand Down
2 changes: 1 addition & 1 deletion canopen_monitor/can/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ def node_id(self: Message) -> int:
This is a property that is arbitratily decided in an Object Dictionary
and can sometimes have a name attatched to it
:Example:
.. example::
0x621 and 0x721 are addressing the same device on the network,
because both of them share the Node ID of 0x21
Expand Down
3 changes: 1 addition & 2 deletions canopen_monitor/parse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
for parsing CANOpen messages according to Object Definiton files or Electronic
Data Sheet files, provided by the end user.
"""
import enum
from .eds import EDS, load_eds_file
from .canopen import CANOpenParser

__all__ = [
'CANOpenParser',
'EDS',
'load_eds_file',
]
]
5 changes: 4 additions & 1 deletion canopen_monitor/parse/canopen.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .sdo import SDOParser
from .utilities import FailedValidationError


class CANOpenParser:
"""
A convenience wrapper for the parse function
Expand Down Expand Up @@ -53,7 +54,9 @@ def parse(self, message: Message) -> str:
# Call the parse function and save the result
# On error, return the message data
try:
parsed_message = parse_function(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()
Expand Down
36 changes: 23 additions & 13 deletions canopen_monitor/parse/eds.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,40 @@
import string
from re import finditer
from typing import Union
import canopen_monitor.parse as cmp
from dateutil.parser import parse as dtparse
from re import sub, finditer


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
That is, string like "PDO_group" become "pdo_group" instead of "p_d_o_group"
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"
:param old_str: The string to convert to camel_case
:type old_str: str
:return: the camel-cased string
:rtype: 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
# 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
# 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
# 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()
Expand Down Expand Up @@ -60,10 +70,10 @@ def __init__(self, data):
key = camel_to_snake(key)

# Turn date-time-like objects into datetimes
if ('time' in key):
value = dtparse(value).time()
elif ('date' in key):
if ('date' in key):
value = dtparse(value).date()
elif ('time' in key):
value = dtparse(value).time()

# Set the attribute
self.__setattr__(key, value)
Expand Down
10 changes: 5 additions & 5 deletions canopen_monitor/parse/hb.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@

STATE_BYTE_IDX = 0


def parse(cob_id: int, data: list, eds_config: EDS):
"""
Parse Heartbeat message
Arguments
---------
@:param: data: a byte string containing the heartbeat message,
byte 0 is the heartbeat state info.
:param data: a byte string containing the heartbeat message, byte 0 is the
heartbeat state info.
Returns
-------
`str`: The parsed message
:return: the parsed message
:rtype: str
"""
states = {
0x00: "Boot-up",
Expand Down
77 changes: 48 additions & 29 deletions canopen_monitor/parse/pdo.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,43 @@ 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.value[0] <= cob_id < MessageType.PDO1_RX.value[0]: # PDO1 tx
pdo_type = PDO1_TX
elif MessageType.PDO1_RX.value[0] <= cob_id < MessageType.PDO2_TX.value[0]: # PDO1 rx
pdo_type = PDO1_RX
elif MessageType.PDO2_TX.value[0] <= cob_id < MessageType.PDO2_RX.value[0]: # PDO2 tx
pdo_type = PDO2_TX
elif MessageType.PDO2_RX.value[0] <= cob_id < MessageType.PDO3_TX.value[0]: # PDO2 rx
pdo_type = PDO2_RX
elif MessageType.PDO3_TX.value[0] <= cob_id < MessageType.PDO3_RX.value[0]: # PDO3 tx
pdo_type = PDO3_TX
elif MessageType.PDO3_RX.value[0] <= cob_id < MessageType.PDO4_TX.value[0]: # PDO3 rx
pdo_type = PDO3_RX
elif MessageType.PDO4_TX.value[0] <= cob_id < MessageType.PDO4_RX.value[0]: # PDO4 tx
pdo_type = PDO4_TX
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.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.value[0] and MessageType.PDO4_RX.value[1] + 1")
msg_type = MessageType.cob_id_to_type(cob_id)
pdo_type = {
MessageType.PDO1_TX: PDO1_TX,
MessageType.PDO1_RX: PDO1_RX,
MessageType.PDO2_TX: PDO2_TX,
MessageType.PDO2_RX: PDO2_RX,
MessageType.PDO3_TX: PDO3_TX,
MessageType.PDO3_RX: PDO3_RX,
MessageType.PDO4_TX: PDO4_TX,
MessageType.PDO4_RX: PDO4_RX,
MessageType.UKNOWN: None
}[msg_type]

if(not pdo_type or msg_type.supertype is not MessageType.PDO):
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.value[0]}"
f" and {MessageType.PDO4_RX.value[1] + 1}")

if len(data) > 8 or len(data) < 1:
raise FailedValidationError(data, cob_id - MessageType.PDO1_TX.value[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.value[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)}")

Expand All @@ -67,12 +74,18 @@ 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.value[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.value[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]")

Expand All @@ -88,9 +101,12 @@ 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.value[0], cob_id, __name__,
f"Unable to find eds data for pdo type "
f"{hex(pdo_type)} index {i}")
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}")

pdo_definition = int(eds_record.default_value, 16).to_bytes(4, "big")

Expand Down Expand Up @@ -121,7 +137,10 @@ 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.value[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]")

Expand Down
3 changes: 2 additions & 1 deletion canopen_monitor/parse/sdo.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,8 @@ class SDOBlockUploadInitiateNoData:
This class is used by the SDO parser to parse the SDO block initiate
messages containing no data when uploading
This message type is the first message sent from the client during an SDO Upload
This message type is the first message sent from the client during an SDO
Upload
This message contains information about the number of blocks that the client
expects before returning a confirmation
Expand Down
12 changes: 1 addition & 11 deletions canopen_monitor/ui/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ def __init__(self: PopupWindow,
self.style = (style or curses.color_pair(0))
self._pad.attron(self.style)


def setUIDimension(self, p_height, p_width):
"""Set UI Dimension (x,y) by giving parent
height and width"""
Expand All @@ -45,15 +44,13 @@ 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)


def setWindowProperties(self:PopupWindow, header, content, footer):
def setWindowProperties(self: PopupWindow, header, content, footer):
"""Set default window properties"""
self.header = header
self.content = content
self.footer = footer
self.enabled = False


def break_lines(self: PopupWindow,
max_width: int,
content: [str]) -> [str]:
Expand All @@ -74,7 +71,6 @@ def determine_to_break_content(self, content, i, length, line, max_width, mid):
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)
Expand All @@ -85,12 +81,10 @@ def toggle(self: PopupWindow) -> bool:
self.enabled = not self.enabled
return self.enabled


def __draw_header(self: PopupWindow) -> None:
"""Add the header line to the window"""
self.add_line(0, 1, self.header, underline=True)


def __draw__footer(self: PopupWindow) -> None:
"""Add the footer to the window"""
f_width = len(self.footer) + 2
Expand All @@ -99,13 +93,11 @@ def __draw__footer(self: PopupWindow) -> None:
self.footer,
underline=True)


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)


def draw(self: PopupWindow) -> None:
if(self.enabled):
super().resize(self.v_height, self.v_width)
Expand All @@ -117,5 +109,3 @@ def draw(self: PopupWindow) -> None:
else:
# super().clear()
...


0 comments on commit dca4280

Please sign in to comment.