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

Commit

Permalink
Merge branch 'master' of github.com:oresat/CANopen-monitor
Browse files Browse the repository at this point in the history
  • Loading branch information
dmitri-mcguckin committed May 9, 2021
2 parents 819958d + 84e4cf9 commit 6fc6730
Show file tree
Hide file tree
Showing 15 changed files with 3,478 additions and 80 deletions.
4 changes: 2 additions & 2 deletions canopen_monitor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import os

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

APP_NAME = 'canopen-monitor'
APP_DESCRIPTION = 'An NCurses-based TUI application for tracking activity' \
Expand Down
4 changes: 2 additions & 2 deletions canopen_monitor/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ def main():

# Start the can bus and the curses app
with MagicCANBus(args.interfaces, no_block=args.no_block) as bus, \
App(mt) as app:
App(mt, eds_configs, bus) as app:
while True:
# Bus updates
for message in bus:
if message is not None:
mt += message

# User Input updates
app._handle_keyboard_input()
app.handle_keyboard_input()

# Draw update
app.draw(bus.statuses)
Expand Down
155 changes: 125 additions & 30 deletions canopen_monitor/app.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from __future__ import annotations
import curses
import curses.ascii
import datetime as dt
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
from .can import MessageTable, MessageType
from .ui import MessagePane, PopupWindow
APP_URL, CACHE_DIR
from .can import MessageTable, MessageType, MagicCANBus
from .ui import MessagePane, PopupWindow, InputPopup, SelectionPopup
from .parse import eds

# Key Constants not defined in curses
# _UBUNTU key constants work in Ubuntu
Expand Down Expand Up @@ -45,17 +49,37 @@ class KeyMap(Enum):
value[2]: curses input value key
"""

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}
F1 = {'name': 'F1', 'description': 'Toggle app info menu',
'key': curses.KEY_F1}
F2 = {'name': 'F2', 'description': 'Toggle this menu', 'key': curses.KEY_F2}
F3 = {'name': 'F3', 'description': 'Toggle eds file select',
'key': curses.KEY_F3}
F4 = {'name': 'F4', 'description': 'Toggle add interface',
'key': curses.KEY_F4}
F5 = {'name': 'F5', 'description': 'Toggle remove interface',
'key': curses.KEY_F5}
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:
Expand All @@ -71,13 +95,16 @@ class App:
:type selected_pane: MessagePane
"""

def __init__(self: App, message_table: MessageTable):
def __init__(self: App, message_table: MessageTable, eds_configs: dict,
bus: MagicCANBus):
"""
App Initialization function
:param message_table: Reference to shared message table object
:type MessageTable
"""
self.table = message_table
self.eds_configs = eds_configs
self.bus = bus
self.selected_pane_pos = 0
self.selected_pane = None
self.key_dict = {
Expand All @@ -93,7 +120,10 @@ def __init__(self: App, message_table: MessageTable):
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
KeyMap.F2.value['key']: self.f2,
KeyMap.F3.value['key']: self.f3,
KeyMap.F4.value['key']: self.f4,
KeyMap.F5.value['key']: self.f5,
}

def __enter__(self: App) -> App:
Expand All @@ -109,6 +139,7 @@ def __enter__(self: App) -> App:
self.screen.scrollok(True) # Enable window scroll
self.screen.keypad(True) # Enable special key input
self.screen.nodelay(True) # Disable user-input blocking
curses.noecho() # disable user-input echo
curses.curs_set(False) # Disable the cursor
self.__init_color_pairs() # Enable colors and create pairs

Expand Down Expand Up @@ -136,6 +167,14 @@ def __enter__(self: App) -> App:
list(KeyMap))),
footer='F2: exit window',
style=curses.color_pair(1))
self.add_if_win = InputPopup(self.screen,
header='Add Interface',
footer='ENTER: save, F4: exit window',
style=curses.color_pair(1))
self.remove_if_win = SelectionPopup(self.screen,
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),
'State': ('state', 0),
'Status': ('message', 0)},
Expand Down Expand Up @@ -165,6 +204,8 @@ def __enter__(self: App) -> App:
name='Miscellaneous',
message_table=self.table)
self.__select_pane(self.hb_pane, 0)
self.popups = [self.hotkeys_win, self.info_win, self.add_if_win,
self.remove_if_win]
return self

