neu-rah/ArduinoMenu

EDIT via rotary encoder

cemik1 opened this issue · 1 comments

Based on your examples I prepared MENU controlled by rotary encoder. It works well (FIELDs timeOn and timeOff are fully controlled) except EDIT field. Trying edit buf1 string value by encoder only down change is possible. Encoder counter is displayed and both move directions are properly detected (up and down). It is also possible edit this string by serial input so menu system seems be correct. Could you advice is it my fault or some library issue?
I have also tested it with SSD1306 display and other encoder/button library with the same result.
Platform ESP32, project enclosed

#include <Arduino.h>

/********************
Generic Rotary/Button input
output: serial
input: serial, clickable rotary encoder

purpose:

  Having a generic rotary event-based implementation,
  leaving rotary and button libraries up to the user.
  Example uses QDEC and AceButton, but could be anything
  that suits your particular hardware and/or needs.

  TODO: userland rotary/button event mapping to menu actions,
  as doubleclick/longpress are now hardcoded to back.
  
***/

#include <menu.h>
#include <menuIO/chainStream.h>
#include <menuIO/rotaryEventIn.h>
#include <menuIO/serialIn.h>
#include <menuIO/serialOut.h>

// some example libraries to handle the rotation and clicky part
// of the encoder. These will generate our events.
#include <qdec.h> //https://github.com/SimpleHacks/QDEC
#include <AceButton.h> // https://github.com/bxparks/AceButton

// Encoder
#define ROTARY_PIN_A    (uint16_t)(32) // the first pin connected to the rotary encoder
#define ROTARY_PIN_B    (uint16_t)(33) // the second pin connected to the rotary encoder
const int ROTARY_PIN_BUT  = 35;

int8_t counterEnc = 0;  // encoder counter

using namespace ::ace_button;
using namespace ::SimpleHacks;
QDecoder qdec(ROTARY_PIN_A, ROTARY_PIN_B, true); // rotary part
AceButton button(ROTARY_PIN_BUT); // button part
//--//

#define LEDPIN LED_BUILTIN

// AndroidMenu 
// https://github.com/neu-rah/ArduinoMenu
#define MAX_DEPTH 1

unsigned int timeOn=10;
unsigned int timeOff=90;

using namespace Menu;

//char* constMEM hexDigit MEMMODE="0123456789ABCDEF";
const char hexDigit[] = "0123456789ABCDEF";
//char* constMEM hexNr[] MEMMODE={hexDigit,hexDigit};
const char* hexNr[] ={hexDigit,hexDigit};
char buf1[]="11"; 

MENU(mainMenu, "Blink menu", Menu::doNothing, Menu::noEvent, Menu::wrapStyle
  ,FIELD(timeOn,"On","ms",0,1000,10,1, Menu::doNothing, Menu::noEvent, Menu::noStyle)
  ,FIELD(timeOff,"Off","ms",0,10000,10,1,Menu::doNothing, Menu::noEvent, Menu::noStyle)
  ,FIELD(counterEnc,"Encoder"," step",-128,127,0,0,Menu::doNothing, Menu::noEvent, Menu::noStyle)
  ,EDIT("Hex",buf1,hexNr,Menu::doNothing,Menu::noEvent,Menu::noStyle)  
  ,EXIT("<Back")
);

RotaryEventIn reIn(
  RotaryEventIn::EventType::BUTTON_CLICKED | // select
  RotaryEventIn::EventType::BUTTON_DOUBLE_CLICKED | // back
  RotaryEventIn::EventType::BUTTON_LONG_PRESSED | // also back
  RotaryEventIn::EventType::ROTARY_CCW | // up
  RotaryEventIn::EventType::ROTARY_CW // down
); // register capabilities, see AndroidMenu MenuIO/RotaryEventIn.h file

serialIn serial(Serial);

MENU_INPUTS(in,&reIn, &serial);

MENU_OUTPUTS(out,MAX_DEPTH
  //,U8G2_OUT(u8g2,colors,fontX,fontY,offsetX,offsetY,{0,0,U8_Width/fontX,U8_Height/fontY})
  ,SERIAL_OUT(Serial)
  ,NONE 
);
NAVROOT(nav,mainMenu,MAX_DEPTH,in,out);
//--//

