Emulating a Python REPL

Hi all, I’m currently playing with an Elecrow HMI 5" module sporting an ESP32-S3-WROOM-1-N4R8 MCU. My custom build using kdschlosser’s repo works fine (thanks for the help Kevin!), and I added ulab/scipy with a custom numeric integration module and enabled FP64 math. So far so good.

I would like to use lv_keyboard and lv_textarea as a simple Python terminal, and thus make the textarea behave like a Python REPL, i.e. have a non-deletable prompt and such. I understand that I can add callbacks e.g. for the OK button and fetch the last text line as text entry for Python eval() or exec(). But I have no idea how to make a prompt persistent so that it cannot be deleted. As I understand, the keyboard just sends characters to the textarea. How do I prevent backspace from working if the current line is just ">>> " (or anything else) ? I think the question is if callbacks are executed before a character is appended to the textarea.

TIA!

The best way to handle this is going to be duplicating the MicroPython terminal and appending that data to the data in the text area.

This is a discussion about redirecting the terminal over bluetooth but it would work much in the same manner with redirection to a display…

Thank you - I found a solution that works for me [TM] using a kb_event_cb. In fact I do not want to expose the Micropython REPL proper because it is just too powerful :wink:

Right now I am fighting with the keyboard settings. The long_press_repeat_time is way too long for my taste - I get tons of double and triple characters in the textarea. The long_press_repeat_time.setter seems not to be exposed for the keyboard widget, though. Setting all flags to NO_REPEAT seems to require a loop over the ctrl_map since I can’t find the set_btn_ctrl_all() method for the keyboard. It appears that the internal lv.buttonmatrix is no longer exposed.

Also, there seems to be no clean way to create control buttons (like “SHIFT”) to not enter their button text in the textarea (like the “ABC” and “abc” buttons in the default keyboard). LV_KEYBOARD_CTRL_BUTTON_FLAGS as used in lv_keyboard.c does not do the trick.

Any hint is appreciated …

in order to get better timekeeping in LVGL this is what I recommend doing.

When you create the task_handler.TaskHandler pass a 10 to the constructor.so like this… task_handler.TaskHandler(10) This will shorten up the time between calls to lv.tick_inc to 10 milliseconds instead of the default 33 milliseconds… after you create the touch driver right after add this line…

indev.enable_input_priority()

that will override the mechanics in LVGL so when input occurs it will loop the indev and updating the display so any updates hat are made to the display as a result of input happen immediately. This also will handle the long press better this way because the timing keeping will be a narrower window and because LVGL will realize the touch release much sooner.

To implement a shift button you would need to create a custom button to handle that along with a second keyboard layout. I have not messed about with the keyboard widget so I don’t know what to do exactly. I will mess about with it today to see what I come up with for ya.

This is to run on the unix port but you can change the relevant display code for your display.

_WIDTH = 800
_HEIGHT = 600

_BUFFER_SIZE = _WIDTH * _HEIGHT * 3

import lcd_bus  # NOQA

bus = lcd_bus.SDLBus(flags=0)

buf1 = bus.allocate_framebuffer(_BUFFER_SIZE, 0)

import lvgl as lv  # NOQA
import sdl_display  # NOQA

lv.init()

display = sdl_display.SDLDisplay(
    data_bus=bus,
    display_width=_WIDTH,
    display_height=_HEIGHT,
    frame_buffer1=buf1,
    color_space=lv.COLOR_FORMAT.RGB888
)
display.init()

import sdl_pointer

mouse = sdl_pointer.SDLPointer()


def split_lines(text):
    t = []
    line = ''

    for char in text:
        if char == '\n':
            if line:
                t.append(line)
                line = ''
            continue

        line += char
    if line:
        t.append(line)

    return t


def get_indent(text):
    text = split_lines(text)
    last_line = text[-1]
    indent = ''
    for char in last_line:
        if char == ' ':
            indent += char
        else:
            break

    return indent


def get_code(text):
    text = split_lines(text)

    lines = []

    for i in range(len(text) - 1, -1, -1):
        line = text[i]
        if line.startswith('>>>'):
            lines.insert(0, line[3:])
            break
        lines.insert(0, line)

    code = '\n'.join(lines)
    return code


