Project - OLED Menu


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...
TIP - How To Backup Original Firmware which is towards the bottom of Hints Tips Gotcha's

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.

It is small and neat with 3 convenient onboard navigation buttons and a 1.3" OLED 128 x 64 display. It also has LIPO battery connector with onboard charger and an external aerial connector, making it handy for portable use. In addition it has a green gpio02 user LED, side reset button, and handy DIL header expansion pins,

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 Menu script is an example of a single level menu which only needs to move up or down a list of available options then select an option by pressing the middle button or rotary encoder switch - so it basically just needs to respond to up (left), down (right) and select (middle) events 

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.

After adding a rotary encoder which uses the same pins as the buttons, and changing between them using a software flag, I noticed that with the flag set wrong I could still move down the menu and select an item with the buttons. So I tried to use a spare gpio for the other rotary pin to have both the rotary controller and triple buttons all connected and used at the same time. Big mistake. The button pins are hard-wired, and the critical 'flashing-mode' pins cannot be used for rotary encoders because unlike pushbuttons they can 'park' in hi or lo positions, causing problems at bootup. Pin 16 was the only candidate even though it cannot be used for interrupt, but sometimes it seemed to do nothing, then other times it caused reboots with every slightest movement. So I built a mini breadboard for quick easy access to all the expansion pins, which could later also be used for quickly trying out various sensors. I must have wasted a couple of days before discovering that one of the two designated GND pins was NOT actually 0v, and gpio16 was in an intimate reboot relationship with ReSeT. So I'm afraid the buttons or rotary encoder must still be selected by software flag. It could certainly be done using a different ESP device with all the gpio's available, but this TTGO module is still convenient enough for using the rotary encoder to navigate with the left hand while holding the OLED module in the right with the select button always under the thumb.

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

______________





Robin Baker,
Jul 2, 2019, 10:59 AM
v.1
xbm.zip
(5k)
Margaret Baker,
Jul 4, 2019, 3:53 AM
v.1
Robin Baker,
Jul 2, 2019, 10:59 AM
v.1
Robin Baker,
Jul 2, 2019, 11:00 AM
v.1