peterhinch / micropython-nano-gui

A lightweight MicroPython GUI library for display drivers based on framebuf class
MIT License
508 stars 87 forks source link

OSError: [Errno 19] ENODEV #53

Closed beyonlo closed 1 year ago

beyonlo commented 1 year ago

Hi, I get a example from your code and changes somethings (just for tests), but after running many hours I have this error:

press
release
press
release
double click
release
press
release
press
release
double click
release
Task exception wasn't retrieved
future: <Task> coro= <generator object 'multi_fields' at 3df79d40>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "display.py", line 112, in multi_fields
  File "gui/core/nanogui.py", line 77, in refresh
  File "ssd1306.py", line 99, in show
  File "ssd1306.py", line 119, in write_cmd
OSError: [Errno 19] ENODEV

Ps: I'm using only one button for the display, so the idea is press go to next window, double press back to previous window and long press enter to a new menu of windows

The code is this:

start.py:

    async def _start_display(self):
    task_dict[1] = asyncio.create_task(multi_fields())

display.py:

# mono_test.py Demo program for nano_gui on an SSD1306 OLED display.

# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2018-2021 Peter Hinch

# https://learn.adafruit.com/monochrome-oled-breakouts/wiring-128x32-spi-oled-display
# https://www.proto-pic.co.uk/monochrome-128x32-oled-graphic-display.html

# V0.33 16th Jan 2021 Hardware configuration is now defined in color_setup to be
# consistent with other displays.
# V0.32 5th Nov 2020 Replace uos.urandom for minimal ports
import gc

from machine import Pin, Timer
import uasyncio as asyncio
#from primitives import Pushbutton
from abutton import Pushbutton

import uasyncio as asyncio

import utime
# import uos
from color_setup import ssd
# On a monochrome display Writer is more efficient than CWriter.
from gui.core.writer import Writer
from gui.core.nanogui import refresh
from gui.widgets.meter import Meter
from gui.widgets.label import Label

# Fonts
import gui.fonts.arial10 as arial10
import gui.fonts.courier20 as fixed
import gui.fonts.font6 as small

# Some ports don't support uos.urandom.
# See https://github.com/peterhinch/micropython-samples/tree/master/random
def xorshift64star(modulo, seed = 0xf9ac6ba4):
    x = seed
    def func():
        nonlocal x
        x ^= x >> 12
        x ^= ((x << 25) & 0xffffffffffffffff)  # modulo 2**64
        x ^= x >> 27
        return (x * 0x2545F4914F6CDD1D) % modulo
    return func

async def fields():
    ssd.fill(0)
    refresh(ssd)
    Writer.set_textpos(ssd, 0, 0)  # In case previous tests have altered it
    wri = Writer(ssd, fixed, verbose=False)
    wri.set_clip(False, False, False)
    textfield = Label(wri, 0, 2, wri.stringlen('longer'))
    numfield = Label(wri, 25, 2, wri.stringlen('99.99'), bdcolor=None)
    countfield = Label(wri, 0, 90, wri.stringlen('1'))
    n = 1
    random = xorshift64star(65535)
    for s in ('short', 'longer', '1', ''):
        textfield.value(s)
        numfield.value('{:5.2f}'.format(random() /1000))
        countfield.value('{:1d}'.format(n))
        n += 1
        refresh(ssd)
        #utime.sleep(2)
        await asyncio.sleep(1)
    textfield.value('Done', True)
    refresh(ssd)

async def multi_fields():
    ssd.fill(0)
    refresh(ssd)
    Writer.set_textpos(ssd, 0, 0)  # In case previous tests have altered it
    wri = Writer(ssd, small, verbose=False)
    wri.set_clip(False, False, False)
    '''
    Label(wri, 25, 0, 'Canais 1,2,3,4,5,6', True)
    refresh(ssd)
    utime.sleep(2)
    ssd.fill(0)
    refresh(ssd)
    '''
    nfields = []
    dy = small.height() + 6
    y = 2
    col = 15
    width = wri.stringlen('999.99')
    for txt in ('1:', '2:', '3:'):
        Label(wri, y, 0, txt)
        nfields.append(Label(wri, y, col, width, bdcolor=None))  # Draw border
        y += dy
    #print(len(nfields))

    dy = small.height() + 6
    y = 2
    col = 82
    for txt in ('4:', '5:', '6:'):
        Label(wri, y, 67, txt)
        nfields.append(Label(wri, y, col, width, bdcolor=None))  # Draw border
        y += dy
    #print(len(nfields))
    #print(nfields)

    random = xorshift64star(2**24 - 1)
    #for _ in range(5):
    while True:
        for field in nfields:
            value = random() / 16777
            field.value('{:5.2f}'.format(value))
        start_time = utime.ticks_ms()
        refresh(ssd)
        #await ssd.do_refresh()
        end_time = utime.ticks_ms()
        diff_time = utime.ticks_diff(end_time, start_time)
        #print('{} {}ms'.format('The time to the refresh display is:', diff_time))
        #utime.sleep(1)
        await asyncio.sleep_ms(500)
        #ssd.fill(0)
        #print(gc.mem_free())
    ssd.fill(0)
    Label(wri, 25, 0, 'Grafico canais 1,2,3', True)
    refresh(ssd)
    utime.sleep(2)

