commit 00ba6d1af6b2edfcdd0cf9fe0c91fcb5ec69b342 Author: Patrick Haßel Date: Thu Aug 7 10:18:39 2025 +0200 ModeTimer on console (no real leds, no wifi) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03f4a3c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.pio diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..4c3e9c8 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,9 @@ +[env:MatrixDisplay2025] +platform = espressif32 +board = esp32dev +framework = arduino +lib_deps = https://github.com/adafruit/Adafruit_NeoPixel +upload_port = /dev/ttyACM0 +upload_speed = 921600 +monitor_port = /dev/ttyACM0 +monitor_speed = 115200 diff --git a/src/Button.h b/src/Button.h new file mode 100644 index 0000000..a1b28b1 --- /dev/null +++ b/src/Button.h @@ -0,0 +1,71 @@ +#ifndef BUTTON_H +#define BUTTON_H + +#include + +enum ButtonEvent { + BUTTON_PRESSED, + BUTTON_PRESSED_LONG, + BUTTON_RELEASED, + BUTTON_RELEASED_SHORT, + BUTTON_RELEASED_LONG, +}; + +class Button { + +public: + + typedef void (*callback_t)(ButtonEvent event); + +private: + + const uint8_t pin; + + const callback_t callback; + + bool lastState = false; + + bool pressedLong = false; + + unsigned long lastMillis = 0; + +public: + + explicit Button(const uint8_t pin, const callback_t callback) : pin(pin), callback(callback) { + // + } + + void setup() { + pinMode(pin, INPUT_PULLUP); + lastMillis = millis(); + lastState = digitalRead(pin) == LOW; + } + + void loop() { + const auto currentMillis = millis(); + const auto currentState = digitalRead(pin) == LOW; + const auto duration = currentMillis - lastMillis; + if (lastState != currentState && duration >= 200) { + if (currentState) { + callback(BUTTON_PRESSED); + } else { + callback(BUTTON_RELEASED); + if (pressedLong) { + callback(BUTTON_RELEASED_LONG); + } else { + callback(BUTTON_RELEASED_SHORT); + } + pressedLong = false; + } + lastState = currentState; + lastMillis = currentMillis; + } + if (lastState && duration >= 3000 && !pressedLong) { + pressedLong = true; + callback(BUTTON_PRESSED_LONG); + } + } + +}; + +#endif diff --git a/src/Display.cpp b/src/Display.cpp new file mode 100644 index 0000000..9eb7866 --- /dev/null +++ b/src/Display.cpp @@ -0,0 +1,414 @@ +#include "Display.h" + +#define __ false // NOLINT(*-reserved-identifier) +#define XX true + +Color WHITE = {255, 255, 255}; + +Color BLACK = {0, 0, 0}; + +Color RED = {255, 0, 0}; + +Color GREEN = {0, 255, 0}; + +Color BLUE = {0, 0, 255}; + +Color YELLOW = {255, 255, 0}; + +Color CYAN = {0, 255, 255}; + +Color MAGENTA = {255, 0, 255}; + +Color ORANGE = {255, 127, 0}; + +Color GREY = {128, 128, 128}; + +const bool DOT7[][7][1] = { + { + {__}, + {__}, + {__}, + {__}, + {__}, + {__}, + {XX}, + }, + { + {__}, + {__}, + {__}, + {__}, + {__}, + {XX}, + {XX}, + }, + { + {__}, + {XX}, + {__}, + {__}, + {__}, + {XX}, + {__}, + }, + { + {__}, + {XX}, + {__}, + {__}, + {__}, + {XX}, + {XX}, + }, +}; + +const bool SPECIAL7[][7][3] = { + { + {__,__,__}, + {__,__,__}, + {__,__,__}, + {__,__,__}, + {__,__,__}, + {__,__,__}, + {__,__,__}, + }, + { + {__,__,__}, + {__,__,__}, + {__,__,__}, + {XX,XX,XX}, + {__,__,__}, + {__,__,__}, + {__,__,__}, + }, +}; + +const bool NUM7[][7][4] = { + { + {__, XX, XX, __}, + {XX, __, __, XX}, + {XX, __, __, XX}, + {XX, __, __, XX}, + {XX, __, __, XX}, + {XX, __, __, XX}, + {__, XX, XX, __}, + }, + { + {__, __, XX, XX}, + {__, XX, __, XX}, + {XX, __, __, XX}, + {__, __, __, XX}, + {__, __, __, XX}, + {__, __, __, XX}, + {__, __, __, XX}, + }, + { + {__, XX, XX, __}, + {XX, __, __, XX}, + {__, __, __, XX}, + {__, __, XX, __}, + {__, XX, __, __}, + {XX, __, __, __}, + {XX, XX, XX, XX}, + }, + { + {__, XX, XX, __}, + {XX, __, __, XX}, + {__, __, __, XX}, + {__, __, XX, __}, + {__, __, __, XX}, + {XX, __, __, XX}, + {__, XX, XX, __}, + }, + { + {XX, __, __, XX}, + {XX, __, __, XX}, + {XX, __, __, XX}, + {XX, XX, XX, XX}, + {__, __, __, XX}, + {__, __, __, XX}, + {__, __, __, XX}, + }, + { + {XX, XX, XX, XX}, + {XX, __, __, __}, + {XX, __, __, __}, + {XX, XX, XX, __}, + {__, __, __, XX}, + {__, __, __, XX}, + {XX, XX, XX, __}, + }, + { + {__, XX, XX, __}, + {XX, __, __, __}, + {XX, __, __, __}, + {XX, XX, XX, __}, + {XX, __, __, XX}, + {XX, __, __, XX}, + {__, XX, XX, __}, + }, + { + {XX, XX, XX, XX}, + {__, __, __, XX}, + {__, __, XX, __}, + {__, XX, __, __}, + {XX, __, __, __}, + {XX, __, __, __}, + {XX, __, __, __}, + }, + { + {__, XX, XX, __}, + {XX, __, __, XX}, + {XX, __, __, XX}, + {__, XX, XX, __}, + {XX, __, __, XX}, + {XX, __, __, XX}, + {__, XX, XX, __}, + }, + { + {__, XX, XX, __}, + {XX, __, __, XX}, + {XX, __, __, XX}, + {__, XX, XX, XX}, + {__, __, __, XX}, + {__, __, __, XX}, + {__, XX, XX, __}, + }, +}; + +const bool ALPHA7[][7][4] = { + { + {__, XX, XX, __}, + {XX, __, __, XX}, + {XX, __, __, XX}, + {XX, XX, XX, XX}, + {XX, __, __, XX}, + {XX, __, __, XX}, + {XX, __, __, XX}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {XX, XX, XX, XX}, + {XX, __, __, __}, + {XX, __, __, __}, + {XX, XX, XX, __}, + {XX, __, __, __}, + {XX, __, __, __}, + {XX, XX, XX, XX}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {XX, XX, XX, __}, + {XX, __, __, XX}, + {XX, __, __, XX}, + {XX, XX, XX, __}, + {XX, __, __, __}, + {XX, __, __, __}, + {XX, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, XX, XX, __}, + {XX, __, __, __}, + {XX, __, __, __}, + {__, XX, XX, __}, + {__, __, __, XX}, + {__, __, __, XX}, + {__, XX, XX, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {XX, __, __, XX}, + {XX, __, __, XX}, + {XX, __, __, XX}, + {XX, __, __, XX}, + {XX, __, __, XX}, + {XX, __, __, XX}, + {__, XX, XX, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, + { + {__, __, __, __}, + {__, __, __, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, XX, XX, __}, + {__, __, __, __}, + {__, __, __, __}, + }, +}; diff --git a/src/Display.h b/src/Display.h new file mode 100644 index 0000000..97c2efb --- /dev/null +++ b/src/Display.h @@ -0,0 +1,244 @@ +#ifndef DISPLAY_H +#define DISPLAY_H + +#include + +enum Align { + ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT +}; + +enum Font { + FONT5, FONT7 +}; + +struct Color { + uint8_t r, g, b; +}; + +extern Color WHITE; + +extern Color BLACK; + +extern Color RED; + +extern Color GREEN; + +extern Color BLUE; + +extern Color YELLOW; + +extern Color CYAN; + +extern Color MAGENTA; + +extern Color ORANGE; + +extern Color GREY; + +extern const bool DOT7[][7][1]; + +extern const bool SPECIAL7[][7][3]; + +extern const bool NUM7[][7][4]; + +extern const bool ALPHA7[][7][4]; + +class Display { + + Adafruit_NeoPixel leds; + + bool pixels[8 * 32]{}; + + static int mapPixel(const int x, const int y) { + return y * 32 + (y % 2 == 0 ? x : 31 - x); + } + +public: + + explicit Display(const uint8_t pin): leds(8 * 32, pin,NEO_GRB + NEO_KHZ800) { + // + } + + void setBrightness(int i) { +#if LEDS_ENABLED + leds.setBrightness(max(0, min(i, 255))); +#endif + } + + void setup() { +#if LEDS_ENABLED + leds.begin(); +#endif + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 32; x++) { + pixels[mapPixel(x, y)] = false; + } + } + setBrightness(32); + clear(); + show(); + } + + void show() { +#if LEDS_ENABLED + leds.show(); +#endif + } + + void setPixel(const int x, const int y, const Color &color) { + const auto index = mapPixel(x, y); + if (index >= 8 * 32) { + Serial.printf("[ERROR] No pixel at (%d, %d) = %d >= %d\n", x, y, index, 8 * 32); + return; + } +#if LEDS_ENABLED + leds.setPixelColor(index, color.r, color.g, color.b); +#endif + pixels[index] = color.r > 0 || color.g > 0 || color.b > 0; + } + + void rect(const int x0, const int y0, const int w, const int h, const Color &color) { + for (int y = y0; y < y0 + h; y++) { + for (int x = x0; x < x0 + w; x++) { + setPixel(x, y, color); + } + } + } + + void print(int x, const int y, const Align align, const Font font, const Color &color, const char *format, ...) { + char text[32]; + va_list args; + va_start(args, format); + vsnprintf(text, sizeof text, format, args); + va_end(args); + const auto width = measure(text, font); + x = align == ALIGN_LEFT ? x : align == ALIGN_RIGHT ? x - width : x - width / 2; + for (const char *c = text; *c; c++) { + x += printChar(x, y, *c, font, color) + 1; + } + } + + int printChar(const int x, const int y, const char c, const Font font, const Color &color) { + if (isDot(c)) { + if (c == '.') { + return printSymbol(x, y, 0, color, DOT7); + } + if (c == ',') { + return printSymbol(x, y, 1, color, DOT7); + } + if (c == ':') { + return printSymbol(x, y, 2, color, DOT7); + } + if (c == ';') { + return printSymbol(x, y, 3, color, DOT7); + } + } + if (isSpecial(c)) { + if (c == ' ') { + return printSymbol(x, y, 0, color, SPECIAL7); + } + if (c == '-') { + return printSymbol(x, y, 1, color, SPECIAL7); + } + } + if (isNumber(c)) { + return printSymbol(x, y, c - '0', color, NUM7); + } + if (isLower(c)) { + return printSymbol(x, y, c - 'a', color, ALPHA7); + } + if (isUpper(c)) { + return printSymbol(x, y, c - 'A', color, ALPHA7); + } + Serial.printf("UNKNOWN SYMBOL '%c'\n", c); + return 0; + } + + template + int printSymbol(const int x, const int y, const int index, const Color &color, const bool symbols[][symbolHeight][symbolWidth]) { + for (int innerY = 0; innerY < symbolHeight; innerY++) { + for (int innerX = 0; innerX < symbolWidth; innerX++) { + if (symbols[index][innerY][innerX]) { + setPixel(x + innerX, y + innerY, color); + } + } + } + return symbolWidth; + } + + void clear() { + rect(0, 0, 32, 8, BLACK); + } + + void log() const { + Serial.print("+"); + for (int x = 0; x < 32; x++) { + Serial.print("--"); + } + Serial.print("+\n"); + + for (int y = 0; y < 8; y++) { + Serial.print("|"); + for (int x = 0; x < 32; x++) { + if (pixels[mapPixel(x, y)]) { + Serial.print("##"); + } else { + Serial.print(" "); + } + } + Serial.print("|\n"); + } + + Serial.print("+"); + for (int x = 0; x < 32; x++) { + Serial.print("--"); + } + Serial.print("+\n"); + + Serial.print('\n'); + } + + static uint8_t measure(const char *text, const Font font) { + uint8_t width = 0; + for (const char *c = text; *c; c++) { + if (c != text) { + width++; // space between chars + } + width += getWidth(*c, font); + } + return width; + } + + static uint8_t getWidth(const char c, const Font font) { + if (isDot(c)) { + return 1; + } + if (isSpecial(c)) { + return 3; + } + return font == FONT5 ? 3 : 4; + } + + static bool isDot(const char c) { + return c == ':' || c == ';' || c == '.' || c == ','; + } + + static bool isSpecial(const char c) { + return c == ' ' || c == '-'; + } + + static bool isNumber(const char c) { + return c >= '0' && c <= '9'; + } + + static bool isLower(const char c) { + return c >= 'a' && c <= 'z'; + } + + static bool isUpper(const char c) { + return c >= 'A' && c <= 'Z'; + } + +}; + +#endif diff --git a/src/beep.h b/src/beep.h new file mode 100644 index 0000000..fb6270f --- /dev/null +++ b/src/beep.h @@ -0,0 +1,15 @@ +#ifndef BEEP_H +#define BEEP_H + +#include + +inline void beepSet(const bool state) { + digitalWrite(2, state ? HIGH : LOW); +} + +inline void beepSetup() { + pinMode(2,OUTPUT); + beepSet(false); +} + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..c08ff2d --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,47 @@ +#include +#include "Button.h" +#include "mode/Mode.h" +#include "mode/ModeTimer.h" + +Display display(33); + +ModeTimer mode; + +void buttonCallback(const ButtonEvent event) { + if (event == BUTTON_PRESSED) { + mode.buttonOK(); + } + if (BUTTON_PRESSED_LONG) { + mode.buttonESC(); + } +} + +Button button(23, buttonCallback); + +void setup() { + WiFi.disconnect(); + delay(500); + Serial.begin(115200); + Serial.println("\n\n\nStartup!"); + beepSetup(); + display.setup(); + mode.init(); +} + +void loop() { + const uint64_t now = millis(); + static uint64_t lastMillis = 0; + const uint64_t deltaMillis = now - lastMillis; + lastMillis = now; + + mode.step(deltaMillis); + mode.draw(display); + + const auto code = Serial.read(); + if (code == ' ') { + mode.buttonOK(); + } + if (code == 'x') { + mode.buttonESC(); + } +} diff --git a/src/mode/Mode.h b/src/mode/Mode.h new file mode 100644 index 0000000..8a3b474 --- /dev/null +++ b/src/mode/Mode.h @@ -0,0 +1,53 @@ +#ifndef MODE_H +#define MODE_H + +#include +#include + +#include "Display.h" + +class Mode { + +public: + + virtual ~Mode() = default; + + String getName() const { + return name; + } + + virtual void init() { + // + } + + virtual void step(uint64_t deltaMillis) { + // + } + + virtual void draw(Display &display) { + // + } + + virtual void buttonOK() { + // + } + + virtual void buttonESC() { + // + } + + virtual void stop() { + // + } + +protected: + + const char *name; + + explicit Mode(const char *name) : name(name) { + // + } + +}; + +#endif diff --git a/src/mode/ModeTimer.h b/src/mode/ModeTimer.h new file mode 100644 index 0000000..2fd7717 --- /dev/null +++ b/src/mode/ModeTimer.h @@ -0,0 +1,193 @@ +#ifndef MODE_TIMER_H +#define MODE_TIMER_H + +#include "beep.h" +#include "Mode.h" +#include "Rest.h" +#include "Timer.h" + +enum Stage { + FIRST_HALF, SECOND_HALF, LAST_MINUTE, FINALE +}; + +class ModeTimer final : public Mode { + + Timer countdown; + + Timer flash; + + Timer pause; + + Timer beep; + + bool dirty = true; + + bool dirtyPrint = true; + + Rest rest{0}; + + Rest last{0}; + +public: + + explicit ModeTimer() + : Mode("Timer"), + countdown("countdown", [this]() { + flash.start(); + dirty = true; + dirtyPrint = true; + }), + flash("flash", [this]() { + dirty = true; + dirtyPrint = true; + }), + pause("pause", [this]() { + dirty = true; + dirtyPrint = true; + }), + beep("beep", [this]() { beepSet(beep.isEven()); }) { + // + } + + void init() override { + Serial.printf("Mode INIT: %s\n", name); + + Preferences preferences; + preferences.begin("Timer", true); + const auto configMillis = preferences.getULong("configMillis", 40 * 1000); + preferences.end(); + + beepSet(false); + + countdown.stop(); + flash.stop(); + pause.stop(); + beep.stop(); + + countdown.countConfig(configMillis, 1); + flash.countConfig(100, 19); + beep.countConfig(200, 1); + pause.foreverConfig(500); + + dirty = true; + dirtyPrint = true; + } + + void step(const uint64_t deltaMillis) override { + flash.step(deltaMillis); + pause.step(deltaMillis); + beep.step(deltaMillis); + if (!pause.isRunning()) { + countdown.step(deltaMillis); + } + } + + void buttonOK() override { + if (flash.isRunning()) { + return; + } + if (!countdown.isRunning()) { + countdown.start(); + } else { + pause.toggle(); + } + dirty = true; + dirtyPrint = true; + } + + void buttonESC() override { + init(); + } + + void draw(Display &display) override { + rest = Rest(countdown.getRestMillis()); + + display.clear(); + if (flash.isRunning()) { + const auto state = flash.isEven(); + if (state) { + display.rect(0, 0, 32, 8, WHITE); + } + beepSet(true); + } else { + if (countdown.isRunning()) { + if (pause.isRunning() && pause.isEven()) { + display.print(15, 0, ALIGN_CENTER, FONT7, BLUE, "PAUSE"); + } else { + drawTime(display); + } + } else { + beepSet(false); + display.print(15, 0, ALIGN_CENTER, FONT7, RED, "-- : --"); + } + } + if (dirty) { + dirty = false; + display.show(); + if (dirtyPrint) { + dirtyPrint = false; + rest.log(); + display.log(); + } + } + last = rest; + } + +private: + + void doBeep(const uint32_t count) { + beepSet(true); + beep.startCount(count * 2 - 1); + } + + void drawTime(Display &display) { + Color color = RED; + Stage stage = FINALE; + if (rest.millisTotal > countdown.getConfigMillis() / 2) { + color = WHITE; + stage = FIRST_HALF; + } else if (rest.millisTotal > 15 * 1000) { + color = YELLOW; + stage = SECOND_HALF; + } else if (rest.millisTotal > 10 * 1000) { + color = ORANGE; + stage = LAST_MINUTE; + } + + static int lastStage = stage; + if (lastStage != stage) { + if (stage == SECOND_HALF) { + doBeep(1); + } else if (stage == LAST_MINUTE) { + doBeep(2); + } + lastStage = stage; + } + + if (rest.secondChanged(last)) { + if (stage == FINALE) { + doBeep(1); + } + } + + if (rest.daysTotal > 0) { + display.print(16, 0, ALIGN_CENTER, FONT7, color, "%3d. %2d", rest.daysPart, rest.hoursPart); + dirty |= rest.hourChanged(last); + dirtyPrint = true; + } else if (rest.hoursTotal > 0) { + display.print(16, 0, ALIGN_CENTER, FONT7, color, "%2d:%02d:%02d", rest.hoursPart, rest.minutesPart, rest.secondsPart); + dirty |= rest.secondChanged(last); + dirtyPrint = true; + } else if (rest.minutesTotal > 0) { + display.print(16, 0, ALIGN_CENTER, FONT7, color, "%2d:%02d", rest.minutesPart, rest.secondsPart); + dirty |= rest.secondChanged(last); + dirtyPrint = true; + } else { + display.print(16, 0, ALIGN_CENTER, FONT7, color, "%2d.%d", rest.secondsPart, rest.decisPart); + dirty |= rest.deciChanged(last); + dirtyPrint |= rest.secondChanged(last); + } + } + +}; +#endif diff --git a/src/mode/Rest.h b/src/mode/Rest.h new file mode 100644 index 0000000..04a13dc --- /dev/null +++ b/src/mode/Rest.h @@ -0,0 +1,66 @@ +#ifndef REST_H +#define REST_H + +#include + +#include "ModeTimer.h" +#include "ModeTimer.h" + +struct Rest { + + uint64_t millisTotal; + + uint64_t decisTotal; + + uint64_t secondsTotal; + + uint64_t minutesTotal; + + uint64_t hoursTotal; + + uint64_t daysTotal; + + uint32_t decisPart; + + uint32_t secondsPart; + + uint32_t minutesPart; + + uint32_t hoursPart; + + uint32_t daysPart; + + explicit Rest(const uint32_t millisTotal) + : millisTotal(millisTotal), + decisTotal(millisTotal / 100), + secondsTotal(decisTotal / 10), + minutesTotal(secondsTotal / 60), + hoursTotal(minutesTotal / 60), + daysTotal(hoursTotal / 24), + decisPart(decisTotal % 10), + secondsPart(secondsTotal % 60), + minutesPart(minutesTotal % 60), + hoursPart(hoursTotal % 24), + daysPart(daysTotal) { + // + } + + bool deciChanged(const Rest &rest) const { + return decisTotal != rest.decisTotal; + } + + bool secondChanged(const Rest &last) const { + return secondsTotal != last.secondsTotal; + } + + bool hourChanged(const Rest &rest) const { + return hoursTotal != rest.hoursTotal; + } + + void log() const { + Serial.printf("%3d.%02d:%02d:%02d.%d\n", daysPart, hoursPart, minutesPart, secondsPart, decisPart); + } + +}; + +#endif diff --git a/src/mode/Timer.h b/src/mode/Timer.h new file mode 100644 index 0000000..2440514 --- /dev/null +++ b/src/mode/Timer.h @@ -0,0 +1,126 @@ +#ifndef TIMER_H +#define TIMER_H + +class Timer { + + const char *name; + + std::function callback; + + bool running = false; + + bool forever = false; + + uint64_t configCount = 0; + + uint64_t configMillis = 0; + + uint64_t restMillis = 0; + + uint64_t restCount = 0; + + uint64_t doneCount = 0; + +public: + + explicit Timer(const char *name, std::function callback): name(name), callback(std::move(callback)) { + // + } + + void foreverConfig(const uint64_t configMillis) { + this->configMillis = configMillis; + this->configCount = 1; + this->forever = true; + // Serial.printf("Timer(%s) CONFIG: %lld ms, FOREVER\n", name, configMillis); + } + + void countConfig(const uint64_t configMillis, const uint64_t configCount) { + this->configMillis = configMillis; + this->configCount = configCount; + this->forever = false; + // Serial.printf("Timer(%s) CONFIG: %lld ms x %lld\n", name, configMillis, configCount); + } + + void startCount(const uint64_t count) { + configCount = count; + start(); + } + + void start() { + if (configCount > 0 && configMillis > 0) { + doneCount = 0; + restCount = configCount; + restMillis = configMillis; + running = true; + // Serial.printf("Timer(%s) START\n", name); + } else { + // Serial.printf("Timer(%s) CANNOT START ZERO INTERVAL", name); + stop(); + } + } + + void stop() { + if (running || restCount > 0 || restMillis > 0) { + restCount = 0; + restMillis = 0; + running = false; + // Serial.printf("Timer(%s) STOP\n", name); + } + } + + void step(uint64_t deltaMillis) { + auto ticks = 0; + while (running && restCount > 0 && deltaMillis > 0) { + if (restMillis > deltaMillis) { + restMillis -= deltaMillis; + break; + } + ticks++; + doneCount++; + if (forever) { + // Serial.printf("Timer(%s) TICK\n", name); + } else { + restCount--; + // Serial.printf("Timer(%s) TICK %lld/%lld\n", name, configCount - restCount, configCount); + } + deltaMillis -= restMillis; + restMillis = configMillis; + if (restCount == 0) { + stop(); + } + if (callback) { + callback(); + } + } + if (ticks > 1) { + // Serial.printf("[WARN] Skipped %d ticks\n", ticks - 1); + } + } + + bool isRunning() const { + return running; + } + + uint64_t getRestMillis() const { + return restMillis; + } + + uint64_t getConfigMillis() const { + return configMillis; + } + + void toggle() { + if (running) { + stop(); + } else { + start(); + } + } + + bool isEven() const { + return doneCount % 2 == 0; + } + +}; + +#endif