ev3dev / ev3dev-lang-python

Pure python bindings for ev3dev
MIT License
422 stars 146 forks source link

Advise on python data #757

Open kpu979 opened 3 years ago

kpu979 commented 3 years ago

Im hoping to pick smarter people's brains on Python. If this isn't an appropriate forum, please delete this post (hopefully after you point me in the right direction ;) ).

Im trying to develop a menuing system so I can run programs from a menu once our main is run. It takes way too long for the main program to start, and in a competition, we'll not have this kind of time.

I need to create data records to be accessed like a table. Each record will have the menu number, line number, name and program to execute. As the up/down buttons are hit, the display will display accordingly like the file browser, and the center button will execute that line. Is this possible?

WasabiFan commented 3 years ago

Hi!

First, you might find these threads and the associated documentation helpful (the first two links are discussion about creating the class documented at the last one):

It sounds like your use-case might be a good candidate for the Console class, especially if using micropython.

More generically, the data structure I would suggest for storing such menu items is a list, where each item is a class instance. You might define a class like so (fields are just guesses based on your description):

class MenuItem:
    def __init__(self, menu, line, name, program):
        self.menu = menu
        self.line = line
        # etc...

And then you'd define your menu items similar to this:

menu_items = [
    MenuItem(5, 10, "foo", "foo.py"),
    MenuItem(10, 3, "bar", "bar.py"),
]

You would then also store the index of the currently-focused item. When up is pressed, you decrement the index; when down is pressed, you increment it. Then re-draw the menu with the new item highlighted.

Does that help?

kpu979 commented 3 years ago

This is EXACTLY what I was looking for. I found a few articles on this construct, but they were overly complicated and hard to understand.