def keyboard_event_cb(evt):
    key = kb.get_button_text(kb.get_selected_button())

    if key == lv.SYMBOL.NEW_LINE:
        indent = get_indent(ta.get_text())
        ta.add_text('\n' + indent)

    elif key == lv.SYMBOL.OK:
        code = get_code(ta.get_text())

        try:
            eval(code)
        except Exception as err:  # NOQA
            ta.add_text('\n' + str(err))

        ta.add_text('\n>>>')
    elif key == lv.SYMBOL.BACKSPACE:
        text = ta.get_text()

        if len(text) >= 3 and text[-3:] != '>>>':
            ta.set_text(text[:-1])
    else:
        try:
            if len(key) == 1 and 22 <= ord(key) <= 127:
                ta.add_text(key)
        except:
            pass


scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0x000000), 0)

kb = lv.keyboard(scrn)

kb.set_mode(lv.keyboard.MODE.TEXT_LOWER)

ta = lv.textarea(scrn)
ta.align(lv.ALIGN.TOP_MID, 0, 10)
ta.set_size(lv.pct(90), lv.pct(40))
ta.add_state(lv.STATE.FOCUSED)
ta.add_text(">>>")

kb.add_event_cb(keyboard_event_cb, lv.EVENT.CLICKED, None)

import task_handler
th = task_handler.TaskHandler(duration=5)

This might also peak your interest considering what you are doing for a project with the display…

it’s a unit converter… It converts all of them. any unit you can possibly think of. It would need some modification to run on MicroPython but that shouldn’t be that hard to do.

It supports using scientific notation and all of the SI units as well as some really obscure units

Excellent, thank you. This will definitely help. I did something similar earlier this year based on Circuitpython. The code is very raw, though. The FP64 and ulab.scipy.integrate stuff works on your build as well with only minor adjustments - patch included there.

mostly vanilla MicroPython with LVGL added should be quite a bit faster than CircuitPython and less resource hungry as well.

What you might not know is you can compile my project so it will run on Unix/macOS. so you don’t have to keep on uploading files to your ESP and running it that way. The same code that will run on Unix/macOS can be uploaded to the ESP and it will run without having to make any changes.

You can use sys.platform to tell if you are running on darwin/unix so you can run the proper display setup code and maybe add soft buttons to the GUI at the bottom to mimic the hard buttons on your device. This would allow you to do all the development right on a desktop PC. You can even adjust the amount of available heap so it matches what the ESP32 has for available RAM.

Hmmm, I was just about to switch to using os.dupterm() but this is not going to work with the Unix port as it seems.

But thanks for the hint - this allows for much faster turnaround times without hassling with Thonny et al. My simple command line emulation runs fine on the Unix port - the lagginess on the ESP may indeed be a general performance issue and a problem with the touch handling. With the touch, I may need to set most buttons to NO_REPEAT except a few (like cursor left/right or backspace). Since there seems to be no set_button_ctrl_all() in LVGL9 / MP (the underlying buttonmatrix is no longer exposed if I’m not missing something), in a loop. Ugly.

As for the lagginess, I may need to switch to C and use muParserX for the math handling … Too bad because eval() and exec() do it all for free (and I did this on the Circuitpython build recently).

the majority of the lag is going to come from using an RGB display and needing to have it rotated. That coupled with the high pixel count is what is really going to hammer the performance. I did the best that I can to improve the RGB driver and I don’t think that there is going to be anything more that can be squeezed out of it. even if you wrote it in all C code it’s not going to be a large amount better.

With respect to the math stuff. Writing a module that would expose the math functions in the c standard library would not be that hard to do. dealing with float math errors is it’s own animal all together. The decimal like libraries that have seen for MicroPython are written in Python which means they are going to be horrible on the performance side of things. I went down that rabbit hole when I wrote a library for a mems sensor using fusion math which is pretty intensive on the math end of things.

