I am building a small toy controller for the kids. The idea is to allow the kids to write some simple scripts on the PC, store them on SD card and on the esp32/lvgl based target device select one from the UI to run it in a window.
This is a little more complex than it sounds as a typical script like that would use print
for output and would potentially run a few seconds (e.g. containing stuff like “robot, drive forward for ten seconds” using “time.sleep()”. This would usually result in a blocked gui as long as the script runs. So I wrote the “Console” widget. It runs the given script in the background using a _thread and while doing that monitors its output via dupterm. A lvgl task monitors for data catched via dupterm and appends it to the console widgets text view.
I am pretty surprised that this seems to work Actually during my experiments i got all sorts of recursion exceptions and random crashes. But now it seems to work. So I share it here for your entertainment.
This is the widget (console.py):
import lvgl as lv
import uos
import _thread
from uio import IOBase
class Console(lv.label):
class Wrapper(IOBase):
def __init__(self):
self.buffer = ""
def write(self, data):
self.buffer += data.decode('ascii').replace('\r', '')
def get_buffer(self):
retval = self.buffer
self.buffer = ""
return retval
def watcher(self, data):
d = self.wrapper.get_buffer()
if d != "": self.ins_text(lv.LABEL_POS.LAST, d);
if not self.running:
uos.dupterm(None)
self.task.set_repeat_count(0);
def execute(self, code):
exec(code, {} )
self.running = False
def __init__(self, *args, **kwds):
super().__init__(*args, **kwds)
self.set_text("")
self.set_long_mode(lv.label.LONG.BREAK);
def run(self, fname):
# read script
f = open(fname)
code = f.read()
f.close()
# start wrapper to catch script output
self.wrapper = self.Wrapper()
uos.dupterm(self.wrapper)
# run script in background
self.running = True
_thread.start_new_thread( self.execute, ( code, ) )
# start task to read text from wrapper and display it in label
self.task = lv.task_create(self.watcher, 100, lv.TASK_PRIO.MID, None);
It is used like so:
from ili9XXX import ili9341
from xpt2046 import xpt2046
import lvgl as lv
from console import Console
# startup lvgl
lv.init()
disp = ili9341(miso=19, mosi=23, clk=18, cs=5, dc=32, rst=27, spihost=1, power=-1, backlight=33, backlight_on=1, mhz=80, factor=4, hybrid= True)
touch = xpt2046(cs=26, spihost=1, mhz=5, max_cmds=16, cal_x0 = 3783, cal_y0 = 3948, cal_x1 = 242, cal_y1 = 423, transpose = True, samples = 3)
# Create the main screen and load it.
scr = lv.obj()
# Just a button to prove that the lvgl is working
# while the script runs
def on_btn(obj, event):
if event == lv.EVENT.CLICKED:
print("Click!");
btn = lv.btn(scr)
btn.set_event_cb(on_btn)
lv.label(btn).set_text("Click me!");
btn.align(scr, lv.ALIGN.IN_TOP_LEFT, 10, 10)
# setup console
console = Console(scr)
console.set_width(210);
console.align(scr, lv.ALIGN.IN_TOP_LEFT, 10, 60)
lv.scr_load(scr)
# execute "hello.py" in console
console.run("hello.py")
while True:
pass
and is able to run scripts like this hello.py:
import time
print("Hello world!");
for i in range(10):
print("I:", i);
time.sleep(1)