Compare commits

...

15 Commits

27 changed files with 668 additions and 496 deletions

View File

@ -7,7 +7,7 @@
Config config("/test.json"); Config config("/test.json");
DisplayMatrix<32, 8> display(27); DisplayMatrix<32, 8> display(13);
class NodeTest final : public PatrixNode { class NodeTest final : public PatrixNode {
@ -19,18 +19,10 @@ public:
void setup() override { void setup() override {
config.read(); config.read();
display.setup(); display.setup(10);
display.setBrightness(6);
display.clear(); display.clear();
display.printf(1, 1, LEFT, Blue, "Test");
display.foreground = Blue; display.drawLine(0, 7, 32, 0, 1, Red);
display.printf("Test");
display.foreground = Red;
display.cursorX = 0;
display.cursorY = 7;
display.drawLine(32, 0);
} }
void loop() override { void loop() override {

View File

@ -4,7 +4,6 @@ void setup() {
logSetup(); logSetup();
bootDelay(); bootDelay();
patrixNode.setup(); patrixNode.setup();
httpSetup();
} }
void loop() { void loop() {

View File

@ -15,14 +15,14 @@ class Config final {
const String path; const String path;
JsonDocument config;
unsigned long dirty = 0; unsigned long dirty = 0;
bool doForceNextHexBuffer = true; bool doForceNextHexBuffer = true;
public: public:
JsonDocument json;
explicit Config(String path) : path(std::move(path)) { explicit Config(String path) : path(std::move(path)) {
// //
} }
@ -33,12 +33,12 @@ public:
fsMount(); fsMount();
auto file = LittleFS.open(path, "r"); auto file = LittleFS.open(path, "r");
if (!file) { if (!file) {
error("failed to open file for config: %s", path.c_str()); error("Failed to open file for config: %s", path.c_str());
return; return;
} }
if (!deserialize(file)) { if (!deserialize(file)) {
config.clear(); json.clear();
config = config.to<JsonObject>(); json = json.to<JsonObject>();
} }
file.close(); file.close();
} }
@ -47,16 +47,16 @@ public:
fsMount(); fsMount();
auto write = LittleFS.open(path, "w"); auto write = LittleFS.open(path, "w");
if (!write) { if (!write) {
error("failed to open file for config write: %s", path.c_str()); error("Failed to open file for config write: %s", path.c_str());
return; return;
} }
const auto size = measureJson(config); const auto size = measureJson(json);
if (serializeJson(config, write) == size) { if (serializeJson(json, write) == size) {
char buffer[256]; char buffer[256];
serializeJson(config, buffer, sizeof buffer); serializeJson(json, buffer, sizeof buffer);
info("config written: %s => %s", path.c_str(), buffer); info("Config written: %s => %s", path.c_str(), buffer);
} else { } else {
error("failed to write config: %s", path.c_str()); error("Failed to write config: %s", path.c_str());
} }
write.close(); write.close();
} }
@ -69,35 +69,82 @@ public:
} }
template<typename T> template<typename T>
T get(const char *key, T fallback) { bool has(const char *key) {
if (config[key].is<T>()) { return json[key].is<T>();
return config[key].as<T>();
}
warn("config key \"%s\" not found!", key);
return fallback;
} }
template<typename T> template<typename T>
void set(const char *key, T value) { T get(const char *key, T fallback) {
config[key] = value; if (json[key].is<T>()) {
dirty = max(1UL, millis()); // avoid special value zero (=> not dirty) return json[key].as<T>();
}
warn("Config key \"%s\" not found!", key);
return fallback;
}
bool setIfNot(const char *key, const int value) {
auto changed = !json[key].is<int>();
if (!changed) {
changed = json[key].as<int>() != value;
}
if (changed) {
set(key, value);
info("Changed key \"%s\" to: %d", key, value);
}
return changed;
}
bool setIfNot(const char *key, const double value) {
auto changed = !json[key].is<double>();
if (!changed) {
changed = json[key].as<double>() != value;
}
if (changed) {
set(key, value);
info("Changed key \"%s\" to: %d", key, value);
}
return changed;
}
template<typename T>
void set(const String& key, T value) {
json[key] = value;
dirty = millis();
if (dirty == 0) {
// avoid special value zero (=> not dirty)
dirty--;
}
}
unsigned long getAutoWriteInMillis() const {
if (dirty == 0) {
return 0;
}
return CONFIG_WRITE_DELAY_MILLIS - (millis() - dirty);
}
time_t getAutoWriteAtEpoch() const {
if (dirty == 0) {
return 0;
}
return time(nullptr) + static_cast<long>(getAutoWriteInMillis() / 1000);
} }
private: private:
bool deserialize(File file) { bool deserialize(File file) {
if (deserializeJson(config, file) != DeserializationError::Ok) { if (deserializeJson(json, file) != DeserializationError::Ok) {
error("failed to deserialize config: %s", file.path()); error("Failed to deserialize config: %s", file.path());
return false; return false;
} }
if (!config.is<JsonObject>()) { if (!json.is<JsonObject>()) {
error("config not a json-object: %s", file.path()); error("Config not a json-object: %s", file.path());
return false; return false;
} }
char buffer[256]; char buffer[256];
serializeJson(config, buffer, sizeof buffer); serializeJson(json, buffer, sizeof buffer);
info("config loaded: %s => %s", path.c_str(), buffer); info("Config loaded: %s => %s", path.c_str(), buffer);
config = config.as<JsonObject>(); json = json.as<JsonObject>();
return true; return true;
} }

View File

