'TypeError: argument has wrong type' when setting indev group. Need help with creating input devices

Hello. I’ve been struggling with trying to create an input device with Micropython, I can’t seem to be able to translate C code into Python. I would appreciate some guidance. But now I have an error.

This is my code:

import lvgl as lv
from machine import Pin
from utime import sleep

test = Pin(2, Pin.IN)

lv.init()

def joystick_read(drv: lv.indev_drv_t, data: lv.indev_data_t):
    if test.value() == 1:
        print("Next!")
        data.key = lv.KEY.NEXT
    
    if key_pressed():
        data.state = lv.INDEV_STATE.PR
    else:
        data.state = LV.INDEV_STATE.REL

    return False

indev_drv = lv.indev_drv_t()
indev_drv.init()
indev_drv.type = lv.INDEV_TYPE.KEYPAD
indev_drv.read_cb = joystick_read
keypad = indev_drv.register()

g = lv.group_t()
lv.indev_t.set_group(keypad, g)

while True:
    print("waiting...")
    sleep(2)

I can’t get past line 28 as I get:

Traceback (most recent call last):
  File "<stdin>", line 28, in <module>
TypeError: argument has wrong type

This occurs when trying to do:

lv.indev_t.set_group(keypad, g)

Am I calling the method wrong? Thanks in advance.

EDIT: I forgot to mention, I tried some things and discovered that this happens because keypad is NoneType instead of what it should be. So I do not know why this happens.

I managed to make it detect my input and presses now, but it does not navigate between objects nor trigger the button’s callback function. I need a hand here.

Updated code:

import lvgl as lv
from utime import sleep_ms
from machine import Pin

lv.init()

testpin = Pin(12, Pin.IN)

from ili9XXX import ili9341
disp = ili9341(mosi=23, miso=19, clk=18, dc=21, cs=5, rst=22, power=-1, backlight=-1)

def hello(source, event):
    print("Hello")

scr = lv.obj()

btn1 = lv.btn(scr)
btn1.set_pos(32, 16)
btn1.add_event_cb(hello, lv.EVENT.READY, None)
label = lv.label(btn1)
label.set_text("Button")

btn2 = lv.btn(scr)
btn2.set_pos(128, 128)
label = lv.label(btn2)
label.set_text("Button 2")

def test(drv, data):
    if testpin.value() != 0:
        data.key = lv.KEY.ENTER
        data.state = lv.INDEV_STATE.PRESSED
    else:
        data.key = 0
        data.state = lv.INDEV_STATE.RELEASED

    return False

indev = lv.indev_drv_t()
indev.init()
indev.type = lv.INDEV_TYPE.ENCODER
indev.read_cb = test
keypad = indev.register()

g = lv.group_create()
lv.group_t.add_obj(g, btn1)
lv.group_t.add_obj(g, btn2)
lv.indev_t.set_group(keypad, g)

lv.scr_load(scr)

while True:
    print(".")
    sleep_ms(500)

Hi @GianK128 ,

indev_drv.register() returns indev_t as explained in the docs, therefore it makes more sense to write:

...
keypad = indev_drv.register()
keypad.set_group(g)

That’s probably because of some typos you have on joystick_read (LV.INDEV_STATE.REL should be lv.INDEV_STATE.RELEASED).
I believe you should see some related error messages on the terminal.

In order to navigate, you need to simulate “left” (or “right”) key, as explained in the docs.
In your code you only set lv.KEY.ENTER so it is not supposed to navigate.

You don’t see the callback being triggered because you registered it with lv.EVENT.READY which is unrelated here. Try to replacing it by lv.EVENT.PRESSED for example.

Hello @amirgon, thanks for answering.

Today looking at more examples I managed to understand a little more how the input devices work. But I still can’t get to change the focus between elements in the screen. I’ll share my updated code:

import lvgl as lv
from utime import sleep_ms
from machine import Pin, ADC
import lodepng as png
from imagetools import get_png_info, open_png

lv.init()

vrx = ADC(Pin(32, Pin.IN))
vry = ADC(Pin(33, Pin.IN))
sw = Pin(25, Pin.IN)

vrx.width(ADC.WIDTH_10BIT)
vry.width(ADC.WIDTH_10BIT)
vrx.atten(ADC.ATTN_11DB)
vry.atten(ADC.ATTN_11DB)

from ili9XXX import ili9341
disp = ili9341(mosi=23, miso=19, clk=18, dc=21, cs=5, rst=22, power=-1, backlight=-1)

scr = lv.obj()

def test(drv, data):
    read_x = vrx.read()
    read_y = vry.read()
    press = sw.value()

    if read_x > 900:
        print("right")
        data.key = lv.KEY.PREV
    elif read_x < 100:
        print("left")
        data.key = lv.KEY.NEXT
    else:
        data.key = 0

    if press == 1:
        print("pressed")
        data.key = lv.KEY.ENTER
        data.state = lv.INDEV_STATE.PRESSED
    else:
        data.key = 0
        data.state = lv.INDEV_STATE.RELEASED

    return False

indev = lv.indev_drv_t()
indev.init()
indev.type = lv.INDEV_TYPE.ENCODER
indev.read_cb = test
keypad = indev.register()

g = lv.group_create()
keypad.set_group(g)

After that, I have this code to draw a certain screen:

