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

Commit

Permalink
Merge pull request #63 from oresat/eds-parse-last
Browse files Browse the repository at this point in the history
OD Parse-Last Crash Patch
  • Loading branch information
dmitri-mcguckin authored May 11, 2021
2 parents 6fc6730 + 4518e49 commit 93310de
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 35 deletions.
2 changes: 1 addition & 1 deletion canopen_monitor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

MAJOR = 3
MINOR = 3
PATCH = 1
PATCH = 2

APP_NAME = 'canopen-monitor'
APP_DESCRIPTION = 'An NCurses-based TUI application for tracking activity' \
Expand Down
26 changes: 8 additions & 18 deletions canopen_monitor/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import argparse
from . import APP_NAME, APP_VERSION, APP_DESCRIPTION, CONFIG_DIR, CACHE_DIR
from .app import App
from .meta import Meta
from .can import MagicCANBus, MessageTable
from .parse import CANOpenParser, load_eds_file

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


Expand Down Expand Up @@ -50,27 +52,15 @@ def main():
sys.exit(0)

try:
if(len(args.interfaces) == 0):
print('Warning: no interfaces config was found and you did not'
' specify any interface arguments')
print(f'\t(see {APP_NAME} -h for details)\n')
print('This means the monitor will not be listening to anything.')
while(True):
answer = input('Would you like to continue anyways? [y/N]: ')
if(answer.upper() == 'N' or answer == ''):
sys.exit(0)
elif(answer.upper() == 'Y'):
break
else:
print(f'Invalid response: {answer}')

init_dirs()
eds_configs = load_eds_files()
mt = MessageTable(CANOpenParser(eds_configs))
meta = Meta(CONFIG_DIR, CACHE_DIR)
interfaces = meta.load_devices(args.interfaces)

# Start the can bus and the curses app
with MagicCANBus(args.interfaces, no_block=args.no_block) as bus, \
App(mt, eds_configs, bus) as app:
with MagicCANBus(interfaces, no_block=args.no_block) as bus, \
App(mt, eds_configs, bus, meta) as app:
while True:
# Bus updates
for message in bus:
Expand Down
11 changes: 8 additions & 3 deletions canopen_monitor/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .can import MessageTable, MessageType, MagicCANBus
from .ui import MessagePane, PopupWindow, InputPopup, SelectionPopup
from .parse import eds
from .meta import Meta

