// Pill dispenser // Requires libraries EasyButton, BlinkControl, FastLED, ServoEasing // Optionally requires libraries GxEPD2, GEM #include #include const int longpress_duration = 2000; // time in milliseconds // use built-in buttons and LEDs when possible #if defined(ESP32) // dirty hack: assume that any ESP32 board is a Lilygo T5 V2.2 #define LILYGO_T5_V22 #include "boards.h" // source: https://github.com/lewisxhe/GxEPD/blob/master/src/boards.h #define HAS_DISPLAY const byte BUTTON_PIN_MOVE = BUTTON_1; const byte BUTTON_PIN_SELECT = BUTTON_2; const byte BUTTON_PIN_ACTION = BUTTON_3; BlinkControl led(LED_PIN); #define LEDS_PIN 12 #define SERVO_PIN 5 // see ServoEasing PinDefinitionsAndMore.h #else const byte BUTTON_PIN_MOVE = 4; const byte BUTTON_PIN_SELECT = 5; const byte BUTTON_PIN_ACTION = 2; BlinkControl led(LED_BUILTIN); // pin 13 is built-in LED on many Arduino boards #define LEDS_PIN 6 #define SERVO_PIN 9 // see ServoEasing PinDefinitionsAndMore.h #endif #define LEDS_AMOUNT 10 // serial port speed #define BAUDRATE 115200 EasyButton button_move(BUTTON_PIN_MOVE); EasyButton button_select(BUTTON_PIN_SELECT); EasyButton button_action(BUTTON_PIN_ACTION); #include CRGB leds[LEDS_AMOUNT]; #if defined(HAS_DISPLAY) #define ENABLE_GxEPD2_GFX 0 #include #include // source: https://github.com/ZinggJM/GxEPD2/blob/master/examples/GxEPD2_Example/GxEPD2_display_selection.h#L192 GxEPD2_BW displayEPaper(GxEPD2_290(EPD_CS, EPD_DC, EPD_RSET, EPD_BUSY)); // FIXME: really disable GLCD, since it cannot build on ESP32 // as dirty workaround, edit file `libraries/GEM/src/config.h` #define GEM_DISABLE_GLCD 1 #include // Create variables that will be editable through the menu and assign them initial values int number = -512; boolean enablePrint = false; // Create two menu item objects of class GEMItem, linked to number and enablePrint variables GEMItem menuItemInt("Number:", number); GEMItem menuItemBool("Enable print:", enablePrint); // Create menu button that will trigger printData() function. It will print value of our number variable // to Serial monitor if enablePrint is true. We will write (define) this function later. However, we should // forward-declare it in order to pass to GEMItem constructor void printData(); // Forward declaration GEMItem menuItemButton("Print", printData); // Create menu page object of class GEMPage. Menu page holds menu items (GEMItem) and represents menu level. // Menu can have multiple menu pages (linked to each other) with multiple menu items each GEMPage menuPageMain("Main Menu"); GEM_adafruit_gfx menu(displayEPaper, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO); #endif #define MAX_EASING_SERVOS 1 #include "ServoEasing.hpp" ServoEasing servo; #define SERVO_POS_INIT 60 #define SERVO_POS_MAX 100 volatile int pills_requested = 0; void setup() { Serial.begin(BAUDRATE); while (!Serial) { ; } Serial.println(); button_move.begin(); button_select.begin(); button_action.begin(); button_move.onPressed(onButtonMovePressed); button_move.onPressedFor(longpress_duration, onButtonMoveLongpressed); button_select.onPressed(onButtonSelectPressed); button_action.onPressed(onButtonActionPressed); led.begin(); led.breathe(2000); FastLED.addLeds(leds, LEDS_AMOUNT); #if defined(HAS_DISPLAY) displayEPaper.init(115200, true, 2, false); displayEPaper.setTextColor(GxEPD_BLACK); displayEPaper.firstPage(); do { displayEPaper.fillScreen(GxEPD_WHITE); // comment out next line to have no or minimal Adafruit_GFX code displayEPaper.print("Hello World!"); } while (displayEPaper.nextPage()); menu.init(); setupMenu(); menu.drawMenu(); #endif Serial.println(F("Attach servo at pin " STR(SERVO_PIN))); if (servo.attach(SERVO_PIN, SERVO_POS_INIT) == INVALID_SERVO) { led.blink2(); Serial.println(F("Error attaching servo")); } // Wait for servo to reach start position. delay(500); servo.setSpeed(90); // This speed is taken if no further speed argument is given. } void loop() { led.loop(); button_move.read(); button_select.read(); button_action.read(); servoLoop(); } #if (ESP32) IRAM_ATTR // avoid crash e.g. when other code reads SD card #endif void onButtonMovePressed() { Serial.println("button MOVE clicked"); led.blink2(); } #if (ESP32) IRAM_ATTR // avoid crash e.g. when other code reads SD card #endif void onButtonMoveLongpressed() { Serial.println("button MOVE long-clicked"); led.off(); } #if (ESP32) IRAM_ATTR // avoid crash e.g. when other code reads SD card #endif void onButtonSelectPressed() { Serial.println("button SELECT clicked"); led.on(); } #if (ESP32) IRAM_ATTR // avoid crash e.g. when other code reads SD card #endif void onButtonActionPressed() { Serial.println("button ACTION clicked"); led.fastBlinking(); pills_requested++; rolling_green(); } void rolling_green() { for (int dot = 0; dot < LEDS_AMOUNT; dot++) { leds[dot] = CRGB::Green; FastLED.show(); // clear this LED for the next time around the loop leds[dot] = CRGB::White; delay(60); } } #if defined(HAS_DISPLAY) void setupMenu() { // Add menu items to menu page menuPageMain.addMenuItem(menuItemInt); menuPageMain.addMenuItem(menuItemBool); menuPageMain.addMenuItem(menuItemButton); // Add menu page to menu and set it as current menu.setMenuPageCurrent(menuPageMain); } void printData() { // If enablePrint flag is set to true (checkbox on screen is checked)... if (enablePrint) { // ...print the number to Serial Serial.print("Number is: "); Serial.println(number); } else { Serial.println("Printing is disabled, sorry:("); } } #endif // source: https://robojax.com/learn/arduino/?vid=robojax_Servo_PB1_move_return void servoLoop() { if (pills_requested > 1) { servo.easeTo(SERVO_POS_MAX); delay(500); servo.easeTo(SERVO_POS_INIT); pills_requested--; } }