Compare commits

...

4 Commits

10 changed files with 186 additions and 120 deletions

View File

@ -2,6 +2,7 @@
platform = espressif32 platform = espressif32
board = esp32dev board = esp32dev
framework = arduino framework = arduino
board_build.filesystem = littlefs
lib_deps = https://github.com/adafruit/Adafruit_NeoPixel lib_deps = https://github.com/adafruit/Adafruit_NeoPixel
upload_port = /dev/ttyUSB0 upload_port = /dev/ttyUSB0
upload_speed = 460800 upload_speed = 460800

View File

@ -1,6 +1,6 @@
#include "Color.h" #include "Color.h"
#define ___ 0 #define ___ 0 // NOLINT(*-reserved-identifier)
#define QQQ 64 #define QQQ 64
#define TTT 85 #define TTT 85
#define HHH 127 #define HHH 127

View File

@ -1,12 +1,12 @@
#ifndef WS2812B_COLOR_H #ifndef SPORTTAFEL_COLOR_H
#define WS2812B_COLOR_H #define SPORTTAFEL_COLOR_H
#include <Arduino.h> #include <Arduino.h>
struct Color { struct Color {
uint8_t r, g, b; uint8_t r, g, b;
Color(uint8_t r, uint8_t g, uint8_t b) : r(r), g(g), b(b) { Color(const uint8_t r, const uint8_t g, const uint8_t b) : r(r), g(g), b(b) {
// //
} }

View File

@ -1,38 +1,47 @@
#include "button.h" #include "button.h"
#include <Arduino.h> #include <Arduino.h>
#include "countdown.h"
#define BUTTON_GPIO 25
#define BUTTON_INVERT true
#define BUTTON_PULL_UP true
#define BUTTON_DEBOUNCE 100
#define BUTTON_LONG_PRESS 1000
bool buttonLastState = false; bool buttonLastState = false;
bool buttonLongPressed = false; bool buttonLongPressedSent = false;
unsigned long buttonLastMillis = 0; unsigned long buttonLastMillis = 0;
bool buttonRead() {
return BUTTON_INVERT ^ (digitalRead(BUTTON_GPIO) == HIGH);
}
void buttonSetup() { void buttonSetup() {
pinMode(25, INPUT_PULLUP); pinMode(BUTTON_GPIO, BUTTON_PULL_UP ? INPUT_PULLUP : INPUT);
buttonLastState = digitalRead(25) == LOW; buttonLastState = buttonRead();
buttonLastMillis = millis(); buttonLastMillis = millis();
} }
void buttonLoop() { void buttonLoop() {
const auto currentMillis = millis(); const auto now = millis();
const auto durationMillis = currentMillis - buttonLastMillis; const auto duration = now - buttonLastMillis;
if (durationMillis >= 100) { if (duration < BUTTON_DEBOUNCE) {
const auto currentState = digitalRead(25) == LOW; return;
if (buttonLastState != currentState) {
buttonLastState = currentState;
buttonLastMillis = currentMillis;
buttonLongPressed = false;
if (!buttonLastState) {
if (durationMillis < 1000) {
countdownShortPress();
} }
const auto state = buttonRead();
if (buttonLastState != state) {
if (state) {
buttonLongPressedSent = false;
} else if (duration < BUTTON_LONG_PRESS && !buttonLongPressedSent) {
buttonShortPressed();
} }
} buttonLastState = state;
} buttonLastMillis = now;
if (buttonLastState && currentMillis - buttonLastMillis >= 1000 && !buttonLongPressed) { } else if (buttonLastState && duration >= BUTTON_LONG_PRESS && !buttonLongPressedSent) {
buttonLongPressed = true; buttonLongPressedSent = true;
countdownLongPress(); buttonLongPressed();
} }
} }

View File

@ -1,8 +1,12 @@
#ifndef WS2812B_BUTTON_H #ifndef SPORTTAFEL_BUTTON_H
#define WS2812B_BUTTON_H #define SPORTTAFEL_BUTTON_H
void buttonSetup(); void buttonSetup();
void buttonLoop(); void buttonLoop();
void buttonShortPressed();
void buttonLongPressed();
#endif #endif

View File

@ -1,5 +1,5 @@
#include "countdown.h" #include "countdown.h"
#include <LittleFS.h>
#include "display.h" #include "display.h"
enum State { enum State {
@ -8,9 +8,11 @@ enum State {
State state = CONFIG; State state = CONFIG;
long configMillis = 6 * 60 * 1000; bool countdownConfigDirty = false;
long rest = configMillis; long countdownMillis = 6 * 60 * 1000;
long countdownRest = countdownMillis;
unsigned long last = 0; unsigned long last = 0;
@ -21,42 +23,18 @@ void updateTime() {
const auto now = max(1UL, millis()); const auto now = max(1UL, millis());
if (last != 0) { if (last != 0) {
const auto diff = now - last; const auto diff = now - last;
rest -= (long) diff; countdownRest -= static_cast<long>(diff);
if (rest < 0) { if (countdownRest < 0) {
rest = 0; countdownRest = 0;
} }
} }
last = now; last = now;
} }
void drawDashes() {
displayClear();
drawChar(0, '-', true, RED);
drawChar(1, '-', true, RED);
drawChar(2, '-', true, RED);
drawChar(3, '-', true, RED);
displayShow();
}
void drawWhite() {
displayClear();
drawChar(0, '8', true, WHITE);
drawChar(1, '8', true, WHITE);
drawDots(WHITE, WHITE, WHITE, WHITE);
drawChar(2, '8', true, WHITE);
drawChar(3, '8', true, WHITE);
displayShow();
}
void drawBlack() {
displayClear();
displayShow();
}
void drawSequence() { void drawSequence() {
for (int x = 0; x < 3; ++x) { for (int x = 0; x < 3; ++x) {
for (int i = 0; i < 2; ++i) { for (int i = 0; i < 2; ++i) {
drawWhite(); drawAll(WHITE);
delay(100); delay(100);
drawBlack(); drawBlack();
delay(100); delay(100);
@ -66,20 +44,20 @@ void drawSequence() {
} }
} }
void setState(State newState) { void setState(const State newState) {
if (state == newState) { if (state == newState) {
return; return;
} }
state = newState; state = newState;
const char *name; const char* name;
switch (state) { switch (state) {
case CONFIG: case CONFIG:
rest = configMillis; countdownRest = countdownMillis;
name = "CONFIG"; name = "CONFIG";
break; break;
case READY: case READY:
rest = configMillis; countdownRest = countdownMillis;
name = "READY"; name = "READY";
break; break;
case RUNNING: case RUNNING:
@ -103,15 +81,15 @@ void setState(State newState) {
void countdownUpdate() { void countdownUpdate() {
switch (state) { switch (state) {
case CONFIG: case CONFIG:
drawMillis(rest, MAGENTA); drawMillis(countdownRest, MAGENTA);
break; break;
case READY: case READY:
drawMillis(rest, GREEN); drawMillis(countdownRest, GREEN);
break; break;
case RUNNING: { case RUNNING: {
if (rest > 0) { if (countdownRest > 0) {
const auto color = rest >= 60000 ? WHITE : (rest >= 10000 ? YELLOW : RED); const auto color = countdownRest >= 60000 ? WHITE : (countdownRest >= 10000 ? YELLOW : RED);
drawMillis(rest, color); drawMillis(countdownRest, color);
} else { } else {
setState(END); setState(END);
setState(READY); setState(READY);
@ -119,7 +97,7 @@ void countdownUpdate() {
break; break;
} }
case PAUSED: case PAUSED:
drawMillis(rest, BLUE); drawMillis(countdownRest, BLUE);
break; break;
case END: case END:
drawDashes(); drawDashes();
@ -127,7 +105,50 @@ void countdownUpdate() {
} }
} }
void countdownConfigLoad() {
if (LittleFS.begin(true)) {
Serial.println("Filesystem mounted.");
File file = LittleFS.open("/countdownMillis");
if (file) {
const String content = file.readString();
if (content) {
countdownMillis = content.toInt();
Serial.printf("Read countdownMillis from file: countdownMillis=%lu, file=%s", countdownMillis, file.path());
} else {
Serial.printf("WARN: Cannot parse content of: %s\n", file.path());
}
file.close();
} else {
Serial.printf("WARN: Cannot open file for READ: %s\n", file.path());
}
LittleFS.end();
Serial.println("Filesystem unmounted.");
} else {
Serial.println("ERROR: Failed to mount filesystem.");
}
}
void countdownConfigWrite() {
if (LittleFS.begin(true)) {
Serial.println("Filesystem mounted.");
File file = LittleFS.open("/countdownMillis", "w", true);
if (file) {
file.printf("%lu", countdownMillis);
Serial.printf("Wrote countdownMillis to file: countdownMillis=%lu, file=%s", countdownMillis, file.path());
file.close();
countdownConfigDirty = false;
} else {
Serial.printf("WARN: Cannot open file for WRITE: %s\n", file.path());
}
LittleFS.end();
Serial.println("Filesystem unmounted.");
} else {
Serial.println("ERROR: failed to mount filesystem");
}
}
void countdownSetup() { void countdownSetup() {
countdownConfigLoad();
setState(READY); setState(READY);
} }
@ -136,11 +157,15 @@ void countdownLoop() {
countdownUpdate(); countdownUpdate();
} }
void countdownLongPress() { void buttonShortPressed() {
switch (state) { switch (state) {
case CONFIG: case CONFIG: {
setState(READY); const auto seconds = (countdownMillis / 30000) * 30 % (15 * 60) + 30;
countdownMillis = seconds * 1000;
countdownRest = countdownMillis;
countdownConfigDirty = true;
break; break;
}
case READY: case READY:
case END: case END:
setState(CONFIG); setState(CONFIG);
@ -152,14 +177,14 @@ void countdownLongPress() {
} }
} }
void countdownShortPress() { void buttonLongPressed() {
switch (state) { switch (state) {
case CONFIG: { case CONFIG:
auto seconds = (configMillis / 30000) * 30 % (15 * 60) + 30; if (countdownConfigDirty) {
configMillis = seconds * 1000; countdownConfigWrite();
rest = configMillis;
break;
} }
setState(READY);
break;
case RUNNING: case RUNNING:
setState(PAUSED); setState(PAUSED);
break; break;

View File

@ -1,12 +1,8 @@
#ifndef WS2812B_COUNTDOWN_H #ifndef SPORTTAFEL_COUNTDOWN_H
#define WS2812B_COUNTDOWN_H #define SPORTTAFEL_COUNTDOWN_H
void countdownSetup(); void countdownSetup();
void countdownLoop(); void countdownLoop();
void countdownLongPress();
void countdownShortPress();
#endif #endif

View File

@ -1,5 +1,7 @@
#include "display.h" #include "display.h"
#include <Adafruit_NeoPixel.h>
#define DIGIT_COUNT 4 #define DIGIT_COUNT 4
#define SEGMENTS_PER_DIGIT 7 #define SEGMENTS_PER_DIGIT 7
#define LEDS_PER_SEGMENT 6 #define LEDS_PER_SEGMENT 6
@ -30,7 +32,7 @@ uint8_t CHARS[] = {
0b00000010, // - 0b00000010, // -
}; };
int toIndex(char c) { int toIndex(const char c) {
if (c >= '0' && c <= '9') { if (c >= '0' && c <= '9') {
return c - '0'; return c - '0';
} }
@ -45,11 +47,12 @@ int toIndex(char c) {
return 5; return 5;
case '-': case '-':
return 13; return 13;
} default:
return -1; return -1;
}
} }
uint8_t mapChar(char c) { uint8_t mapChar(const char c) {
const int index = toIndex(c); const int index = toIndex(c);
if (index >= 0) { if (index >= 0) {
return CHARS[index]; return CHARS[index];
@ -72,11 +75,11 @@ void displayShow() {
pixels.show(); pixels.show();
} }
void drawChar(int digit, char c, const Color& color) { void drawChar(const int digit, const char c, const Color& color) {
drawChar(digit, c, true, color); drawChar(digit, c, true, color);
} }
void drawChar(int digit, char c, bool showIfZero, const Color& color) { void drawChar(const int digit, const char c, const bool showIfZero, const Color& color) {
if (c == '0' && !showIfZero) { if (c == '0' && !showIfZero) {
return; return;
} }
@ -97,14 +100,14 @@ void drawChar(int digit, char c, bool showIfZero, const Color& color) {
} }
} }
void drawNumber(int digit, int number, bool zero, const Color& color) { void drawNumber(const int digit, const int number, const bool zero, const Color& color) {
const char ten = '0' + (number / 10 % 10); const char ten = '0' + (number / 10 % 10);
const char one = '0' + (number % 10); const char one = '0' + (number % 10);
drawChar(digit + 0, ten, zero, color); drawChar(digit + 0, ten, zero, color);
drawChar(digit + 1, one, true, color); drawChar(digit + 1, one, true, color);
} }
void drawDot(int dot, const Color& c) { void drawDot(const int dot, const Color& c) {
int index = 2 * SEGMENTS_PER_DIGIT * LEDS_PER_SEGMENT + dot * LEDS_PER_DOT; int index = 2 * SEGMENTS_PER_DIGIT * LEDS_PER_SEGMENT + dot * LEDS_PER_DOT;
pixels.setPixelColor(index + 0, c.r, c.g, c.b); pixels.setPixelColor(index + 0, c.r, c.g, c.b);
pixels.setPixelColor(index + 1, c.r, c.g, c.b); pixels.setPixelColor(index + 1, c.r, c.g, c.b);
@ -117,11 +120,11 @@ void drawDots(const Color& bottom, const Color& middleBottom, const Color& middl
drawDot(3, top); drawDot(3, top);
} }
void drawMillisDeci(unsigned long millisTotal, const Color& color) { void drawMillisDeci(const unsigned long millisTotal, const Color& color) {
const auto secondsTotal = millisTotal / 1000; const auto secondsTotal = millisTotal / 1000;
const auto minutes = (int) secondsTotal / 60; const auto minutes = static_cast<int>(secondsTotal) / 60;
const auto seconds = (int) secondsTotal % 60; const auto seconds = static_cast<int>(secondsTotal) % 60;
const auto deci = (int) millisTotal / 100 % 10; const auto deci = static_cast<int>(millisTotal) / 100 % 10;
pixels.clear(); pixels.clear();
if (minutes > 0) { if (minutes > 0) {
@ -138,10 +141,10 @@ void drawMillisDeci(unsigned long millisTotal, const Color& color) {
pixels.show(); pixels.show();
} }
void drawMillis(unsigned long millisTotal, const Color& color) { void drawMillis(const unsigned long millisTotal, const Color& color) {
const auto secondsTotal = (unsigned long) ceil(millisTotal / 1000.0); const auto secondsTotal = static_cast<unsigned long>(ceil(millisTotal / 1000.0));
const auto minutes = (int) secondsTotal / 60; const auto minutes = static_cast<int>(secondsTotal) / 60;
const auto seconds = (int) secondsTotal % 60; const auto seconds = static_cast<int>(secondsTotal) % 60;
pixels.clear(); pixels.clear();
if (minutes > 0) { if (minutes > 0) {
@ -151,3 +154,27 @@ void drawMillis(unsigned long millisTotal, const Color& color) {
drawNumber(2, seconds, minutes > 0, color); drawNumber(2, seconds, minutes > 0, color);
pixels.show(); pixels.show();
} }
void drawDashes() {
displayClear();
drawChar(0, '-', true, RED);
drawChar(1, '-', true, RED);
drawChar(2, '-', true, RED);
drawChar(3, '-', true, RED);
displayShow();
}
void drawAll(const Color& color) {
displayClear();
drawChar(0, '8', true, color);
drawChar(1, '8', true, color);
drawDots(color, color, color, color);
drawChar(2, '8', true, color);
drawChar(3, '8', true, color);
displayShow();
}
void drawBlack() {
displayClear();
displayShow();
}

View File

@ -1,7 +1,5 @@
#ifndef WS2812B_DISPLAY_H #ifndef SPORTTAFEL_DISPLAY_H
#define WS2812B_DISPLAY_H #define SPORTTAFEL_DISPLAY_H
#include <Adafruit_NeoPixel.h>
#include "Color.h" #include "Color.h"
@ -23,4 +21,10 @@ void drawMillisDeci(unsigned long millisTotal, const Color& color);
void drawMillis(unsigned long millisTotal, const Color& color); void drawMillis(unsigned long millisTotal, const Color& color);
void drawDashes();
void drawAll(const Color&);
void drawBlack();
#endif #endif

View File

@ -1,6 +1,6 @@
#include "display.h"
#include "button.h" #include "button.h"
#include "countdown.h" #include "countdown.h"
#include "display.h"
void setup() { void setup() {
delay(500); delay(500);