def draw_edit_screen(username, usericon):
    h1 = lv.label(scr)
    h1.set_pos(96, 16)
    h1.set_text("Perfil")

    h1 = lv.label(scr)
    h1.set_pos(145, 80)
    h1.set_text(username)

    with open("008-man.png", 'rb') as i:
        png_data = i.read()

    png_img_dsc = lv.img_dsc_t({
        'data_size': len(png_data),
        'data': png_data
    })

    img2 = lv.img(scr)
    img2.set_pos(40, 72)
    img2.set_zoom(256+256)
    raw_dsc = lv.img_dsc_t()
    get_png_info(None, png_img_dsc, raw_dsc.header)
    dsc = lv.img_decoder_dsc_t({'src': png_img_dsc})
    if open_png(None, dsc) == lv.RES.OK:
        raw_dsc.data = dsc.img_data
        raw_dsc.data_size = raw_dsc.header.w * raw_dsc.header.h * lv.color_t.__SIZE__
        img2.set_src(raw_dsc)

    btn1 = lv.btn(scr)
    btn1.set_pos(16, 150)
    btn1.set_width(200)
    label1 = lv.label(btn1)
    label1.set_text(lv.SYMBOL.OK + "  Seleccionar perfil")
    g.add_obj(btn1)

    btn1 = lv.btn(scr)
    btn1.set_pos(16, 190)
    btn1.set_width(200)
    label1 = lv.label(btn1)
    label1.set_text(lv.SYMBOL.EDIT + "  Editar nombre")
    g.add_obj(btn1)

    btn1 = lv.btn(scr)
    btn1.set_pos(16, 230)
    btn1.set_width(200)
    label1 = lv.label(btn1)
    label1.set_text(lv.SYMBOL.EDIT + "  Editar icono")
    g.add_obj(btn1)

    btn1 = lv.btn(scr)
    btn1.set_pos(16, 270)
    btn1.set_width(200)
    label1 = lv.label(btn1)
    label1.set_text(lv.SYMBOL.TRASH + "  Borrar perfil")
    g.add_obj(btn1)

draw_edit_screen("Gian", "008-man.png")

lv.scr_load(scr)

This screen ends up looking like this (sorry for the bad lighting):

So, I can see the items are getting focused, and when I declare data.key as lv.KEY.ENTER it triggers the buttons callback function. But when it is set as either lv.KEY.RIGHT, lv.KEY.LEFT, also NEXT / PREV, it does not ‘travel’ between buttons (it does print ‘right’ or ‘left’ though, so maybe I am adding them wrong to the groups?).

EDIT: Sorry, forgot to mention, in this code I have removed the button event callbacks, as now I am just trying to get it to move between items.

You should use KEYPAD if you are sending NEXT/PREV keys, and ENCODER if you are sending RIGHT/LEFT.

I really recommend you first try your code in the simulator (either the unix port or the web simulator) before running it on a device. That would make it easier for you to try things, find your bugs etc.

I took your code, fixed it and tried it on the online simulator. Instead of a physical keypad/encoder I just added “Next/Prev/Enter” buttons.

Please have a look and try it out: Link to online simulation

Thank you very much for the example! It made me understand the data.state variable. Now it works. As I am using a analog joystick for input, I changed it a little and now it works perfectly, here’s the updated code:

class Joystick:
    last_key = ""

    def __init__(self):
        self.key = lv.KEY.ENTER
        self.state = lv.INDEV_STATE.RELEASED

    def read_cb(self, drv, data):    
        read_x = vrx.read()
        press = sw.value()
        this_key = ""

        if read_x > 954:
            self.next(lv.INDEV_STATE.PRESSED)
            this_key = "right"
        elif read_x < 50:
            self.prev(lv.INDEV_STATE.PRESSED)
            this_key = "left"
        elif press == 0:
            self.enter(lv.INDEV_STATE.PRESSED)
            this_key = "enter"
        else:
            if self.last_key == "right":
                self.next(lv.INDEV_STATE.RELEASED)
            elif self.last_key == "left":
                self.prev(lv.INDEV_STATE.RELEASED)
            elif self.last_key == "enter":
                self.enter(lv.INDEV_STATE.RELEASED)

        data.key = self.key
        data.state = self.state
        self.last_key = this_key
        return False

    def send_key(self, event, key):
        self.key = key
        self.state = event

    def next(self, event):
        self.send_key(event, lv.KEY.RIGHT)

    def prev(self, event):
        self.send_key(event, lv.KEY.LEFT)

    def enter(self, event):
        self.send_key(event, lv.KEY.ENTER)

scr = lv.obj()

joystick = Joystick()
indev = lv.indev_drv_t()
indev.init()
indev.type = lv.INDEV_TYPE.ENCODER
indev.read_cb = joystick.read_cb
keypad = indev.register()

g = lv.group_create()
keypad.set_group(g)

It may not be the most optimal changes, and the variable names might be a little confusing now, but I’m glad it works.

One thing I was also trying to do was to use lv.INDEV_TYPE.KEYPAD instead of ENCODER and change between PREV/NEXT, to LEFT/RIGHT and UP/DOWN when focused in a keyboard and in edit mode. What I tried to do is:
In Joystick class:

def focus_keyboard_cb(self, group):
    # Change mode when focused in keyboard and edit mode (printing results)
    print(group.get_editing())
    if isinstance(group.get_focused(), lv.keyboard):
        print("is keyboard")

and then after declaring group:

g.focus_cb = joystick.focus_keyboard_cb

but it doen’t call this function when entering edit mode, just when focusing the object, which is correct according to the documentation. If I change the keys just after focusing, I won’t be able to navigate out of the keyboard, so I guess this is not possible? Might try a little more later.

Anyways, thanks for your help, I really appreciate it. I’ll mark your answer as the solution.

Actually I’m not sure about that one.
This is a general question (non micropython specific) so I suggest you post it under HowTo category, where it can be exposed to a wider audience.