def __exit__(self: App, type, value, traceback) -> None:
Expand All @@ -179,7 +220,7 @@ 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.echo() # Enable user-input echo
curses.curs_set(True) # Enable the cursor
curses.resetty() # Restore the terminal state
curses.endwin() # Destroy the virtual screen
Expand Down Expand Up @@ -254,28 +295,81 @@ 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()
self.toggle_popup(self.info_win)

def f2(self):
"""
Toggles KeyMap
:return: None
"""
if self.info_win.enabled:
self.info_win.toggle()
self.info_win.clear()
self.hotkeys_win.toggle()
self.toggle_popup(self.hotkeys_win)

def _handle_keyboard_input(self: App) -> None:
def f3(self):
"""
Toggles Add File window
:return: None
"""
filepath = fileopenbox(title='Select Object Dictionary Files',
filetypes=[['*.dcf', '*.eds', '*.xdd',
'Object Dictionary Files']],
multiple=False,
default='~/.cache/canopen-monitor/')

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

def f4(self) -> None:
"""
Toggles Add Interface Popup
:return: None
"""
self.toggle_popup(self.add_if_win)

def f5(self) -> None:
"""
Toggles Remove Interface Popup
:return: None
"""
self.remove_if_win.content = self.bus.interface_list
self.toggle_popup(self.remove_if_win)

def toggle_popup(self, selected_popup) -> None:
for popup in self.popups:
if popup != selected_popup and popup.enabled:
popup.toggle()
popup.clear()

selected_popup.toggle()

def handle_keyboard_input(self: App) -> None:
"""
Retrieves keyboard input and calls the associated key function
"""
keyboard_input = self.screen.getch()
curses.flushinp()

if self.add_if_win.enabled:
if keyboard_input == curses.KEY_ENTER or \
keyboard_input == 10 or keyboard_input == 13:
value = self.add_if_win.get_value()
if value != "":
self.bus.add_interface(value)
self.add_if_win.toggle()
else:
self.add_if_win.read_input(keyboard_input)

elif self.remove_if_win.enabled:
if keyboard_input == curses.KEY_ENTER or \
keyboard_input == 10 or keyboard_input == 13:
value = self.remove_if_win.get_value()
if value != "":
self.bus.remove_interface(value)
self.remove_if_win.toggle()
else:
self.remove_if_win.read_input(keyboard_input)

try:
self.key_dict[keyboard_input]()
except KeyError:
Expand Down Expand Up @@ -334,7 +428,8 @@ def __draw__footer(self: App) -> None:
:return: None
"""
height, width = self.screen.getmaxyx()
footer = '<F1>: Info, <F2>: Hotkeys'
footer = '<F1>: Info, <F2>: Hotkeys, <F3>: Add OD File, ' \
'<F4>: Add Interface, <F5> Remove Interface'
self.screen.addstr(height - 1, 1, footer)

def draw(self: App, ifaces: [tuple]) -> None:
Expand All @@ -343,7 +438,7 @@ def draw(self: App, ifaces: [tuple]) -> None:
:param ifaces: CAN Bus Interfaces
:return: None
"""
window_active = self.info_win.enabled or self.hotkeys_win.enabled
window_active = any(popup.enabled for popup in self.popups)
self.__draw_header(ifaces) # Draw header info

# Draw panes
Expand All @@ -352,8 +447,8 @@ def draw(self: App, ifaces: [tuple]) -> None:
self.misc_pane.draw()

# Draw windows
self.info_win.draw()
self.hotkeys_win.draw()
for popup in self.popups:
popup.draw()

self.__draw__footer()

Expand Down
2 changes: 1 addition & 1 deletion canopen_monitor/can/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def restart(self: Interface) -> None:
>>> iface.start()
"""
self.stop()
self.start()
self.start(False)

def recv(self: Interface) -> Message:
"""A wrapper for `pyvit.hw.SocketCanDev.recv()`
Expand Down
Loading

0 comments on commit 6fc6730

Please sign in to comment.