as far as your issues go with using the keyboard widget in LVGL. Any time I have wanted to alter the behavior of one of LVGL’s widgets I would simply write a new widget to do what I wanted it to do. A keyboard widget is what? just a collection of buttons. I will key out a fast example of how to go about doing that.

here is an example of how to go about writing a custom widget for the keyboard in MicroPython.

This is not complete and is not going to work. It has most of the mechanics there but not all. You should be able to get a good idea of what to do using this…

import lvgl as lv

UNUSED_KEY = '\x00'
TAB_KEY = '\t'
CAPS_KEY = ['CAPS', 'CAPS']
ENTER_KEY = lv.SYMBOL.NEW_LINE
BACKSPACE_KEY = lv.SYMBOL.BACKSPACE
L_SHIFT_KEY = ['SHIFT', 'SHIFT']
R_SHIFT_KEY = L_SHIFT_KEY



L_ALT_KEY = lv.SYMBOL.CLOSE
R_ALT_KEY = lv.SYMBOL.SETTINGS
L_START_KEY = lv.SYMBOL.SAVE
R_START_KEY = lv.SYMBOL.TRASH
L_CTRL_KEY = lv.SYMBOL.KEYBOARD
R_CTRL_KEY = lv.SYMBOL.PLAY
MENU_KEY = lv.SYMBOL.BARS


PART_KEY = 0x100000


# Sum of the min & max of (a, b, c)
def _hilo(a, b, c):
    if c < b:
        b, c = c, b
    if b < a:
        a, b = b, a
    if c < b:
        b, c = c, b

    return a + c


def _complement(r, g, b):
    k = _hilo(r, g, b)
    return tuple(k - u for u in (r, g, b))



class Key(lv.button):

    def __init__(self, parent):
        super().__init__(parent)
        self._latched = False
        self._choices = None
        self._special_key = None
        self._lower_keycode = None
        self._upper_keycode = None
        self._shifted_keycode = None
        self._shifted = False
        self._capslock = False

        self._label = lv.label(self)
        self._label.set_text(' ')
        self._label.center()

        idle_color = self.get_text_color(0)
        r, g, b = _complement(idle_color.red, idle_color.green, idle_color.blue)
        lv.obj.set_text_color(self, lv.color_t(dict(red=r, green=g, blue=b)), lv.STATE.PRESSED)
        
    def set_text_color(self, color, selector):
        if selector == 0:
            idle_color = self.get_text_color(0)
            curr_press_color = _complement(idle_color.red, idle_color.green, idle_color.blue)
            press_color = self.get_text_color(lv.STATE.PRESSED)
            
            if (press_color.red, press_color.green, press_color.blue) == curr_press_color:
                r, g, b = _complement(color.red, color.green. color.blue)
                lv.obj.set_text_color(self, lv.color_t(dict(red=r, green=g, blue=b)), lv.STATE.PRESSED)
            
            lv.obj.set_text_color(self, color, 0)
        
        
    def set_keycode(self, keycode):
        if isinstance(keycode, (list, tuple)):
            self._choices = keycode
            self._label.set_text(keycode[0])
        elif len(keycode) == 2:
            self._lower_keycode = keycode[0]
            self._upper_keycode = None
            self._shifted_keycode = keycode[1]
        elif keycode == '\t':
            self._special_key = keycode
            self._label.set_text('TAB')
        elif keycode == UNUSED_KEY:
            self._special_key = keycode
            self._label.set_text(' ')
        else:
            self._lower_keycode = keycode.lower()
            self._upper_keycode = keycode.upper()
            self._shifted_keycode = None
            self._label.set_text(keycode.lower())
        
        self._label.center()
        
    def set_state(self, state, value):
        if self._choices is not None and state == lv.STATE.PRESSED:
            self._label.set_text(self._choices[int(value)])
            self._label.layout()
            self._label.set_align(self._label.get_align())
            
            lv.obj.set_state(self, state, value)
        
    def set_shift(self, value):
        self._shifted = value

        if value:
            if self._choices in (L_SHIFT_KEY, R_SHIFT_KEY):
                self.set_state(lv.STATE.PRESSED, True)
                
            if self._shifted_keycode is not None:
                self._label.set_text(self._shifted_key)

            elif self._upper_keycode is not None:
                self._label.set_text(self._upper_keycode)
        else:
            if self._choices in (L_SHIFT_KEY, R_SHIFT_KEY):
                self.set_state(lv.STATE.PRESSED, False)
            
            if self._capslock:
                if self._upper_keycode is not None:
                    self._label.set_text(self._upper_keycode)
            else:
                self._label.set_text(self._lower_keycode)

        self._label.layout()
        self._label.set_align(self._label.get_align())

    def get_shift(self):
        return self._shifted

    def set_capslock(self, value):
        self._capslock = value
        if value:
            if self._choices == CAPS_KEY:
                self.set_state(lv.STATE.PRESSED, True)
            
            if not self._shifted and self._upper_keycode is not None:
                self._label.set_text(self._upper_keycode)
        else:
            if self._choices == CAPS_KEY:
                self.set_state(lv.STATE.PRESSED, False)
            
            if not self._shifted and self._upper_keycode is not None:
                self._label.set_text(self._lower_keycode)

    def get_capslock(self):
        return self._capslock
                
    def get_keycode(self):
        if self._special_key is not None:
            return self._special_key
        
        if self._choices is not None:
            return self._choices
        
        return self._label.get_text()