// This is the ISR (interrupt service routine) for rotary events
// We will convert/relay events to the RotaryEventIn object
// Callback config in setup()
void IsrForQDEC(void) { 
  QDECODER_EVENT event = qdec.update();
  if (event & QDECODER_EVENT_CW) { reIn.registerEvent(RotaryEventIn::EventType::ROTARY_CW); counterEnc++;}
  else if (event & QDECODER_EVENT_CCW) { reIn.registerEvent(RotaryEventIn::EventType::ROTARY_CCW); counterEnc--;}

}

// This is the handler/callback for button events
// We will convert/relay events to the RotaryEventIn object
// Callback config in setup()
void handleButtonEvent(AceButton* /* button */, uint8_t eventType, uint8_t buttonState) {
  switch (eventType) {
    case AceButton::kEventClicked:
      reIn.registerEvent(RotaryEventIn::EventType::BUTTON_CLICKED);
      break;
    case AceButton::kEventDoubleClicked:
      reIn.registerEvent(RotaryEventIn::EventType::BUTTON_DOUBLE_CLICKED);
      break;
    case AceButton::kEventLongPressed:
      reIn.registerEvent(RotaryEventIn::EventType::BUTTON_LONG_PRESSED);
      break;
  }
}

bool blink(int timeOn,int timeOff) {
  return millis()%(unsigned long)(timeOn+timeOff)<(unsigned long)timeOn;
}

void setup() {
  pinMode(LEDPIN, OUTPUT);

  Serial.begin(115200);
  while(!Serial);

  // setup rotary encoder
  qdec.begin();
  attachInterrupt(digitalPinToInterrupt(ROTARY_PIN_A), IsrForQDEC, CHANGE);
  attachInterrupt(digitalPinToInterrupt(ROTARY_PIN_B), IsrForQDEC, CHANGE);

  // setup rotary button
  pinMode(ROTARY_PIN_BUT, INPUT);
  ButtonConfig* buttonConfig = button.getButtonConfig();
  buttonConfig->setEventHandler(handleButtonEvent);
  buttonConfig->setFeature(ButtonConfig::kFeatureClick);
  buttonConfig->setFeature(ButtonConfig::kFeatureDoubleClick);
  buttonConfig->setFeature(ButtonConfig::kFeatureLongPress);
  buttonConfig->setFeature(ButtonConfig::kFeatureSuppressClickBeforeDoubleClick);
  buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterClick);
  buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterDoubleClick);
  
  mainMenu[2].disable(); // only display counterEnc value
}


void loop() {
  // put your main code here, to run repeatedly:
  button.check(); // acebutton check, rotary is on ISR
  nav.doInput(); // menu check
  if (nav.changed(0)) {
    nav.doOutput();
  }  
  digitalWrite(LEDPIN, blink(timeOn,timeOff));
}

Menu.zip

I think I found problem source. There is encoder CW move event definition in rotaryEventIn.h file:
ROTARY_CW = 1 << 4,
It means 0x08 is code for this event,
But during text field processing (items.cpp file) this value is decoded as backspace event first:

void textField::parseInput(navNode& nav,menuIn& in) {
  trace(MENU_DEBUG_OUT<<"navTarget::parseInput"<<endl);
  if (/*charEdit&&*/in.available()) {
    char c=in.peek();
    if (options->useNavChars&&(c==options->getCmdChar(upCmd)
      ||c==options->getCmdChar(downCmd)
      ||c==options->getCmdChar(enterCmd)
      ||c==options->getCmdChar(escCmd))) {
        navTarget::parseInput(nav,in);
        return;
      }
    switch(c) {//special cases
      case 0x0D://enter
        in.read();
        charEdit=false;
        dirty=true;
        // edited=false;
        cursor=0;
        nav.root->exit();
        return;
      case 0x08://backspace
        in.read();
        buffer()[cursor]=validator(cursor)[0];
        if (cursor) cursor--;
        dirty=true;
        return;

I am only amateur c++ coder so my solution is to change CW definition in rotaryEventIn.h to:
ROTARY_CW = 1 << 5
Now project works as expected and text field can be edited by rotary encoder.

Please update library in proper and elegant way.