@ -32,16 +32,28 @@ const char *getResetReason(const RESET_REASON reason) {
} }
} }
bool isFailureReset(const RESET_REASON reason) {
switch (reason) {
case POWERON_RESET:
case SW_RESET:
case DEEPSLEEP_RESET:
case SDIO_RESET:
case SW_CPU_RESET:
case EXT_CPU_RESET: return false;
default: return true;
}
}
void bootReasonLoad() { void bootReasonLoad() {
const auto r0 = rtc_get_reset_reason(0); const auto r0 = rtc_get_reset_reason(0);
const auto r1 = rtc_get_reset_reason(1); const auto r1 = rtc_get_reset_reason(1);
failureReset = (r0 != POWERON_RESET && r0 != DEEPSLEEP_RESET) || (r1 != POWERON_RESET && r1 != DEEPSLEEP_RESET); failureReset = isFailureReset(r0) || isFailureReset(r1);
if (failureReset) { if (failureReset) {
warn("Forcing OTA delay because of failure-reset: r0=%s, r1=%s", getResetReason(r0), getResetReason(r1)); warn("Forcing OTA delay because of failure-reset: r0=%s, r1=%s", getResetReason(r0), getResetReason(r1));
} }
} }
void boot_waitForWifi() { void boot_waitForWiFi() {
info("Waiting for WiFi..."); info("Waiting for WiFi...");
while (!isWiFiConnected()) { while (!isWiFiConnected()) {
wifiLoop(); wifiLoop();
@ -77,7 +89,7 @@ void bootDelay() {
info("Boot delay active..."); info("Boot delay active...");
boot_waitForWifi(); boot_waitForWiFi();
if (failureReset || patrixNode.waitForOTA) { if (failureReset || patrixNode.waitForOTA) {
boot_waitForOTA(); boot_waitForOTA();

View File

@ -27,17 +27,16 @@ void clockLoop() {
const auto now = time(nullptr); const auto now = time(nullptr);
if (isCorrectTime(now)) { if (isCorrectTime(now)) {
startupTime = now - clockOffset; startupTime = now - clockOffset;
info("clock set after %ld seconds! So startup was at %s", clockOffset, getStartupStr()); info("Clock set after %ld seconds! So startup was at %s", clockOffset, getStartupStr());
} else { } else {
clockOffset = now; clockOffset = now;
} }
} }
bool isClockSet() { bool isClockSet() {
return startupTime != 0; return isCorrectTime(time(nullptr));
} }
char *getClockStr(const time_t epoch) { char *getClockStr(const time_t epoch) {
auto now = epoch; auto now = epoch;
if (now == 0) { if (now == 0) {

View File

@ -7,13 +7,14 @@
auto fsMounted = false; auto fsMounted = false;
bool fsMount() { bool fsMount() {
if (!fsMounted) {
fsMounted = LittleFS.begin(true);
}
if (fsMounted) { if (fsMounted) {
info("filesystem mounted: %3d%% used (%d bytes)", static_cast<int>(round(100.0 * LittleFS.usedBytes() / LittleFS.totalBytes())), LittleFS.usedBytes()); return true;
}
fsMounted = LittleFS.begin(true);
if (fsMounted) {
info("Filesystem mounted: %3d%% used (%d bytes)", static_cast<int>(round(100.0 * LittleFS.usedBytes() / LittleFS.totalBytes())), LittleFS.usedBytes());
} else { } else {
error("failed to mount filesystem"); error("Failed to mount filesystem");
} }
return fsMounted; return fsMounted;
} }
@ -25,11 +26,11 @@ bool fsIsMounted() {
void fsList(FS& fs, const char *path, const String& indent) { // NOLINT(*-no-recursion) void fsList(FS& fs, const char *path, const String& indent) { // NOLINT(*-no-recursion)
auto dir = fs.open(path); auto dir = fs.open(path);
if (!dir) { if (!dir) {
error("not found: %s", path); error("Not found: %s", path);
return; return;
} }
if (!dir.isDirectory()) { if (!dir.isDirectory()) {
error("not a directory: %s", path); error("Not a directory: %s", path);
return; return;
} }

View File

@ -1,12 +1,18 @@
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include <patrix/core/log.h> #include <patrix/core/log.h>
#include <patrix/core/system.h> #include <patrix/core/system.h>
#include <patrix/node/PatrixNode.h> #include <patrix/node/PatrixNode.h>
#include "filesystem.h"
#include "wifi.h"
AsyncWebServer server(80); AsyncWebServer server(80);
AsyncWebSocket ws("/ws"); AsyncWebSocket ws("/ws");
auto httpSetUp = false;
void httpReboot(AsyncWebServerRequest *request) { void httpReboot(AsyncWebServerRequest *request) {
info("Rebooting..."); info("Rebooting...");
request->redirect("/"); request->redirect("/");
@ -18,6 +24,7 @@ void httpReboot(AsyncWebServerRequest *request) {
} }
void httpSetup() { void httpSetup() {
fsMount();
ws.onEvent([](AsyncWebSocket *socket, AsyncWebSocketClient *client, AwsEventType type, void *arg, unsigned char *message, unsigned length) { ws.onEvent([](AsyncWebSocket *socket, AsyncWebSocketClient *client, AwsEventType type, void *arg, unsigned char *message, unsigned length) {
const char *t; const char *t;
switch (type) { switch (type) {
@ -45,14 +52,29 @@ void httpSetup() {
}); });
server.addHandler(&ws); server.addHandler(&ws);
server.serveStatic("/", LittleFS, "/http/");
server.on("/reboot", HTTP_GET, httpReboot); server.on("/reboot", HTTP_GET, httpReboot);
server.begin(); server.begin();
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, PUT");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type");
httpSetUp = true;
info("Webserver started.");
} }
void httpLoop() { void httpLoop() {
if (!httpSetUp && isWiFiConnected()) {
httpSetup();
}
if (httpSetUp) {
ws.cleanupClients(); ws.cleanupClients();
}
} }
void websocketSendAll(const String& payload) { void websocketSendAll(const String& payload) {
if (httpSetUp) {
ws.textAll(payload); ws.textAll(payload);
}
} }

View File

@ -1,9 +1,9 @@
#ifndef PATRIX_HTTP_H #ifndef PATRIX_HTTP_H
#define PATRIX_HTTP_H #define PATRIX_HTTP_H
#include <Arduino.h> #include <ESPAsyncWebServer.h>
void httpSetup(); extern AsyncWebServer server;
void httpLoop(); void httpLoop();

View File

@ -3,6 +3,7 @@
#include <patrix/core/log.h> #include <patrix/core/log.h>
#include <patrix/core/mqtt.h> #include <patrix/core/mqtt.h>
#include <patrix/core/wifi.h> #include <patrix/core/wifi.h>
#include <patrix/node/PatrixNode.h>
#define MQTT_RETRY_DELAY_MILLIS 3000 #define MQTT_RETRY_DELAY_MILLIS 3000
@ -32,14 +33,25 @@ void mqttLoop() {
mqtt.setKeepAlive(10); mqtt.setKeepAlive(10);
mqtt.setBufferSize(512); mqtt.setBufferSize(512);
mqttLastConnectMillis = millis(); mqttLastConnectMillis = millis();
info("mqtt connecting: host=%s, port=%d", mqttHost, mqttPort); info("MQTT connecting: host=%s, port=%d", mqttHost, mqttPort);
if (mqtt.connect(WiFiClass::getHostname(), mqttWillTopic, 1, true, "false")) { if (mqtt.connect(WiFiClass::getHostname(), mqttWillTopic, 1, true, "false")) {
info("mqtt connected"); info("MQTT connected");
yield(); yield();
mqtt.publish(mqttWillTopic, "true", true); mqtt.publish(mqttWillTopic, "true", true);
mqtt.setCallback([](char *topic, uint8_t *payload, unsigned int length) {
char message[128];
if (length > sizeof message - 1) {
Serial.printf("MQTT: received too long message: topic=%s, length=%d", topic, length);
return;
}
memcpy(message, payload, length);
message[length] = 0;
patrixNode.mqttMessage(topic, message);
yield();
});
yield(); yield();
} else { } else {
error("mqtt failed to connect"); error("MQTT failed to connect");
} }
} }
} }
@ -47,3 +59,11 @@ void mqttLoop() {
bool mqttPublish(const String& topic, const String& payload, const Retain retain) { bool mqttPublish(const String& topic, const String& payload, const Retain retain) {
return mqtt.publish(topic.c_str(), payload.c_str(), retain == RETAIN); return mqtt.publish(topic.c_str(), payload.c_str(), retain == RETAIN);
} }
void mqttSubscribe(const String& topic) {
mqtt.subscribe(topic.c_str());
}
void mqttUnsubscribe(const String& topic) {
mqtt.unsubscribe(topic.c_str());
}

View File

@ -11,4 +11,8 @@ void mqttLoop();
bool mqttPublish(const String& topic, const String& payload, Retain retain); bool mqttPublish(const String& topic, const String& payload, Retain retain);
void mqttSubscribe(const String& topic);
void mqttUnsubscribe(const String& topic);
#endif #endif

View File

@ -18,7 +18,7 @@ auto wifiHost = WIFI_HOST;
void wifiSetupOTA() { void wifiSetupOTA() {
ArduinoOTA.onStart([] { ArduinoOTA.onStart([] {
info("beginning ota update..."); info("Beginning ota update...");
Serial.print("OTA-UPDATE: 0%"); Serial.print("OTA-UPDATE: 0%");
}); });
ArduinoOTA.onProgress([](const unsigned done, const unsigned total) { ArduinoOTA.onProgress([](const unsigned done, const unsigned total) {
@ -54,7 +54,7 @@ void wifiSetupOTA() {
} }
void wifiOff() { void wifiOff() {
info("wifi disabled"); info("WiFi disabled");
wifiEnabled = false; wifiEnabled = false;
WiFi.disconnect(); WiFi.disconnect();
} }
@ -64,10 +64,10 @@ void wifiLoop() {
if (wifiConnected != currentState) { if (wifiConnected != currentState) {
wifiConnected = currentState; wifiConnected = currentState;
if (wifiConnected) { if (wifiConnected) {
info("wifi connected as %s", WiFi.localIP().toString().c_str()); info("WiFi connected as %s", WiFi.localIP().toString().c_str());
wifiSetupOTA(); wifiSetupOTA();
} else { } else {
warn("wifi disconnected"); warn("WiFi disconnected");
ArduinoOTA.end(); ArduinoOTA.end();
WiFi.disconnect(); WiFi.disconnect();
} }

View File

@ -0,0 +1,314 @@
#ifndef PATRIX_DISPLAY_height
#define PATRIX_DISPLAY_height
#include <Adafruit_NeoPixel.h>
#include "DisplayMatrix_FontCommon.h"
#include "DisplayMatrix_FontLower.h"
#include "DisplayMatrix_FontNumberSegments.h"
#include "DisplayMatrix_FontSpecial.h"
#include "DisplayMatrix_FontUpper.h"
#include <patrix/core/log.h>
inline void fixRect(int& x0, int& y0, int& w, int& h) {
if (w < 0) {
x0 += w;
w = -w;
}
if (h < 0) {
y0 += h;
h = -h;
}
}
enum Align {
LEFT, CENTER, RIGHT
};
class Display {
Adafruit_NeoPixel leds;
bool dirty = true;
protected:
virtual RGB _read_pixel_(int x, int y) = 0;
virtual void _write_pixel_(int x, int y, RGBA color) = 0;
public:
const uint8_t width;
const uint8_t height;
const uint16_t pixelCount;
Display(const int16_t pin, const uint8_t width, const uint8_t height)
: leds(width * height, pin),
width(width),
height(height),
pixelCount(width * height) {
//
}
virtual ~Display() = default;
// basic ----------------------------------------------------------------------------------------
void setup(const int brightness) {
leds.begin();
setBrightness(brightness);
clear();
}
void loop() {
if (dirty) {
for (auto y = 0; y < height; y++) {
for (auto x = 0; x < width; x++) {
const auto rgb = _read_pixel_(x, y);
const auto index = y * width + (y % 2 == 0 ? x : width - x - 1);
leds.setPixelColor(index, rgb.r, rgb.g, rgb.b);
}
}
leds.show();
dirty = false;
}
}
uint8_t setBrightness(int brightness) {
brightness = max(0, min(brightness, 255));
if (leds.getBrightness() != brightness) {
leds.setBrightness(brightness);
dirty = true;
}
return brightness;
}
uint8_t getBrightness() const {
return leds.getBrightness();
}
// draw -----------------------------------------------------------------------------------------
void clear() {
fillRect(0, 0, width, height, Black);
}
void setPixel(const double x, const double y, const RGBA color) {
setPixel(static_cast<int>(round(x)), static_cast<int>(round(y)), color);
}
void setPixel(const int x, const int y, const RGBA color) {
if (x < 0 || x >= width || y < 0 || y >= height) {
return;
}
_write_pixel_(x, y, color);
dirty = true;
}
void fillRect(int x0, int y0, int w, int h, const RGBA color) {
fixRect(x0, y0, w, h);
for (auto y = y0; y < y0 + h; ++y) {
for (auto x = x0; x < x0 + w; ++x) {
setPixel(x, y, color);
}
}
}
void drawLine(int x0, int y0, int w, int h, const int thickness, const RGBA color) {
fixRect(x0, y0, w, h);
for (auto t = 0; t < thickness; ++t) {
const auto offset = t % 2 == 0 ? t / 2 : -t / 2;
if (h == 0) {
for (auto x = x0; x <= x0 + w; ++x) {
setPixel(x, y0, color);
}
} else if (w == 0) {
for (auto y = y0; y <= y0 + h; ++y) {
setPixel(x0, y, color);
}
} else if (w >= h) {
const auto m = static_cast<double>(h) / w;
for (auto x = x0; x <= x0 + w; ++x) {
const auto y = static_cast<int>(round(offset + x * m));
setPixel(x, y, color);
}
} else {
const auto m = static_cast<double>(w) / h;
for (auto y = y0; y <= y0 + h; ++y) {
const auto x = static_cast<int>(round(offset + y * m));
setPixel(x, y, color);
}
}
}
}
// print ----------------------------------------------------------------------------------------
void printf(const int x, const int y, const Align align, const RGBA color, const char *format, ...) {
va_list args;
va_start(args, format);
printf(x, y, align, color, format, args);
va_end(args);
}
void printf(const int x, const int y, const Align align, const RGBA color, const char *format, const va_list args) {
char buffer[64];
vsnprintf(buffer, sizeof buffer, format, args);
print(x, y, align, color, buffer);
}
void print(const int x0, const int y0, const Align align, const RGBA color, const char *message) {
auto x = x0;
auto y = y0;
doAlign(x, align, message);
print(x, y, color, message, true);
}
void doAlign(int& x, const Align align, const char *message) {
if (align != LEFT) {
auto w = 0;
auto h = 0;
measure(message, w, h);
if (align == RIGHT) {
x = x - w;
} else if (align == CENTER) {
x = x - w / 2;
}
} else {}
}
void measure(const char *message, int& w, int& h) {
w = 0;
h = 0;
print(w, h, White, message, false);
}
void print(int& x, int& y, const RGBA color, const char *message, const bool doDraw) {
auto character = message;
while (*character != '\0') {
print(x, x, y, color, &character, doDraw);
x++; // separate characters
}
}
void print(const int x0, int& x, int& y, const RGBA color, const char **cPP, const bool doDraw) {
const auto character = *(*cPP)++;
if ('0' <= character && character <= '9') {
return print(x, y, reinterpret_cast<bool *>(FONT_NUMBER_SEGMENTS[character - '0']), countof(FONT_NUMBER_SEGMENTS[character - '0'][0]), countof(FONT_NUMBER_SEGMENTS[character - '0']), color, doDraw);
}
if ('A' <= character && character <= 'Z') {
return print(x, y, reinterpret_cast<bool *>(FONT_UPPER[character - 'A']), countof(FONT_UPPER[character - 'A'][0]), countof(FONT_UPPER[character - 'A']), color, doDraw);
}
if ('a' <= character && character <= 'z') {
return print(x, y, reinterpret_cast<bool *>(FONT_LOWER[character - 'a']), countof(FONT_LOWER[character - 'a'][0]), countof(FONT_LOWER[character - 'a']), color, doDraw);
}
if (character == 0xC2) {
const auto secondCharacter = *(*cPP)++;
if (secondCharacter == 0xB0) {
return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_DEGREE), countof(FONT_CHAR_DEGREE[0]), countof(FONT_CHAR_DEGREE), color, doDraw);
}
return printERROR(x, y, color, doDraw);
}
if (character == 0xE2) {
const auto secondCharacter = *(*cPP)++;
if (secondCharacter == 0x82) {
const auto thirdCharacter = *(*cPP)++;
if (thirdCharacter == 0xAC) {
return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_EURO), countof(FONT_CHAR_EURO[0]), countof(FONT_CHAR_EURO), color, doDraw);
}
return printERROR(x, y, color, doDraw);
}
return printERROR(x, y, color, doDraw);
}
if (character == ' ') {
x += 3;
}
if (character == '\n') {
x = x0;
y += 6;
return;
}
switch (character) {
case '-': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_MINUS), countof(FONT_CHAR_MINUS[0]), countof(FONT_CHAR_MINUS), color, doDraw);
case '+': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_PLUS), countof(FONT_CHAR_PLUS[0]), countof(FONT_CHAR_PLUS), color, doDraw);
case '_': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_UNDERLINE), countof(FONT_CHAR_UNDERLINE[0]), countof(FONT_CHAR_UNDERLINE), color, doDraw);
case '%': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_PERCENT), countof(FONT_CHAR_PERCENT[0]), countof(FONT_CHAR_PERCENT), color, doDraw);
case '?': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_QUESTION), countof(FONT_CHAR_QUESTION[0]), countof(FONT_CHAR_QUESTION), color, doDraw);
case '!': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_EXCLAMATION), countof(FONT_CHAR_EXCLAMATION[0]), countof(FONT_CHAR_EXCLAMATION), color, doDraw);
case '.': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_POINT), countof(FONT_CHAR_POINT[0]), countof(FONT_CHAR_POINT), color, doDraw);
case ',': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_COMMA), countof(FONT_CHAR_COMMA[0]), countof(FONT_CHAR_COMMA), color, doDraw);
case ';': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_SEMICOLON), countof(FONT_CHAR_SEMICOLON[0]), countof(FONT_CHAR_SEMICOLON), color, doDraw);
case ':': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_COLON), countof(FONT_CHAR_COLON[0]), countof(FONT_CHAR_COLON), color, doDraw);
case '#': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_SHARP), countof(FONT_CHAR_SHARP[0]), countof(FONT_CHAR_SHARP), color, doDraw);
case '~': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_TILDE), countof(FONT_CHAR_TILDE[0]), countof(FONT_CHAR_TILDE), color, doDraw);
case '*': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_ASTERISK), countof(FONT_CHAR_ASTERISK[0]), countof(FONT_CHAR_ASTERISK), color, doDraw);
case '"': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_QUOTE_DOUBLE), countof(FONT_CHAR_QUOTE_DOUBLE[0]), countof(FONT_CHAR_QUOTE_DOUBLE), color, doDraw);
case '\'': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_QUOTE_SINGLE), countof(FONT_CHAR_QUOTE_SINGLE[0]), countof(FONT_CHAR_QUOTE_SINGLE), color, doDraw);
case '=': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_EQUALS), countof(FONT_CHAR_EQUALS[0]), countof(FONT_CHAR_EQUALS), color, doDraw);
case '(': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_PAR_L), countof(FONT_CHAR_PAR_L[0]), countof(FONT_CHAR_PAR_L), color, doDraw);
case ')': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_PAR_R), countof(FONT_CHAR_PAR_R[0]), countof(FONT_CHAR_PAR_R), color, doDraw);
case '[': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_BRACKET_L), countof(FONT_CHAR_BRACKET_L[0]), countof(FONT_CHAR_BRACKET_L), color, doDraw);
case ']': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_BRACKET_R), countof(FONT_CHAR_BRACKET_R[0]), countof(FONT_CHAR_BRACKET_R), color, doDraw);
case '{': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_CURL_L), countof(FONT_CHAR_CURL_L[0]), countof(FONT_CHAR_CURL_L), color, doDraw);
case '}': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_CURL_R), countof(FONT_CHAR_CURL_R[0]), countof(FONT_CHAR_CURL_R), color, doDraw);
case '/': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_SLASH), countof(FONT_CHAR_SLASH[0]), countof(FONT_CHAR_SLASH), color, doDraw);
case '\\': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_BACKSLASH), countof(FONT_CHAR_BACKSLASH[0]), countof(FONT_CHAR_BACKSLASH), color, doDraw);
case '&': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_AND), countof(FONT_CHAR_AND[0]), countof(FONT_CHAR_AND), color, doDraw);
case '|': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_PIPE), countof(FONT_CHAR_PIPE[0]), countof(FONT_CHAR_PIPE), color, doDraw);
case '$': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_DOLLAR), countof(FONT_CHAR_DOLLAR[0]), countof(FONT_CHAR_DOLLAR), color, doDraw);
case '@': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_AT), countof(FONT_CHAR_AT[0]), countof(FONT_CHAR_AT), color, doDraw);
case '<': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_LT), countof(FONT_CHAR_LT[0]), countof(FONT_CHAR_LT), color, doDraw);
case '>': return print(x, y, reinterpret_cast<bool *>(FONT_CHAR_GT), countof(FONT_CHAR_GT[0]), countof(FONT_CHAR_GT), color, doDraw);
default: return printERROR(x, y, color, doDraw);
}
}
void print(int& x0, const int& y0, const bool *s, const int w, const int h, const RGBA color, const bool doDraw) {
auto maxWidth = 0;
for (auto y = 0; y < h; y++) {
for (auto x = 0; x < w; x++) {
const auto symbolIndex = y * w + x;
const auto active = *(s + symbolIndex);
if (active) {
maxWidth = max(x + 1, maxWidth);
const auto pixelX = x0 + x;
const auto pixelY = y0 + y;
if (doDraw) {
setPixel(pixelX, pixelY, color);
}
}
}
}
x0 += maxWidth;
}
void print(int& x0, const int& y0, const RGBA *s, const int w, const int h) {
for (auto y = 0; y < h; y++) {
for (auto x = 0; x < w; x++) {
const auto symbolIndex = y * w + x;
const auto color = *(s + symbolIndex);
const auto pixelX = x0 + x;
const auto pixelY = y0 + y;
setPixel(pixelX, pixelY, color);
}
}
x0 += w;
}
void printERROR(int& x, const int& y, const RGBA color, const bool doDraw) {
return print(x, y, reinterpret_cast<bool *>(FONT_ERROR_), countof(FONT_ERROR_[0]), countof(FONT_ERROR_), color, doDraw);
}
};
#endif

