log, boot, wifi, clock, http, filesystem, Display, App, AppMatch [INACTIVE Adafruit_NeoPixel]
This commit is contained in:
commit
65a7f170f9
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/.pio/
|
||||
/.idea/
|
||||
18
platformio.ini
Normal file
18
platformio.ini
Normal file
@ -0,0 +1,18 @@
|
||||
[env:Sporttafel]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
board_build.filesystem = littlefs
|
||||
lib_deps = bblanchon/ArduinoJson
|
||||
; https://github.com/adafruit/Adafruit_NeoPixel
|
||||
https://github.com/me-no-dev/ESPAsyncWebServer.git
|
||||
build_flags = -DWIFI_SSID=\"HappyNet\"
|
||||
-DWIFI_PKEY=\"1Grausame!Sackratte7\"
|
||||
-DWIFI_HOST=\"Sporttafel\"
|
||||
;upload_port = 10.0.0.119
|
||||
;upload_protocol = espota
|
||||
upload_port = /dev/ttyUSB0
|
||||
upload_speed = 921600
|
||||
monitor_port = /dev/ttyUSB0
|
||||
monitor_speed = 115200
|
||||
monitor_filters = esp32_exception_decoder
|
||||
13
src/INDEX_HTML.cpp
Normal file
13
src/INDEX_HTML.cpp
Normal file
@ -0,0 +1,13 @@
|
||||
#include "INDEX_HTML.h"
|
||||
|
||||
const char *INDEX_HTML = R"(<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
<title>Sporttafel</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Sporttafel</h1>
|
||||
</body>
|
||||
</html>
|
||||
)";
|
||||
6
src/INDEX_HTML.h
Normal file
6
src/INDEX_HTML.h
Normal file
@ -0,0 +1,6 @@
|
||||
#ifndef INDEX_HTML_H
|
||||
#define INDEX_HTML_H
|
||||
|
||||
extern const char *INDEX_HTML;
|
||||
|
||||
#endif
|
||||
49
src/app/App.cpp
Normal file
49
src/app/App.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include "App.h"
|
||||
|
||||
#include "AppMatch.h"
|
||||
|
||||
App *app = nullptr;
|
||||
|
||||
uint8_t getHeapUsagePercent() {
|
||||
return (ESP.getHeapSize() - ESP.getFreeHeap()) / ESP.getHeapSize();
|
||||
}
|
||||
|
||||
void appStart(const String& name) {
|
||||
appStop();
|
||||
Serial.printf("Loading app: \"%s\" (heap usage: %d%%)\n", name.c_str(), getHeapUsagePercent());
|
||||
|
||||
if (name.equals(APP_MATCH_NAME)) {
|
||||
app = new AppMatch();
|
||||
} else {
|
||||
Serial.printf("No such app: \"%s\"\n", name.c_str());
|
||||
return;
|
||||
}
|
||||
Serial.printf("App instantiated: \"%s\" (heap usage: %d%%)\n", app->getName(), getHeapUsagePercent());
|
||||
|
||||
app->start();
|
||||
Serial.printf("App started: \"%s\" (heap usage: %d%%)\n", app->getName(), getHeapUsagePercent());
|
||||
}
|
||||
|
||||
void appStop() {
|
||||
if (app == nullptr) {
|
||||
return;
|
||||
}
|
||||
const auto name = app->getName();
|
||||
Serial.printf("Stopping app: \"%s\" (heap usage: %d%%)\n", name, getHeapUsagePercent());
|
||||
|
||||
app->stop();
|
||||
Serial.printf("App stopped: \"%s\" (heap usage: %d%%)\n", name, getHeapUsagePercent());
|
||||
|
||||
delete app;
|
||||
app = nullptr;
|
||||
Serial.printf("App unloaded: \"%s\" (heap usage: %d%%)\n", name, getHeapUsagePercent());
|
||||
|
||||
display.clear();
|
||||
display.flush();
|
||||
}
|
||||
|
||||
void appLoop() {
|
||||
if (app != nullptr) {
|
||||
app->loop();
|
||||
}
|
||||
}
|
||||
98
src/app/App.h
Normal file
98
src/app/App.h
Normal file
@ -0,0 +1,98 @@
|
||||
#ifndef APP_H
|
||||
#define APP_H
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
class App {
|
||||
|
||||
const char *name;
|
||||
|
||||
JsonDocument configJson;
|
||||
|
||||
JsonObject config = configJson.to<JsonObject>();
|
||||
|
||||
bool dirty = true;
|
||||
|
||||
public:
|
||||
|
||||
explicit App(const char *name)
|
||||
: name(name) {
|
||||
//
|
||||
}
|
||||
|
||||
virtual ~App() = default;
|
||||
|
||||
const char *getName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
void start() {
|
||||
configLoad();
|
||||
_start();
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
static auto lastMillis = millis();
|
||||
const auto dtMillis = millis() - lastMillis;
|
||||
_loop(dtMillis);
|
||||
if (dirty) {
|
||||
dirty = false;
|
||||
_draw();
|
||||
}
|
||||
}
|
||||
|
||||
void stop() {
|
||||
_stop();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
template<typename T>
|
||||
T configRead(const char *key, T fallback) {
|
||||
if (config[key].is<T>()) {
|
||||
return config[key].as<T>();
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
virtual void _start() {
|
||||
//
|
||||
}
|
||||
|
||||
virtual void _loop(unsigned long dtMillis) {
|
||||
//
|
||||
}
|
||||
|
||||
virtual void _draw() {
|
||||
//
|
||||
}
|
||||
|
||||
virtual void _stop() {
|
||||
//
|
||||
}
|
||||
|
||||
void markDirty() {
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void configLoad() {
|
||||
auto file = LittleFS.open(String("/apps/") + name + ".json", "r");
|
||||
deserializeJson(configJson, file);
|
||||
config = configJson.to<JsonObject>();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
extern App *app;
|
||||
|
||||
void appStart(const String& name);
|
||||
|
||||
void appStop();
|
||||
|
||||
void appLoop();
|
||||
|
||||
#endif
|
||||
165
src/app/AppMatch.h
Normal file
165
src/app/AppMatch.h
Normal file
@ -0,0 +1,165 @@
|
||||
#ifndef APP_MATCH_H
|
||||
#define APP_MATCH_H
|
||||
|
||||
#include <display/Display.h>
|
||||
|
||||
#include "App.h"
|
||||
|
||||
#define APP_MATCH_NAME "match"
|
||||
|
||||
#define CONFIG_SECONDS_KEY "seconds"
|
||||
#define CONFIG_SECONDS_DEFAULT (6 * 60)
|
||||
|
||||
class AppMatch final : public App {
|
||||
|
||||
unsigned long configMillis = 0;
|
||||
|
||||
unsigned long totalMillis = 0;
|
||||
|
||||
unsigned long totalCentis = 0;
|
||||
|
||||
unsigned long totalSeconds = 0;
|
||||
|
||||
unsigned long totalMinutes = 0;
|
||||
|
||||
unsigned long partCentis = 0;
|
||||
|
||||
unsigned long partSeconds = 0;
|
||||
|
||||
bool blinkState = false;
|
||||
|
||||
unsigned long blinkIntervalMillis = 0;
|
||||
|
||||
unsigned long blinkMillis = 0;
|
||||
|
||||
unsigned long updateSeconds = totalSeconds;
|
||||
|
||||
enum State {
|
||||
PAUSE, MINUTES, SECONDS, END
|
||||
};
|
||||
|
||||
State state = PAUSE;
|
||||
|
||||
public:
|
||||
|
||||
explicit AppMatch()
|
||||
: App(APP_MATCH_NAME) {
|
||||
//
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void _start() override {
|
||||
configMillis = configRead<unsigned long>(CONFIG_SECONDS_KEY,CONFIG_SECONDS_DEFAULT) * 1000;
|
||||
totalMillis = configMillis;
|
||||
setState(PAUSE);
|
||||
}
|
||||
|
||||
void _loop(const unsigned long dtMillis) override {
|
||||
if (state != PAUSE) {
|
||||
if (totalMillis <= dtMillis) {
|
||||
totalMillis = 0;
|
||||
} else {
|
||||
totalMillis -= dtMillis;
|
||||
}
|
||||
}
|
||||
|
||||
totalCentis = totalMillis / 10;
|
||||
totalSeconds = totalCentis / 100;
|
||||
totalMinutes = totalSeconds / 60;
|
||||
|
||||
partCentis = totalCentis % 100;
|
||||
partSeconds = totalSeconds % 60;
|
||||
|
||||
if (state != PAUSE) {
|
||||
if (totalMinutes > 0) {
|
||||
setState(MINUTES);
|
||||
} else if (totalMillis > 0) {
|
||||
setState(SECONDS);
|
||||
} else {
|
||||
setState(END);
|
||||
}
|
||||
}
|
||||
|
||||
if (blinkIntervalMillis > 0) {
|
||||
const auto now = millis();
|
||||
if (blinkMillis - now > 500) {
|
||||
blinkMillis = now;
|
||||
blinkState = !blinkState;
|
||||
markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
if (state == MINUTES) {
|
||||
if (updateSeconds != totalSeconds) {
|
||||
updateSeconds = totalSeconds;
|
||||
markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
if (state == SECONDS) {
|
||||
markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
void _draw() override {
|
||||
display.clear();
|
||||
if (blinkIntervalMillis == 0 || blinkState) {
|
||||
if (totalMinutes > 0) {
|
||||
display.setColor(totalMillis < configMillis / 2 ? YELLOW : GREEN);
|
||||
display.printf("%2d:%02d", totalMinutes, partSeconds);
|
||||
Serial.printf("%2d:%02d", totalMinutes, partSeconds);
|
||||
} else if (totalMillis > 0) {
|
||||
display.setColor(RED);
|
||||
display.printf("%2d.%02d", partSeconds, partCentis);
|
||||
} else {
|
||||
display.printf("00:00");
|
||||
}
|
||||
}
|
||||
display.flush();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void blinkEnable(const unsigned long intervalMillis) {
|
||||
blinkState = true;
|
||||
blinkIntervalMillis = intervalMillis;
|
||||
blinkMillis = millis();
|
||||
}
|
||||
|
||||
void setState(const State newState) {
|
||||
if (state == newState) {
|
||||
return;
|
||||
}
|
||||
state = newState;
|
||||
switch (state) {
|
||||
case PAUSE:
|
||||
blinkEnable(500);
|
||||
break;
|
||||
case MINUTES:
|
||||
updateSeconds = totalSeconds;
|
||||
blinkEnable(0);
|
||||
case SECONDS:
|
||||
blinkEnable(0);
|
||||
break;
|
||||
case END:
|
||||
blinkEnable(100);
|
||||
break;
|
||||
}
|
||||
Serial.printf("state changed to %s", getStateName());
|
||||
markDirty();
|
||||
}
|
||||
|
||||
const char *getStateName() const {
|
||||
switch (state) {
|
||||
case PAUSE: return "PAUSE";
|
||||
case MINUTES: return "MINUTES";
|
||||
case SECONDS: return "SECONDS";
|
||||
case END: return "END";
|
||||
default: return "[???]";
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
29
src/boot.cpp
Normal file
29
src/boot.cpp
Normal file
@ -0,0 +1,29 @@
|
||||
#include "boot.h"
|
||||
|
||||
#include "wifi.h"
|
||||
#include "clock.h"
|
||||
#include "log.h"
|
||||
|
||||
void bootDelay() {
|
||||
info("Waiting for WiFi...");
|
||||
while (!isWiFiConnected()) {
|
||||
wifiLoop();
|
||||
yield();
|
||||
}
|
||||
|
||||
info("Waiting for clock to be set...");
|
||||
while (!isClockSet()) {
|
||||
wifiLoop();
|
||||
clockLoop();
|
||||
yield();
|
||||
}
|
||||
|
||||
info("Waiting 5 seconds for OTA update...");
|
||||
const auto start = millis();
|
||||
while (millis() - start < 5000) {
|
||||
wifiLoop();
|
||||
yield();
|
||||
}
|
||||
|
||||
info("Boot delay complete.");
|
||||
}
|
||||
6
src/boot.h
Normal file
6
src/boot.h
Normal file
@ -0,0 +1,6 @@
|
||||
#ifndef BOOT_H
|
||||
#define BOOT_H
|
||||
|
||||
void bootDelay();
|
||||
|
||||
#endif //BOOT_H
|
||||
68
src/clock.cpp
Normal file
68
src/clock.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
#include "clock.h"
|
||||
|
||||
#include <WiFi.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "wifi.h"
|
||||
|
||||
#define CLOCK_GMT_OFFSET_SECONDS 3600
|
||||
#define CLOCK_DST_OFFSET_SECONDS 3600
|
||||
#define CLOCK_NTP_SERVER2_URL "de.pool.ntp.org"
|
||||
#define CLOCK_EPOCH_SECONDS_MIN 1735686000
|
||||
|
||||
time_t clockOffset = 0;
|
||||
|
||||
time_t startupTime = 0;
|
||||
|
||||
auto ntpSet = false;
|
||||
|
||||
void clockLoop() {
|
||||
if (isClockSet()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ntpSet && isWiFiConnected()) {
|
||||
configTime(CLOCK_GMT_OFFSET_SECONDS, CLOCK_DST_OFFSET_SECONDS, WiFi.gatewayIP().toString().c_str(), CLOCK_NTP_SERVER2_URL);
|
||||
ntpSet = true;
|
||||
}
|
||||
|
||||
const auto now = time(nullptr);
|
||||
if (isCorrectTime(now)) {
|
||||
clockOffset = now;
|
||||
} else {
|
||||
startupTime = now - clockOffset;
|
||||
info("clock set after %ld seconds! So startup was at %s", clockOffset, getClockStr());
|
||||
}
|
||||
}
|
||||
|
||||
bool isClockSet() {
|
||||
return startupTime != 0;
|
||||
}
|
||||
|
||||
char buffer[20];
|
||||
|
||||
char *getClockStr() {
|
||||
const auto now = time(nullptr);
|
||||
tm t{0};
|
||||
localtime_r(&now, &t);
|
||||
snprintf(buffer, sizeof buffer, "%04d-%02d-%02d %02d:%02d:%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool isCorrectTime(const time_t now) {
|
||||
return now < CLOCK_EPOCH_SECONDS_MIN;
|
||||
}
|
||||
|
||||
time_t clockCorrect(const time_t t) {
|
||||
if (!isClockSet() || isCorrectTime(t)) {
|
||||
return t;
|
||||
}
|
||||
return t + clockOffset;
|
||||
}
|
||||
|
||||
void clockCorrect(time_t *t) {
|
||||
if (!isClockSet() || isCorrectTime(*t)) {
|
||||
return;
|
||||
}
|
||||
*t += clockOffset;
|
||||
}
|
||||
18
src/clock.h
Normal file
18
src/clock.h
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef CLOCK_H
|
||||
#define CLOCK_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
void clockLoop();
|
||||
|
||||
bool isClockSet();
|
||||
|
||||
char * getClockStr();
|
||||
|
||||
bool isCorrectTime(time_t now);
|
||||
|
||||
time_t clockCorrect(time_t t);
|
||||
|
||||
void clockCorrect(time_t *t);
|
||||
|
||||
#endif
|
||||
34
src/display/Color.cpp
Normal file
34
src/display/Color.cpp
Normal file
@ -0,0 +1,34 @@
|
||||
#include "Color.h"
|
||||
|
||||
#define ____ 0
|
||||
#define QUAR 64
|
||||
#define HALF 128
|
||||
#define FULL 255
|
||||
|
||||
const Color BLACK = {____, ____, ____};
|
||||
|
||||
const Color WHITE = {FULL, FULL, FULL};
|
||||
|
||||
const Color RED = {FULL, ____, ____};
|
||||
|
||||
const Color GREEN = {____, FULL, ____};
|
||||
|
||||
const Color ORANGE = {FULL, QUAR, ____};
|
||||
|
||||
const Color BLUE = {____, ____, FULL};
|
||||
|
||||
const Color YELLOW = {FULL, FULL, ____};
|
||||
|
||||
const Color MAGENTA = {FULL, ____, FULL};
|
||||
|
||||
const Color VIOLET = {HALF, ____, FULL};
|
||||
|
||||
const Color TURQUOISE = {____, FULL, FULL};
|
||||
|
||||
Color gray(uint8_t brightness) {
|
||||
return {brightness, brightness, brightness};
|
||||
}
|
||||
|
||||
Color randomColor() {
|
||||
return {(uint8_t) random(255), (uint8_t) random(255), (uint8_t) random(255)};
|
||||
}
|
||||
40
src/display/Color.h
Normal file
40
src/display/Color.h
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef PIXEL_H
|
||||
#define PIXEL_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
struct Color {
|
||||
|
||||
uint8_t r;
|
||||
|
||||
uint8_t g;
|
||||
|
||||
uint8_t b;
|
||||
|
||||
};
|
||||
|
||||
Color gray(uint8_t brightness);
|
||||
|
||||
Color randomColor();
|
||||
|
||||
extern const Color BLACK;
|
||||
|
||||
extern const Color WHITE;
|
||||
|
||||
extern const Color RED;
|
||||
|
||||
extern const Color GREEN;
|
||||
|
||||
extern const Color ORANGE;
|
||||
|
||||
extern const Color BLUE;
|
||||
|
||||
extern const Color YELLOW;
|
||||
|
||||
extern const Color MAGENTA;
|
||||
|
||||
extern const Color VIOLET;
|
||||
|
||||
extern const Color TURQUOISE;
|
||||
|
||||
#endif
|
||||
3
src/display/Display.cpp
Normal file
3
src/display/Display.cpp
Normal file
@ -0,0 +1,3 @@
|
||||
#include "Display.h"
|
||||
|
||||
Display display;
|
||||
140
src/display/Display.h
Normal file
140
src/display/Display.h
Normal file
@ -0,0 +1,140 @@
|
||||
#ifndef DISPLAY_H
|
||||
#define DISPLAY_H
|
||||
|
||||
// #include <Adafruit_NeoPixel.h>
|
||||
|
||||
#include "Color.h"
|
||||
#include "font.h"
|
||||
|
||||
#define PIXELS_PER_SEGMENT 3
|
||||
#define SEGMENTS_PER_DIGIT 7
|
||||
#define PIXELS_PER_DOT 1
|
||||
#define DOTS_PER_DIGIT 4
|
||||
#define DIGITS 4
|
||||
#define MAX_STR_LEN (2 * DIGITS - 1)
|
||||
#define PIXELS_PER_DIGIT_SEGMENTS (PIXELS_PER_SEGMENT * SEGMENTS_PER_DIGIT)
|
||||
#define PIXELS_PER_DIGIT_DOTS (PIXELS_PER_DOT * DOTS_PER_DIGIT)
|
||||
#define PIXELS_PER_DIGIT_AND_DOTS (PIXELS_PER_DIGIT_SEGMENTS + PIXELS_PER_DIGIT_DOTS)
|
||||
#define PIXEL_COUNT (DIGITS * PIXELS_PER_DIGIT_AND_DOTS - PIXELS_PER_DIGIT_DOTS)
|
||||
#define PIXEL_BYTE_COUNT (PIXEL_COUNT * sizeof(Color))
|
||||
|
||||
class Display {
|
||||
|
||||
// Adafruit_NeoPixel leds;
|
||||
|
||||
Color buffer[PIXEL_COUNT] = {};
|
||||
|
||||
Color color = WHITE;
|
||||
|
||||
public:
|
||||
|
||||
Display() /* : leds(PIXEL_COUNT, GPIO_NUM_13) */ {
|
||||
Serial.printf("%20s = %d\n", "PIXELS_PER_SEGMENT", PIXELS_PER_SEGMENT);
|
||||
Serial.printf("%20s = %d\n", "PIXELS_PER_DOT", PIXELS_PER_DOT);
|
||||
Serial.printf("%20s = %d\n", "SEGMENTS_PER_DIGIT", SEGMENTS_PER_DIGIT);
|
||||
Serial.printf("%20s = %d\n", "DOTS_PER_DIGIT", DOTS_PER_DIGIT);
|
||||
Serial.printf("%20s = %d\n", "DIGITS", DIGITS);
|
||||
Serial.printf("%20s = %d\n", "PIXELS_PER_DIGIT_SEGMENTS", PIXELS_PER_DIGIT_SEGMENTS);
|
||||
Serial.printf("%20s = %d\n", "PIXELS_PER_DIGIT_DOTS", PIXELS_PER_DIGIT_DOTS);
|
||||
Serial.printf("%20s = %d\n", "PIXELS_PER_DIGIT_AND_DOTS", PIXELS_PER_DIGIT_AND_DOTS);
|
||||
Serial.printf("%20s = %d\n", "PIXEL_COUNT", PIXEL_COUNT);
|
||||
Serial.printf("%20s = %d\n", "PIXEL_BYTE_COUNT", PIXEL_BYTE_COUNT);
|
||||
// leds.begin();
|
||||
setBrightness(6);
|
||||
clear();
|
||||
flush();
|
||||
}
|
||||
|
||||
void setColor(const Color color) {
|
||||
this->color = color;
|
||||
}
|
||||
|
||||
void setBrightness(const int brightness) {
|
||||
// leds.setBrightness(brightness);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
memset(buffer, 0, PIXEL_BYTE_COUNT);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
// memcpy(leds.getPixels(), buffer, PIXEL_BYTE_COUNT);
|
||||
// leds.show();
|
||||
}
|
||||
|
||||
int printf(const char *format, ...) {
|
||||
char buffer[MAX_STR_LEN + 1];
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsnprintf(buffer, sizeof buffer, format, args);
|
||||
va_end(args);
|
||||
|
||||
return print(buffer);
|
||||
}
|
||||
|
||||
int print(const char *str) {
|
||||
auto pixel = 0;
|
||||
for (auto character = str; *character != 0 && character - str < MAX_STR_LEN; character++) {
|
||||
pixel = print(pixel, *character);
|
||||
}
|
||||
return pixel;
|
||||
}
|
||||
|
||||
int print(const int pixel, const char character) {
|
||||
switch (character) {
|
||||
case '\'':
|
||||
case '"':
|
||||
case '`':
|
||||
case '.': return printDots(pixel, false, false, false, true);
|
||||
case ',': return printDots(pixel, false, false, true, true);
|
||||
case ':': return printDots(pixel, false, true, true, false);
|
||||
case ';': return printDots(pixel, false, true, true, true);
|
||||
case '|': return printDots(pixel, true, true, true, true);
|
||||
default: return printCharacter(pixel, character);
|
||||
}
|
||||
}
|
||||
|
||||
int printDots(int pixel, const bool dot0, const bool dot1, const bool dot2, const bool dot3) {
|
||||
pixel = pixel / PIXELS_PER_DIGIT_AND_DOTS - PIXELS_PER_DIGIT_DOTS;
|
||||
if (pixel < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (dot0) {
|
||||
buffer[pixel] = color;
|
||||
}
|
||||
pixel++;
|
||||
if (dot1) {
|
||||
buffer[pixel] = color;
|
||||
}
|
||||
pixel++;
|
||||
if (dot2) {
|
||||
buffer[pixel] = color;
|
||||
}
|
||||
pixel++;
|
||||
if (dot3) {
|
||||
buffer[pixel] = color;
|
||||
}
|
||||
return pixel;
|
||||
}
|
||||
|
||||
int printCharacter(int pixel, const char character) {
|
||||
pixel = pixel / PIXELS_PER_DIGIT_AND_DOTS;
|
||||
const auto symbol = getSymbol(character);
|
||||
for (auto s = *symbol; s < *symbol + SYMBOL_SIZE; s++) {
|
||||
if (*s) {
|
||||
buffer[pixel++] = color;
|
||||
buffer[pixel++] = color;
|
||||
buffer[pixel++] = color;
|
||||
} else {
|
||||
pixel += 3;
|
||||
}
|
||||
}
|
||||
return pixel;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
extern Display display;
|
||||
|
||||
#endif
|
||||
68
src/display/font.cpp
Normal file
68
src/display/font.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
#include "font.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
SYMBOL SYMBOLS[][SYMBOL_SIZE] = {
|
||||
{X,X,X,X,X,X,_}, // 0
|
||||
{_,_,X,X,_,_,_}, // 1
|
||||
{_,X,X,_,X,X,X}, // 2
|
||||
{_,X,X,X,X,_,X}, // 3
|
||||
{X,_,X,X,_,_,X}, // 4
|
||||
{X,X,_,X,X,_,X}, // 5
|
||||
{X,X,_,X,X,X,X}, // 6
|
||||
{_,X,X,X,_,_,_}, // 7
|
||||
{X,X,X,X,X,X,X}, // 8
|
||||
{X,X,X,X,X,_,X}, // 9
|
||||
{X,X,X,X,_,X,X}, // A
|
||||
{X,_,_,X,X,X,X}, // B
|
||||
{X,X,_,_,X,X,_}, // C
|
||||
{_,_,X,X,X,X,X}, // D
|
||||
{X,X,_,_,X,X,X}, // E
|
||||
{X,X,_,_,_,X,X}, // F
|
||||
{X,X,_,X,X,X,_}, // G
|
||||
{X,_,X,X,_,X,X}, // H
|
||||
{_,_,_,X,_,_,_}, // I
|
||||
{_,_,X,X,X,_,_}, // J
|
||||
{X,X,_,X,_,X,X}, // K
|
||||
{X,_,_,X,X,_,_}, // L
|
||||
{_,X,_,X,_,X,X}, // M
|
||||
{_,_,_,X,_,X,X}, // N
|
||||
{_,_,_,X,X,X,X}, // O
|
||||
{X,X,X,_,_,X,X}, // P
|
||||
{X,X,X,X,_,_,X}, // Q
|
||||
{_,_,_,_,_,X,X}, // R
|
||||
{X,X,_,X,X,_,X}, // S
|
||||
{X,_,_,_,X,X,X}, // T
|
||||
{_,_,_,X,X,X,_}, // U
|
||||
{X,_,X,_,X,_,_}, // V
|
||||
{X,_,X,_,X,_,X}, // W
|
||||
{_,_,_,X,_,X,_}, // X
|
||||
{X,_,X,X,X,_,X}, // Y
|
||||
{_,X,X,_,X,X,X}, // Z
|
||||
{_,_,_,_,_,_,X}, // -
|
||||
{_,_,_,_,X,_,_}, // _
|
||||
{X,X,X,_,_,_,X}, // °
|
||||
{_,_,_,_,_,_,_}, //
|
||||
};
|
||||
|
||||
SYMBOL *getSymbol(const char character) {
|
||||
if (character >= '0' && character <= '9') {
|
||||
return SYMBOLS[character - '0'];
|
||||
}
|
||||
if (character >= 'a' && character <= 'z') {
|
||||
return SYMBOLS[character - 'a' + 10];
|
||||
}
|
||||
if (character >= 'A' && character <= 'Z') {
|
||||
return SYMBOLS[character - 'A' + 10];
|
||||
}
|
||||
switch (character) {
|
||||
case '-': return SYMBOLS[36];
|
||||
case '_': return SYMBOLS[37];
|
||||
case '^': return SYMBOLS[38];
|
||||
case ' ': return SYMBOLS[39];
|
||||
default: {
|
||||
Serial.printf("[ERROR] NO SYMBOL MAPPING FOR CHARACTER \"%c\" = #%d\n", character, character);
|
||||
return SYMBOLS[SYMBOL_SIZE - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/display/font.h
Normal file
17
src/display/font.h
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef FONT_H
|
||||
#define FONT_H
|
||||
|
||||
#define X true
|
||||
#define _ false
|
||||
|
||||
constexpr auto SYMBOL_COUNT = 39;
|
||||
|
||||
constexpr auto SYMBOL_SIZE = 7;
|
||||
|
||||
typedef const bool SYMBOL[SYMBOL_SIZE];
|
||||
|
||||
extern SYMBOL SYMBOLS[][SYMBOL_SIZE];
|
||||
|
||||
SYMBOL *getSymbol(char character);
|
||||
|
||||
#endif
|
||||
11
src/filesystem.cpp
Normal file
11
src/filesystem.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
#include "filesystem.h"
|
||||
|
||||
#include <LittleFS.h>
|
||||
|
||||
void filesystemMount() {
|
||||
if (LittleFS.begin(true)) {
|
||||
Serial.println("filesystem mounted");
|
||||
} else {
|
||||
Serial.println("failed to mount filesystem");
|
||||
}
|
||||
}
|
||||
6
src/filesystem.h
Normal file
6
src/filesystem.h
Normal file
@ -0,0 +1,6 @@
|
||||
#ifndef FILESYSTEM_H
|
||||
#define FILESYSTEM_H
|
||||
|
||||
void filesystemMount() ;
|
||||
|
||||
#endif
|
||||
53
src/http.cpp
Normal file
53
src/http.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include "http.h"
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#include "INDEX_HTML.h"
|
||||
#include "log.h"
|
||||
|
||||
AsyncWebServer server(80);
|
||||
|
||||
AsyncWebSocket ws("/ws");
|
||||
|
||||
void httpIndex(AsyncWebServerRequest *request) {
|
||||
request->send(200, "text/html", INDEX_HTML);
|
||||
}
|
||||
|
||||
void httpSetup() {
|
||||
ws.onEvent([](AsyncWebSocket *socket, AsyncWebSocketClient *client, AwsEventType type, void *arg, unsigned char *message, unsigned length) {
|
||||
const char *t;
|
||||
switch (type) {
|
||||
case WS_EVT_CONNECT:
|
||||
t = "CONNECT";
|
||||
break;
|
||||
case WS_EVT_DISCONNECT:
|
||||
t = "DISCONNECT";
|
||||
break;
|
||||
case WS_EVT_PONG:
|
||||
t = "PONG";
|
||||
break;
|
||||
case WS_EVT_ERROR:
|
||||
t = "ERROR";
|
||||
break;
|
||||
case WS_EVT_DATA:
|
||||
t = "DATA";
|
||||
break;
|
||||
default:
|
||||
t = "[???]";
|
||||
break;
|
||||
}
|
||||
debug("%s: %s (%d bytes)", client->remoteIP().toString().c_str(), t, length);
|
||||
});
|
||||
server.addHandler(&ws);
|
||||
|
||||
server.on("/", HTTP_GET, httpIndex);
|
||||
server.begin();
|
||||
}
|
||||
|
||||
void httpLoop() {
|
||||
ws.cleanupClients();
|
||||
}
|
||||
|
||||
void httpPublish(char *payload) {
|
||||
ws.textAll(payload);
|
||||
}
|
||||
10
src/http.h
Normal file
10
src/http.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef HTTP_H
|
||||
#define HTTP_H
|
||||
|
||||
void httpSetup();
|
||||
|
||||
void httpLoop();
|
||||
|
||||
void httpPublish(char *payload);
|
||||
|
||||
#endif
|
||||
71
src/log.cpp
Normal file
71
src/log.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
#include "log.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdarg>
|
||||
|
||||
#include "clock.h"
|
||||
|
||||
auto logLevel = DEBUG;
|
||||
|
||||
void log(const LogLevel level, const char *format, const va_list args) {
|
||||
if (level > logLevel) {
|
||||
return;
|
||||
}
|
||||
Serial.print(getClockStr());
|
||||
switch (level) {
|
||||
case ERROR:
|
||||
Serial.print(" [ERROR] ");
|
||||
break;
|
||||
case WARN:
|
||||
Serial.print(" [WARN ] ");
|
||||
break;
|
||||
case INFO:
|
||||
Serial.print(" [INFO ] ");
|
||||
break;
|
||||
case DEBUG:
|
||||
Serial.print(" [DEBUG] ");
|
||||
break;
|
||||
}
|
||||
|
||||
char message[256];
|
||||
vsnprintf(message, sizeof message, format, args);
|
||||
Serial.print(message);
|
||||
Serial.print("\n");
|
||||
|
||||
yield();
|
||||
}
|
||||
|
||||
void log(const LogLevel level, const char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
log(level, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void error(const char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
log(ERROR, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void warn(const char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
log(WARN, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void info(const char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
log(INFO, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void debug(const char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
log(DEBUG, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
21
src/log.h
Normal file
21
src/log.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef LOG_H
|
||||
#define LOG_H
|
||||
|
||||
enum LogLevel {
|
||||
ERROR = 0,
|
||||
WARN = 1,
|
||||
INFO = 2,
|
||||
DEBUG = 3
|
||||
};
|
||||
|
||||
void log(LogLevel level, const char *format, ...);
|
||||
|
||||
void error(const char *format, ...);
|
||||
|
||||
void warn(const char *format, ...);
|
||||
|
||||
void info(const char *format, ...);
|
||||
|
||||
void debug(const char *format, ...);
|
||||
|
||||
#endif
|
||||
23
src/main.cpp
Normal file
23
src/main.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "boot.h"
|
||||
#include "filesystem.h"
|
||||
#include "http.h"
|
||||
#include "wifi.h"
|
||||
#include "app/AppMatch.h"
|
||||
|
||||
void setup() {
|
||||
delay(500);
|
||||
Serial.begin(115200);
|
||||
Serial.print("Startup\n");
|
||||
bootDelay();
|
||||
filesystemMount();
|
||||
httpSetup();
|
||||
appStart(APP_MATCH_NAME);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
wifiLoop();
|
||||
httpLoop();
|
||||
appLoop();
|
||||
}
|
||||
92
src/wifi.cpp
Normal file
92
src/wifi.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
#include "wifi.h"
|
||||
|
||||
#include <ArduinoOTA.h>
|
||||
#include "log.h"
|
||||
|
||||
#define WIFI_TIMEOUT_MILLIS 10000
|
||||
|
||||
auto wifiEnabled = true;
|
||||
|
||||
auto wifiConnected = false;
|
||||
|
||||
auto wifiTryMillis = 0UL;
|
||||
|
||||
auto wifiSsid = WIFI_SSID;
|
||||
|
||||
auto wifiPkey = WIFI_PKEY;
|
||||
|
||||
auto wifiHost = WIFI_HOST;
|
||||
|
||||
void wifiSetupOTA() {
|
||||
ArduinoOTA.onStart([] {
|
||||
info("beginning ota update...");
|
||||
Serial.print("OTA-UPDATE: 0%");
|
||||
});
|
||||
ArduinoOTA.onProgress([](const unsigned done, const unsigned total) {
|
||||
Serial.printf("\rOTA-UPDATE: %3.0f%%", 100.0 * done / total);
|
||||
});
|
||||
ArduinoOTA.onEnd([] {
|
||||
Serial.print("\rOTA-UPDATE: COMPLETE\n");
|
||||
info("OTA update complete");
|
||||
});
|
||||
ArduinoOTA.onError([](const ota_error_t errorCode) {
|
||||
auto name = "[???]";
|
||||
switch (errorCode) {
|
||||
case OTA_AUTH_ERROR:
|
||||
name = "AUTH";
|
||||
break;
|
||||
case OTA_BEGIN_ERROR:
|
||||
name = "BEGIN";
|
||||
break;
|
||||
case OTA_CONNECT_ERROR:
|
||||
name = "CONNECT";
|
||||
break;
|
||||
case OTA_RECEIVE_ERROR:
|
||||
name = "RECEIVE";
|
||||
break;
|
||||
case OTA_END_ERROR:
|
||||
name = "END";
|
||||
break;
|
||||
}
|
||||
Serial.printf("\nOTA-UPDATE: ERROR #%d=%s\n", errorCode, name);
|
||||
error("OTA update failed: #%d=%s", errorCode, name);
|
||||
});
|
||||
ArduinoOTA.begin();
|
||||
}
|
||||
|
||||
void wifiOff() {
|
||||
info("wifi disabled");
|
||||
wifiEnabled = false;
|
||||
}
|
||||
|
||||
void wifiLoop() {
|
||||
const auto currentState = WiFi.localIP() != 0;
|
||||
if (wifiConnected != currentState) {
|
||||
wifiConnected = currentState;
|
||||
if (wifiConnected) {
|
||||
info("wifi connected: %s", WiFi.localIP().toString().c_str());
|
||||
wifiSetupOTA();
|
||||
} else {
|
||||
warn("wifi disconnected");
|
||||
ArduinoOTA.end();
|
||||
WiFi.disconnect();
|
||||
}
|
||||
} else if (!wifiConnected) {
|
||||
if (!wifiEnabled) {
|
||||
return;
|
||||
}
|
||||
if (wifiTryMillis == 0 || millis() - wifiTryMillis >= WIFI_TIMEOUT_MILLIS) {
|
||||
wifiTryMillis = millis();
|
||||
WiFiClass::hostname(wifiHost);
|
||||
WiFi.setAutoReconnect(true);
|
||||
info(R"(connecting to SSID "%s" with hostname "%s")", wifiSsid, wifiHost);
|
||||
WiFi.begin(wifiSsid, wifiPkey);
|
||||
}
|
||||
} else {
|
||||
ArduinoOTA.handle();
|
||||
}
|
||||
}
|
||||
|
||||
bool isWiFiConnected() {
|
||||
return wifiConnected;
|
||||
}
|
||||
10
src/wifi.h
Normal file
10
src/wifi.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef WIFI_H
|
||||
#define WIFI_H
|
||||
|
||||
void wifiOff();
|
||||
|
||||
void wifiLoop();
|
||||
|
||||
bool isWiFiConnected();
|
||||
|
||||
#endif
|
||||
Loading…
Reference in New Issue
Block a user