class Keyboard(lv.obj):

    def __init__(self, parent):
        # you can only use super to inherit the parents
        # classes methods and attributes
        super().__init__(parent)

        self._pressed_button = None
        self._hor_key_pad = 0.02
        self._ver_key_pad = 0.02
        
        self._key_maps = {}
        self._key_factors = {}
        
        self._active_map = None
        self._buffer_maps = False
        
        self._loaded_maps = {}


    def __caps_callback(self):
        for key in self._loaded_maps.get(self._active_map, []):
        
    def set_size(self, width, height):
        lv.obj.set_size(self, width, height)
        self.__set_key_sizes()
        
    def set_keys(self, mapping_number, keycodes, key_width_factors, hor_key_pad, ver_key_pad):
        self._hor_key_pad = hor_key_pad
        self._ver_key_pad = ver_key_pad
        
        self._key_maps[mapping_number] = keycodes[:]
        self._key_factors[mapping_number] = key_width_factors[:]
        
    def buffer_maps(self, value):
        self._buffer_maps = value

    def set_active_map(self, map_number):
        if map_number not in self._key_maps:
            raise RuntimeError(f'keymap {map_number} does not exist')
        
        if self._buffer_maps:
            for key in self._loaded_maps.get(self._active_map, []):
                key.add_flag(lv.obj.FLAG.HIDDEN)
                
            if map_number in self._loaded_maps:
                for key in self._loaded_maps[map_number]:
                    key.remove_flag(lv.obj.FLAG.HIDDEN)
        else:
            for key in self._loaded_maps.get(self._active_map, []):
                key.delete()
            
            if self._active_map in self._loaded_maps:
                del self._loaded_maps[self._active_map]

        self._active_map = map_number
        keymap = self._key_maps[map_number]
        active_map = []
        self._loaded_maps[self._active_map] = active_map
        
        for row in keymap:
            for keycode in row:
                key = Key(self)
                key.set_keycode(keycode)
                active_map.append(key)
        
    def __set_key_sizes(self):
        self.layout()
        
        width = self.get_width()
        height = self.get_height()

        keys = self._loaded_maps[self._active_map]
        factors = self._key_factors[self._active_map]
        num_rows = len(factors)
        
        keys_per_row = int(max([sum(row) for row in key_spacing]))

        hor_key_pad = int(self._hor_key_pad * width)
        ver_key_pad = int(self._ver_key_pad * height)

        if hor_key_pad % 2:
            hor_key_pad -= 1
            
        if ver_key_pad % 2:
            ver_key_pad -= 1

        key_width = int(width / keys_per_row) - hor_key_pad
        key_height = int(height / num_rows) - ver_key_pad

        hor_key_pad = int(hor_key_pad / 2)
        ver_key_pad = int(ver_key_pad / 2)
        
        key_count = 0   
        for row in factors:
            for factor in row:
                key = keys[key_count]
                key.set_style_pad_left(hor_key_pad, 0)
                key.set_style_pad_right(hor_key_pad, 0)
                key.set_style_pad_top(ver_key_pad, 0)
                key.set_style_pad_bottom(ver_key_pad, 0)
                key.set_size(int(key_width * factor), key_height)
                key_count += 1
                
    # this will handle all of the get_style and set_style methods for the keys
    # you will need to passed an OR'ed selector value that has PART_KEY to 
    # target the keys
    def __getattribute__(self, item):
        if hasattr(Test, item):
            attr = getattr(Test, item)
            if 'bound_method' in str(type(attr)):
                if item.startswith('set_style'):
                    def func_wrapper(*args):
                        args = list(args)

                        if args[-1] & PART_KEY:
                            args[-1] |= ~PART_KEY

                            for key in self._loaded_maps.get(self._active_map, []):
                                getattr(key, item)(*args)
                        else:
                            getattr(lv.obj, item)(self, *args)

                elif item.startswith('get_style'):
                    def func_wrapper(*args):
                        args = list(args)

                        if args[-1] & PART_KEY:
                            args[-1] |= ~PART_KEY

                            key = self._loaded_maps.get(self._active_map, [])[0]

                            return getattr(key, item)(*args)
                        else:
                            return getattr(lv.obj, item)(self, *args)

                else:
                    def func_wrapper(*args, **kwargs):
                        return attr(self, *args, **kwargs)

                return func_wrapper

        raise AttributeError(item)
    
    def _pressed_cb(self, evt):
        # register each key to call this callback passing the index of the key as user data
        pass