View File

@ -1,103 +1,32 @@
#ifndef DISPLAY_MATRIX_H #ifndef DISPLAY_MATRIX_H
#define DISPLAY_MATRIX_H #define DISPLAY_MATRIX_H
#include <Adafruit_NeoPixel.h> #include "Display.h"
#include "DisplayMatrix_FontCommon.h"
template<int W, int H> template<int W, int H>
class DisplayMatrix { class DisplayMatrix final : public Display {
Adafruit_NeoPixel leds;
RGB matrix[H][W] = {}; RGB matrix[H][W] = {};
bool dirty = true; protected:
RGB _read_pixel_(int x, int y) override {
return matrix[y][x];
}
void _write_pixel_(const int x, const int y, const RGBA color) override {
matrix[y][x] = color;
}
public: public:
RGBA foreground = White; explicit DisplayMatrix(const int16_t pin)
: Display(pin, W, H) {
RGBA background = Transparent;
uint8_t brightness = 10;
uint8_t alpha = 255;
int cursorX = 0;
int cursorY = 0;
// basic ----------------------------------------------------------------------------------------
explicit DisplayMatrix(const int pin): leds(W * H, pin) {
// //
} }
void setup() { ~DisplayMatrix() override = default;
leds.begin();
leds.setBrightness(brightness);
clear();
}
void loop() {
if (dirty) {
for (auto y = 0; y < H; y++) {
for (auto x = 0; x < W; x++) {
auto rgb = matrix[y][x];
leds.setPixelColor(y * W + x, rgb.r, rgb.g, rgb.b);
}
}
leds.show();
dirty = false;
}
}
void setBrightness(const uint8_t brightness) {
if (leds.getBrightness() != brightness) {
leds.setBrightness(brightness);
dirty = true;
}
}
// draw -----------------------------------------------------------------------------------------
void clear();
void fillRect(int w = W, int h = H);
void drawLine(int w, int h, int thickness = 1);
// print ----------------------------------------------------------------------------------------
void printf(const char *format, ...);
void print(const char *str);
void print(const char **cPP);
void print(Symbol1 s);
void print(Symbol2 s);
void print(Symbol3 s);
void print(Symbol4 s);
void print(Symbol5 s);
void print(bool **s, size_t w, size_t h);
void print(SymbolRGBA8x8 s);
void print(RGBA **s, size_t w, size_t h);
}; };
// ReSharper disable once CppUnusedIncludeDirective
#include <patrix/display/DisplayMatrix_draw.h>
// ReSharper disable once CppUnusedIncludeDirective
#include <patrix/display/DisplayMatrix_print.h>
#endif #endif

