neu-rah / ArduinoMenu

Arduino generic menu/interactivity system
GNU Lesser General Public License v2.1
933 stars 189 forks source link

Displaying 'dynamic' data in menu items. #333

Open bytelabsco opened 3 years ago

bytelabsco commented 3 years ago

Hello,

I'm new to Arduino development, so forgive me if these are questions with obvious answers.

I've been able to use the excellent PCF8574 example to put together a small program where I'm able to navigate through a menu using a rotary encoder, and then toggle the LCD backlight on and off. Now I would like to add a potentiometer to the circuit, and add menu options to set an upper and lower threshold for the values. I would like to set those threshold values using the potentiometer itself.

So that interaction would be using the rotary encoder to navigate through the menus to the set upper threshold and set lower threshold options. So that submenu may look like:

Upper [1023] Lower [0] Back

You would then select which threshold to set (by clicking the rotary encoder), and a the display would show something like:

Set Upper: ####

where #### is the current value of the potentiometer. Then, instead of using the rotary encoder (or other defined inputs) to set that threshold value, I want to use the potentiometer. I would turn it to the position that is Upper or Lower, while displaying the value output from the potentiometer. Once the user has the potentiometer where they want it, they would then click the rotary encoder to set that value as the new threshold, and then take them back to the threshold menu.

Initially, I thought a Field might solve my problems:

FIELD(lowerThreshold, "Set Lower","", 0, 1023, 10, 1, doNothing, noEvent, noStyle)

But I'm not sure how I can limit the menu system to only listen to the button click from the rotary encoder to "Lock in" the value (exitEvent?) and the potentiometer to set the range between 0 and 1023. Additionally, I don't want to "Step" the values, I just want whatever value the pot is returning.

Then I thought about using a customField, so I checked out that example, but that seems way over my head.

Can you offer any guidance on this, or point me to an example I may have overlooked -- or is custom field the right path, and I just need to commit more time to understanding it?

bytelabsco commented 3 years ago

I've made a little progress, but I'm still not confident that this is the right path. Here's some trimmed down code for where I am currently:

int highThreshold = 1023;
int currentPotReading = 0;

result setThresholdHigh() {
  highThreshold = currentPotReading;
  return proceed;  
}

class highThresholdPrompt : public prompt {
public:
  String promptVal = "";

  highThresholdPrompt(constMEM promptShadow& p) : prompt(p) {}

  Used printTo(navRoot &root, bool sel, menuOut& out, idx_t idx, idx_t len, idx_t) override {

    promptVal = "Set High: ";
    promptVal += highThreshold;

    out.print(promptVal);
  }
};

MENU(thresholdSubMenu, "Threshold Settings", doNothing, anyEvent, noStyle,
  altOP(highThresholdPrompt, "", setThresholdHigh, enterEvent),
  EXIT("Back")
);

// Nav Definition, Setup, Loop and Potentiometer reading, and low threshold code omitted for clarity

How this is currently working is the user will navigate through the menu to the Threshold Settings menu. The custom prompt will then display "Set High: {{Current highThreshold value}}" (eg: Set High: 1023). The user can then adjust the potentiometer to where the max should be, and then select the Set HIgh... menu item with the rotary encoder. That then triggers the enterEvent, and the setThresholdHigh function is called, which sets the highThreshold variable to the current reading of the potentiometer.

There are a couple issues I'm still facing.

1) Once that highThreshold value has changed, the custom prompt is not actually updated until the user interacts with the menu again. I tried to force a refresh by calling nav.printMenu(), but that didn't seem to force a redraw.

2) I would really love for this to behave more like a field. So the user would use the rotary encoder, and selected "Set High: {{current highThreshold value}}" to 'Activate' it. The prompt would then look like "Set High: {{current currentPotReading value}}". The user would then adjust the potentiometer to the desired max, and then click the rotary encoder again to set highThreshold equal to currentPotReading.

Do I just need to use a custom field instead of prompt, and then override the same printTo function? If I go that route, how do I ignore the defined inputs for the values while stepping/tuning, and instead just use the currentPotReading value?

neu-rah commented 3 years ago

the navigation should be automatic

prompts do not have self updates, on the contrary they have minimal draw to avoid flick. So, only when user interacts they will be forced to draw. However fields do, please derive from field

but notice that we already have fields to change values within a range or from an enumerated list of values

dhysuiej commented 1 year ago

I'm trying to force the redraw of a readonly-field, is that posible? @neu-rah could you please Pointe where to find information about how to derive from field,

Currently my menu looks like this:

MENU(mainMenu,"Main menu",doNothing,noEvent,wrapStyle
  //v--this one--v
  ,altFIELD(decPlaces<1>::menuField,input,"Sensor:","\xb0" "C",0,MAX_TEMP,1,0,doNothing,noEvent,noStyle)
  ,altFIELD(decPlaces<1>::menuField,setPoint,"Temp:","\xb0" "C",0,MAX_TEMP,1,0,setTempEvent,exitEvent,wrapStyle)
  ,SUBMENU(setHeat)
  ,FIELD(speed,"Speed:","%",-MAX_SPEED,MAX_SPEED,1,0,setSpeedEvent,exitEvent,wrapStyle)
  ,SUBMENU(setMotor)
  ,EXIT("<Back")
);

The one bellow the comment "this one" needs to be updated each time the input variable changes but that doesn't seem to be the case.

neu-rah commented 1 year ago

hi, even read-only fields should self update when the original variable value changes, so just change the value, they are read-only just for the menu system (unless you made it const, that would also be for c++, ofcoz)

input=x//<-some value here

this project makes a very nice use of it https://github.com/jarkman/ServoBox

dhysuiej commented 1 year ago

@neu-rah thanks for your quick reply. I'm not sure why but only after adding mainMenu[0].dirty = true; I was able to update by hand the field. Also, it seems better that way, since I'm able to check the precision, for instance to update the field only when it changes in one whole unit or include up to an arbitrary number of decimal places.