/*
* oled-menu-pages
* based on Corridor 2 Hardware Rev. B
*
* Made by @Schlueri 2017
*
* Menu and display pages
*
*/
// ========================================================
// INCLUDES
// ========================================================
// for display
#include "U8glib.h"
// for timer
#include <TimerOne.h>
// for rotary encoder
#include <Rotary.h>
// ========================================================
// DEBUG
// ========================================================
//#define DEBUG
#define DEBUG_BAUD 9600
// ========================================================
// DEFAULTS
// ========================================================
#define VERSION_TITLE "OLED MENU PAGES"
#define VERSION_HW "Corridor 2 Rev. B"
#define VERSION_SUBTITLE "by @schlueri 2017"
#define VERSION "0.32B"
// Status LED
#define STATUS_LED_PIN 9
bool statusLedOn = false;
// Timer
#define TIMER 1000
// Display and menu stuff
int displayCurrentPage = 0;
bool setNeedsDisplay = false;
// Main menu fixed to 3 items, left, center, right...
#define MENU_SELECTED_TIMEOUT 4000
#define MENU_POS_Y 30
#define MENU_POS_Y_HIDDEN 45
#define MENU_ANIMATION_PIXEL_STEP 4
String menuItems[3] = {"Plyn1", "Plyn2", "SETUP"};
int menuActive = 1; // left active
int menuSelected = menuActive; // selected
bool menuPageMode = false; // true => rotary encoder control page and not menu
// Menu animation
bool menuAnimationRunning = false;
int menuPosY = MENU_POS_Y;
// Rotary encoder with switch
#define ROTARY_SWITCH 10 // A1
#define ROTARY_PIN1 4
#define ROTARY_PIN2 2
#define ROTARY_ACCEL_OFFSET1 20
#define ROTARY_ACCEL_OFFSET2 50
#define ROTARY_ACCEL_OFFSET3 70
unsigned long rotaryLastMove;
bool rotaryButtonPressed = false;
// Action button
//#define BUTTON1_PIN 16 // A2
// Test slider
int sliderPosX =40;
int sliderPosXpopita = 100;
// Logic
long int heartbeat = 0;
#define HEARTBEAT_TRIGGER 1000
#define HEARTBEAT_TRIGGER_TIME 50
// ========================================================
// PAGES STUFF
// ========================================================
#define SETUP_MENU_ITEMS 8
String setupMenuItems[SETUP_MENU_ITEMS] = {"EXIT", "SERWIS", "TRYB PRACY", "LED","VERSION & INFO"};
int setupMenuSelected = 0;
// ========================================================
// INITS
// ========================================================
// Display
U8GLIB_SSD1306_128X32 u8g(U8G_I2C_OPT_NONE); // Fast I2C / TWI
// Rotary Encoder
Rotary rotary = Rotary(ROTARY_PIN1, ROTARY_PIN2);
// ========================================================
// SETUP
// ========================================================
void setup() {
// put your setup code here, to run once:
// Status LED
pinMode(STATUS_LED_PIN, OUTPUT);
digitalWrite(STATUS_LED_PIN, HIGH);
#ifdef DEBUG
Serial.begin(DEBUG_BAUD);
Serial.println("DEBUG MODE");
Serial.print("Version ");
Serial.print(VERSION);
Serial.print("\n\n");
#endif
// Menu Button
pinMode(ROTARY_SWITCH, INPUT);
digitalWrite(ROTARY_SWITCH, INPUT_PULLUP);
#ifdef BUTTON1_PIN
pinMode(BUTTON1_PIN, INPUT);
digitalWrite(BUTTON1_PIN, INPUT_PULLUP);
#endif
Timer1.initialize(TIMER);
Timer1.attachInterrupt(timerEvent);
//delay(1000);
}
// ========================================================
// LOOP
// ========================================================
void loop() {
// put your main code here, to run repeatedly:
digitalWrite(STATUS_LED_PIN, statusLedOn);
if (menuAnimationRunning) {
if (menuPageMode && menuPosY < MENU_POS_Y_HIDDEN) {
// do animation
menuPosY = menuPosY + MENU_ANIMATION_PIXEL_STEP;
setNeedsDisplay = true;
}
if (!menuPageMode && menuPosY > MENU_POS_Y) {
// do animation
menuPosY = menuPosY - MENU_ANIMATION_PIXEL_STEP;
setNeedsDisplay = true;
}
}
if (menuAnimationRunning && (menuPosY == MENU_POS_Y || menuPosY == MENU_POS_Y_HIDDEN)) {
// looks like animation is done
menuAnimationRunning = false;
}
if (setNeedsDisplay) {
noInterrupts();
displayRenderCurrentPage();
setNeedsDisplay = false;
interrupts();
}
}
// ========================================================
// TIMER
// ========================================================
void timerEvent() {
// Heartbeat
if (heartbeat > HEARTBEAT_TRIGGER) {
statusLedOn = true;
}
if (heartbeat > HEARTBEAT_TRIGGER + HEARTBEAT_TRIGGER_TIME) {
statusLedOn = false;
heartbeat = 0;
}
heartbeat++;
// Menu logic
unsigned long timeOffset = millis() - rotaryLastMove;
if (timeOffset > MENU_SELECTED_TIMEOUT) {
// deselect menu
menuSelected = menuActive;
rotaryLastMove = millis();
setNeedsDisplay = true;
}
// Rotary Encoder
unsigned char result = rotary.process();
if (result) {
if (!menuPageMode) {
if (result == DIR_CW) {
// right
if (menuSelected < 3) {
menuSelected++;
}
} else {
// left
if (menuSelected > 1) {
menuSelected--;
}
}
setNeedsDisplay = true;
} else {
// Acceleration
byte acceleration = 1;
unsigned long timeOffset = millis() - rotaryLastMove;
//Serial.println(timeOffset);
if (displayCurrentPage == 0) {
if (timeOffset < ROTARY_ACCEL_OFFSET1) {
acceleration = 16;
} else if (timeOffset < ROTARY_ACCEL_OFFSET2) {
acceleration = 4;
} else if (timeOffset < ROTARY_ACCEL_OFFSET3) {
acceleration = 2;
}
// Development test => control slider
if (result == DIR_CW) {
// right
if (sliderPosX < 128) {
sliderPosX = sliderPosX + acceleration;
}
} else {
// left
if (sliderPosX > 0) {
sliderPosX = sliderPosX - acceleration;
}
}
setNeedsDisplay = true;
}
////////////////////////////////////////////
if ( displayCurrentPage == 1) {
if (timeOffset < ROTARY_ACCEL_OFFSET1) {
acceleration = 16;
} else if (timeOffset < ROTARY_ACCEL_OFFSET2) {
acceleration = 4;
} else if (timeOffset < ROTARY_ACCEL_OFFSET3) {
acceleration = 2;
}
// Development test => control slider
if (result == DIR_CW) {
// right
if (sliderPosXpopita < 128) {
sliderPosXpopita = sliderPosXpopita + acceleration;
}
} else {
// left
if (sliderPosXpopita > 0) {
sliderPosXpopita = sliderPosXpopita - acceleration;
}
}
setNeedsDisplay = true;
}
if (displayCurrentPage == 2) {
if (result == DIR_CW) {
// right
setupMenuSelected++;
} else {
// left
setupMenuSelected--;
}
if (setupMenuSelected > SETUP_MENU_ITEMS - 1) {
setupMenuSelected = SETUP_MENU_ITEMS - 1;
}
if (setupMenuSelected < 1) {
setupMenuSelected = 0;
}
setNeedsDisplay = true;
}
}
rotaryLastMove = millis();
}
// Rotary button
if (buttonEvent()) {
rotaryLastMove = millis();
if (menuActive == menuSelected) {
if (!menuPageMode) {
// give controls to page (button press on selected page)
menuPageMode = true;
menuAnimationRunning = true;
sliderPosX;
sliderPosXpopita;
setNeedsDisplay = true;
} else {
menuPageMode = false;
menuAnimationRunning = true;
setNeedsDisplay = true;
sliderPosX;
sliderPosXpopita;
setupMenuSelected = 0;
}
}
if (!menuPageMode) {
menuActive = menuSelected;
if (menuActive == 1) {
displayCurrentPage = 0;
}
if (menuActive == 2) {
displayCurrentPage = 1;
}
if (menuActive == 3) {
displayCurrentPage = 2;
}
setNeedsDisplay = true;
}
}
}
// ========================================================
// MENU BUTTON
// ========================================================
bool buttonEvent() {
bool result = false;
bool menuButton = false;
if (digitalRead(ROTARY_SWITCH) == 1) {
menuButton = true;
}
if (menuButton && !rotaryButtonPressed) {
rotaryButtonPressed = true;
} else if (!menuButton && rotaryButtonPressed) {
rotaryButtonPressed = false;
result = true;
// FIXME: debounce for try, check if it's really needed
//delay(4);
}
return result;
}
// ========================================================
// DISPLAY - Screen Drawing
// ========================================================
void displayRenderCurrentPage() {
// OLED Display update
u8g.firstPage();
do {
if (displayCurrentPage == 0) {
u8g.setFont(u8g_font_6x13B);
u8g.drawStr(0, 12, "Wodeczka");
}
if (displayCurrentPage == 1) {
u8g.setFont(u8g_font_6x13B);
u8g.drawStr(0, 12, "Popita");
}
if (displayCurrentPage == 2) {
if (!menuPageMode) {
u8g.setFont(u8g_font_6x13B);
u8g.drawStr(0, 10, "Ustawienia");
u8g.setFont(u8g_font_5x7);
u8g.drawStr(76, 10, "Push to go ");
} else {
drawPageMenu();
if (setupMenuSelected == 0) {
u8g.drawStr(0, 28, "PRESS BUTTON TO EXIT");
}
if (setupMenuSelected == 7) {
// Version & Info
u8g.setFont(u8g_font_5x7);
u8g.setPrintPos(0, 28);
u8g.print(VERSION_TITLE);
u8g.print(" ");
u8g.print(VERSION);
}
}
}
if (displayCurrentPage == 0 ) {
u8g.setFont(u8g_font_5x7);
if (menuPageMode) {
u8g.drawStr(108, 10, "PAGE");
u8g.setPrintPos(0, 20);
u8g.print("VALUE ");
u8g.print(sliderPosX);
drawSlider(26);
} else {
u8g.drawStr(108, 10, "MENU");
}
}
///////////////////////////
if (displayCurrentPage == 1) {
u8g.setFont(u8g_font_5x7);
if (menuPageMode) {
u8g.drawStr(108, 10, "PAGE");
u8g.setPrintPos(0, 20);
u8g.print("VALUE ");
u8g.print(sliderPosXpopita);
drawSliderpopita(26);
} else {
u8g.drawStr(108, 10, "MENU");
}
}
drawMenuBar();
} while ( u8g.nextPage() );
}
void drawPageMenu() {
u8g.setFont(u8g_font_6x12);
if (displayCurrentPage == 2) {
String text = setupMenuItems[setupMenuSelected];
// center text
int textWidth = u8g.getStrWidth(text.c_str());
int textX = (128 - textWidth) / 2;
int textXPadding = 4;
u8g.drawRBox(textX - textXPadding, 0, textWidth + textXPadding + textXPadding, 11, 2);
u8g.setColorIndex(0);
u8g.setPrintPos(textX, 11 - 2);
u8g.print(text);
u8g.setColorIndex(1);
bool drawLeftTriangle = false;
bool drawRightTriangle = false;
if (setupMenuSelected < SETUP_MENU_ITEMS - 1) {
drawRightTriangle = true;
}
if (setupMenuSelected > 0) {
drawLeftTriangle = true;
}
if (drawLeftTriangle) {
// Triangle left
u8g.drawTriangle(4, 1, 4, 9, 0, 5);
}
if (drawRightTriangle) {
// Triangle right
u8g.drawTriangle(128 - 5, 1, 128 - 5, 9, 127, 5);
}
u8g.drawHLine(0, 12, 128);
}
}
void drawSlider(int yPos) {
u8g.drawFrame(0, yPos, 128, 6);
if (sliderPosX < 1) {
sliderPosX = 0;
}
if (sliderPosX > 128) {
sliderPosX = 128;
}
u8g.drawVLine(sliderPosX, yPos, 6);
}
void drawSliderpopita(int yPos) {
u8g.drawFrame(0, yPos, 128, 6);
if (sliderPosXpopita < 1) {
sliderPosXpopita = 0;
}
if (sliderPosXpopita > 128) {
sliderPosXpopita = 128;
}
u8g.drawVLine(sliderPosXpopita, yPos, 6);
}
void drawMenuBar() {
int textX = 0;
int textY = menuPosY;
int textWidth = 0;
int textXPadding = 4;
u8g.setFont(u8g_font_6x12);
u8g.setColorIndex(1);
u8g.drawHLine(0, textY - 11 - 2, 128);
if (textY < MENU_POS_Y_HIDDEN) {
// center menu
String text = menuItems[1];
textWidth = u8g.getStrWidth(text.c_str());
textX = (128 - textWidth) / 2;
if (menuActive == 2) {
u8g.drawRBox(textX - textXPadding, textY + 2 - 11, textWidth + textXPadding + textXPadding, 11, 2);
u8g.setColorIndex(0);
}
if (menuActive != menuSelected && menuSelected == 2) {
u8g.drawRFrame(textX - textXPadding, textY + 2 - 11, textWidth + textXPadding + textXPadding, 11, 2);
u8g.setColorIndex(1);
}
u8g.setPrintPos(textX, textY);
u8g.print(text);
u8g.setColorIndex(1);
// left menu
text = menuItems[0];
textX = textXPadding;
textWidth = u8g.getStrWidth(text.c_str());
if (menuActive == 1) {
u8g.drawRBox(textX - textXPadding, textY + 2 - 11, textWidth + textXPadding + textXPadding, 11, 2);
u8g.setColorIndex(0);
}
if (menuActive != menuSelected && menuSelected == 1) {
u8g.drawRFrame(textX - textXPadding, textY + 2 - 11, textWidth + textXPadding + textXPadding, 11, 2);
u8g.setColorIndex(1);
}
u8g.setPrintPos(textX, textY);
u8g.print(text);
u8g.setColorIndex(1);
// right menu
text = menuItems[2];
textWidth = u8g.getStrWidth(text.c_str());
textX = 128 - textWidth - textXPadding;
if (menuActive == 3) {
u8g.drawRBox(textX - textXPadding, textY + 2 - 11, textWidth + textXPadding + textXPadding, 11, 2);
u8g.setColorIndex(0);
}
if (menuActive != menuSelected && menuSelected == 3) {
u8g.drawRFrame(textX - textXPadding, textY + 2 - 11, textWidth + textXPadding + textXPadding, 11, 2);
u8g.setColorIndex(1);
}
u8g.setPrintPos(textX, textY);
u8g.print(text);
u8g.setColorIndex(1);
}
}