First check-in of enhanced command line interface (CLI):
* Uses curses * Displays a "chat history" with requests and responses * Shows filtered logs from mycroft-skills.log, mycroft-voice.log * Start of framework for special ":" commands (for log searching, etc)pull/541/head
parent
ffa6d4c50b
commit
bd68c478d7
|
@ -18,73 +18,294 @@
|
|||
|
||||
import sys
|
||||
import time
|
||||
import subprocess
|
||||
from cStringIO import StringIO
|
||||
from threading import Thread, Lock
|
||||
|
||||
import curses
|
||||
import curses.ascii
|
||||
|
||||
from mycroft.messagebus.client.ws import WebsocketClient
|
||||
from mycroft.messagebus.message import Message
|
||||
from mycroft.tts import TTSFactory
|
||||
from mycroft.util.log import getLogger
|
||||
|
||||
tts = TTSFactory.create()
|
||||
tts = None
|
||||
ws = None
|
||||
mutex = Lock()
|
||||
logger = getLogger("CLIClient")
|
||||
|
||||
utterances = []
|
||||
chat = []
|
||||
mergedLog = []
|
||||
line = "What time is it"
|
||||
bQuiet = '--quiet' in sys.argv
|
||||
scr = None
|
||||
|
||||
##############################################################################
|
||||
# Helper functions
|
||||
|
||||
def clamp(n, smallest, largest):
|
||||
return max(smallest, min(n, largest))
|
||||
|
||||
|
||||
def stripNonAscii(text):
|
||||
return ''.join([i if ord(i) < 128 else ' ' for i in text])
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Log file monitoring
|
||||
|
||||
class LogMonitorThread(Thread):
|
||||
def __init__(self, filename):
|
||||
Thread.__init__(self)
|
||||
self.filename = filename
|
||||
|
||||
def run(self):
|
||||
global mergedLog
|
||||
|
||||
proc = subprocess.Popen(["tail", "-f", self.filename],
|
||||
stdout=subprocess.PIPE)
|
||||
while True:
|
||||
output = proc.stdout.readline().strip()
|
||||
if output == "" and proc.poll() is not None:
|
||||
break
|
||||
|
||||
# TODO: Filter log output (black and white listing lines)
|
||||
if "enclosure.mouth.viseme" in output:
|
||||
continue
|
||||
|
||||
if output:
|
||||
mergedLog.append(output)
|
||||
draw_screen()
|
||||
|
||||
|
||||
def startLogMonitor(filename):
|
||||
thread = LogMonitorThread(filename)
|
||||
thread.setDaemon(True) # this thread won't prevent prog from exiting
|
||||
thread.start()
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Capturing output from Mycroft
|
||||
|
||||
def handle_speak(event):
|
||||
global chat
|
||||
mutex.acquire()
|
||||
ws.emit(Message("recognizer_loop:audio_output_start"))
|
||||
if not bQuiet:
|
||||
ws.emit(Message("recognizer_loop:audio_output_start"))
|
||||
try:
|
||||
utterance = event.data.get('utterance')
|
||||
print(">> " + utterance)
|
||||
tts.execute(utterance)
|
||||
chat.append(">> " + utterance)
|
||||
draw_screen()
|
||||
if not bQuiet:
|
||||
if not tts:
|
||||
tts = TTSFactory.create()
|
||||
tts.init(ws)
|
||||
tts.execute(utterance)
|
||||
finally:
|
||||
mutex.release()
|
||||
ws.emit(Message("recognizer_loop:audio_output_end"))
|
||||
|
||||
|
||||
def handle_quiet(event):
|
||||
try:
|
||||
utterance = event.data.get('utterance')
|
||||
print(">> " + utterance)
|
||||
finally:
|
||||
pass
|
||||
if not bQuiet:
|
||||
ws.emit(Message("recognizer_loop:audio_output_end"))
|
||||
|
||||
|
||||
def connect():
|
||||
# Once the websocket has connected, just watch it for speak events
|
||||
ws.run_forever()
|
||||
|
||||
##############################################################################
|
||||
# Screen handling
|
||||
|
||||
def main():
|
||||
global ws
|
||||
ws = WebsocketClient()
|
||||
tts.init(ws)
|
||||
if '--quiet' in sys.argv:
|
||||
ws.on('speak', handle_quiet)
|
||||
def init_screen():
|
||||
global CLR_CHAT_HEADING
|
||||
global CLR_CHAT_RESP
|
||||
global CLR_CHAT_QUERY
|
||||
global CLR_CMDLINE
|
||||
global CLR_INPUT
|
||||
global CLR_LOG
|
||||
global CLR_LOG_DEBUG
|
||||
|
||||
if curses.has_colors():
|
||||
bg = curses.COLOR_BLACK
|
||||
for i in range(0, curses.COLORS):
|
||||
curses.init_pair(i + 1, i, bg)
|
||||
|
||||
# Colors
|
||||
# 1 = black on black
|
||||
# 2 = dk red
|
||||
# 3 = dk green
|
||||
# 4 = dk yellow
|
||||
# 5 = dk blue
|
||||
# 6 = dk purple
|
||||
# 7 = dk cyan
|
||||
# 8 = lt gray
|
||||
# 9 = dk gray
|
||||
# 10= red
|
||||
# 11= green
|
||||
# 12= yellow
|
||||
# 13= blue
|
||||
# 14= purple
|
||||
# 15= cyan
|
||||
# 16= white
|
||||
|
||||
CLR_CHAT_HEADING = curses.color_pair(3)
|
||||
CLR_CHAT_RESP = curses.color_pair(7)
|
||||
CLR_CHAT_QUERY = curses.color_pair(8)
|
||||
CLR_CMDLINE = curses.color_pair(15)
|
||||
CLR_INPUT = curses.color_pair(16)
|
||||
CLR_LOG = curses.color_pair(8)
|
||||
CLR_LOG_DEBUG = curses.color_pair(4)
|
||||
|
||||
def draw_screen():
|
||||
global scr
|
||||
scr.clear()
|
||||
|
||||
# Display log output at the top
|
||||
scr.addstr(0, 0, "Log Output", curses.A_REVERSE)
|
||||
scr.addstr(1, 0, "=" * (curses.COLS-1), CLR_LOG)
|
||||
cLogLines = curses.LINES-13
|
||||
|
||||
cLogs = len(mergedLog)
|
||||
y = 2
|
||||
for i in range(clamp(cLogs-cLogLines, 0, cLogs-1), cLogs):
|
||||
log = mergedLog[i]
|
||||
log = log[26:] # skip date/time at the front of log line
|
||||
|
||||
# Categorize log line
|
||||
if "Skills - DEBUG - " in log:
|
||||
log = log.replace("Skills - DEBUG - ", "")
|
||||
clr = CLR_LOG_DEBUG
|
||||
else:
|
||||
clr = CLR_LOG
|
||||
|
||||
# limit line to screen width (show tail end)
|
||||
log = ("..."+log[-(curses.COLS-3):]) if len(log) > curses.COLS else log
|
||||
scr.addstr(y, 0, log, clr)
|
||||
y += 1
|
||||
|
||||
# Log legend in the lower-right
|
||||
scr.addstr(curses.LINES-10, curses.COLS/2 + 2, "Log Output Legend", curses.A_REVERSE)
|
||||
scr.addstr(curses.LINES-9, curses.COLS/2 + 2, "=" * (curses.COLS/2 - 4))
|
||||
scr.addstr(curses.LINES-8, curses.COLS/2 + 2, "mycroft-skills.log, debug info", CLR_LOG_DEBUG)
|
||||
scr.addstr(curses.LINES-7, curses.COLS/2 + 2, "mycroft-skills.log, non debug", CLR_LOG)
|
||||
scr.addstr(curses.LINES-6, curses.COLS/2 + 2, "mycroft-voice.log", CLR_LOG)
|
||||
|
||||
# History log in the middle
|
||||
scr.addstr(curses.LINES-10, 0, "History", CLR_CHAT_HEADING)
|
||||
scr.addstr(curses.LINES-9, 0, "=" * (curses.COLS/2), CLR_CHAT_HEADING)
|
||||
|
||||
cChat = len(chat)
|
||||
if cChat:
|
||||
y = curses.LINES-8
|
||||
for i in range(cChat-clamp(cChat, 1,5), cChat):
|
||||
chat_line = chat[i]
|
||||
if chat_line.startswith(">> "):
|
||||
clr = CLR_CHAT_RESP
|
||||
else:
|
||||
clr = CLR_CHAT_QUERY
|
||||
scr.addstr(y, 0, stripNonAscii(chat_line), clr)
|
||||
y += 1
|
||||
|
||||
# Command line at the bottom
|
||||
l = line
|
||||
if len(line) > 0 and line[0] == ":":
|
||||
scr.addstr(curses.LINES-2, 0, "Command ('help' for options):", CLR_CMDLINE)
|
||||
scr.addstr(curses.LINES-1, 0, ":", CLR_CMDLINE)
|
||||
l = line[1:]
|
||||
else:
|
||||
ws.on('speak', handle_speak)
|
||||
scr.addstr(curses.LINES-2, 0, "Input (Ctrl+C to quit):", CLR_CMDLINE)
|
||||
scr.addstr(curses.LINES-1, 0, ">", CLR_CMDLINE)
|
||||
scr.addstr(curses.LINES-1, 2, l, CLR_INPUT)
|
||||
scr.refresh()
|
||||
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
|
||||
def handle_cmd(cmd):
|
||||
if "show" in cmd and "log" in cmd:
|
||||
pass
|
||||
elif "errors" in cmd:
|
||||
# Look in all logs for error messages, print here
|
||||
pass
|
||||
|
||||
|
||||
def main(stdscr):
|
||||
global scr
|
||||
global ws
|
||||
global line
|
||||
|
||||
scr = stdscr
|
||||
init_screen()
|
||||
|
||||
ws = WebsocketClient()
|
||||
|
||||
ws.on('speak', handle_speak)
|
||||
event_thread = Thread(target=connect)
|
||||
event_thread.setDaemon(True)
|
||||
event_thread.start()
|
||||
|
||||
history = []
|
||||
hist_idx = -1 # index, from the bottom
|
||||
try:
|
||||
input = ""
|
||||
while True:
|
||||
draw_screen()
|
||||
# TODO: Change this mechanism
|
||||
# Sleep for a while so all the output that results
|
||||
# from the previous command finishes before we print.
|
||||
time.sleep(1.5)
|
||||
print("Input (Ctrl+C to quit):")
|
||||
line = sys.stdin.readline()
|
||||
ws.emit(
|
||||
Message("recognizer_loop:utterance",
|
||||
{'utterances': [line.strip()]}))
|
||||
# time.sleep(1.5)
|
||||
|
||||
# print("Input (Ctrl+C to quit):")
|
||||
c = scr.getch()
|
||||
if c == curses.KEY_ENTER or c == 10 or c == 13:
|
||||
if line == "":
|
||||
continue
|
||||
|
||||
if line[:1] == ":":
|
||||
handle_cmd(line[1:])
|
||||
else:
|
||||
history.append(line)
|
||||
chat.append(line)
|
||||
ws.emit(
|
||||
Message("recognizer_loop:utterance",
|
||||
{'utterances': [line.strip()]}))
|
||||
hist_idx = -1
|
||||
line = ""
|
||||
elif c == curses.KEY_UP:
|
||||
hist_idx = clamp(hist_idx+1, -1, len(history)-1)
|
||||
if hist_idx >= 0:
|
||||
line = history[len(history)-hist_idx-1]
|
||||
else:
|
||||
line = ""
|
||||
elif c == curses.KEY_DOWN:
|
||||
hist_idx = clamp(hist_idx-1, -1, len(history)-1)
|
||||
if hist_idx >= 0:
|
||||
line = history[len(history)-hist_idx-1]
|
||||
else:
|
||||
line = ""
|
||||
elif curses.ascii.isascii(c):
|
||||
line += chr(c)
|
||||
elif c == curses.KEY_BACKSPACE:
|
||||
line = line[:-1]
|
||||
else:
|
||||
line += str(c)
|
||||
pass
|
||||
# if line.startswith("*"):
|
||||
# handle_cmd(line.strip("*"))
|
||||
# else:
|
||||
|
||||
except KeyboardInterrupt, e:
|
||||
# User hit Ctrl+C to quit
|
||||
print("")
|
||||
pass
|
||||
except KeyboardInterrupt, e:
|
||||
logger.exception(e)
|
||||
event_thread.exit()
|
||||
sys.exit()
|
||||
|
||||
startLogMonitor("scripts/logs/mycroft-skills.log")
|
||||
startLogMonitor("scripts/logs/mycroft-voice.log")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
curses.wrapper(main)
|
||||
|
||||
|
|
Loading…
Reference in New Issue