One last question... Once the menu_items object is built, how do I access it? cur_menu = 1 cur_line = 2 cur_name = menu_item[cur_menu,cur_line].name ( I know this isn't right... I just cannot figure out how to retrieve this)

kpu979 commented 3 years ago

this is what I came up with (untested). Am I on the right track? ( i don't know how to format the below code so it appears correct... sorry :( )

class MenuItem:
    def __init__(self, menu, line,linename, linetype, lineprog):
        self.menu = menu
        self.line = line
        self.linename = linename
        self.linetype = linetype
        self.lineprog = lineprog

def getmenuitem(list,menu,line):
    #validations if menu is within range

    #validations if line is within range 

    #if all valid, find record
    for obj in list:
        if obj.menu == menu and obj.line == line:
            return list.linename, list.linetype, list.lineprog
        else:
            return 'xxx','x','xxx','xxx'

#setting up the menu
menu = []
menu.append(MenuItem(0,0,'M','Tools','1'))
menu.append(MenuItem(0,1,'M','Missions','2'))
menu.append(MenuItem(0,3,'P','DoIt','all_missions(eve)'))
menu.append(MenuItem(1,0,'P','AAASetup','aaasetup()'))
menu.append(MenuItem(1,1,'P','Calibratcs','calibratecs'))
menu.append(MenuItem(2,0,'P','mission01','mission01(eve)'))

cur_menu = 0
cur_line = 0
cur_tuple = getmenuitem(menu,cur_menu,cur_line)
cur_linename = cur_tuple[0]
cur_linetype = cur_tuple[1]
cur_linedisp = cur_tuple[2]
cur_lineprog = cur_tuple[3]
kpu979 commented 3 years ago

ok, I fleshed out the rest of the code, but I have no way of actually executing it. Im not in possession of our EV3.

Again, I don't know how to post code that looks nice here, but I'll try.

#!/usr/bin/env python3

from ev3dev2.button import Button
from globals import debug_print
import time
import ev3dev2.fonts as fonts
from  ev3dev2.console import Console

class MenuItem:
    def __init__(self, menu, line,linename, linetype, lineprog):
        self.menu = menu
        self.line = line
        self.linename = linename
        self.linetype = linetype
        self.lineprog = lineprog

class Menu(object):
    def __init(self, menu = 0, line = 0,  **kwargs):
        self.menu = Menu
        self.line = line

    #sets buttons so when called and pressed will do what was coded to do once pressed
    def left(self,state):
        if state:
            #debug_print('Left button pressed')
            self.menu = self.menu -1
            if self.menu <0:
                self.menu = 0
        #else:
        #    debug_print('Left button released')
        self.refreshflag = True
        return 0

    def right(self,state):  
        if state:
            debug_print('right button pressed')
            #self.turret.on(speed=-45)
        #else:
            #debug_print('right button released')
            #self.turret.off()
        self.refreshflag = True
        return 0

    def up(self,state):
        if state:
            debug_print('Up button pressed')
            self.line = self.line -1
            if self.line <0:
                self.line = self.max_line
        #else:
            #debug_print('Up button released')
        self.refreshflag = True
        return 0

    def down(self,state):
        if state:
            debug_print('Down button pressed')
            self.line = self.line + 1
            if self.line > self.max_line:
                self.line = 0
        #else:
        #    debug_print('Down button released')
        self.refreshflag = True
        return 0

    def enter(self,state):
        if state:
            debug_print('Enter button pressed')
            if self.cur_linetype == 'M':
                self.cur_menu = int(self.cur_lineprog)
                self.cur_line = 0
            else:
                self.cur_lineprog    #this should run the function          
        #else:
        #    debug_print('Enter button released')
        self.refreshflag = True

    def getmenuitem(self,menulist,menu,line):
        for obj in menulist:
            if obj.menu == menu and obj.line == line:
                return menulist.linename, menulist.linetype, menulist.lineprog
            else:
                return 'xxx','x','xxx','xxx'

    def displaymenu(self,menulist,menuname, menu,line):
        lcdfontB = "Lat15-TerminusBold16"       
        lcd = Console(font=lcdfontB)

       #clear screen
        lcd.reset_console()
        #display menu name top center
        lcd.text_at(menuname, column=1,row=1, alignment="C",inverse=True)

        for obj in menulist:
            if obj.menu == menu:
                cur_tuple = self.getmenuitem(menu,obj.menu,obj.line)
                cur_linename = cur_tuple[0]

                lcd.text_at(cur_linename, column = 1,row=2+obj.line, alignment="C",inverse=(obj.line == line))

    def getmaxline(self,menulist,menu):
        maxline = 0
        for obj in menulist:
            if obj.menu == menu:
                if obj.line > maxline:
                    maxline = obj.line
        return maxline

    def runmenu(self):
        #set up menu names
        menuname = []
        menuname.append('Main')
        menuname.append('Tools')
        menuname.append('Missions')

        #setting up the menu lines
        menu = []
        menu.append(MenuItem(0,0,'M',menuname[1],'1'))
        menu.append(MenuItem(0,1,'M',menuname[2],'2'))
        menu.append(MenuItem(0,3,'P','DoIt','all_missions(eve)'))
        menu.append(MenuItem(1,0,'P','AAASetup','eve.aaasetup()'))
        menu.append(MenuItem(1,1,'P','Calibratcs','eve.calibratecs()'))
        menu.append(MenuItem(2,0,'P','mission01','mission01(eve)'))
        menu.append(MenuItem(2,1,'P','mission02','mission02(eve)'))
        menu.append(MenuItem(2,2,'P','mission03','mission03(eve)'))

        cur_menu = 0
        cur_line = 0
        refreshflag = True

        # set up button controls
        btn = Button()
        btn.on_left = self.left
        btn.on_right = self.right
        btn.on_up = self.up
        btn.on_down = self.down
        btn.on_enter = self.enter

        while True:
            if btn.check_buttons(buttons=['backspace']):
                break    
            #calculate max_line based on menu
            self.max_line = self.getmaxline(menu,cur_menu)

            #display menu on screen 
            if refreshflag:
                self.displaymenu(menu,menuname[cur_menu],cur_menu,cur_line)
                refreshflag = False

            btn.process() 
            cur_tuple = self.getmenuitem(menu,cur_menu,cur_line)
            #self.cur_linename = cur_tuple[0]
            self.cur_linetype = cur_tuple[1]
            self.cur_lineprog = cur_tuple[2]

            time.sleep(0.01)
WasabiFan commented 3 years ago

I edited your post to format the code; see here for documentation on how to do that in the future: https://docs.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks

That looks very reasonable to me! I don't see any obvious problems, although there will likely be some debugging involved once you get it running.

Minor note -- it looks like this is supposed to be lowercase menu:

def __init(self, menu = 0, line = 0,  **kwargs):
        self.menu = Menu
kpu979 commented 3 years ago

Thank you so much for your help. If I ever get a hold of an EV3 to debug this, I'll post the final code.