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

Commit

Permalink
Pdo extensions (#72)
Browse files Browse the repository at this point in the history
* Add support for PDO extensions close #58 #68

* Fix ecss time support

* Fix import/flake issues

* Fix extended pdo data reading

* Add column class for tracking table col width

Co-authored-by: Brian ONeill <Boneill3@users.noreply.github.com>
Co-authored-by: dmitri-mcguckin <dmitri.mcguckin26@gmail.com>
  • Loading branch information
3 people authored Aug 24, 2021
1 parent 2f85fbb commit f93fa77
Show file tree
Hide file tree
Showing 15 changed files with 10,897 additions and 791 deletions.
6 changes: 3 additions & 3 deletions canopen_monitor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import os

MAJOR = 3
MINOR = 3
PATCH = 3
MAJOR = 4
MINOR = 0
PATCH = 0

APP_NAME = 'canopen-monitor'
APP_DESCRIPTION = 'An NCurses-based TUI application for tracking activity' \
Expand Down
24 changes: 3 additions & 21 deletions canopen_monitor/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,14 @@
from .app import App
from .meta import Meta
from .can import MagicCANBus, MessageTable
from .parse import CANOpenParser, load_eds_file, EDS, DataType
from .parse import CANOpenParser, load_eds_files


def init_dirs():
os.makedirs(CONFIG_DIR, exist_ok=True)
os.makedirs(CACHE_DIR, exist_ok=True)


def load_eds_files(filepath: str = CACHE_DIR) -> dict:
configs = {}
for file in os.listdir(filepath):
full_path = f'{filepath}/{file}'
if file.lower().endswith(".eds") or file.lower().endswith(".dcf"):
config = load_eds_file(full_path)
configs[config.node_id] = config
return configs


def enable_ecss_time(configs: dict) -> None:
for od in configs:
if '0x2101' in od:
od['0x2101'].data_type = DataType.ECSS_TIME.value


def main():
parser = argparse.ArgumentParser(prog=APP_NAME,
description=APP_DESCRIPTION,
Expand Down Expand Up @@ -61,15 +45,13 @@ def main():
init_dirs()
meta = Meta(CONFIG_DIR, CACHE_DIR)
features = meta.load_features()
eds_configs = load_eds_files()
if features.ecss_time:
enable_ecss_time(eds_configs)
eds_configs = load_eds_files(CACHE_DIR, features.ecss_time)
mt = MessageTable(CANOpenParser(eds_configs))
interfaces = meta.load_interfaces(args.interfaces)

# Start the can bus and the curses app
with MagicCANBus(interfaces, no_block=args.no_block) as bus, \
App(mt, eds_configs, bus, meta) as app:
App(mt, eds_configs, bus, meta, features) as app:
while True:
# Bus updates
for message in bus:
Expand Down
62 changes: 45 additions & 17 deletions canopen_monitor/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
from easygui import fileopenbox
from shutil import copy
from enum import Enum
from . import APP_NAME, APP_VERSION, APP_LICENSE, APP_AUTHOR, APP_DESCRIPTION, \
APP_URL, CACHE_DIR
from . import APP_NAME, \
APP_VERSION, \
APP_LICENSE, \
APP_AUTHOR, \
APP_DESCRIPTION, \
APP_URL, CACHE_DIR
from .can import MessageTable, MessageType, MagicCANBus
from .ui import MessagePane, PopupWindow, InputPopup, SelectionPopup
from .ui import MessagePane, PopupWindow, InputPopup, SelectionPopup, Column
from .parse import eds
from .meta import Meta
from .meta import Meta, FeatureConfig

# Key Constants not defined in curses
# _UBUNTU key constants work in Ubuntu
Expand Down Expand Up @@ -42,6 +46,23 @@ def pad_hex(value: int, pad: int = 3) -> str:
return f'0x{hex(value).upper()[2:].rjust(pad, "0")}'


def trunc_timedelta(value: dt.timedelta, pad: int = 0):
TIME_UNITS = {'d': 86400, 'h': 3600, 'm': 60, 's': 1, 'ms': 0.1}
time_str = ""
seconds = value.total_seconds()

for name, unit_len in TIME_UNITS.items():
if(name == 'ms' and time_str != ''):
continue
res = int(seconds // unit_len)
seconds -= (res * unit_len)

if(res > 0):
time_str += f'{res}{name}'

return time_str


class KeyMap(Enum):
"""
Enumerator of valid keyboard input
Expand Down Expand Up @@ -97,18 +118,21 @@ class App:
"""

def __init__(self: App, message_table: MessageTable, eds_configs: dict,
bus: MagicCANBus, meta: Meta):
bus: MagicCANBus, meta: Meta, features: FeatureConfig):
"""
App Initialization function
:param message_table: Reference to shared message table object
:type MessageTable
:param features: Application feature settings
:type features: FeatureConfig
"""
self.table = message_table
self.eds_configs = eds_configs
self.bus = bus
self.selected_pane_pos = 0
self.selected_pane = None
self.meta = meta
self.features = features
self.key_dict = {
KeyMap.UP_ARR.value['key']: self.up,
KeyMap.S_UP_ARR.value['key']: self.shift_up,
Expand All @@ -123,7 +147,8 @@ def __init__(self: App, message_table: MessageTable, eds_configs: dict,
KeyMap.RESIZE.value['key']: self.resize,
KeyMap.F1.value['key']: self.f1,
KeyMap.F2.value['key']: self.f2,
KeyMap.F3.value['key']: self.f3,
# TODO: F3 Disabled until easywin is replaced
# KeyMap.F3.value['key']: self.f3,
KeyMap.F4.value['key']: self.f4,
KeyMap.F5.value['key']: self.f5,
}
Expand Down Expand Up @@ -177,10 +202,10 @@ def __enter__(self: App) -> App:
header='Remove Interface',
footer='ENTER: remove, F5: exit window',
style=curses.color_pair(1))
self.hb_pane = MessagePane(cols={'Node ID': ('node_name', 0),
'State': ('state', 0),
'Status': ('message', 0),
'Error': ('error', 0)},
self.hb_pane = MessagePane(cols=[Column('Node ID', 'node_name'),
Column('State', 'state'),
Column('Status', 'message'),
Column('Error', 'error')],
types=[MessageType.HEARTBEAT],
parent=self.screen,
height=int(height / 2) - 1,
Expand All @@ -189,12 +214,15 @@ def __enter__(self: App) -> App:
x=0,
name='Heartbeats',
message_table=self.table)
self.misc_pane = MessagePane(cols={'COB ID': ('arb_id', 0, pad_hex),
'Node Name': ('node_name', 0),
'Type': ('type', 0),
'Age': ('age', 0),
'Message': ('message', 0),
'Error': ('error', 0)},
self.misc_pane = MessagePane(cols=[Column('COB ID', 'arb_id',
fmt_fn=pad_hex),
Column('Node Name', 'node_name'),
Column('Type', 'type'),
Column('Age',
'age',
trunc_timedelta),
Column('Message', 'message'),
Column('Error', 'error')],
types=[MessageType.NMT,
MessageType.SYNC,
MessageType.TIME,
Expand Down Expand Up @@ -321,7 +349,7 @@ def f3(self):
default='~/.cache/canopen-monitor/')

if (filepath is not None):
file = eds.load_eds_file(filepath)
file = eds.load_eds_file(filepath, self.features.ecss_time)
copy(filepath, CACHE_DIR)
self.eds_configs[file.node_id] = file

Expand Down
7 changes: 5 additions & 2 deletions canopen_monitor/can/message_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ def __len__(self: MessageTable) -> int:
def filter(self: MessageTable,
types: MessageType,
start: int = 0,
end: int = None) -> [Message]:
end: int = None,
sort_by: str = 'arb_id',
reverse=False) -> [Message]:
end = len(self.table) if end is None else end
messages = list(filter(lambda x: x.type in types
or x.supertype in types, self.table.values()))
return messages[start:end]
slice = messages[start:end]
return sorted(slice, key=lambda x: getattr(x, sort_by), reverse=reverse)

def __contains__(self: MessageTable, node_id: int) -> bool:
return node_id in self.table
Expand Down
4 changes: 2 additions & 2 deletions canopen_monitor/parse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
for parsing CANOpen messages according to Object Definiton files or Electronic
Data Sheet files, provided by the end user.
"""
from .eds import EDS, load_eds_file
from .eds import EDS, load_eds_file, load_eds_files, DataType
from .canopen import CANOpenParser
from .utilities import DataType

__all__ = [
'CANOpenParser',
'EDS',
'load_eds_file',
'DataType',
'load_eds_files'
]
Loading

0 comments on commit f93fa77

Please sign in to comment.