neu-rah / ArduinoMenu

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

EDIT via rotary encoder #414

Open cemik1 opened 1 year ago

cemik1 commented 1 year ago

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