# width factors for the buttons. These will allow the buttons to be dynamically sized
# tab           1.56    73px
# caps lock     2.13   100px
# left shift    2.70   127px
# left ctrl     2.13   100px
# left start    1.56    73px
# left alt      1.56    73px
# space         5.52   259px
# right alt     1.58    74px
# right start   1.58    74px
# right shift   2.69   126px
# enter         2.13   100px wide @ bottom   1.58  74px width @ top  86px tall

# how the button widths are calculated is by using int(sum(row)) which will return
# the largest number of "keys" in a single row. a key is defined as a 1.0 factor. 
# in the case below a row is 15 keys wide. The width of the screen is then divided 
#15 and rounded down to the closest whole int. that width is then multiplied by the factors 
# seen below to determine what the width should be for the key.
# this allows for dynamic resizing of the keyboard. Think display orientation.
key_sizes = [
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    [1.56, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.58],
    [2.13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.13],
    [2.70, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.58],
    [2.13, 1.56, 1.56, 5.52, 1.58, 1.58, 1, 1]
]

# There are 4 types of keys, 
# keys controlled by shift, keys that are strings that have either 1 or 2 characters. 
# keys controlled by caps, keys that are strings and only have a single character
# keys that "latch", keys that are lists or tuples of strings
# keys that are special, keys that are assigned to LV_SYMBOL_* constants
keys =[ 
    ['`~', '1!', '2@', '3#', '4$', '5%', '6^', '7&', '8*', '9(', '0)', '-_', '=+', '\\|', BACKSPACE_KEY],
    [TAB_KEY, 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[{', ']}', ENTER_KEY],
    [CAPS_KEY, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';:', '\'"', ENTER_KEY],
    [L_SHIFT_KEY, 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',<', '.>', '/?', R_SHIFT_KEY],
    [L_CTRL_KEY, L_START_KEY, L_ALT_KEY, ' ', R_ALT_KEY, R_START_KEY, MENU_KEY, R_CTRL_KEY],
]

# These factors set the spacing between the keys. they are multiplied to the width and the height
# and rounded down to the nearest even integer. so if you want a 6 pixel space between keys at 
# the tops and bottoms and the keyboard takes up 1/2 of a 480pixel tall display. you have 
# `6 / 240` to come up with the factor of 0.025. You may have to nudge the factor up a slight bit 
# like 0.026 to get it over the number you want. You are able to control the rounding this way 
# since it is always going to round down to the closest even integer. 
hor_key_pad = 0.0075
ver_key_pad = 0.025


set_keys(0, keys, key_sizes, hor_key_pad, ver_key_pad)
set_active_map(0)
# you can supply multiple key maps and sizes but the pads must always remain the same
# remember to supply a new id for each new map.
1 Like

As for the math stuff and fp64 rounding errors (not to mention fp32!), it should be quite easy to use a decimal library like MAPM-5 and build a simple wrapper exposing the functions. I did not bother to do this yet because fp64 works for me (for now) but it is definitely an option. I used MAPM-5 once in Arduino mode on a device emulating a HP-41 calculator (similar to the DM-42 clone of the HP-42S based on Free42 and a decimal library). Nice, compact, and just works, although the library has been abandoned long ago.

If there is a decimal library that is written in C code that you want a binding built for let me know and I can write a cmake files that will handle generating the binding code to make the library accessible from inside of MicroPython.

I just recently extended the binding build system so you can compile external projects into the firmware using the build command USER_C_MODULE="path to external cmake file". You also have the ability to attach components that are in the ESP component registry as well. You use the COMPONENT="idf component nomenclature" build command if that is something you want to do. The latter is not going to automatically write any kind of binding code so that will also need to be used with user c module

this would be of great interest to you I am sure.

Hi Kevin, at the moment I have no imminent need for such a module but how about we hack such a generic Decimal module based on MAPM for Micropython in general? This needs a thin compatibility layer on top which mimicks the Python Decimal interface, or a bit more streamlined, what Jeff Epler did for Circuitpython (jepler_udecimal including the trig functions). Such a module would be useful for everyone who needs more than fp32 math on any platform and who cannot live with fp64 rounding errors (1). It could be a standard submodule in Micropython (git clone in extmod and recompile). This is completely orthogonal to lvgl though. (complex numbers could be handled more easily in Python I guess - I’ve got a complex decimal module already based on jepler but this could be easily adapted. )

(Update: same goes for uncertainities. I’ve currently a simplified module for Circuitpython but implementing it in C would be nifty.)

(1) Although enabling fp64 e.g. on esp32 is really straightforward - check the patch in my circuitpython-calculator repo. It was more cumbersome on Circuitpython because the guys changed a couple of type declarations and ifdefs in random places which I had to streamline to make it compile. (They really castrated Micropython if you ask me - why would you intentionally remove interrupts and dual-core handling, given processors like esp32-s3 or rp2040/2350?)

By the way adding ulab to your repo was really straightforward on esp32 - I check it out in ext_mod and add it to the cmake file. On the Unix build, however, it is not recognized this way. I read through your builder scripts (builder/unix.py I suppose) and could not find why. Any idea?

On the Unix build, btw, rotation did not work. I worked around this by setting up 480x800 in portrait mode. The thing works nicely now except some minor glitches in the history logic. As soon as all is ok I’ll move back to the esp display. Wonder how it will perform. To be sure, I re-read the muparserx source code and found that the math handling would not be hard in a C version of my gadget. It even supports matrices (rudimentary but adding inversion or LU decomposition would be straightforward) and simple control structures. It’s not far from python eval() or exec().

Any glitches or issues you have come across in the unix port, open an issue on my repo and I will be more than happy to work on getting them corrected. PR’s are also welcome that’s for sure.

The CMAKE build system makes more sense when adding a user_c_module and with the ESP32 port I was able to add in the ability to be able to add additional modules from the command line. Unfortunately the unix build does not work in this manner as it doesn’t use CMAKE to compile it. It uses MakeFile instead. To add an extension module you need to place it into the ext_mod folder and you MUST have a “micropython.mk” file to instruct the build system how to compile your extension. With the unix build you can compile any external libraries that are used as a dynamic library and link to link to it instead of needing to compile the external library directly making a single binary. With the unix port the SDL2 library gets compiled as a static library and that library get compiled into the MicroPython binary but for all intents and purposes it is not technically compiled into the MicroPython code per se.

The reason why I mentioned that esp32 component is because the math operations are optimized to run on the ESP32 for both power and also speed. It is going to be better at handling floating point math better than anything in the C standard library. It will be faster and it will also consume less power. Since what you are making is a calculator I would guess that you are going to want to make it so that it will run on a battery yes? Power consumption is something you will want to keep as low as possible I would think…

With regards to the using of multiple cores. I cannot agree more on the limitation of only being able to access one of those cores from inside of MicroPython. I have started working on correcting this limitation with the ESP32 MCU’s. You can see the code here. I have added a threading module and also a multiprocessing module to MicroPython. I have added the Thread, Process, Lock, Rlock, Event and Semaphore classes for both modules. The API matches that used in CPython. I have been writing the code so it will work on the Linux and also the macOS ports as well. It has not been tested yet for functionality yet. It would need more work before it is usable. But it is a step in the right direction to address not being able to access the second core.

1 Like

Hi Kevin, thanks for pointing out IQmath. I read the documentation and source code (which is always fun) and I think it’s a great module for embedded and real-time applications which do not need more than 6 or 7 significant digits of precision (because they do everything internally with int32_t) but which need really fast computations. Super for typical IoT applications. (Well they do Newton-Raphson for sqrt() and use tabled Taylor coefficients for the trig functions.)

A portable scientific calculator is a different use case, though. The computation time is not that important, but you want as many significant digits of precision as possible to mitigate error propagation in chain calculations. FP64 is good enough for that (yeah, sin(pi) = 1.224646799147353e-16 but so what), or a decimal library if you need arbitrary precision (I might really go for MAPM …). So this is on par with desktop Python.

As far as power consumption, well, this 5" Elecrow part uses 300 mA idle, which makes it a high-power pocket calculator so to speak. The ESP32-S3 typically goes up by about 30 mA from idle to benchmark load (measured on Circuitpython earlier this year) which means that most of the power consumption comes from the TFT display anyway. The only mitigation that comes to mind is switching the MCU to light sleep and dim the TFT backlight to 5% after a certain timeout, say, 30 seconds, and waking it up on the next touch.

You know mow about the math end of things than I do. One day I will spend the time to dig in a learn the floating point math and how things are done. I never really bothered diving in deep to learn about the underpinning of how floats work. at 50 years old IDK if I will ever dive into it. Never know tho considering 7 years ago the only thing i had programmed up until then was a VCR. I don’t think I am doing too shabby considering I am self taught. Python is also my strong language specifically using ctypes to connect to external libraries. I am also pretty good with IO related stuff. I wrote an IR encoder/decoder that handles some obnoxious number of IR protocols. a few years back I dove into ZWave because I set up some home automation in my house. It’s not a “smart home” It’s actual automation where things happen without me ever having to press a button. Things like being able to carry a single remote control around the house and it will automatically switch to controlling the devices in the room I am in. it does this without me ever having to tell it to. Or when someone rings my doorbell any active display in the house will have an overlay pop up on it showing my front door camera. If I get an incoming call on my cell if a movie is playing it will get paused. When a movie starts playing the lights dim slow like you see in the movie theaters. While I don’t have any children I have friends that do and family that does so I wrote a program that uses the closed caption files for movies and it locates words that are not child friendly and when one pops up it mutes the sound until the text goes away. It works pretty decent for the most part, sometimes the caption stays up on the screen for a bit so the audio stays muted for a while. If I am home and my wife is 100 yards away form the house and it’s dark outside it will turn the outside lights on. If one of us leaves the house and the other is still at home it will turn the outside lights off. If there is no one home and it is dark out as soon as someone gets 100 yards away from the house it will turn the outside and some inside lights on. it will shutdown everything when the last person leaves the house. When no one is home it actually turns on and off lights at random for a random duration at random brightness, this way any would be buglers will think someone is home. My security system does facial recognition to unlock my front door and it is able to tell if someone is using a photo of me. The code I wrote for that knows where the sun is in the sky and what the weather is so it is able to determine where shadows on my face should be.