This project is actually a suite of programs, comprised of this OLED Menu, plus OLED versions of I2C Scanner, Wifi Scanner, FTP Backup, and a Combination Lock.
The menu is dual purpose, it can be integrated into scripts to provide an internal 'branching' menu
for navigation and selection within the script, or it can be used just
as a convenient file 'launcher' for running external script 'apps',.. the included 4 scripts are just some simple demo apps which don't need additional hardware.
This version was written to make best use of a 'TTGO ESP8266 OLED SH1106 1.3Inch "Weather Station" Wifi Meteo Module' which costs about a tenner - mine was free p&p, but check it, because the unwary can pay half as much again for delivery.
Mine came with 'de-auther' firmware installed - if you wish to make a backup of the original supplied firmware, see...
The device I received was not identical to the details on the left, and I discovered some pin differences the long hard way - I still don't know the correct details, but have added some pink and blue comments to prevent others from falling into the same traps.
Because of the DIL header pins it is very easy to connect sensors, and even a plug-on rotary encoder.
(expansion could have could have been even
better if they had included 2 additional pins for access to i2c gpio's 4 & 5).
The TTGO module is not essential though - other ESP8266 modules and 128 x 64 OLED displays could be used instead.
Even the 3 user buttons are not essential, because a rotary encoder can be used for menu navigation if preferred.
The OLED Menu script has some optional feature 'flags' which allow it to be tailored for different needs: 3 selectable font sizes
optional frame - (border) optional files mode - to switch between internal branching type menu or external file launcher type menu optional wraparound - from first to last and vice versa optional item numbers - useful for screen wraparounds with more items than available screen space
optional tabbed numbers - pads single-digit numbers to be aligned with multi-digit numbers so the names are also aligned The option flags are commented in the script, and their effects can be seen in the following demo videos, which also shows newcomers how quick and convenient it is to make and test changes to Annex 'Rapid Development System' scripts...
When used as a file menu to launch an 'app', press the reset button to autorun the menu again (assuming the menu has been configured to autorun).
The Combination Lock is an example of a multi-level menu, which needs to also navigate between several lists of up and down menu choices. The middle 'select' button toggles between 2 sub-menu modes, one mode uses the left and right controls to adjust the value of the current digit, the other mode uses the left and right controls to move between the different digits.
Each menu level effectively multiplies the number of event responses needed to differentiate between the different options of the different menu levels, although in the case of the combination lock the 4 'digit' sub-menu options are all the same.
The script also has provision for differentiating between long and short button presses, but using it will obviously double the number of response events required, so the longpress value has initially been set impractically high to avoid the long press events from being accidentally triggered by slow thoughtful fingers. A more manageable option could be to keep the up/down navigation buttons single purpose, but have a dual purpose select button which allows item selection with a short press or return to previous menu (or exit) using a long press. A realistic longpress delay might be somewhere around 1500 (1.5 secs). The more button response events, and the more menu levels, then the more If Then or Select Case conditional logic will be needed to handle all the combinations.
A sanity-saver is to set yourself a 'breadcrumb trail' to keep track of where your events were triggered from, so it is easier to know what appropriate action to take. (see TIP - Breadcrumb Trail, in Hints Tips Gotchas for more info) Set a 'branch$' variable inside any relevant sub-menu branches, so event handlers can read where the button presses occurred and respond accordingly,eg: Menu1: branch$="menu1" Menu9: branch$="menu9" MiddleButtonEvent: Select Case branch$ Case "menu1" gosub DoThis Case "menu9" gosub DoThat There may be better ways to do something in Annex, but there is not really a right way or wrong way... cos whatever works to let you make your own bit of magic with Annex must be better than not making that magic - so notice the 'naughty' subroutine branches added at the very bottom of the script. They are only temporary example gosubs for the internal menu names to branch to, but a bit of experimentation allowed each entire branch to be squeezed onto a single multi-statement line (11 complete subroutines on 11 lines of code!).. thanks CiccioCB for giving us such an amazing box of tricks to do magic with.
Copy and paste the script below, I've been running my menu and suite of Apps all from the / (root) to avoid root-relative path problems
Images are all in /img/ folder though, so you know where to put the wasp.xbm file (available from bottom of this page) if you want line 5 to load the Annex wasp.
Note that the script decides whether to GOSUB to an internal subroutine branch, or BAS.LAUNCH an external file, based on the 'file=' flag, and if file=1 it also instructs it to add the path$ prefix and ext$ suffix to the selected name to make it the full /path/filename.ext
Note also that filenames are case-sensitive, so trying to launch "lock.bas" if it has been saved as "Lock.bas" will fail.
Basic:
'OLED Menu - developed on Annex 1.39 beta 1, by Electroguard
I2C.SETUP 5,4 OLED.INIT 1,1 OLED.CLS oled.image 64,0,"/img/wasp.xbm" OLED.FONT 3 oled.print 6,40,"Menu" pause 1000 font=1 '1=smallest, 3=largest nav=0 '0=buttons, 1=rotary encoder wrap=1 '0=stop at ends, 1=wraparound from last to first and vie versa linenumbers=1 '1=include item numbers tabbed=1 '1=reserve digit pad spaces for lower numbers frame=0 '1=draw box around menu contents file=0 '1=treat list of menu items as partial filenames to re-combine with path$ prefix and ext$ suffix path$="/" 'file path to be added to menu item names if file=1 ext$=".bas" 'file extension to be added to menu item names if file=1 pos=1 offset=0 if font=1 then w=10: h=10: t=1: cx=6: cy=6: cr=3: b=10: lines=6 if font=2 then w=12: h=15: t=1: cx=7: cy=8: cr=4: b=14: lines=4 if font=3 then w=24: h=21: t=0: cx=8: cy=12: cr=4: b=19: lines=3 OLED.FONT font OLED.COLOR 1 list$ = "one,two,three,four,five,six,seven,eight,nine,ten,eleven" 'needs files=0 flag in order to branch to corresponding internal subroutine name 'list$ = "/xbmview.bas,/ftpcopy.bas,/lock.bas,/i2cscan.bas,/wifiscan.bas" just a comparison example toshow the disadvantage of displaying /path/filename.ext 'list$ = "xbmview,ftpcopy,lock,i2scan,wifiscan" 'needs files=1 flag to be set in order to launch the selected external file delim$="," options=word.count(list$,delim$) digits=len(str$(options)) if options < lines then lines = options choice$="" branch$="" debounce = 100 'debounce duration for buttons and rotary encoder contacts longpress=100000 'if needing to differentiate between long and short button presses then somewhere between 1500 and 2000 is a practical duration if nav=0 then gosub buttons else if nav=1 then gosub rotary else wlog "invalid nav option": end gosub display ledpin=2: ledoff=1: pin.mode ledpin, output: pin(ledpin)=ledoff 'user LED - only being used to show when an item is selected wait display: branch$="display" OLED.COLOR 1 OLED.CLS for line = 1 to lines col=0 if linenumbers=1 then n$=str$(line+offset) if tabbed=1 then if font=2 then n$=space$((digits-len(n$)))+n$ else n$=space$((digits-len(n$))*2)+n$ 'workaround for half space fonts 1 & 3 endif OLED.PRINT 12,t+(line*h)-h,n$+" "+word$(list$,line+offset,delim$),col else OLED.PRINT 12,t+(line*h)-h, word$(list$,line+offset,delim$),col endif next line op$=str$(line)+" " + word$(list$,pos,delim$) oled.circle cx,t+(pos*h)-h+cy,cr,2 if frame=1 then oled.rect 0,t,128,64-t return rotary: rotaryA=13 'Rotary encoder pulse B output pin rotaryB=12 'Rotary encoder pulse A output pin mpin=14 'Rotary encoder selector button pin mstart=millis: mstop=millis: pin.mode mpin, input, pullup pin.mode rotaryA, input, pullup pin.mode rotaryB, input, pullup interrupt rotaryA, rotation interrupt mpin, mpressed return rotation: interrupt rotaryA, off if pin(rotaryA)=0 then if pin(rotaryB)=0 then gosub moveup else gosub movedown endif pause debounce interrupt rotaryA, rotation return buttons: lstart=millis: lstop=millis: mstart=millis: mstop=millis: rstart=millis: rstop=millis lpin=12: pin.mode lpin, input, pullup 'left button mpin=14: pin.mode mpin, input, pullup 'middle button rpin=13: pin.mode rpin, input, pullup 'right button interrupt lpin, lpressed interrupt mpin, mpressed interrupt rpin, rpressed return moveup: if pos>1 then pos=pos-1 else if offset>0 then offset=offset-1 ELSE if wrap=1 then pos=lines: offset=options-lines gosub display return movedown: if pos<lines then pos=pos+1 else if lines+offset<options then offset=offset+1 ELSE if wrap=1 then pos=1: offset=0 gosub display return selected: if pin(ledpin)=ledoff then selected$=word$(list$,pos+offset,delim$) if file=1 then 'flag to denote selection is external file selected$=path$+selected$+ext$ 'add path and extension to create fully-qualified root-relative filename if file.exists(selected$)=1 then 'load and run the selected file, NOTE: filenames ares case-sensitive if bas.load selected$<>0 then wlog "Load failed": oled.cls: oled.print 10,10,"Load failed" else wlog "File not found": wlog selected$: oled.cls: oled.font 1: oled.print 10,10,"File not found": oled.print 10,30, selected$ endif else 'branch to gosub internal menu name oled.cls: oled.print 10,10,selected$ 'show selected item for confirmation if branch$="display" then gosub selected$ 'check where was selected from by checking branch$ bread-crumb trail endif else gosub display endif pin(ledpin)=1-pin(ledpin) 'when menu item is selected it toggles the user led as confirmation return lpressed: interrupt lpin, off pause debounce if pin(lpin) = 0 then lstart = millis else lstop = millis if lstop > lstart then if lstop - lstart < longpress then gosub moveup else oled.cls OLED.PRINT 10,10, "Long LEFT" endif endif interrupt lpin, lpressed return mpressed: interrupt mpin, off pause debounce if pin(mpin) = 0 then mstart = millis else mstop = millis if mstop > mstart then OLED.CLS if mstop - mstart < longpress then gosub selected else oled.cls OLED.PRINT 10,10, "Long MIDDLE" endif endif interrupt mpin, mpressed return rpressed: interrupt rpin, off pause debounce if pin(rpin) = 0 then rstart = millis else rstop = millis if rstop > rstart then if rstop - rstart < longpress then gosub movedown else oled.cls OLED.PRINT 10,10, "Long RIGHT" endif endif interrupt rpin, rpressed return 'additional internal branches for demo example of files=0 and list$ = "one,two,three,four,five,six,seven,eight,nine,ten,eleven" one: :branch$="one":wlog "This is branch 1":return two: :branch$="two":wlog "This is branch 2":return three: :branch$="three":wlog "This is branch 3":return four: :branch$="four":wlog "This is branch 4":return five: :branch$="five":wlog "This is branch 5":return six: :branch$="six":wlog "This is branch 6":return seven: :branch$="seven":wlog "This is branch 7":return eight: :branch$="eight":wlog "This is branch 8":return nine: :branch$="nine":wlog "This is branch 9":return ten: :branch$="ten":wlog "This is branch 10":return eleven: :branch$="eleven":wlog "This is branch 11":return '------------- End ------------- The project hasn't all been plain sailing, there are battle scars from lost skirmishs even though the war was eventually won... so take note to avoid the same pain.
I twice tried to do tabulation routines to allow eg: Wifi scanner SSID names and associated RSSI values to be in neat tidy columns, but the results were all over the place. I eventually gave up and put the fixed length RSSI numbers first followed by the variable length SSID names. It was only later when trying to tabulate the last digits of single and multi-digit line numbers to be aligned, that I discovered the OLED font 1 and 3 spaces are only half a character width (needing 2 spaces to equal the width of a single character), whereas a font 2 space is a full character width. So be aware that I've added a workaround to keep things aligned which will actually cause them to be mis-aligned if the snag is fixed in a future release.
This TTGO module was only bought to see if it could offer a useful ESP8266 Menu project for others to perhaps benefit from, which it did after CiccioCB introduced BAS.LOAD and updated the OLED drivers for it - but it's time to move on, so is being left up to others to improve, with perhaps nested sub-menu's, and menu.ini settings file for Apps to read and auto-configure themselves by. So remember that Apps such as the combination lock will also require their option flags for nav (buttons or rotary-encoder) to be set in the Apps as well as the OLED Menu (it's not difficult to have the Apps read a config file which is saved by the OLED Menu).
Another possibility is to clone the OLED Menu into a TFT version (after the different init, is basically a task of changing output text to suit a different screen size).
And yet another might be to add a Gesture Control module to offer non-contact menu navigation... which may be much easier than one would think, because I've kept the button and rotary-encoder subroutines as separate 'navigation sensor modules' whose left (up), middle (select), right (down) trigger events all branch to common MoveUP, MoveDOWN, Selected subroutines - so adding gesture control could be as easy as branching as needed to those same 3 subroutines.
Hopefully community spirit will add more shared Apps for others to use, especially as the OLED Menu opens up entirely new possibilities for App creation and usage.
For example: a DoSomething.bas script 'App' could be launched to show a list of relevant available files to select from, then DoSomething to a selected file... perhaps change everything to lower case, or count total words used, or show line numbers for all matched opening and closing IF ENDIFs, or anything else a user chooses to do - bearing in mind that Annex can read and save any text-based files, such as .bas .ini .txt .html .css etc.
App Suite
Nothing much to say about the 'apps' - CiccioCB already provided examples for I2C scanner and WiFi scanner yonks ago, so these versions are not really new, they are merely tweaked for OLED use. Similarly for the FTP Copier, which obviously needs an account and permissions on an FTP server to be able to work at all... but it serves to show how easy and practical it could be to have regular Over The Air (OTA) backups of Annex devices.
Those 3 apps are basically just launch, let them do their thing, then reboot back to the OLED Menu after.
The combination lock is a bit different, because it needs to be configured for whatever navigation is required, ie: nav=0 for buttons or nav=1 for rotary-encoder.
It was only intended as a demo, but when unlocked by the correct code (default is 1234) the (green) user LED could just as easily operate an access relay. Some of the apps will display am XBM image if present in the /img/ folder... they can be found at the bottom of this page. FTP Copier
Save as /ftpcopy.bas
Basic:
'FTP Copier
I2C.SETUP 5,4 OLED.INIT 1, 1 OLED.CLS OLED.FONT 2 oled.print 26,0,"FTP Copy" pause 2000 OLED.FONT 1 oled.cls OLED.COLOR 0 FTP: ' FTP function example ' cicciocb 2019 ' send all the local files to the remote server ' in the folder / 'starts from the root d$ = FILE.DIR$("/") 'Then lists all the files While D$ <> "" oled.print 0,0, "file... " OLED.COLOR 0 oled.rect 0,20, 128,16,1 oled.rect 0,50, 128,16,1 OLED.COLOR 1 oled.print 0,20, d$ oled.print 2,50,bas.ftp$( "192.168.1.57", "robin", "hood", d$, "/") d$ = FILE.DIR$ pause 200 Wend end Combination Lock
Save as /lock.bas
Basic:
'Combination Lock
nav=0 '0=horizontal, 1=vertical if nav=0 then gosub buttons if nav=1 then gosub rotary I2C.SETUP 5,4 OLED.INIT 1, 1 OLED.CLS OLED.FONT 2 oled.print 2,6," Digital" oled.print 2,26," Combination" oled.print 2,46," Lock" OLED.FONT 3 OLED.COLOR 1 lockpin=2: unlocked=0 pin.mode lockpin, output: pin(lockpin)=not unlocked x=20:y=26:z=y+26:v=3:w=25:s=3 combination$="1234" d1=val(mid$(combination$,1,1)):d2=val(mid$(combination$,2,1)):d3=val(mid$(combination$,3,1)):d4=val(mid$(combination$,4,1)) pos=0 dim digit$(5)= "0","0","0","0","0" dim position(5)= 0,x,x+w,x+(2*w),x+(3*w) mode=0 timer0 360,ticker oled.cls gosub showpos pos=1 wait ticker: col = 1-col OLED.COLOR col for c=1 to 4 if c=pos then oled.color col oled.print position(c),y,digit$(c),1-col oled.color 1 oled.rect position(c),y-2,15,v,1 oled.rect position(c),z,15,v,1 else oled.color 1 oled.print position(c),y,digit$(c),0 oled.rect position(c),z,15,v,1 oled.rect position(c),y-2,15,v,1 endif next c return showpos: oled.color 1 for c=1 to 4 oled.rect position(c),y-2,15,v,1 oled.rect position(c),z,15,v,1 next c return rotary: rotaryA=13 'Rotary encoder pulse A output pin rotaryB=12 'Rotary encoder pulse B output pin mpin=14 'Rotary encoder selector button pin debounce = 100 lasttime = 0 thistime = 0 start = millis stop = millis mstart=millis: mstop=millis: longpress=2000 pin.mode mpin, input, pullup pin.mode rotaryA, input, pullup pin.mode rotaryB, input, pullup interrupt rotaryA, rotation interrupt mpin, mpressed return rotation: interrupt rotaryA, off if pin(rotaryA)=0 then if pin(rotaryB)=0 then gosub moveup else gosub movedown endif pause debounce interrupt rotaryA, rotation return buttons: longpress=1900 lstart=millis: lstop=millis:mstart=millis:mstop=millis:rstart=millis:rstop=millis lpin=12: pin.mode lpin, input, pullup mpin=14: pin.mode mpin, input, pullup rpin=13: pin.mode rpin, input, pullup interrupt lpin,lpressed interrupt mpin,mpressed interrupt rpin,rpressed return movedown: if mode=0 then if digit$(pos)="" then digit$(pos)="0" number=val(digit$(pos)) if number<9 then number=number+1 else number=0 digit$(pos)=str$(number) endif if mode=1 then pos=pos+1: if pos>4 then pos=1 return moveup: if mode=0 then if digit$(pos)="" then digit$(pos)="0" number=val(digit$(pos)) if number>1 then number=number-1 else number=9 digit$(pos)=str$(number) endif if mode=1 then pos=pos-1 if pos<1 then pos=4 return selected: if digit$(1)+digit$(2)+digit$(3)+digit$(4)=combination$ then gosub unlocked else mode=1-mode wlog str$(mode) endif return unlocked: pin(lockpin)=unlocked timer0 0 oled.cls oled.font 2 OLED.COLOR 1 oled.print 25,25,"UNLOCKED" return lpressed: interrupt lpin, off if pin(lpin) = 0 then lstart = millis else lstop = millis if Lstop > Lstart then if lstop - lstart < longpress then gosub moveup else oled.cls OLED.PRINT 10,10, "Long LEFT" endif endif pause 100 interrupt lpin, lpressed return mpressed: interrupt mpin, off if pin(mpin) = 0 then mstart = millis else mstop = millis if mstop > mstart then if mstop - mstart < longpress then gosub selected else oled.cls OLED.PRINT 10,10, "Long MIDDLE" endif endif pause 100 interrupt mpin, mpressed return rpressed: interrupt rpin, off if pin(rpin) = 0 then rstart = millis else rstop = millis if rstop > rstart then if rstop - rstart < longpress then gosub movedown else oled.cls OLED.PRINT 10,10, "Long RIGHT" endif endif pause 100 interrupt rpin, rpressed return '--------- End ------------ I2C Scanner
Save as /i2cscan.bas
Basic:
'I2C Scanner for SH1106 OLED
I2C.SETUP 5,4 ' SH1106 i2c pins reversed to normal oled.init 1,1 ' last digit configures for SH1106 oled.cls oled.color 2 oled.image 0,0,"/img/xbm4.xbm" oled.font 3 message$="I2C Scan" oled.print 13,18,message$,1 pause 2000 oled.cls oled.font 1 message$="I2C scan started" oled.print 27,0,message$ oled.font 2: h=15 found=0 for i = 0 to 120 i2c.begin i if i2c.end = 0 then found=found+1 oled.print 0,(found*h)-4,"DEC=" + str$(i) + " HEX=" + ucase$(hex$(i)) pause 10 end if next i oled.font 1 oled.print 33,54," scan ended" end '---------- End ----------- WiFi Scanner
Save as /wifiscan.bas
Basic:
'Wifi scanner modified for SH1106 OLED
I2C.SETUP 5, 4 'configured for SH1106 OLED oled.init 1,1 oled.cls oled.color 2 oled.image 34,4,"/img/xbm2.xbm" oled.font 2 message$=" WiFi Scan " oled.print 13,48,message$,1 oled.font 1 buttonpin=0: pin.mode buttonpin, input, pullup interrupt buttonpin, pressed selectpin=14: pin.mode selectpin, input, pullup interrupt selectpin, selected do networks$="" WIFI.SCAN While WIFI.NETWORKS(A$)= -1 Wend n=wifi.networks(a$) if n>0 then for c=1 to n n$=word$(a$,c,chr$(10)) ssid$=word$(n$,1,",") rsi$=word$(n$,3,",") networks$=networks$+rsi$+" "+ssid$+chr$(10) next c oled.cls oled.print 0,0,networks$ endif loop until pin(buttonpin)=0 end selected: if pin(selectpin)=0 then oled.cls oled.print 0,0,"Rebooted..." reboot endif return end pressed: if pin(buttonpin)=0 then ' if fontsize=1 then fontsize=2 else fontsize=1 endif return end '-------------- End --------------- SUPPLEMENT 1
XBM Viewer
This offers a good opportunity to show how quick and easy it is to integrate into the menu - so the script below is an exact clone of the OLED Menu, with just a few lines commented out and a few extra lines added... all changes are highlighted in the script.
It is basically just a matter of building a menu list of all available files which are of interest (XBM images in this case) then displaying the selected image. (see video demo) The full pathnames are listed because the entire device is searched for images (not just /img/), so files might be found anywhere. Press once to display the selected image, then press again to return to the menu.
I've added a zip below which contains the few xbm images that I have (not many), so more images from other users (weather icons etc) could be a useful contribution.
If the XBM Viewer will be launched from the OLED Menu it obviously needs to be included into the
menu's list$ string, ie: list$ = "xbmview,ftpcopy,lock,i2scan,wifiscan" (which I've already done retrospectively to the OLED Menu on this page).
Although this project is all about the menu and launching apps, it is worth making the point that all of these 'apps' are actually stand-alone scripts which can be ran by themselves, so they don't have to be launched from the menu.
Save the script below as /xbmview.bas
Basic:
'XBM Viewer
'---------- Original Menu ----------- 'OLED Menu - developed on Annex 1.39 beta 1, by Electroguard I2C.SETUP 5,4 OLED.INIT 1,1 OLED.CLS 'oled.image 64,0,"/img/wasp.xbm" OLED.FONT 3 'oled.print 6,40,"Menu" oled.print 0,20,"XBMViewer" pause 1500 font=1 '1=smallest, 3=largest nav=0 '0=buttons, 1=rotary encoder wrap=1 '0=stop at ends, 1=wraparound from last to first and vie versa linenumbers=1 '1=include item numbers tabbed=1 '1=reserve digit pad spaces for lower numbers frame=1 '1=draw box around menu contents file=0 '1=treat list of menu items as partial filenames to re-combine with path$ prefix and ext$ suffix path$="/" 'file path to be added to menu item names if file=1 ext$=".bas" 'file extension to be added to menu item names if file=1 pos=1 offset=0 if font=1 then w=10: h=10: t=1: cx=6: cy=6: cr=3: b=10: lines=6 if font=2 then w=12: h=15: t=1: cx=7: cy=8: cr=4: b=14: lines=4 if font=3 then w=24: h=21: t=0: cx=8: cy=12: cr=4: b=19: lines=3 OLED.FONT font OLED.COLOR 1 'list$ = "one,two,three,four,five,six,seven,eight,nine,ten,eleven" 'needs files=0 flag in order to branch to corresponding internal subroutine name 'list$ = "/xbmview.bas,/ftpcopy.bas,/lock.bas,/i2cscan.bas,/wifiscan.bas" just a comparison example toshow the disadvantage of displaying /path/filename.ext 'list$ = "xbmview,ftpcopy,lock,i2scan,wifiscan" 'needs files=1 flag to be set in order to launch the selected external file 'XBM addin XBMimages=0: list$ = "" d$ = FILE.DIR$("/") ' searchs all files and folders While D$ <> "" ' wlog d$ d$ = FILE.DIR$ if ucase$(word$(d$,2,".")) = "XBM" then list$ = list$ + "," + d$: f=f+1 'build menu list of all .XBM images Wend' if instr(list$,",") = 1 then list$=mid$(list$,2) 'remove leading comma 'wlog "number of xbm images = " + str$(f) 'wlog list$ 'just need to insert actual viewer code in selected: subroutine 'XBM end delim$="," options=word.count(list$,delim$) digits=len(str$(options)) if options < lines then lines = options choice$="" branch$="" debounce = 100 'debounce duration for buttons and rotary encoder contacts longpress=100000 'if needing to differentiate between long and short button presses then somewhere between 1500 and 2000 is a practical duration if nav=0 then gosub buttons else if nav=1 then gosub rotary else wlog "invalid nav option": end gosub display ledpin=2: ledoff=1: pin.mode ledpin, output: pin(ledpin)=ledoff 'user LED - only being used to show when an item is selected wait display: branch$="display" OLED.COLOR 1 OLED.CLS for line = 1 to lines col=0 if linenumbers=1 then n$=str$(line+offset) if tabbed=1 then if font=2 then n$=space$((digits-len(n$)))+n$ else n$=space$((digits-len(n$))*2)+n$ 'workaround for half space fonts 1 & 3 endif OLED.PRINT 12,t+(line*h)-h,n$+" "+word$(list$,line+offset,delim$),col else OLED.PRINT 12,t+(line*h)-h, word$(list$,line+offset,delim$),col endif next line op$=str$(line)+" " + word$(list$,pos,delim$) oled.circle cx,t+(pos*h)-h+cy,cr,2 if frame=1 then oled.rect 0,t,128,64-t return rotary: rotaryA=13 'Rotary encoder pulse B output pin rotaryB=12 'Rotary encoder pulse A output pin mpin=14 'Rotary encoder selector button pin mstart=millis: mstop=millis: pin.mode mpin, input, pullup pin.mode rotaryA, input, pullup pin.mode rotaryB, input, pullup interrupt rotaryA, rotation interrupt mpin, mpressed return rotation: interrupt rotaryA, off if pin(rotaryA)=0 then if pin(rotaryB)=0 then gosub moveup else gosub movedown endif pause debounce interrupt rotaryA, rotation return buttons: lstart=millis: lstop=millis: mstart=millis: mstop=millis: rstart=millis: rstop=millis lpin=12: pin.mode lpin, input, pullup 'left button mpin=14: pin.mode mpin, input, pullup 'middle button rpin=13: pin.mode rpin, input, pullup 'right button interrupt lpin, lpressed interrupt mpin, mpressed interrupt rpin, rpressed return moveup: if pos>1 then pos=pos-1 else if offset>0 then offset=offset-1 ELSE if wrap=1 then pos=lines: offset=options-lines gosub display return movedown: if pos<lines then pos=pos+1 else if lines+offset<options then offset=offset+1 ELSE if wrap=1 then pos=1: offset=0 gosub display return selected: if pin(ledpin)=ledoff then selected$=word$(list$,pos+offset,delim$) if file=1 then 'flag to denote selection is external file selected$=path$+selected$+ext$ 'add path and extension to create fully-qualified root-relative filename if file.exists(selected$)=1 then 'load and run the selected file, NOTE: filenames ares case-sensitive if bas.load selected$<>0 then wlog "Load failed": oled.cls: oled.print 10,10,"Load failed" else wlog "File not found": wlog selected$: oled.cls: oled.font 1: oled.print 10,10,"File not found": oled.print 10,30, selected$ endif else 'branch to gosub internal menu name oled.cls: oled.image 0,0,selected$ 'xbm viewer code added ' oled.cls: oled.print 10,10,selected$ 'show selected item for confirmation ' if branch$="display" then gosub selected$ 'check where was selected from by checking branch$ bread-crumb trail endif else gosub display endif pin(ledpin)=1-pin(ledpin) 'when menu item is selected it toggles the user led as confirmation return lpressed: interrupt lpin, off pause debounce if pin(lpin) = 0 then lstart = millis else lstop = millis if lstop > lstart then if lstop - lstart < longpress then gosub moveup else oled.cls OLED.PRINT 10,10, "Long LEFT" endif endif interrupt lpin, lpressed return mpressed: interrupt mpin, off pause debounce if pin(mpin) = 0 then mstart = millis else mstop = millis if mstop > mstart then OLED.CLS if mstop - mstart < longpress then gosub selected else oled.cls OLED.PRINT 10,10, "Long MIDDLE" endif endif interrupt mpin, mpressed return rpressed: interrupt rpin, off pause debounce if pin(rpin) = 0 then rstart = millis else rstop = millis if rstop > rstart then if rstop - rstart < longpress then gosub movedown else oled.cls OLED.PRINT 10,10, "Long RIGHT" endif endif interrupt rpin, rpressed return '------------- End ------------- SUPPLEMENT 2 Speaking Phrase List or Talking Menu The versatile OLED Menu can use rotary-encoder or navigation buttons, or could be controlled by joystick or other 2-axis navigation and selection capability. Simply adding Text-To-Speech capability could offer a talking menu, or an 'impairement communications' device for speaking selected phrases. This SAM TTS firmware basically offers 'free speech' ! This XFS5152CE hardware TTS module offers better quality, but costs more. Both receive text messages on their serial ports which they translate into speech. Talking Menu The internal branching menu already displays the chosen menu item on the OLED, so sending that same text via serial2 on gpio0 would allow it to be spoken. Speaking Phrase List The external files menu could list all .txt text files in the menu (or those deliberately named with .tts extension), then speak the text contained in the chosen file. This could be as simple as changing "XBM" to "TXT" or TTS" in the following line of the XBM Viewer... if ucase$(word$(d$,2,".")) = "XBM" then list$ = list$ + "," + d$: f=f+1 'build menu list of all .XBM images if ucase$(word$(d$,2,".")) = "TXT" then list$ = list$ + "," + d$: f=f+1 'build menu list of all .txt files if ucase$(word$(d$,2,".")) = "TTS" then list$ = list$ + "," + d$: f=f+1 'build menu list of all TEXT TO SPEECH text files Then changing the following line in selected: from''' oled.cls: oled.image 0,0,selected$ 'xbm viewer code added to... tts$ = file.read$(selected$) 'read contents of specified text file print2 tts$ 'send contents of specified text file to be spoken on TTS device (this is assuming tts$="" and serial2.mode 115200,0,3 were both previously defined in the top part of the script... but the point is merely to show how easily speech capability can be added to the menu) There is a limit of how much text can be sent to a TTS device at any one time, but they are all capable of 'busy' handshaking if you need to send longer phrases. In both cases (Talking Menu or Speaking Phrase List) the controlling device could be announced on one or more remote devices using network versions of the TTS announcers, to attract the attention of other people who may be concentrating on other things elsewhere. Network version of the SAM firmware TTS Network version of the XFS5152CE hardware TTS ______________ |