View File

@ -83,7 +83,7 @@ Symbol5 FONT_LOWER[] = {
{X,_,_,_,_}, {X,_,_,_,_},
{X,_,_,_,_}, {X,_,_,_,_},
{X,_,_,_,_}, {X,_,_,_,_},
{_,X,_,_,_}, {X,X,_,_,_},
}, },
{ {
{_,_,_,_,_}, {_,_,_,_,_},

View File

@ -1,74 +0,0 @@
#include "DisplayMatrix_FontNumber.h"
Symbol4 FONT_NUMBER[] = {
{
{_,X,X,_},
{X,_,_,X},
{X,_,_,X},
{X,_,_,X},
{_,X,X,_},
},
{
{_,_,X,_},
{_,X,X,_},
{X,_,X,_},
{_,_,X,_},
{_,_,X,_},
},
{
{_,X,X,_},
{X,_,_,X},
{_,_,X,_},
{_,X,_,_},
{X,X,X,X},
},
{
{_,X,X,_},
{X,_,_,X},
{_,_,X,_},
{X,_,_,X},
{_,X,X,_},
},
{
{X,_,_,X},
{X,_,_,X},
{X,X,X,X},
{_,_,_,X},
{_,_,_,X},
},
{
{X,X,X,X},
{X,_,_,_},
{X,X,X,_},
{_,_,_,X},
{X,X,X,_},
},
{
{_,X,X,_},
{X,_,_,_},
{X,X,X,_},
{X,_,_,X},
{_,X,X,_},
},
{
{X,X,X,X},
{_,_,_,X},
{_,_,X,_},
{_,X,_,_},
{X,_,_,_},
},
{
{_,X,X,_},
{X,_,_,X},
{_,X,X,_},
{X,_,_,X},
{_,X,X,_},
},
{
{_,X,X,_},
{X,_,_,X},
{_,X,X,X},
{_,_,_,X},
{_,X,X,_},
},
};

View File

@ -1,8 +0,0 @@
#ifndef DISPLAY_MATRIX_FONT_NUMBER_H
#define DISPLAY_MATRIX_FONT_NUMBER_H
#include "DisplayMatrix_FontCommon.h"
extern Symbol4 FONT_NUMBER[];
#endif

View File

@ -0,0 +1,74 @@
#include "DisplayMatrix_FontCommon.h"
Symbol4 FONT_NUMBER_SEGMENTS[] = {
{
{X,X,X},
{X,_,X},
{X,_,X},
{X,_,X},
{X,X,X},
},
{
{_,_,X},
{_,X,X},
{X,_,X},
{_,_,X},
{_,_,X},
},
{
{X,X,X},
{_,_,X},
{X,X,X},
{X,_,_},
{X,X,X},
},
{
{X,X,X},
{_,_,X},
{_,X,X},
{_,_,X},
{X,X,X},
},
{
{X,_,X},
{X,_,X},
{X,X,X},
{_,_,X},
{_,_,X},
},
{
{X,X,X},
{X,_,_},
{X,X,X},
{_,_,X},
{X,X,X},
},
{
{X,X,X},
{X,_,_},
{X,X,X},
{X,_,X},
{X,X,X},
},
{
{X,X,X},
{_,_,X},
{_,_,X},
{_,_,X},
{_,_,X},
},
{
{X,X,X},
{X,_,X},
{X,X,X},
{X,_,X},
{X,X,X},
},
{
{X,X,X},
{X,_,X},
{X,X,X},
{_,_,X},
{X,X,X},
},
};

View File

@ -0,0 +1,8 @@
#ifndef DISPLAY_MATRIX_FONT_NUMBER_SEGMENTS_H
#define DISPLAY_MATRIX_FONT_NUMBER_SEGMENTS_H
#include "DisplayMatrix_FontCommon.h"
extern Symbol4 FONT_NUMBER_SEGMENTS[];
#endif

View File

@ -1,13 +1,5 @@
#include "DisplayMatrix_FontSpecial.h" #include "DisplayMatrix_FontSpecial.h"
Symbol3 FONT_CHAR_SPACE = {
{_,_,_},
{_,_,_},
{_,_,_},
{_,_,_},
{_,_,_},
};
Symbol3 FONT_CHAR_MINUS = { Symbol3 FONT_CHAR_MINUS = {
{_,_,_}, {_,_,_},
{_,_,_}, {_,_,_},
@ -237,11 +229,11 @@ Symbol4 FONT_ERROR_ = {
}; };
Symbol5 FONT_CHAR_PERCENT = { Symbol5 FONT_CHAR_PERCENT = {
{X,_,_,_,X}, {X,_,_},
{_,_,_,X,_}, {_,_,X},
{_,_,X,_,_}, {_,X,_},
{_,X,_,_,_}, {X,_,_},
{X,_,_,_,X}, {_,_,X},
}; };
//@formatter:off //@formatter:off

View File

@ -3,7 +3,6 @@
#include "DisplayMatrix_FontCommon.h" #include "DisplayMatrix_FontCommon.h"
extern Symbol3 FONT_CHAR_SPACE;
extern Symbol3 FONT_CHAR_MINUS; extern Symbol3 FONT_CHAR_MINUS;
extern Symbol3 FONT_CHAR_PLUS; extern Symbol3 FONT_CHAR_PLUS;
extern Symbol3 FONT_CHAR_UNDERLINE; extern Symbol3 FONT_CHAR_UNDERLINE;

View File

@ -2,11 +2,11 @@
Symbol5 FONT_UPPER[] = { Symbol5 FONT_UPPER[] = {
{ {
{_,_,X,_,_}, {_,X,X,_,_},
{_,X,_,X,_}, {X,_,_,X,_},
{X,_,_,_,X}, {X,X,X,X,_},
{X,X,X,X,X}, {X,_,_,X,_},
{X,_,_,_,X}, {X,_,_,X,_},
}, },
{ {
{X,X,X,_,_}, {X,X,X,_,_},
@ -44,11 +44,11 @@ Symbol5 FONT_UPPER[] = {
{X,_,_,_,_}, {X,_,_,_,_},
}, },
{ {
{_,X,X,X,_}, {_,X,X,_,_},
{X,_,_,_,_}, {X,_,_,_,_},
{X,_,X,X,X}, {X,_,X,X,_},
{X,_,_,_,X}, {X,_,_,X,_},
{_,X,X,X,_}, {_,X,X,_,_},
}, },
{ {
{X,_,_,X,_}, {X,_,_,X,_},
@ -100,11 +100,11 @@ Symbol5 FONT_UPPER[] = {
{X,_,_,_,X}, {X,_,_,_,X},
}, },
{ {
{_,X,X,X,_}, {_,X,X,_,_},
{X,_,_,_,X}, {X,_,_,X,_},
{X,_,_,_,X}, {X,_,_,X,_},
{X,_,_,_,X}, {X,_,_,X,_},
{_,X,X,X,_}, {_,X,X,_,_},
}, },
{ {
{X,X,X,_,_}, {X,X,X,_,_},
@ -114,11 +114,11 @@ Symbol5 FONT_UPPER[] = {
{X,_,_,_,_}, {X,_,_,_,_},
}, },
{ {
{_,X,X,X,_}, {_,X,X,_,_},
{X,_,_,_,X},
{X,_,X,_,X},
{X,_,_,X,_}, {X,_,_,X,_},
{_,X,X,_,X}, {X,_,_,X,_},
{X,_,X,_,_},
{_,X,_,X,_},
}, },
{ {
{X,X,X,_,_}, {X,X,X,_,_},
@ -135,23 +135,23 @@ Symbol5 FONT_UPPER[] = {
{X,X,X,_,_}, {X,X,X,_,_},
}, },
{ {
{X,X,X,X,X}, {X,X,X,_,_},
{_,_,X,_,_}, {_,X,_,_,_},
{_,_,X,_,_}, {_,X,_,_,_},
{_,_,X,_,_}, {_,X,_,_,_},
{_,_,X,_,_}, {_,X,_,_,_},
},
{
{X,_,_,X,_},
{X,_,_,X,_},
{X,_,_,X,_},
{X,_,_,X,_},
{_,X,X,_,_},
}, },
{ {
{X,_,_,_,X}, {X,_,_,_,X},
{X,_,_,_,X}, {X,_,_,_,X},
{X,_,_,_,X}, {X,_,_,_,X},
{X,_,_,_,X},
{_,X,X,X,_},
},
{
{X,_,_,_,X},
{X,_,_,_,X},
{_,X,_,X,_},
{_,X,_,X,_}, {_,X,_,X,_},
{_,_,X,_,_}, {_,_,X,_,_},
}, },

View File

@ -1,14 +1,18 @@
#include "DisplayMatrix_common.h" #include "DisplayMatrix_common.h"
//@formatter:off // ReSharper disable CppVariableCanBeMadeConstexpr
// @formatter:off
const RGBA Transparent { 0, 0, 0, 0}; const RGBA Transparent { 0, 0, 0, 0};
const RGBA Black { 0, 0, 0, 255}; const RGBA Black { 0, 0, 0, 255};
const RGBA Red {255, 0, 0, 255}; const RGBA Red {255, 0, 0, 255};
const RGBA Yellow {255, 255, 0, 255}; const RGBA Yellow {255, 255, 0, 255};
const RGBA Orange {255, 127, 0, 255};
const RGBA Green { 0, 255, 0, 255}; const RGBA Green { 0, 255, 0, 255};
const RGBA Cyan { 0, 255, 255, 255}; const RGBA Cyan { 0, 255, 255, 255};
const RGBA Blue { 0, 0, 255, 255}; const RGBA Blue { 0, 0, 255, 255};
const RGBA Magenta {255, 0, 255, 255}; const RGBA Magenta {255, 0, 255, 255};
const RGBA Violet {192, 0, 255, 255};
const RGBA Gray {127, 127, 127, 255}; const RGBA Gray {127, 127, 127, 255};
const RGBA White {255, 255, 255, 255}; const RGBA White {255, 255, 255, 255};
//@formatter:on // @formatter:on
// ReSharper restore CppVariableCanBeMadeConstexpr

View File

@ -13,6 +13,38 @@ struct RGBA {
uint8_t g; uint8_t g;
uint8_t b; uint8_t b;
uint8_t a; uint8_t a;
static RGBA gray(const uint8_t brightness) {
return gray(brightness, 255);
}
static RGBA gray(const uint8_t brightness, const uint8_t alpha) {
return {brightness, brightness, brightness, alpha};
}
static RGBA rnd() {
return rnd(255);
}
static RGBA rnd(const uint8_t alpha) {
return {static_cast<uint8_t>(random(255)), static_cast<uint8_t>(random(255)), static_cast<uint8_t>(random(255)), alpha};
}
RGBA factor(const double factor) const {
return {
limit(r * factor),
limit(g * factor),
limit(b * factor),
a
};
}
protected:
static uint8_t limit(const double newValue) {
return static_cast<uint8_t>(round(max(0.0, min(255.0, newValue))));
}
}; };
struct RGB { struct RGB {
@ -20,18 +52,26 @@ struct RGB {
uint8_t g; uint8_t g;
uint8_t b; uint8_t b;
void operator =(const RGBA& color) { RGB &operator =(const RGBA& color) {
blend(&this->r, color.r, color.a); r = color.r;
blend(&this->g, color.g, color.a); g = color.g;
blend(&this->b, color.b, color.a); b = color.b;
// blend(&this->r, color.r, color.a);
// blend(&this->g, color.g, color.a);
// blend(&this->b, color.b, color.a);
return *this;
} }
private: protected:
static uint8_t limit(const double newValue) {
return static_cast<uint8_t>(round(max(0.0, min(255.0, newValue))));
}
static void blend(uint8_t *target, const uint8_t color, const uint8_t alpha) { static void blend(uint8_t *target, const uint8_t color, const uint8_t alpha) {
const auto factor = alpha / 255.0; const auto factor = alpha / 255.0;
const auto newValue = (*target * (1 - factor) + color * factor) / 2.0; const auto newValue = (*target * (1 - factor) + color * factor) / 2.0;
*target = static_cast<uint8_t>(round(max(0.0, min(255.0, newValue)))); *target = limit(newValue);
} }
}; };
@ -40,10 +80,12 @@ extern const RGBA Transparent;
extern const RGBA Black; extern const RGBA Black;
extern const RGBA Red; extern const RGBA Red;
extern const RGBA Yellow; extern const RGBA Yellow;
extern const RGBA Orange;
extern const RGBA Green; extern const RGBA Green;
extern const RGBA Cyan; extern const RGBA Cyan;
extern const RGBA Blue; extern const RGBA Blue;
extern const RGBA Magenta; extern const RGBA Magenta;
extern const RGBA Violet;
extern const RGBA Gray; extern const RGBA Gray;
extern const RGBA White; extern const RGBA White;

View File

@ -1,52 +0,0 @@
#ifndef DISPLAY_MATRIX_DRAW_H
#define DISPLAY_MATRIX_DRAW_H
template<int W, int H>
void DisplayMatrix<W, H>::clear() {
const auto backup = foreground;
foreground = Black;
fillRect();
foreground = backup;
}
template<int W, int H>
void DisplayMatrix<W, H>::fillRect(const int w, const int h) {
if (w < 1 || h < 1) {
return;
}
for (auto y = cursorY; y < cursorY + h; ++y) {
for (auto x = cursorX; x < cursorX + w; ++x) {
matrix[y][x] = foreground;
}
}
dirty = true;
}
template<int W, int H>
void DisplayMatrix<W, H>::drawLine(const int w, const int h, const int thickness) {
if (w < 1 && h < 1) {
return;
}
if (w >= h) {
const auto m = static_cast<double>(h) / w;
for (auto t = 0; t < thickness; ++t) {
const auto offset = t % 2 == 0 ? t / 2 : -t / 2;
for (auto x = 0; x < w; ++x) {
const auto y = static_cast<int>(round(offset + x * m));
matrix[cursorY + y][cursorX + x] = foreground;
}
}
} else {
const auto m = static_cast<double>(w) / h;
for (auto t = 0; t < thickness; ++t) {
const auto offset = t % 2 == 0 ? t / 2 : -t / 2;
for (auto y = 0; y < w; ++y) {
const auto x = static_cast<int>(round(offset + y * m));
matrix[cursorY + y][cursorX + x] = foreground;
}
}
}
dirty = true;
}
#endif

View File

@ -1,154 +0,0 @@
#ifndef DISPLAYMATRIX_PRINT_H
#define DISPLAYMATRIX_PRINT_H
#include "DisplayMatrix_FontLower.h"
#include "DisplayMatrix_FontNumber.h"
#include "DisplayMatrix_FontSpecial.h"
#include "DisplayMatrix_FontUpper.h"
template<int W, int H>
void DisplayMatrix<W, H>::printf(const char *format, ...) {
char buffer[64];
va_list args;
va_start(args, format);
vsnprintf(buffer, sizeof buffer, format, args);
va_end(args);
print(buffer);
}
template<int W, int H>
void DisplayMatrix<W, H>::print(const char *str) {
while (*str != '\0') {
print(&str);
}
}
template<int W, int H>
void DisplayMatrix<W, H>::print(const char **cPP) {
const auto a = *(*cPP)++;
auto b = '\0';
auto c = '\0';
if ('a' <= a && a <= 'z') {
return print(FONT_LOWER[a - 'a']);
}
if ('A' <= a && a <= 'Z') {
return print(FONT_UPPER[a - 'A']);
}
if ('0' <= a && a <= '9') {
return print(FONT_NUMBER[a - '0']);
}
if (a == 0xC2) {
b = *(*cPP)++;
switch (b) {
case 0xB0: return print(FONT_CHAR_DEGREE);
default: return print(FONT_ERROR_);
}
}
if (a == 0xE2) {
b = *(*cPP)++;
switch (b) {
case 0x82:
c = *(*cPP)++;
switch (c) {
case 0xAC: return print(FONT_CHAR_EURO);
default: return print(FONT_ERROR_);
}
default: return print(FONT_ERROR_);
}
}
switch (a) {
case ' ': return print(FONT_CHAR_SPACE);
case '-': return print(FONT_CHAR_MINUS);
case '+': return print(FONT_CHAR_PLUS);
case '_': return print(FONT_CHAR_UNDERLINE);
case '%': return print(FONT_CHAR_PERCENT);
case '?': return print(FONT_CHAR_QUESTION);
case '!': return print(FONT_CHAR_EXCLAMATION);
case '.': return print(FONT_CHAR_POINT);
case ',': return print(FONT_CHAR_COMMA);
case ';': return print(FONT_CHAR_SEMICOLON);
case ':': return print(FONT_CHAR_COLON);
case '#': return print(FONT_CHAR_SHARP);
case '~': return print(FONT_CHAR_TILDE);
case '*': return print(FONT_CHAR_ASTERISK);
case '"': return print(FONT_CHAR_QUOTE_DOUBLE);
case '\'': return print(FONT_CHAR_QUOTE_SINGLE);
case '=': return print(FONT_CHAR_EQUALS);
case '(': return print(FONT_CHAR_PAR_L);
case ')': return print(FONT_CHAR_PAR_R);
case '[': return print(FONT_CHAR_BRACKET_L);
case ']': return print(FONT_CHAR_BRACKET_R);
case '{': return print(FONT_CHAR_CURL_L);
case '}': return print(FONT_CHAR_CURL_R);
case '/': return print(FONT_CHAR_SLASH);
case '\\': return print(FONT_CHAR_BACKSLASH);
case '&': return print(FONT_CHAR_AND);
case '|': return print(FONT_CHAR_PIPE);
case '$': return print(FONT_CHAR_DOLLAR);
case '@': return print(FONT_CHAR_AT);
case '<': return print(FONT_CHAR_LT);
case '>': return print(FONT_CHAR_GT);
default: return print(FONT_ERROR_);
}
}
template<int W, int H>
void DisplayMatrix<W, H>::print(Symbol1 s) {
print(reinterpret_cast<bool **>(s),countof(Symbol1),countof(Symbol1[0]));
}
template<int W, int H>
void DisplayMatrix<W, H>::print(Symbol2 s) {
print(reinterpret_cast<bool **>(s),countof(Symbol2),countof(Symbol2[0]));
}
template<int W, int H>
void DisplayMatrix<W, H>::print(Symbol3 s) {
print(reinterpret_cast<bool **>(s),countof(Symbol3),countof(Symbol3[0]));
}
template<int W, int H>
void DisplayMatrix<W, H>::print(Symbol4 s) {
print(reinterpret_cast<bool **>(s),countof(Symbol4),countof(Symbol4[0]));
}
template<int W, int H>
void DisplayMatrix<W, H>::print(Symbol5 s) {
print(reinterpret_cast<bool **>(s),countof(Symbol5),countof(Symbol5[0]));
}
template<int W, int H>
void DisplayMatrix<W, H>::print(bool **s, const size_t w, const size_t h) {
auto xMax = 0;
for (auto y = 0; y < h; y++) {
for (auto x = 0; x < w; x++) {
const auto c = s[y][x];
if (c) {
matrix[y][x] = foreground;
xMax = x > xMax ? x : xMax;
} else {
matrix[y][x] = background;
}
}
}
cursorX += xMax;
}
template<int W, int H>
void DisplayMatrix<W, H>::print(SymbolRGBA8x8 s) {
print(reinterpret_cast<RGBA **>(s),countof(SymbolRGBA8x8),countof(SymbolRGBA8x8[0]));
}
template<int W, int H>
void DisplayMatrix<W, H>::print(RGBA **s, const size_t w, const size_t h) {
for (auto y = 0; y < h; y++) {
for (auto x = 0; x < w; x++) {
const auto color = s[y][x];
matrix[y][x] = color;
}
}
cursorX += w;
}
#endif

View File

@ -1,4 +1,4 @@
#include <patrix/node/PatrixNode.h> #include <patrix/node/PatrixNode.h>
// ReSharper disable once CppUseAuto // ReSharper disable once CppUseAuto
PatrixNode patrixNode = patrixGetNode(); PatrixNode &patrixNode = patrixGetNode();

View File

@ -22,16 +22,18 @@ public:
virtual ~PatrixNode() = default; virtual ~PatrixNode() = default;
virtual void setup() {} virtual void setup() = 0;
virtual void loop() {} virtual void loop() = 0;
virtual void mqttMessage(char *topic, char *message) {}
virtual void websocketEvent(AsyncWebSocket *socket, AsyncWebSocketClient *client, AwsEventType type, void *arg, unsigned char *message, unsigned length) {} virtual void websocketEvent(AsyncWebSocket *socket, AsyncWebSocketClient *client, AwsEventType type, void *arg, unsigned char *message, unsigned length) {}
}; };
extern PatrixNode patrixNode; extern PatrixNode& patrixNode;
PatrixNode& patrixGetNode(); PatrixNode &patrixGetNode();
#endif #endif