async def meter1():
    ssd.fill(0)
    refresh(ssd)
    wri = Writer(ssd, arial10, verbose=False)
    m0 = Meter(wri, 5, 2, height = 45, divisions = 4,  legends=('0.0', '50', '100', '145!', '150!'), label='1', style=Meter.BAR)
    m1 = Meter(wri, 5, 44, height = 45, divisions = 4, legends=('0.0', '50', '100', '195!', '200!'), label='2', style=Meter.BAR)
    m2 = Meter(wri, 5, 86, height = 45, divisions = 4, legends=('0.0', '50', '100', '235!', '240!'), label='3', style=Meter.BAR)
    steps = 10
    random = xorshift64star(2**24 - 1)
    c = 1
    for n in range(steps + 1):
        #m0.value(random() / 16777216)
        m0.value(c/steps)
        m1.value(n/steps)
        m2.value(1 - n/steps)
        refresh(ssd)
        #Label(wri, 64, 0, '1', False)
        #utime.sleep(1)
        await asyncio.sleep(1)
        c += 1
    ssd.fill(0)

    Writer.set_textpos(ssd, 0, 0)  # In case previous tests have altered it
    wri = Writer(ssd, small, verbose=False)
    wri.set_clip(False, False, False)
    Label(wri, 25, 0, 'Grafico canais 4,5,6', True)
    refresh(ssd)
    utime.sleep(2)

async def meter2():
    ssd.fill(0)
    refresh(ssd)
    wri = Writer(ssd, arial10, verbose=False)
    m0 = Meter(wri, 5, 2, height = 45, divisions = 4,  legends=('0.0', '50', '100', '245!', '250!'), label='4', style=Meter.BAR)
    m1 = Meter(wri, 5, 44, height = 45, divisions = 4, legends=('0.0', '50', '200', '295!', '300!'), label='5', style=Meter.BAR)
    m2 = Meter(wri, 5, 86, height = 45, divisions = 4, legends=('0.0', '50', '200', '335!', '340!'), label='6', style=Meter.BAR)
    steps = 10
    random = xorshift64star(2**24 - 1)
    c = 1
    for n in range(steps + 1):
        #m0.value(random() / 16777216)
        m0.value(c/steps)
        m1.value(n/steps)
        m2.value(1 - n/steps)
        refresh(ssd)
        #Label(wri, 64, 0, '1', False)
        #utime.sleep(1)
        await asyncio.sleep(1)
        c += 1

tstr = '''Test assumes a 128*64 (w*h) display. Edit WIDTH and HEIGHT in ssd1306_setup.py for others.
Device pinouts are comments in ssd1306_setup.py.

Test runs to completion.
'''

'''
print(tstr)
print('Basic test of fields.')
#fields()
print('More fields.')
multi_fields()
print('Meters 1.')
meter1()
print('Done.')
print('Meters 2.')
meter2()
print('Done.')
'''

def double():
    print('double click')
    global task_dict, double_active
    double_active = True
    task_running_now = asyncio.current_task()
    #print(dir(task_running_now))
    #print('The currently running task is: {}'.format(task_running_now))
    #print(task_dict[1])
    try:
        task_dict[1].cancel()
        task_dict[2].cancel()
    except:
        print('Error')
    task_dict[1] = asyncio.create_task(multi_fields())

def release():
    print('release')
    global task_dict, double_active
    await asyncio.sleep_ms(150)
    if double_active:
        return
    task_running_now = asyncio.current_task()
    #print(dir(task_running_now))
    #print('The currently running task is: {}'.format(task_running_now))
    #print(task_dict[1])
    try:
        task_dict[1].cancel()
        task_dict[2].cancel()
    except:
        print('Error')
    task_dict[2] = asyncio.create_task(meter2())

def press():
    print('press')
    global task_dict, double_active
    double_active = False

def long():
    print('long press')

global task_dict, double_active

task_dict = {}
double_active = False
pin = Pin(12, Pin.IN, Pin.PULL_UP)
pb = Pushbutton(pin)
pb.press_func(press, ())
pb.release_func(release, ())
pb.double_func(double, ())
pb.long_func(long, ())
peterhinch commented 1 year ago

You seem to be re-inventing micro-gui which is designed for user input. I'm not sure how I can help with debugging such a large chunk of code. Have you considered just using micro-gui?

beyonlo commented 1 year ago

