mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-12-27 07:21:18 +01:00
Merge pull request #1437 from OlegHahm/pyterm_improvements2
Pyterm improvements
This commit is contained in:
commit
04e256e7cf
@ -149,7 +149,7 @@ flash: all
|
||||
$(FLASHER) $(FFLAGS)
|
||||
|
||||
term:
|
||||
$(TERMPROG) $(PORT)
|
||||
$(TERMPROG) $(TERMFLAGS) $(PORT)
|
||||
|
||||
doc:
|
||||
make -BC $(RIOTBASE) doc
|
||||
|
||||
@ -9,7 +9,7 @@ export AS = $(PREFIX)as
|
||||
export LINK = $(PREFIX)gcc
|
||||
export SIZE = $(PREFIX)size
|
||||
export OBJCOPY = $(PREFIX)objcopy
|
||||
export TERMPROG = $(RIOTBASE)/dist/tools/pyterm/pyterm.py
|
||||
export TERMPROG = $(RIOTBASE)/dist/tools/pyterm/pyterm
|
||||
|
||||
#define the flash-tool and default port depending on the host operating system
|
||||
OS := $(shell uname)
|
||||
@ -36,6 +36,7 @@ export LINKFLAGS += -g3 -ggdb -std=gnu99 $(CPU_USAGE) $(FPU_USAGE) -mlittle-endi
|
||||
export LINKFLAGS += -T$(LINKERSCRIPT)
|
||||
export OFLAGS += -O binary
|
||||
export FFLAGS += -R -e -w -v -b bin/$(BOARD)/$(APPLICATION).hex
|
||||
export TERMFLAGS += -p
|
||||
|
||||
# use the nano-specs of the NewLib when available
|
||||
ifeq ($(shell $(LINK) -specs=nano.specs -E - 2>/dev/null >/dev/null </dev/null ; echo $$?),0)
|
||||
|
||||
@ -13,7 +13,7 @@ export LINK = $(PREFIX)gcc
|
||||
export SIZE = $(PREFIX)size
|
||||
export OBJCOPY = $(PREFIX)objcopy
|
||||
export LINKFLAGS = -mmcu=$(MCU) -lgcc
|
||||
TERMPROG = $(RIOTBASE)/dist/tools/pyterm/pyterm.py
|
||||
TERMPROG = $(RIOTBASE)/dist/tools/pyterm/pyterm
|
||||
|
||||
export FLASHER ?= mspdebug
|
||||
export PORT ?= /dev/ttyUSB0
|
||||
@ -22,6 +22,7 @@ export FFLAGS += -j $(PROGRAMMER)
|
||||
ifeq ($(strip $(PROGRAMMER)),uif)
|
||||
export FFLAGS += -d $(PORT)
|
||||
endif
|
||||
export TERMFLAGS += -p
|
||||
|
||||
export FFLAGS += "prog $(HEXFILE)"
|
||||
|
||||
|
||||
@ -13,13 +13,14 @@ export LINK = $(PREFIX)gcc
|
||||
export SIZE = $(PREFIX)size
|
||||
export OBJCOPY = $(PREFIX)objcopy
|
||||
FLASHER = lpc2k_pgm
|
||||
TERMPROG ?= $(RIOTBASE)/dist/tools/pyterm/pyterm.py
|
||||
TERMPROG ?= $(RIOTBASE)/dist/tools/pyterm/pyterm
|
||||
LINKFLAGS = -gdwarf-2 -mcpu=arm7tdmi-s -static -lgcc -nostartfiles -T$(RIOTBASE)/cpu/$(CPU)/linkerscript.x
|
||||
|
||||
ifeq ($(strip $(PORT)),)
|
||||
export PORT = /dev/ttyUSB0
|
||||
endif
|
||||
export FFLAGS = $(PORT) $(HEXFILE)
|
||||
export TERMFLAGS += -p
|
||||
include $(RIOTBOARD)/msba2-common/Makefile.dep
|
||||
|
||||
export INCLUDES += -I$(RIOTBOARD)/msba2-common/include -I$(RIOTBOARD)/msba2-common/drivers/include
|
||||
|
||||
@ -18,7 +18,7 @@ export LINK = $(PREFIX)gcc
|
||||
export SIZE = $(PREFIX)size
|
||||
export OBJCOPY = $(PREFIX)objcopy
|
||||
FLASHER = mc1322x-load.pl
|
||||
TERM = pyterm.py
|
||||
TERM = pyterm
|
||||
LINKFLAGS = -mcpu=arm7tdmi-s -static -lgcc -nostartfiles -T$(RIOTBASE)/cpu/$(CPU)/mc1322x.lds
|
||||
|
||||
ifeq ($(strip $(PORT)),)
|
||||
@ -26,6 +26,7 @@ ifeq ($(strip $(PORT)),)
|
||||
endif
|
||||
export FFLAGS = -t $(PORT) -f $(HEXFILE) -c 'bbmc -l redbee-econotag reset'
|
||||
export OFLAGS = -O binary --gap-fill=0xff
|
||||
export TERMFLAGS += -p
|
||||
|
||||
export INCLUDES += -I$(RIOTCPU)/$(CPU)/include/ -I$(RIOTBOARD)/$(BOARD)/include/
|
||||
export INCLUDES += -I$(RIOTCPU)/$(CPU)/maca/include
|
||||
|
||||
@ -22,7 +22,7 @@ export AS = $(PREFIX)as
|
||||
export LINK = $(PREFIX)gcc
|
||||
export SIZE = $(PREFIX)size
|
||||
export OBJCOPY = $(PREFIX)objcopy
|
||||
export TERMPROG = $(RIOTBASE)/dist/tools/pyterm/pyterm.py
|
||||
export TERMPROG = $(RIOTBASE)/dist/tools/pyterm/pyterm
|
||||
export FLASHER = st-flash
|
||||
export DEBUGGER = $(RIOTBOARD)/$(BOARD)/dist/debug.sh
|
||||
|
||||
@ -38,6 +38,7 @@ export LINKFLAGS += -T$(LINKERSCRIPT)
|
||||
export OFLAGS = -O binary
|
||||
export FFLAGS = write bin/$(BOARD)/$(APPLICATION).hex 0x08000000
|
||||
export DEBUGGER_FLAGS = $(RIOTBOARD)/$(BOARD)/dist/gdb.conf bin/$(BOARD)/$(APPLICATION).elf
|
||||
export TERMFLAGS += -p
|
||||
|
||||
# use the nano-specs of the NewLib when available
|
||||
ifeq ($(shell $(LINK) -specs=nano.specs -E - 2>/dev/null >/dev/null </dev/null ; echo $$?),0)
|
||||
|
||||
@ -22,7 +22,7 @@ export AS = $(PREFIX)as
|
||||
export LINK = $(PREFIX)gcc
|
||||
export SIZE = $(PREFIX)size
|
||||
export OBJCOPY = $(PREFIX)objcopy
|
||||
export TERMPROG = $(RIOTBASE)/dist/tools/pyterm/pyterm.py
|
||||
export TERMPROG = $(RIOTBASE)/dist/tools/pyterm/pyterm
|
||||
export FLASHER = st-flash
|
||||
export DEBUGGER = $(RIOTBOARD)/$(BOARD)/dist/debug.sh
|
||||
|
||||
@ -37,6 +37,7 @@ export LINKFLAGS += -T$(LINKERSCRIPT)
|
||||
export OFLAGS = -O binary
|
||||
export FFLAGS = write bin/$(BOARD)/$(APPLICATION).hex 0x8000000
|
||||
export DEBUGGER_FLAGS = $(RIOTBOARD)/$(BOARD)/dist/gdb.conf $(BINDIR)/$(APPLICATION).elf
|
||||
export TERMFLAGS += -p
|
||||
|
||||
# use newLib nano-specs if available
|
||||
ifeq ($(shell $(LINK) -specs=nano.specs -E - 2>/dev/null >/dev/null </dev/null ; echo $$?),0)
|
||||
|
||||
@ -14,12 +14,13 @@ export LINK = $(PREFIX)gcc
|
||||
export SIZE = $(PREFIX)size
|
||||
export OBJCOPY = $(PREFIX)objcopy
|
||||
export LINKFLAGS = -mmcu=$(MCU) -lgcc $(BINDIR)msp430_common/startup.o
|
||||
export TERMPROG = $(RIOTBASE)/dist/tools/pyterm/pyterm.py
|
||||
export TERMPROG = $(RIOTBASE)/dist/tools/pyterm/pyterm
|
||||
export FLASHER = goodfet.bsl
|
||||
ifeq ($(strip $(PORT)),)
|
||||
export PORT = /dev/ttyUSB0
|
||||
endif
|
||||
export FFLAGS = --telosb -c $(PORT) -r -e -I -p $(HEXFILE)
|
||||
export TERMFLAGS += -p
|
||||
|
||||
export INCLUDES += -I$(RIOTCPU)/msp430-common/include -I$(RIOTBOARD)/$(BOARD)/include -I$(RIOTBASE)/drivers/cc2420/include -I$(RIOTBASE)/sys/net/include
|
||||
export OFLAGS = -O ihex
|
||||
|
||||
@ -9,7 +9,7 @@ export AS = $(PREFIX)as
|
||||
export LINK = $(PREFIX)gcc
|
||||
export SIZE = $(PREFIX)size
|
||||
export OBJCOPY = $(PREFIX)objcopy
|
||||
export TERMPROG = $(RIOTBASE)/dist/tools/pyterm/pyterm.py
|
||||
export TERMPROG = $(RIOTBASE)/dist/tools/pyterm/pyterm
|
||||
|
||||
#define the flash-tool and default port depending on the host operating system
|
||||
OS := $(shell uname)
|
||||
@ -36,6 +36,7 @@ export LINKFLAGS += -g3 -ggdb -std=gnu99 $(CPU_USAGE) $(FPU_USAGE) -mlittle-endi
|
||||
export LINKFLAGS += -T$(LINKERSCRIPT)
|
||||
export OFLAGS += -O binary
|
||||
export FFLAGS += -R -e -w -v -b bin/$(BOARD)/$(APPLICATION).hex
|
||||
export TERMFLAGS += -p
|
||||
|
||||
# use the nano-specs of the NewLib when available
|
||||
ifeq ($(shell $(LINK) -specs=nano.specs -E - 2>/dev/null >/dev/null </dev/null ; echo $$?),0)
|
||||
|
||||
@ -14,13 +14,14 @@ export LINK = $(PREFIX)gcc
|
||||
export SIZE = $(PREFIX)size
|
||||
export OBJCOPY = $(PREFIX)objcopy
|
||||
export LINKFLAGS = -mmcu=$(MCU) -lgcc $(BINDIR)msp430_common/startup.o
|
||||
export TERMPROG = $(RIOTBASE)/dist/tools/pyterm/pyterm.py
|
||||
export TERMPROG = $(RIOTBASE)/dist/tools/pyterm/pyterm
|
||||
export FLASHER = goodfet.bsl
|
||||
ifeq ($(strip $(PORT)),)
|
||||
export PORT = /dev/ttyUSB0
|
||||
endif
|
||||
export FFLAGS = --z1 -I -c $(PORT) -r -e -p $(HEXFILE)
|
||||
export OFLAGS = -O ihex
|
||||
export TERMFLAGS += -p
|
||||
|
||||
export INCLUDES += -I $(RIOTCPU)/msp430-common/include -I$(RIOTBOARD)/$(BOARD)/include -I$(RIOTBASE)/drivers/cc2420/include -I$(RIOTBASE)/sys/net/include
|
||||
|
||||
|
||||
647
dist/tools/pyterm/pyterm
vendored
Executable file
647
dist/tools/pyterm/pyterm
vendored
Executable file
@ -0,0 +1,647 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2014 Oliver Hahm <oliver.hahm@inria.fr>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
|
||||
try:
|
||||
import configparser
|
||||
except ImportError:
|
||||
import ConfigParser as configparser
|
||||
|
||||
|
||||
import cmd, serial, socket, sys, threading, readline, time, logging, \
|
||||
os, argparse, re, codecs, signal
|
||||
|
||||
### import twisted if available, define dummy classes otherwise
|
||||
try:
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.protocol import Protocol, \
|
||||
ReconnectingClientFactory
|
||||
except ImportError:
|
||||
logging.getLogger("").warn("Twisted not available, please install "
|
||||
"it if you want to use pyterm's JSON "
|
||||
"capabilities")
|
||||
|
||||
class Protocol():
|
||||
def __init__(self):
|
||||
pass
|
||||
class ReconnectingClientFactory():
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
### set some default options
|
||||
import platform
|
||||
defaulthostname = platform.node()
|
||||
|
||||
### default serial port
|
||||
defaultport = "/dev/ttyUSB0"
|
||||
|
||||
### default baudrate for serial connection
|
||||
defaultbaud = 115200
|
||||
|
||||
### directory to store configuration and log files
|
||||
defaultdir = os.environ['HOME'] + os.path.sep + '.pyterm'
|
||||
|
||||
### configuration file name
|
||||
defaultfile = "pyterm-" + defaulthostname + ".conf"
|
||||
|
||||
### logging subfolder
|
||||
defaultrunname = "default-run"
|
||||
|
||||
### default logging prefix format string
|
||||
default_fmt_str = '%(asctime)s - %(levelname)s # %(message)s'
|
||||
|
||||
class SerCmd(cmd.Cmd):
|
||||
"""Main class for pyterm based on Python's Cmd class.
|
||||
|
||||
Runs an interactive terminal that transfer between stdio and serial
|
||||
port.
|
||||
"""
|
||||
|
||||
def __init__(self, port=None, baudrate=None, tcp_serial=None,
|
||||
confdir=None, conffile=None, host=None, run_name=None):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
port (str): serial port
|
||||
baudrate (int): serial baudrate
|
||||
tcp_serial (iht): TCP port to connect to (alternatively)
|
||||
confdir (str): configuration directory
|
||||
conffile (str): configuration file name
|
||||
host (str): local host name
|
||||
run_name (str): identifier for log filer subdirectory
|
||||
|
||||
"""
|
||||
|
||||
# initialize class members
|
||||
cmd.Cmd.__init__(self)
|
||||
self.port = port
|
||||
self.baudrate = baudrate
|
||||
self.tcp_serial = tcp_serial
|
||||
self.configdir = confdir
|
||||
self.configfile = conffile
|
||||
self.host = host
|
||||
self.run_name = run_name
|
||||
|
||||
if not self.host:
|
||||
self.host = defaulthostname
|
||||
|
||||
if not self.run_name:
|
||||
self.run_name = defaultrunname
|
||||
|
||||
if not os.path.exists(self.configdir):
|
||||
os.makedirs(self.configdir)
|
||||
|
||||
self.aliases = dict()
|
||||
self.triggers = dict()
|
||||
self.filters = []
|
||||
self.ignores = []
|
||||
self.json_regs = dict()
|
||||
self.init_cmd = []
|
||||
self.load_config()
|
||||
self.fmt_str = None
|
||||
if not self.fmt_str:
|
||||
self.fmt_str = default_fmt_str
|
||||
|
||||
# check for a history file
|
||||
try:
|
||||
readline.read_history_file()
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
### create Logging object
|
||||
my_millis = "{:.4f}".format(time.time())
|
||||
date_str = '{}.{}'.format(time.strftime('%Y%m%d-%H:%M:%S'),
|
||||
my_millis[-4:])
|
||||
self.startup = date_str
|
||||
# create formatter
|
||||
formatter = logging.Formatter(self.fmt_str)
|
||||
|
||||
directory = self.configdir + os.path.sep + self.host
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
logging.basicConfig(filename = directory + os.path.sep +
|
||||
self.run_name + '.log',
|
||||
level=logging.DEBUG,
|
||||
format=self.fmt_str)
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(logging.DEBUG)
|
||||
|
||||
# create logger
|
||||
self.logger = logging.getLogger('')
|
||||
self.logger.setLevel(logging.INFO)
|
||||
|
||||
# add formatter to ch
|
||||
ch.setFormatter(formatter)
|
||||
# add ch to logger
|
||||
self.logger.addHandler(ch)
|
||||
|
||||
|
||||
def preloop(self):
|
||||
"""Executed bat program start.
|
||||
"""
|
||||
|
||||
# if no serial or TCP is specified use default serial port
|
||||
if not self.port and not self.tcp_serial:
|
||||
sys.stderr.write("No port specified, using default (%s)!\n"
|
||||
% (defaultport))
|
||||
self.port = defaultport
|
||||
# if a TCP port is specified try to connect
|
||||
if self.tcp_serial:
|
||||
self.logger.info("Connect to localhost:%s"
|
||||
% self.tcp_serial)
|
||||
for res in socket.getaddrinfo('localhost', self.tcp_serial,
|
||||
socket.AF_UNSPEC,
|
||||
socket.SOCK_STREAM):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
try:
|
||||
s = fdsocket(af, socktype, proto)
|
||||
except socket.error as msg:
|
||||
s = None
|
||||
continue
|
||||
try:
|
||||
s.connect(sa)
|
||||
except socket.error as msg:
|
||||
s.close()
|
||||
s = None
|
||||
continue
|
||||
break
|
||||
if s:
|
||||
self.ser = s
|
||||
else:
|
||||
self.logger.error("Something went wrong connecting to "
|
||||
"localhost:%s" % self.tcp_serial)
|
||||
sys.exit(1)
|
||||
# otherwise go for the serial port
|
||||
elif self.port:
|
||||
self.logger.info("Connect to serial port %s" % self.port)
|
||||
self.serial_connect()
|
||||
|
||||
# wait until connection is established and fire startup
|
||||
# commands to the node
|
||||
time.sleep(1)
|
||||
for cmd in self.init_cmd:
|
||||
self.logger.debug("WRITE ----->>>>>> '" + cmd + "'\n")
|
||||
self.onecmd(self.precmd(cmd))
|
||||
|
||||
# start serial->console thread
|
||||
receiver_thread = threading.Thread(target=self.reader)
|
||||
receiver_thread.setDaemon(1)
|
||||
receiver_thread.start()
|
||||
|
||||
def precmd(self, line):
|
||||
"""Check for command prefixes to distinguish between Pyterm
|
||||
interal commands and commands that should be send to the node.
|
||||
"""
|
||||
self.logger.debug("processing line #%s#" % line)
|
||||
if (line.startswith("/")):
|
||||
return "PYTERM_" + line[1:]
|
||||
return line
|
||||
|
||||
def default(self, line):
|
||||
"""In case of no Pyterm specific prefix is detected, split
|
||||
string by colons and send it to the node.
|
||||
"""
|
||||
self.logger.debug("%s is no pyterm command, sending to default "
|
||||
"out" % line)
|
||||
for tok in line.split(';'):
|
||||
tok = self.get_alias(tok)
|
||||
self.ser.write((tok.strip() + "\n").encode("utf-8"))
|
||||
|
||||
def do_help(self, line):
|
||||
"""Do not use Cmd's internal help function, but redirect to the
|
||||
node.
|
||||
"""
|
||||
self.ser.write("help\n".encode("utf-8"))
|
||||
|
||||
def complete_date(self, text, line, begidx, endidm):
|
||||
"""Auto completion for date string.
|
||||
"""
|
||||
date = time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
return ["{}".format(date)]
|
||||
|
||||
def do_PYTERM_reset(self, line):
|
||||
"""Pyterm command: Send a reset to the node.
|
||||
"""
|
||||
self.ser.setDTR(1)
|
||||
self.ser.setDTR(0)
|
||||
|
||||
def do_PYTERM_exit(self, line, unused=None):
|
||||
"""Pyterm command: Exit Pyterm.
|
||||
"""
|
||||
self.logger.info("Exiting Pyterm")
|
||||
# save history file
|
||||
readline.write_history_file()
|
||||
# shut down twisted if running
|
||||
if reactor.running:
|
||||
reactor.callFromThread(reactor.stop)
|
||||
if self.tcp_serial:
|
||||
self.ser.close()
|
||||
return True
|
||||
|
||||
def do_PYTERM_save(self, line):
|
||||
"""Pyterm command: Save Pyterm configuration to file.
|
||||
"""
|
||||
if not self.config.has_section("general"):
|
||||
self.config.add_section("general")
|
||||
self.config.set("general", "port", self.port)
|
||||
if len(self.aliases):
|
||||
if not self.config.has_section("aliases"):
|
||||
self.config.add_section("aliases")
|
||||
for alias in self.aliases:
|
||||
self.config.set("aliases", alias, self.aliases[alias])
|
||||
if len(self.triggers):
|
||||
if not self.config.has_section("triggers"):
|
||||
self.config.add_section("triggers")
|
||||
for trigger in self.triggers:
|
||||
self.config.set("triggers", trigger.pattern,
|
||||
self.triggers[trigger])
|
||||
if len(self.json_regs):
|
||||
if not self.config.has_section("json_regs"):
|
||||
self.config.add_section("json_regs")
|
||||
for j in self.json_regs:
|
||||
self.config.set("json_regs", j,
|
||||
self.json_regs[j].pattern)
|
||||
if len(self.filters):
|
||||
if not self.config.has_section("filters"):
|
||||
self.config.add_section("filters")
|
||||
i = 0
|
||||
for r in self.filters:
|
||||
self.config.set("filters", "filter%i" % i, r.pattern)
|
||||
i += 1
|
||||
if len(self.ignores):
|
||||
if not self.config.has_section("ignores"):
|
||||
self.config.add_section("ignores")
|
||||
i = 0
|
||||
for r in self.ignores:
|
||||
self.config.set("ignores", "ignore%i" % i, r.pattern)
|
||||
i += 1
|
||||
if len(self.init_cmd):
|
||||
if not self.config.has_section("init_cmd"):
|
||||
self.config.add_section("init_cmd")
|
||||
i = 0
|
||||
for ic in self.init_cmd:
|
||||
self.config.set("init_cmd", "init_cmd%i" % i, ic)
|
||||
i += 1
|
||||
|
||||
with open(self.configdir + os.path.sep + self.configfile, 'wb')\
|
||||
as config_fd:
|
||||
self.config.write(config_fd)
|
||||
self.logger.info("Config saved")
|
||||
|
||||
def do_PYTERM_show_config(self, line):
|
||||
"""Pyterm command: Show current configuration.
|
||||
"""
|
||||
for key in self.__dict__:
|
||||
print(str(key) + ": " + str(self.__dict__[key]))
|
||||
|
||||
def do_PYTERM_alias(self, line):
|
||||
"""Pyterm command: Register an alias or show an list of all
|
||||
registered aliases.
|
||||
"""
|
||||
if line.endswith("list"):
|
||||
for alias in self.aliases:
|
||||
self.logger.info("{} = {}".format(alias,
|
||||
self.aliases[alias]))
|
||||
return
|
||||
if not line.count("="):
|
||||
sys.stderr.write("Usage: /alias <ALIAS> = <CMD>\n")
|
||||
return
|
||||
alias = line.split('=')[0].strip()
|
||||
command = line[line.index('=')+1:].strip()
|
||||
self.logger.info("adding command %s for alias %s"
|
||||
% (command, alias))
|
||||
self.aliases[alias] = command
|
||||
|
||||
def do_PYTERM_rmalias(self, line):
|
||||
"""Pyterm command: Unregister an alias.
|
||||
"""
|
||||
if not self.aliases.pop(line, None):
|
||||
sys.stderr.write("Alias not found")
|
||||
|
||||
def get_alias(self, tok):
|
||||
"""Internal function to check for aliases.
|
||||
"""
|
||||
for alias in self.aliases:
|
||||
if tok.split()[0] == alias:
|
||||
return self.aliases[alias] + tok[len(alias):]
|
||||
return tok
|
||||
|
||||
def do_PYTERM_trigger(self, line):
|
||||
"""Pyterm command: Register an trigger Regex.
|
||||
"""
|
||||
if not line.count("="):
|
||||
sys.stderr.write("Usage: /trigger <regex> = <CMD>\n")
|
||||
return
|
||||
trigger = line.split('=')[0].strip()
|
||||
action = line[line.index('=')+1:].strip()
|
||||
self.logger.info("adding action %s for trigger %s" % (action,
|
||||
trigger))
|
||||
self.triggers[re.compile(trigger)] = action
|
||||
|
||||
def do_PYTERM_rmtrigger(self, line):
|
||||
"""Pyterm command: Unregister an trigger.
|
||||
"""
|
||||
if not self.triggers.pop(line, None):
|
||||
sys.stderr.write("Trigger not found")
|
||||
|
||||
def do_PYTERM_ignore(self, line):
|
||||
"""Pyterm command: Ignore lines with these Regexes matching.
|
||||
"""
|
||||
self.ignores.append(re.compile(line.strip()))
|
||||
|
||||
def do_PYTERM_unignore(self, line):
|
||||
"""Pyterm command: Remote an ignore Regex.
|
||||
"""
|
||||
for r in self.ignores:
|
||||
if (r.pattern == line.strip()):
|
||||
self.logger.info("Remove ignore for %s" % r.pattern)
|
||||
self.ignores.remove(r)
|
||||
return
|
||||
sys.stderr.write("Ignore for %s not found\n" % line.strip())
|
||||
|
||||
def do_PYTERM_filter(self, line):
|
||||
"""Pyterm command: Show only lines matching this Regex.
|
||||
"""
|
||||
self.filters.append(re.compile(line.strip()))
|
||||
|
||||
def do_PYTERM_unfilter(self, line):
|
||||
"""Pyterm command: Remove a filter.
|
||||
"""
|
||||
for r in self.filters:
|
||||
if (r.pattern == line.strip()):
|
||||
self.logger.info("Remove filter for %s" % r.pattern)
|
||||
self.filters.remove(r)
|
||||
return
|
||||
sys.stderr.write("Filter for %s not found\n" % line.strip())
|
||||
|
||||
def do_PYTERM_json(self, line):
|
||||
"""Pyterm command: Transfer lines matching this Regex as JSON
|
||||
object.
|
||||
"""
|
||||
self.json_regs[line.split(' ')[0].strip()] = \
|
||||
re.compile(line.partition(' ')[2].strip())
|
||||
|
||||
def do_PYTERM_unjson(self, line):
|
||||
"""Pyterm command: Remove a JSON filter.
|
||||
"""
|
||||
if not self.aliases.pop(line, None):
|
||||
sys.stderr.write("JSON regex with ID %s not found" % line)
|
||||
|
||||
def do_PYTERM_init(self, line):
|
||||
"""Pyterm command: Add an startup command. (Only useful in
|
||||
addition with /save).
|
||||
"""
|
||||
self.init_cmd.append(line.strip())
|
||||
|
||||
def load_config(self):
|
||||
"""Internal function to laod configuration from file.
|
||||
"""
|
||||
self.config = configparser.SafeConfigParser()
|
||||
self.config.read([self.configdir + os.path.sep + \
|
||||
self.configfile])
|
||||
|
||||
for sec in self.config.sections():
|
||||
if sec == "filters":
|
||||
for opt in self.config.options(sec):
|
||||
self.filters.append(
|
||||
re.compile(self.config.get(sec, opt)))
|
||||
if sec == "ignores":
|
||||
for opt in self.config.options(sec):
|
||||
self.ignores.append(
|
||||
re.compile(self.config.get(sec, opt)))
|
||||
if sec == "json_regs":
|
||||
for opt in self.config.options(sec):
|
||||
self.logger.info("add json regex for %s"
|
||||
% self.config.get(sec, opt))
|
||||
self.json_regs[opt] = \
|
||||
re.compile(self.config.get(sec, opt))
|
||||
if sec == "aliases":
|
||||
for opt in self.config.options(sec):
|
||||
self.aliases[opt] = self.config.get(sec, opt)
|
||||
if sec == "triggers":
|
||||
for opt in self.config.options(sec):
|
||||
self.triggers[re.compile(opt)] = \
|
||||
self.config.get(sec, opt)
|
||||
if sec == "init_cmd":
|
||||
for opt in self.config.options(sec):
|
||||
self.init_cmd.append(self.config.get(sec, opt))
|
||||
else:
|
||||
for opt in self.config.options(sec):
|
||||
if opt not in self.__dict__:
|
||||
self.__dict__[opt] = self.config.get(sec, opt)
|
||||
|
||||
def process_line(self, line):
|
||||
"""Processes a valid line from node that should be printed and
|
||||
possibly forwarded.
|
||||
|
||||
Args:
|
||||
line (str): input from node.
|
||||
"""
|
||||
self.logger.info(line)
|
||||
# check if line matches a trigger and fire the command(s)
|
||||
for trigger in self.triggers:
|
||||
self.logger.debug("comparing input %s to trigger %s"
|
||||
% (line, trigger.pattern))
|
||||
m = trigger.search(line)
|
||||
if m:
|
||||
self.onecmd(self.precmd(self.triggers[trigger]))
|
||||
|
||||
# ckecking if the line should be sent as JSON object to a tcp
|
||||
# server
|
||||
if (len(self.json_regs)) and self.factory and \
|
||||
self.factory.myproto:
|
||||
for j in self.json_regs:
|
||||
m = self.json_regs[j].search(line)
|
||||
if m:
|
||||
try:
|
||||
json_obj = '{"jid":%d, ' % int(j)
|
||||
except ValueError:
|
||||
sys.stderr.write("Invalid JID: %s\n" % j)
|
||||
break
|
||||
json_obj += '"raw":"%s", ' % line
|
||||
json_obj += '"date":%s, ' % int(time.time()*1000)
|
||||
for g in m.groupdict():
|
||||
try:
|
||||
json_obj += '"%s":%d, ' \
|
||||
% (g, int(m.groupdict()[g]))
|
||||
except ValueError:
|
||||
json_obj += '"%s":"%s", ' \
|
||||
% (g, m.groupdict()[g])
|
||||
|
||||
# eliminate the superfluous last ", "
|
||||
json_obj = json_obj[:-2]
|
||||
|
||||
json_obj += "}"
|
||||
self.factory.myproto.sendMessage(json_obj)
|
||||
|
||||
def handle_line(self, line):
|
||||
"""Handle line from node and check for further processing
|
||||
requirements.
|
||||
|
||||
Args:
|
||||
line (str): input line from node.
|
||||
"""
|
||||
# First check if line should be ignored
|
||||
ignored = False
|
||||
if (len(self.ignores)):
|
||||
for i in self.ignores:
|
||||
if i.search(line):
|
||||
ignored = True
|
||||
break
|
||||
# now check if filter rules should be applied
|
||||
if (len(self.filters)):
|
||||
for r in self.filters:
|
||||
if r.search(line):
|
||||
if not ignored:
|
||||
self.process_line(line)
|
||||
# if neither nor applies print the line
|
||||
else:
|
||||
if not ignored:
|
||||
self.process_line(line)
|
||||
|
||||
|
||||
def serial_connect(self):
|
||||
self.ser = serial.Serial(port=self.port, dsrdtr=0, rtscts=0)
|
||||
self.ser.baudrate = self.baudrate
|
||||
self.ser.setDTR(0)
|
||||
self.ser.setRTS(0)
|
||||
|
||||
def reader(self):
|
||||
"""Serial or TCP reader.
|
||||
"""
|
||||
output = ""
|
||||
while (1):
|
||||
# check if serial port can be accessed.
|
||||
try:
|
||||
sr = codecs.getreader("UTF-8")(self.ser,
|
||||
errors='replace')
|
||||
c = sr.read(1)
|
||||
# try to re-open it with a timeout of 1s otherwise
|
||||
except (serial.SerialException, ValueError) as se:
|
||||
self.logger.warn("Serial port disconnected, waiting to "
|
||||
"get reconnected...")
|
||||
self.ser.close()
|
||||
time.sleep(1)
|
||||
if os.path.exists(self.port):
|
||||
self.logger.warn("Try to reconnect to %s again..."
|
||||
% (self.port))
|
||||
self.serial_connect()
|
||||
continue
|
||||
if c == '\n' or c == '\r':
|
||||
self.handle_line(output)
|
||||
output = ""
|
||||
else:
|
||||
output += c
|
||||
#sys.stdout.write(c)
|
||||
#sys.stdout.flush()
|
||||
|
||||
class PytermProt(Protocol):
|
||||
def dataReceived(self, data):
|
||||
sys.stdout.write(data)
|
||||
|
||||
def sendMessage(self, msg):
|
||||
self.transport.writeSomeData("%d#%s\n" % (len(msg), msg))
|
||||
|
||||
class PytermClientFactory(ReconnectingClientFactory):
|
||||
|
||||
def __init__(self):
|
||||
self.myproto = None
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
print('Connected.')
|
||||
self.resetDelay()
|
||||
self.myproto = PytermProt()
|
||||
return self.myproto
|
||||
|
||||
def clientConnectionLost(self, connector, reason):
|
||||
if reactor.running:
|
||||
print('Lost connection. Reason:', reason)
|
||||
ReconnectingClientFactory.clientConnectionLost(self, connector,
|
||||
reason)
|
||||
|
||||
def clientConnectionFailed(self, connector, reason):
|
||||
print('Connection failed. Reason:', reason)
|
||||
ReconnectingClientFactory.clientConnectionFailed(self,
|
||||
connector,
|
||||
reason)
|
||||
|
||||
def __stop_reactor(signum, stackframe):
|
||||
sys.stderr.write("Ctrl-C is disabled, type '/exit' instead\n")
|
||||
|
||||
class fdsocket(socket.socket):
|
||||
def read(self, bufsize):
|
||||
return self.recv(bufsize)
|
||||
|
||||
def write(self, string):
|
||||
try:
|
||||
return self.sendall(string)
|
||||
except socket.error as e:
|
||||
logging.getLogger("").warn("Error in TCP connection (%s), "
|
||||
"closing down" % str(e))
|
||||
self.close()
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser(description="Pyterm - The Python "
|
||||
"terminal program")
|
||||
parser.add_argument("-p", "--port",
|
||||
help="Specifies the serial port to use, default is %s"
|
||||
% defaultport,
|
||||
default=defaultport)
|
||||
parser.add_argument("-ts", "--tcp-serial",
|
||||
help="Connect to a TCP port instead of a serial port")
|
||||
parser.add_argument("-b", "--baudrate",
|
||||
help="Specifies baudrate for the serial port, default is %s"
|
||||
% defaultbaud,
|
||||
default=defaultbaud)
|
||||
parser.add_argument('-d', '--directory',
|
||||
help="Specify the Pyterm directory, default is %s"
|
||||
% defaultdir,
|
||||
default=defaultdir)
|
||||
parser.add_argument("-c", "--config",
|
||||
help="Specify the config filename, default is %s"
|
||||
% defaultfile,
|
||||
default=defaultfile)
|
||||
parser.add_argument("-s", "--server",
|
||||
help="Connect via TCP to this server to send output as "
|
||||
"JSON")
|
||||
parser.add_argument("-P", "--tcp_port", type=int,
|
||||
help="Port at the JSON server")
|
||||
parser.add_argument("-H", "--host",
|
||||
help="Hostname of this maschine")
|
||||
parser.add_argument("-rn", "--run-name",
|
||||
help="Run name, used for logfile")
|
||||
args = parser.parse_args()
|
||||
|
||||
myshell = SerCmd(args.port, args.baudrate, args.tcp_serial,
|
||||
args.directory, args.config, args.host, args.run_name)
|
||||
myshell.prompt = ''
|
||||
|
||||
if args.server and args.tcp_port:
|
||||
myfactory = PytermClientFactory()
|
||||
reactor.connectTCP(args.server, args.tcp_port, myfactory)
|
||||
myshell.factory = myfactory
|
||||
reactor.callInThread(myshell.cmdloop, "Welcome to pyterm!\n"
|
||||
"Type '/exit' to exit.")
|
||||
signal.signal(signal.SIGINT, __stop_reactor)
|
||||
reactor.run()
|
||||
else:
|
||||
myshell.cmdloop("Welcome to pyterm!\nType '/exit' to exit.")
|
||||
158
dist/tools/pyterm/pyterm.py
vendored
158
dist/tools/pyterm/pyterm.py
vendored
@ -1,158 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
try:
|
||||
import configparser
|
||||
except ImportError:
|
||||
import ConfigParser as configparser
|
||||
|
||||
import cmd, serial, sys, threading, readline, time, logging, os, codecs
|
||||
|
||||
pytermdir = os.environ['HOME'] + os.path.sep + '.pyterm'
|
||||
|
||||
class SerCmd(cmd.Cmd):
|
||||
|
||||
def __init__(self, port=None):
|
||||
cmd.Cmd.__init__(self)
|
||||
self.port = port
|
||||
self.aliases = dict()
|
||||
self.load_config()
|
||||
try:
|
||||
readline.read_history_file()
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
### create Logging object
|
||||
my_millis = "{:.4f}".format(time.time())
|
||||
date_str = '{}.{}'.format(time.strftime('%Y%m%d-%H:%M:%S'), my_millis[-4:])
|
||||
# create formatter
|
||||
fmt_str = '%(asctime)s - %(levelname)s # %(message)s'
|
||||
formatter = logging.Formatter(fmt_str)
|
||||
logging.basicConfig(filename=pytermdir + os.path.sep + date_str + '.log', level=logging.DEBUG, format=fmt_str)
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(logging.DEBUG)
|
||||
|
||||
# create logger
|
||||
self.logger = logging.getLogger('')
|
||||
self.logger.setLevel(logging.DEBUG)
|
||||
|
||||
# add formatter to ch
|
||||
ch.setFormatter(formatter)
|
||||
# add ch to logger
|
||||
self.logger.addHandler(ch)
|
||||
|
||||
|
||||
def preloop(self):
|
||||
if not self.port:
|
||||
sys.stderr.write("No port specified!\n")
|
||||
sys.exit(-1)
|
||||
self.ser = serial.Serial(port=self.port, baudrate=115200, dsrdtr=0, rtscts=0)
|
||||
self.ser.setDTR(0)
|
||||
self.ser.setRTS(0)
|
||||
|
||||
# start serial->console thread
|
||||
receiver_thread = threading.Thread(target=reader, args=(self.ser,self.logger))
|
||||
receiver_thread.setDaemon(1)
|
||||
receiver_thread.start()
|
||||
|
||||
def default(self, line):
|
||||
for tok in line.split(';'):
|
||||
tok = self.get_alias(tok)
|
||||
self.ser.write((tok.strip() + "\n").encode("utf-8"))
|
||||
|
||||
def do_help(self, line):
|
||||
self.ser.write("help\n".encode("utf-8"))
|
||||
|
||||
def complete_date(self, text, line, begidx, endidm):
|
||||
date = time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
return ["{}".format(date)]
|
||||
|
||||
def do_reset(self, line):
|
||||
self.ser.setDTR(1)
|
||||
self.ser.setDTR(0)
|
||||
|
||||
def do_exit(self, line):
|
||||
readline.write_history_file()
|
||||
sys.exit(0)
|
||||
|
||||
def do_save(self, line):
|
||||
if not self.config.has_section("general"):
|
||||
self.config.add_section("general")
|
||||
self.config.set("general", "port", self.port)
|
||||
if len(self.aliases):
|
||||
if not self.config.has_section("aliases"):
|
||||
self.config.add_section("aliases")
|
||||
for alias in self.aliases:
|
||||
self.config.set("aliases", alias, self.aliases[alias])
|
||||
|
||||
with open(os.path.expanduser('~/.pyterm'), 'wb') as config_fd:
|
||||
self.config.write(config_fd)
|
||||
print("Config saved")
|
||||
|
||||
def do_show_config(self, line):
|
||||
for key in self.__dict__:
|
||||
print(str(key) + ": " + str(self.__dict__[key]))
|
||||
|
||||
def do_alias(self, line):
|
||||
if line.endswith("list"):
|
||||
for alias in self.aliases:
|
||||
print("{} = {}".format(alias, self.aliases[alias]))
|
||||
return
|
||||
if not line.count("="):
|
||||
sys.stderr.write("Usage: alias <ALIAS> = <CMD>\n")
|
||||
return
|
||||
self.aliases[line.split('=')[0].strip()] = line.split('=')[1].strip()
|
||||
|
||||
def do_rmalias(self, line):
|
||||
if not self.aliases.pop(line, None):
|
||||
sys.stderr.write("Alias not found")
|
||||
|
||||
def get_alias(self, tok):
|
||||
for alias in self.aliases:
|
||||
if tok.split()[0] == alias:
|
||||
return self.aliases[alias] + tok[len(alias):]
|
||||
return tok
|
||||
|
||||
def load_config(self):
|
||||
self.config = configparser.SafeConfigParser()
|
||||
self.config.read([pytermdir + os.path.sep + 'pyterm.conf'])
|
||||
|
||||
for sec in self.config.sections():
|
||||
if sec == "aliases":
|
||||
for opt in self.config.options(sec):
|
||||
self.aliases[opt] = self.config.get(sec, opt)
|
||||
else:
|
||||
for opt in self.config.options(sec):
|
||||
if opt not in self.__dict__:
|
||||
self.__dict__[opt] = self.config.get(sec, opt)
|
||||
|
||||
|
||||
def reader(ser, logger):
|
||||
sr = codecs.getreader("UTF-8")(ser, errors='replace')
|
||||
output = ""
|
||||
while (1):
|
||||
c = sr.read(1)
|
||||
if c == '\n' or c == '\r':
|
||||
logger.info(output)
|
||||
output = ""
|
||||
else:
|
||||
output += c
|
||||
#sys.stdout.write(c)
|
||||
#sys.stdout.flush()
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not os.path.exists(pytermdir):
|
||||
os.makedirs(pytermdir)
|
||||
|
||||
if (len(sys.argv) > 1):
|
||||
port = sys.argv[1]
|
||||
else:
|
||||
port = None
|
||||
|
||||
myshell = SerCmd(port)
|
||||
myshell.prompt = ''
|
||||
|
||||
try:
|
||||
myshell.cmdloop("Welcome to pyterm!\nType 'exit' to exit.")
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(0)
|
||||
2
dist/tools/pyterm/setup.py
vendored
2
dist/tools/pyterm/setup.py
vendored
@ -12,4 +12,4 @@ setup(name="pyterm",
|
||||
author_email="ohahm@fu-berlin.de",
|
||||
url="http://riot-os.org/",
|
||||
install_requires=["pyserial"],
|
||||
scripts=["pyterm.py"])
|
||||
scripts=["pyterm"])
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user