# Key Constants not defined in curses
# _UBUNTU key constants work in Ubuntu
Expand Down Expand Up @@ -96,7 +97,7 @@ class App:
"""

def __init__(self: App, message_table: MessageTable, eds_configs: dict,
bus: MagicCANBus):
bus: MagicCANBus, meta: Meta):
"""
App Initialization function
:param message_table: Reference to shared message table object
Expand All @@ -107,6 +108,7 @@ def __init__(self: App, message_table: MessageTable, eds_configs: dict,
self.bus = bus
self.selected_pane_pos = 0
self.selected_pane = None
self.meta = meta
self.key_dict = {
KeyMap.UP_ARR.value['key']: self.up,
KeyMap.S_UP_ARR.value['key']: self.shift_up,
Expand Down Expand Up @@ -175,7 +177,7 @@ 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, hex),
self.hb_pane = MessagePane(cols={'Node ID': ('node_name', 0),
'State': ('state', 0),
'Status': ('message', 0)},
types=[MessageType.HEARTBEAT],
Expand All @@ -187,8 +189,9 @@ def __enter__(self: App) -> App:
name='Heartbeats',
message_table=self.table)
self.misc_pane = MessagePane(cols={'COB ID': ('arb_id', 0, pad_hex),
'Node Name': ('node_name', 0, hex),
'Node Name': ('node_name', 0),
'Type': ('type', 0),
'Age': ('age', 0),
'Message': ('message', 0)},
types=[MessageType.NMT,
MessageType.SYNC,
Expand Down Expand Up @@ -356,6 +359,7 @@ def handle_keyboard_input(self: App) -> None:
value = self.add_if_win.get_value()
if value != "":
self.bus.add_interface(value)
self.meta.save_devices(self.bus)
self.add_if_win.toggle()
else:
self.add_if_win.read_input(keyboard_input)
Expand All @@ -366,6 +370,7 @@ def handle_keyboard_input(self: App) -> None:
value = self.remove_if_win.get_value()
if value != "":
self.bus.remove_interface(value)
self.meta.save_devices(self.bus)
self.remove_if_win.toggle()
else:
self.remove_if_win.read_input(keyboard_input)
Expand Down
1 change: 0 additions & 1 deletion canopen_monitor/assets/dcfs/battery.dcf
Original file line number Diff line number Diff line change
Expand Up @@ -7233,4 +7233,3 @@ AccessType=ro
DefaultValue=00000000000000000000
ParameterValue=
PDOMapping=0

6 changes: 3 additions & 3 deletions canopen_monitor/can/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from enum import Enum
from pyvit.can import Frame

STALE_TIME = dt.timedelta(minutes=2)
DEAD_TIME = dt.timedelta(minutes=4)
STALE_TIME = dt.timedelta(seconds=5)
DEAD_TIME = dt.timedelta(seconds=10)


class MessageType(Enum):
Expand Down Expand Up @@ -141,7 +141,7 @@ class Message(Frame):

def __init__(self: Message, arb_id: int, **kwargs):
super().__init__(arb_id, **kwargs)
self.node_name = MessageType.cob_to_node(self.type, self.arb_id)
self.node_name = 'N/A'
self.message = self.data

@property
Expand Down
3 changes: 2 additions & 1 deletion canopen_monitor/can/message_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

class MessageTable:
def __init__(self: MessageTable, parser=None):
self.parser = parser
self.table = {}
self.parser = parser

def __add__(self: MessageTable, message: Message) -> MessageTable:
if(self.parser is not None):
message.node_name = self.parser.get_name(message)
message.message = self.parser.parse(message)
self.table[message.arb_id] = message
return self
Expand Down
32 changes: 32 additions & 0 deletions canopen_monitor/meta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from .can import MagicCANBus
from os import path
import json


class Meta:
def __init__(self, config_dir, cache_dir):
self.config_dir = config_dir
self.cache_dir = cache_dir
self.interfaces_file = self.config_dir + '/interfaces.json'
self.nodes_file = self.config_dir + '/nodes.json'

def save_devices(self, mcb: MagicCANBus) -> bool:
output = {'interfaces': mcb.interface_list}
with open(self.interfaces_file, "w") as f:
json.dump(output, f)
f.truncate()

def load_devices(self, interface_args: [str]) -> [str]:
if not path.isfile(self.interfaces_file):
return interface_args

with open(self.interfaces_file, "r") as f:
interface_config = json.load(f)
for interface in interface_config['interfaces']:
if interface not in interface_args:
interface_args.append(interface)

return interface_args

def load_node_overrides(self) -> dict:
pass
9 changes: 8 additions & 1 deletion canopen_monitor/parse/canopen.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import Union
from ..can import Message, MessageType
from . import hb as HBParser, \
pdo as PDOParser, \
Expand All @@ -16,6 +17,12 @@ def __init__(self, eds_configs: dict):
self.sdo_parser = SDOParser()
self.eds_configs = eds_configs

def get_name(self, message: Message) -> Union[str, None]:
# import ipdb; ipdb.set_trace()
parser = self.eds_configs.get(message.node_id)
return parser.device_commissioning.node_name \
if parser else hex(message.node_id)

def parse(self, message: Message) -> str:
"""
Detect the type of the given message and return the parsed version
Expand All @@ -30,7 +37,7 @@ def parse(self, message: Message) -> str:
"""
node_id = message.node_id
eds_config = self.eds_configs.get(hex(node_id)) \
eds_config = self.eds_configs.get(node_id) \
if node_id is not None else None

# Detect message type and select the appropriate parse function
Expand Down
8 changes: 4 additions & 4 deletions canopen_monitor/parse/eds.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@ def __len__(self) -> int:
def convert_value(value: str) -> Union[int, str]:
# Turn number-like objects into numbers
if (value != ''):
if (all(c in string.digits for c in value)):
return int(value, 10)
elif (all(c in string.hexdigits for c in value)):
if (all(c in string.hexdigits for c in value)):
return int(value, 16)
elif (all(c in string.digits for c in value)):
return int(value, 10)
else:
return value

Expand All @@ -144,7 +144,7 @@ def __init__(self, eds_data: [str]):

prev = 0
for i, line in enumerate(eds_data):
if line == '':
if line == '' or i == len(eds_data) - 1:
# Handle extra empty strings
if prev == i:
prev = i + 1
Expand Down
2 changes: 1 addition & 1 deletion canopen_monitor/ui/message_pane.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def draw(self: MessagePane) -> None:

def __reset_col_widths(self: Message):
"""
Reset the width of Pane collumn.
Reset the width of Pane collumn.
Based on the length of data to change the width.
"""
for name, data in self.cols.items():
Expand Down
2 changes: 1 addition & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7750,4 +7750,4 @@
ParameterValue=
PDOMapping=0
"""
"""
34 changes: 33 additions & 1 deletion tests/spec_eds_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,20 @@ def test_named_sections(self):
self.eds.mandatory_objects.supported_objects,
"Error parsing Comments named section")

def test_last_index(self):
"""
Parsing should capture the last index if there is no newline
"""
file_check = TEST_EDS.splitlines()
self.assertEqual("PDOMapping=0",
file_check[len(file_check)-1],
"The last line in the EDS test file should not be "
"blank")

self.assertEqual("Last Aolved filepath",
self.eds[hex(0x3102)].parameter_name,
"Error parsing last index location")


class TestDCF(unittest.TestCase):
def setUp(self):
Expand All @@ -88,6 +102,24 @@ def test_get_node_id(self):
"""
DCF Parsing set node id attribute
"""
self.assertEqual(10,
self.assertEqual(0x10,
self.eds.node_id,
"Error parsing node id")

def test_existing_spaces(self):
"""
DCF tests should test importing a file with arbitrary blank lines
This test confirms that the test file contains those blank lines
"""
file_check = TEST_DCF.splitlines()
self.assertEqual("",
file_check[len(file_check) - 1],
"The last line in the DCF Test file should be blank")

self.assertEqual("",
file_check[len(file_check) - 12],
"There should be 2 blank lines before the last index")

self.assertEqual("",
file_check[len(file_check) - 13],
"There should be 2 blank lines before the last index")
Loading

0 comments on commit 93310de

Please sign in to comment.