Hello @peterhinch

You seem to be re-inventing micro-gui which is designed for user input.

I'm sorry! I was thinking that is considered user input is just when user want to configure something, I was thinking that the action to change of windows (next/previous/longpress) is not considered as user input. So, the correct usage of nano-gui is just when user have no buttons, right?

I'm not sure how I can help with debugging such a large chunk of code. Have you considered just using micro-gui?

I look in the micro-gui (https://github.com/peterhinch/micropython-micro-gui) but it do not have option to use only one button. How can I use micro-gui using one button, doing one click for the next window, double click for previous window and long press to execute some function? My project has as requirement need to have just one button, so is impossible to have two or more buttons in this project.

From micro-gui:

Options for data input comprise:

Two pushbuttons: limited capabilities with some widgets unusable for input. Three pushbuttons with full capability. Five pushbuttons: full capability, less "modal" interface. A switch-based navigation joystick: another way to implement five buttons. Via two pushbuttons and a rotary encoder such as this one. An intuitive interface. On ESP32 physical buttons may be replaced with touchpads.

Thank you!

peterhinch commented 1 year ago

I wasn't trying to suggest that what you're doing is impossible, just that you're doing something new. micro-gui does require at least three buttons or a single joystick-style switch.

Unfortunately I am busy with other things and can't take on the task of getting your code working. I'm sure it's possible, but it may be quite a difficult task. Good luck!

beyonlo commented 1 year ago

Hello @peterhinch

I wasn't trying to suggest that what you're doing is impossible, just that you're doing something new. micro-gui does require at least three buttons or a single joystick-style switch.

Understood. Maybe is a good idea to have in the future a feature on the micro-gui to support just one button (one click next, double click previous, and long press to enter/out to/from a menu)?

Unfortunately I am busy with other things and can't take on the task of getting your code working. I'm sure it's possible, but it may be quite a difficult task. Good luck!

No problem, it's okay! Could you please tell me what do you think is better to start a new project using only one button:

  1. Continue using nano-gui re-inventing micro-gui (as you told me above)?
  2. Try to adapt the micro-gui to use one button instead three buttons?

Thank you in advanced!

peterhinch commented 1 year ago

I do not intend to add any form of user input to nano-gui. Its aim is to be extremely lightweight. A UI adds considerable complexity.

I think adapting micro-gui is probably easier as it embodies concepts like Screen and Menu objects and uses asynchronous coding. It already uses the Pushbutton class. One approach might be to develop the screens in 3-button mode, then adapt the UI to re-purpose the long-press and double-click callbacks.

beyonlo commented 1 year ago

I do not intend to add any form of user input to nano-gui. Its aim is to be extremely lightweight. A UI adds considerable complexity.

I was talking about to add that feature in the micro-gui, not nano-gui. So micro-gui can be used by default (without need to adapt) with just one button. Maybe my needs can be needs of others people.

I think adapting micro-gui is probably easier as it embodies concepts like Screen and Menu objects and uses asynchronous coding. It already uses the Pushbutton class. One approach might be to develop the screens in 3-button mode, then adapt the UI to re-purpose the long-press and double-click callbacks.

Nice, I will to learn about micro-gui and to try this option so :)

Thank you very much!

peterhinch commented 1 year ago

I've thought about this some more - here is how I would proceed.

Write prototype code for your application embodying the navigation mechanism as a minimum. I would start out with two or three pushbuttons so that you can use the un-modified library. Use Button widgets for navigation as per demos such as screen_change.py. This way you'll get experience with how navigation with the Button control operates.

With two buttons the functions are Select and Next. Prev is merely a convenience.

Once you're happy that you have workable navigation, the modification to micro-gui is merely to take the Select button and ensure its doubleclick callback activates the Next callback. You might want the long callback mapped onto Prev. You'll need the suppress arg for the Pushbutton physical button.

This navigation isn't quite as you originally specified but it enables a lot of the micro-gui functionality.

I was talking about to add that feature in the micro-gui

The 3-button mode enables the entire functionality of micro-gui to be accessed. This really isn't practical with a single button, so this mode would rule out a great many widgets (such as numeric entry, dropdown lists and suchlike). The subset would need to be documented. I think your requirement is rather specialised, so I don't intend to implement it. However if you produce a documented fork I'd be happy to link to it in the docs.

peterhinch commented 1 year ago

Closing this. Further discussion belongs in micro-gui.

beyonlo commented 1 year ago

@peterhinch I've thought about this some more as well, and I decided to change the hardware to put more two buttons, so will have three buttons working with micro-gui in full capability: Prev, Select and Next.

I never used micro-gui, but I think add more two buttons on hardware will be better decision because as you told me, micro-gui already is ready to works with menus, etc and I will have full capability of it.

Thank you very much for clarify many things to me and help me with this decision!