ModeTimer on console (no real leds, no wifi)
This commit is contained in:
commit
00ba6d1af6
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.pio
|
||||
9
platformio.ini
Normal file
9
platformio.ini
Normal file
@ -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
|
||||
71
src/Button.h
Normal file
71
src/Button.h
Normal file
@ -0,0 +1,71 @@
|
||||
#ifndef BUTTON_H
|
||||
#define BUTTON_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
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
|
||||
414
src/Display.cpp
Normal file
414
src/Display.cpp
Normal file
@ -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, __},
|
||||
{__, __, __, __},
|
||||
{__, __, __, __},
|
||||
},
|
||||
};
|
||||
244
src/Display.h
Normal file
244
src/Display.h
Normal file
@ -0,0 +1,244 @@
|
||||
#ifndef DISPLAY_H
|
||||
#define DISPLAY_H
|
||||
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
|
||||
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 symbolHeight, int symbolWidth>
|
||||
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
|
||||
15
src/beep.h
Normal file
15
src/beep.h
Normal file
@ -0,0 +1,15 @@
|
||||
#ifndef BEEP_H
|
||||
#define BEEP_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
inline void beepSet(const bool state) {
|
||||
digitalWrite(2, state ? HIGH : LOW);
|
||||
}
|
||||
|
||||
inline void beepSetup() {
|
||||
pinMode(2,OUTPUT);
|
||||
beepSet(false);
|
||||
}
|
||||
|
||||
#endif
|
||||
47
src/main.cpp
Normal file
47
src/main.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#include <WiFi.h>
|
||||
#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();
|
||||
}
|
||||
}
|
||||
53
src/mode/Mode.h
Normal file
53
src/mode/Mode.h
Normal file
@ -0,0 +1,53 @@
|
||||
#ifndef MODE_H
|
||||
#define MODE_H
|
||||
|
||||
#include <WString.h>
|
||||
#include <Preferences.h>
|
||||
|
||||
#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
|
||||
193
src/mode/ModeTimer.h
Normal file
193
src/mode/ModeTimer.h
Normal file
@ -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
|
||||
66
src/mode/Rest.h
Normal file
66
src/mode/Rest.h
Normal file
@ -0,0 +1,66 @@
|
||||
#ifndef REST_H
|
||||
#define REST_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#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
|
||||
126
src/mode/Timer.h
Normal file
126
src/mode/Timer.h
Normal file
@ -0,0 +1,126 @@
|
||||
#ifndef TIMER_H
|
||||
#define TIMER_H
|
||||
|
||||
class Timer {
|
||||
|
||||
const char *name;
|
||||
|
||||
std::function<void()> 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<void()> 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
|
||||
Loading…
Reference in New Issue
Block a user