Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@BBBSnowball
Last active June 10, 2021 11:21
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BBBSnowball/6a13c51fc4b89f0b65f18fe7342bc350 to your computer and use it in GitHub Desktop.
Save BBBSnowball/6a13c51fc4b89f0b65f18fe7342bc350 to your computer and use it in GitHub Desktop.
serial monitor for ESP8266, run "pio run -t upload" on ctrl-t ctrl-u, decode stacktraces, paths are hardcoded for platformio with nodemcuv2 board
import serial
from serial.tools import miniterm
import subprocess, sys, re, os
class ESPMiniterm(miniterm.Miniterm):
def handle_menu_key(self, c):
if c == '\x15': # ctrl-u
self.upload_file()
else:
super(ESPMiniterm, self).handle_menu_key(c)
# This will be called for menukey + ctrl-u.
def upload_file(self):
self._stop_reader()
self.serial.close()
while True:
code = subprocess.call(["pio","run","-t","upload"])
if code == 0:
break
print "returncode: %d, press ctrl+u to try again, any other key to continue" % code
try:
c = self.console.getkey()
if c == self.menu_character:
c = self.console.getkey()
except KeyboardInterrupt:
c = '\x03'
if c != '\x15':
break
self.serial.open()
self._start_reader()
class ESPStackDecoder(miniterm.Transform):
# https://github.com/esp8266/Arduino/blob/283eb97cd3f6dac691e8350a612021f1e1ccd8e3/doc/exception_causes.rst
EXCEPTION_CAUSES = {
0: "IllegalInstructionCause",
1: "SyscallCause",
2: "InstructionFetchErrorCause",
3: "LoadStoreErrorCause",
4: "Level1InterruptCause",
5: "AllocaCause",
6: "IntegerDivideByZeroCause",
7: "Reserved for Tensilica",
8: "PrivilegedCause",
9: "LoadStoreAlignmentCause",
10: "Reserved for Tensilica",
11: "Reserved for Tensilica",
12: "InstrPIFDataErrorCause",
13: "LoadStorePIFDataErrorCause",
14: "InstrPIFAddrErrorCause",
15: "LoadStorePIFAddrErrorCause",
16: "InstTLBMissCause",
17: "InstTLBMultiHitCause",
18: "InstFetchPrivilegeCause",
19: "Reserved for Tensilica",
20: "InstFetchProhibitedCause",
21: "Reserved for Tensilica",
22: "Reserved for Tensilica",
23: "Reserved for Tensilica",
24: "LoadStoreTLBMissCause",
25: "LoadStoreTLBMultiHitCause",
26: "LoadStorePrivilegeCause",
27: "Reserved for Tensilica",
28: "LoadProhibitedCause",
29: "StoreProhibitedCause",
30: "Reserved for Tensilica",
31: "Reserved for Tensilica",
32: "CoprocessornDisabled",
33: "CoprocessornDisabled",
34: "CoprocessornDisabled",
35: "CoprocessornDisabled",
36: "CoprocessornDisabled",
37: "CoprocessornDisabled",
38: "CoprocessornDisabled",
39: "CoprocessornDisabled",
}
def __init__(self):
miniterm.Transform.__init__(self)
self.rxbuf = ""
self.in_stack = False
def rx(self, text):
self.rxbuf += text
if '\r' in self.rxbuf or '\n' in self.rxbuf:
#lines = re.split("[\\r\\n]+", self.rxbuf)
#self.rxbuf = lines[-1]
#lines = lines[0:-1]
#for line in lines:
# info = self.handle_line(line)
# if info and info != "":
# #TODO insert at the right position
# text += info
prev = 0
infos = []
for m in re.finditer("(\\r\\n?|\\n)[\\r\\n]*", self.rxbuf):
info = self.handle_line(self.rxbuf[prev:m.start()])
if info:
infos.append([m.end(1) - (len(self.rxbuf)-len(text)),
re.sub("\\r\\n?|\\n", "\r\n", info)])
prev = m.end()
infos.reverse()
for info in infos:
p = max(0, info[0])
text = text[0:p] + info[1] + text[p:]
self.rxbuf = self.rxbuf[prev:]
return text
def handle_line(self, line):
m = re.search("Exception \\(([0-9]*)\\):\s*$", line)
if m:
if int(m.group(1)) in self.EXCEPTION_CAUSES:
return " -> Exception cause: %s\n" % (self.EXCEPTION_CAUSES[int(m.group(1))],)
else:
return " -> Exception cause: ???\n"
m = re.match("^epc1=0x([0-9a-fA-F]{8,8}) ", line)
if m:
#return "address: %s\n" % (m.group(1),)
return self.addr2line([m.group(1)])
if line == ">>>stack>>>":
self.in_stack = True
elif line == "<<<stack<<<" or ":" not in line:
self.in_stack = False
if self.in_stack:
info = ""
addrs = []
for m in re.finditer(" (40[0-2][0-9a-fA-F]{5,5})\\b", line):
#info += "address: %s\n" % (m.group(1),)
addrs.append("0x" + m.group(1))
return self.addr2line(addrs)
def addr2line(self, addrs):
if len(addrs) == 0:
return ""
output = subprocess.check_output([os.environ["HOME"] + "/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-addr2line", "-aipfC", "-e", ".pioenvs/nodemcuv2/firmware.elf"] + addrs)
return output
miniterm.TRANSFORMATIONS['espstackdecoder'] = ESPStackDecoder
#miniterm.main(default_port="/dev/ttyUSB0", default_baudrate=115200)
serial_instance = serial.serial_for_url(
"/dev/ttyUSB0",
115200,
do_not_open = True)
if not hasattr(serial_instance, 'cancel_read'):
# enable timeout for alive flag polling if cancel_read is not available
serial_instance.timeout = 1
serial_instance.open()
miniterm = ESPMiniterm(
serial_instance,
echo=False,
eol='crlf',
filters=["espstackdecoder"])
miniterm.exit_character = unichr(0x03) #unichr(0x1d)
miniterm.menu_character = unichr(0x14)
miniterm.raw = False
miniterm.set_rx_encoding('UTF-8')
miniterm.set_tx_encoding('UTF-8')
miniterm.start()
try:
miniterm.join(True)
except KeyboardInterrupt:
pass
sys.stderr.write("\n--- exit ---\n")
miniterm.join()
miniterm.close()
@2ni
Copy link

2ni commented Jan 10, 2018

Hi, I'm not sure how to "embed" the script in pio env. I'm working mostly on shell. in which command do you reference espminiterm.py and how?

@apanasara
Copy link

how to use is not clear...

@GusPS
Copy link

GusPS commented Jan 10, 2020

Open a Terminal on project directory (where .platformio exists) and run "python PATHTO/espminiterm.py". This will start to output the Serial console. And when an Exception occurs, this just prints the Stack Trace.

Maybe you have to adapt the line 130. I'm using:
output = subprocess.check_output([os.environ["HOME"] + "/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-addr2line", "-aipfC", "-e", ".pio/build/nodemcuv2/firmware.elf"] + addrs)

@h1aji
Copy link

h1aji commented Jun 10, 2021

is this for python2?

@GusPS
Copy link

GusPS commented Jun 10, 2021

Now in new versions of Platformio, you have this already built-in. Check in this page https://docs.platformio.org/core/userguide/device/cmd_monitor.html. It's esp8266_exception_decoder filter

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment