/* DEPRECATION NOTE: Due to limited SRAM size of this chip,
program starts behaving strangely. Migrated to Arduino Mega.
DO NOT CONTINUE */
#include <Adafruit_ILI9341.h>
#include <TaskScheduler.h>
// Keypad
#define PIN_UP 2
#define PIN_RIGHT 3
#define PIN_DOWN 4
#define PIN_LEFT 5
#define PIN_SEL 6
// TFT LCD Display
#define PIN_TFT_CS 10
#define PIN_TFT_DC 9
#define PIN_TFT_RST 8
Adafruit_ILI9341 tft = Adafruit_ILI9341(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST);
// Scheduler & Task setup.
Scheduler runner;
void screenUpdateCallback();
Task screenUpdateTask(50, TASK_FOREVER, &screenUpdateCallback);
void inputDeviceReadCallback();
Task inputDeviceReadTask(100, TASK_FOREVER, &inputDeviceReadCallback);
void uiProcessCallback();
Task uiProcessTask(50, TASK_FOREVER, &uiProcessCallback);
/////////////////
// Actual Code //
/////////////////
// UI
#define UI_INPUT_NULL 0
#define UI_INPUT_UP 1
#define UI_INPUT_DOWN 2
#define UI_INPUT_LEFT 3
#define UI_INPUT_RIGHT 4
#define UI_INPUT_SEL 5
#define UI_INPUT_SIZE 6
int uiInput = UI_INPUT_NULL;
#define UI_LINE_HEIGHT 20
#define UI_LINE_CHAR_SIZE 20
// Pages
#define PAGE_LOADING 0
#define PAGE_MAIN_MENU 1
#define PAGE_DEBUG 2
#define PAGE_CREATE_PALLET 3
#define PAGE_CREATE_BUCKET 4
#define PAGE_FILL_BUCKET 5
#define PAGE_CREATE_ROAST 6
#define PAGE_INPUT_ROAST 7
#define PAGE_START_ROAST 8
#define PAGE_END_ROAST 9
#define PAGE_OUTPUT_ROAST 10
#define PAGE_CREATE_LOT 11
#define PAGE_FILL_LOT 12
#define PAGE_SIZE 13
int cursorOn = 0;
int pageOn = PAGE_LOADING;
bool screenToRefresh = false;
bool screenToClear = false;
long screenToRefreshTimeout = 0;
class MenuItem {
using CallbackFn = void (*)();
public:
String name;
size_t valuesSize = 0;
String *values = nullptr;
size_t selectedIdx = 0;
bool readonly = false;
CallbackFn onSelect = nullptr;
MenuItem() {}
// Update entire values array.
void setValues(String* values_, size_t size) {
if (values) {
delete[] values;
}
values = new String[size];
for (int i = 0; i < size; i++) {
values[i] = values_[i];
}
valuesSize = size;
}
// Update a single value at position. Index must be within the bounds
// of existing values array.
void updateValue(const String& value, size_t index) {
values[index] = value;
}
void select(size_t index) {
selectedIdx = (index < valuesSize) ? index : 0;
}
bool hasValue() const {
return bool(values);
}
String getPadding(size_t strLength) const {
String padding = "";
for (size_t i = strLength; i < UI_LINE_CHAR_SIZE; i++) {
padding += " ";
}
return padding;
}
// Gets the first line. Never indent value.
String titleLine() const {
return name + getPadding(name.length());
}
// Gets the second line, with indent and padding.
String valueLine() const {
if (!hasValue()) {
return "";
}
String line = " " + values[selectedIdx];
return line + getPadding(line.length());
}
~MenuItem() {
delete[] values;
}
};
class Menu {
using CallbackFn = void (*)();
public:
MenuItem *items = nullptr;
size_t itemsSize = 0;
size_t itemsCap = 0;
size_t cursorIdx = 0;
Menu(size_t size) {
items = new MenuItem[size];
itemsCap = size;
}
~Menu() {}
bool addItem(
const String& name,
CallbackFn onSelect = nullptr,
String* values = nullptr,
size_t valuesSize = 0,
size_t selectedIdx = 0,
bool readonly = false) {
if (itemsSize >= itemsCap) {
return false;
}
MenuItem* item = &items[itemsSize++];
item->name = name;
item->onSelect = onSelect;
if (values) {
item->setValues(values, valuesSize);
}
item->readonly = readonly;
}
size_t length() const {
return itemsSize;
}
bool cursorOn(size_t index) const {
return index == cursorIdx;
}
void onUp() {
cursorIdx = (cursorIdx - 1 + itemsSize) % itemsSize;
}
void onDown() {
cursorIdx = (cursorIdx + 1) % itemsSize;
}
void onLeft() {
MenuItem *item = &items[cursorIdx];
if (!item->readonly && item->hasValue()) {
item->selectedIdx = (item->selectedIdx + 1) % item->valuesSize;
}
}
void onRight() {
MenuItem *item = &items[cursorIdx];
if (!item->readonly && item->hasValue()) {
item->selectedIdx = (item->selectedIdx - 1 + item->valuesSize) % item->valuesSize;
}
}
void onSelect() {
MenuItem *item = &items[cursorIdx];
if (item->onSelect) {
item->onSelect();
}
}
};
#define CALLBACK_NOOP nullptr
Menu menuMain(11);
Menu menuCreatePallet(5);
Menu menuCreateBucket(3);
Menu menuFillBucket(4);
Menu menuInputRoast(3);
//Menu menuStartRoast(2);
//Menu menuEndRoast(2);
//Menu menuOutputRoast(3);
//Menu menuCreateLot(4);
//Menu menuFillLot(3);
Menu *pageOnMenu;
/*
Menu menus[] = {
{11},
{5},
};
*/
void setScreenToRefreshIn(long ms) {
if (!screenToRefreshTimeout) {
screenToRefreshTimeout = millis() + ms;
} else {
screenToRefreshTimeout = min(screenToRefreshTimeout, millis() + ms);
}
}
void gotoPage(int page) {
switch (page) {
case PAGE_MAIN_MENU:
pageOn = page;
pageOnMenu = &menuMain;
break;
case PAGE_CREATE_PALLET:
pageOn = page;
pageOnMenu = &menuCreatePallet;
break;
default:
Serial.println("Unimplemented page.");
return;
}
screenToClear = true;
screenToRefresh = true;
}
void loadAssets() {
menuMain.addItem("0- Show Debug Info", []() {
gotoPage(PAGE_DEBUG);
});
menuMain.addItem("1- Add New Pallet", []() {
gotoPage(PAGE_CREATE_PALLET);
} );
menuMain.addItem("2- Add New Bucket");
menuMain.addItem("3- Load Bucket");
menuMain.addItem("4- Add New Roast");
menuMain.addItem("5- Load A Roast");
menuMain.addItem("6- Start Roast");
menuMain.addItem("7- Stop Roast");
menuMain.addItem("8- Weigh Roast");
menuMain.addItem("9- Add New Lot");
menuMain.addItem("10- Load A Lot");
String origins[] = {"NIC", "HON", "COL", "GUAT"};
menuCreatePallet.addItem("Origin", CALLBACK_NOOP, origins, 4);
String vendors[] = {"Royal Coffee"};
menuCreatePallet.addItem("Vendor", CALLBACK_NOOP, vendors, 1);
}
void drawMenuPage(const Menu& menu) {
Serial.println("Drawing menu page");
tft.setCursor(0, 0);
tft.setTextSize(2);
for (int i = 0; i < menu.length(); i++) {
if (menu.cursorOn(i)) {
tft.setTextColor(ILI9341_YELLOW, ILI9341_NAVY);
} else {
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
}
Serial.println(menu.items[i].titleLine());
tft.println(menu.items[i].titleLine());
if (menu.items[i].hasValue()) {
Serial.println(menu.items[i].valueLine());
tft.println(menu.items[i].valueLine());
}
}
}
void screenUpdateCallback() {
if (screenToRefreshTimeout &&
millis() > screenToRefreshTimeout) {
screenToRefreshTimeout = 0;
screenToRefresh = true;
}
if (!screenToClear && !screenToRefresh) {
// Lazy update: no redraw until something happens.
return;
}
if (screenToClear) {
tft.fillScreen(ILI9341_BLACK);
}
drawMenuPage(*pageOnMenu);
screenToClear = false;
screenToRefresh = false;
}
void inputDeviceReadCallback() {
if (!digitalRead(PIN_UP)) {
uiInput = UI_INPUT_UP;
} else if (!digitalRead(PIN_DOWN)) {
uiInput = UI_INPUT_DOWN;
} else if (!digitalRead(PIN_LEFT)) {
uiInput = UI_INPUT_LEFT;
} else if (!digitalRead(PIN_RIGHT)) {
uiInput = UI_INPUT_RIGHT;
} else if (!digitalRead(PIN_SEL)) {
uiInput = UI_INPUT_SEL;
}
}
void uiProcessCallback() {
if (uiInput != UI_INPUT_NULL) {
if (uiInput == UI_INPUT_UP) {
pageOnMenu->onUp();
} else if (uiInput == UI_INPUT_DOWN) {
pageOnMenu->onDown();
} else if (uiInput == UI_INPUT_LEFT) {
pageOnMenu->onLeft();
} else if (uiInput == UI_INPUT_RIGHT) {
pageOnMenu->onRight();
} else if (uiInput == UI_INPUT_SEL) {
pageOnMenu->onSelect();
}
screenToRefresh = true;
uiInput = UI_INPUT_NULL;
}
}
/////////////////////
// END Actual Code //
/////////////////////
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
while (!Serial);
pinMode(PIN_UP, INPUT_PULLUP);
pinMode(PIN_DOWN, INPUT_PULLUP);
pinMode(PIN_LEFT, INPUT_PULLUP);
pinMode(PIN_RIGHT, INPUT_PULLUP);
pinMode(PIN_SEL, INPUT_PULLUP);
// Initialize TFT display
Serial.println("Initialize TFT.");
tft.begin();
tft.setRotation(0);
int width = tft.width();
int height = tft.height();
tft.fillScreen(ILI9341_BLACK);
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
tft.setTextSize(2);
String text;
text = "MENU Playground";
tft.setCursor((width - 12 * text.length()) / 2, height / 2 - 12);
tft.print(text);
// Set up timed tasks.
Serial.println("Initialize Task Scheduler.");
runner.init();
runner.addTask(screenUpdateTask);
runner.addTask(inputDeviceReadTask);
runner.addTask(uiProcessTask);
// delay(1000);
loadAssets();
Serial.println("Ready.");
screenUpdateTask.enable();
inputDeviceReadTask.enable();
uiProcessTask.enable();
gotoPage(PAGE_MAIN_MENU);
}
void loop() {
runner.execute();
}