Compare commits
15 Commits
f2ce84a002
...
2b80b1d671
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b80b1d671 | |||
| d498cd12b4 | |||
| 76120d4607 | |||
| 1a75387aea | |||
| 80df3ec9cc | |||
| 2b61f7ae98 | |||
| 03d6899817 | |||
| 7fa66fb4d5 | |||
| 9a6ca32976 | |||
| 56dda350b4 | |||
| 8e5a4e2501 | |||
| 705e8c7d77 | |||
| d57ef636f2 | |||
| 8bbddc3bb8 | |||
| d7ed172fca |
@ -7,7 +7,7 @@
|
||||
|
||||
Config config("/test.json");
|
||||
|
||||
DisplayMatrix<32, 8> display(27);
|
||||
DisplayMatrix<32, 8> display(13);
|
||||
|
||||
class NodeTest final : public PatrixNode {
|
||||
|
||||
@ -19,18 +19,10 @@ public:
|
||||
|
||||
void setup() override {
|
||||
config.read();
|
||||
display.setup();
|
||||
display.setBrightness(6);
|
||||
|
||||
display.setup(10);
|
||||
display.clear();
|
||||
|
||||
display.foreground = Blue;
|
||||
display.printf("Test");
|
||||
|
||||
display.foreground = Red;
|
||||
display.cursorX = 0;
|
||||
display.cursorY = 7;
|
||||
display.drawLine(32, 0);
|
||||
display.printf(1, 1, LEFT, Blue, "Test");
|
||||
display.drawLine(0, 7, 32, 0, 1, Red);
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
|
||||
@ -4,7 +4,6 @@ void setup() {
|
||||
logSetup();
|
||||
bootDelay();
|
||||
patrixNode.setup();
|
||||
httpSetup();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
@ -15,14 +15,14 @@ class Config final {
|
||||
|
||||
const String path;
|
||||
|
||||
JsonDocument config;
|
||||
|
||||
unsigned long dirty = 0;
|
||||
|
||||
bool doForceNextHexBuffer = true;
|
||||
|
||||
public:
|
||||
|
||||
JsonDocument json;
|
||||
|
||||
explicit Config(String path) : path(std::move(path)) {
|
||||
//
|
||||
}
|
||||
@ -33,12 +33,12 @@ public:
|
||||
fsMount();
|
||||
auto file = LittleFS.open(path, "r");
|
||||
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;
|
||||
}
|
||||
if (!deserialize(file)) {
|
||||
config.clear();
|
||||
config = config.to<JsonObject>();
|
||||
json.clear();
|
||||
json = json.to<JsonObject>();
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
@ -47,16 +47,16 @@ public:
|
||||
fsMount();
|
||||
auto write = LittleFS.open(path, "w");
|
||||
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;
|
||||
}
|
||||
const auto size = measureJson(config);
|
||||
if (serializeJson(config, write) == size) {
|
||||
const auto size = measureJson(json);
|
||||
if (serializeJson(json, write) == size) {
|
||||
char buffer[256];
|
||||
serializeJson(config, buffer, sizeof buffer);
|
||||
info("config written: %s => %s", path.c_str(), buffer);
|
||||
serializeJson(json, buffer, sizeof buffer);
|
||||
info("Config written: %s => %s", path.c_str(), buffer);
|
||||
} else {
|
||||
error("failed to write config: %s", path.c_str());
|
||||
error("Failed to write config: %s", path.c_str());
|
||||
}
|
||||
write.close();
|
||||
}
|
||||
@ -69,35 +69,82 @@ public:
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T get(const char *key, T fallback) {
|
||||
if (config[key].is<T>()) {
|
||||
return config[key].as<T>();
|
||||
}
|
||||
warn("config key \"%s\" not found!", key);
|
||||
return fallback;
|
||||
bool has(const char *key) {
|
||||
return json[key].is<T>();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void set(const char *key, T value) {
|
||||
config[key] = value;
|
||||
dirty = max(1UL, millis()); // avoid special value zero (=> not dirty)
|
||||
T get(const char *key, T fallback) {
|
||||
if (json[key].is<T>()) {
|
||||
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:
|
||||
|
||||
bool deserialize(File file) {
|
||||
if (deserializeJson(config, file) != DeserializationError::Ok) {
|
||||
error("failed to deserialize config: %s", file.path());
|
||||
if (deserializeJson(json, file) != DeserializationError::Ok) {
|
||||
error("Failed to deserialize config: %s", file.path());
|
||||
return false;
|
||||
}
|
||||
if (!config.is<JsonObject>()) {
|
||||
error("config not a json-object: %s", file.path());
|
||||
if (!json.is<JsonObject>()) {
|
||||
error("Config not a json-object: %s", file.path());
|
||||
return false;
|
||||
}
|
||||
char buffer[256];
|
||||
serializeJson(config, buffer, sizeof buffer);
|
||||
info("config loaded: %s => %s", path.c_str(), buffer);
|
||||
config = config.as<JsonObject>();
|
||||
serializeJson(json, buffer, sizeof buffer);
|
||||
info("Config loaded: %s => %s", path.c_str(), buffer);
|
||||
json = json.as<JsonObject>();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -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() {
|
||||
const auto r0 = rtc_get_reset_reason(0);
|
||||
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) {
|
||||
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...");
|
||||
while (!isWiFiConnected()) {
|
||||
wifiLoop();
|
||||
@ -77,7 +89,7 @@ void bootDelay() {
|
||||
|
||||
info("Boot delay active...");
|
||||
|
||||
boot_waitForWifi();
|
||||
boot_waitForWiFi();
|
||||
|
||||
if (failureReset || patrixNode.waitForOTA) {
|
||||
boot_waitForOTA();
|
||||
|
||||
@ -27,17 +27,16 @@ void clockLoop() {
|
||||
const auto now = time(nullptr);
|
||||
if (isCorrectTime(now)) {
|
||||
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 {
|
||||
clockOffset = now;
|
||||
}
|
||||
}
|
||||
|
||||
bool isClockSet() {
|
||||
return startupTime != 0;
|
||||
return isCorrectTime(time(nullptr));
|
||||
}
|
||||
|
||||
|
||||
char *getClockStr(const time_t epoch) {
|
||||
auto now = epoch;
|
||||
if (now == 0) {
|
||||
|
||||
@ -7,13 +7,14 @@
|
||||
auto fsMounted = false;
|
||||
|
||||
bool fsMount() {
|
||||
if (!fsMounted) {
|
||||
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());
|
||||
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 {
|
||||
error("failed to mount filesystem");
|
||||
error("Failed to mount filesystem");
|
||||
}
|
||||
return fsMounted;
|
||||
}
|
||||
@ -25,11 +26,11 @@ bool fsIsMounted() {
|
||||
void fsList(FS& fs, const char *path, const String& indent) { // NOLINT(*-no-recursion)
|
||||
auto dir = fs.open(path);
|
||||
if (!dir) {
|
||||
error("not found: %s", path);
|
||||
error("Not found: %s", path);
|
||||
return;
|
||||
}
|
||||
if (!dir.isDirectory()) {
|
||||
error("not a directory: %s", path);
|
||||
error("Not a directory: %s", path);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,18 @@
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <LittleFS.h>
|
||||
#include <patrix/core/log.h>
|
||||
#include <patrix/core/system.h>
|
||||
#include <patrix/node/PatrixNode.h>
|
||||
|
||||
#include "filesystem.h"
|
||||
#include "wifi.h"
|
||||
|
||||
AsyncWebServer server(80);
|
||||
|
||||
AsyncWebSocket ws("/ws");
|
||||
|
||||
auto httpSetUp = false;
|
||||
|
||||
void httpReboot(AsyncWebServerRequest *request) {
|
||||
info("Rebooting...");
|
||||
request->redirect("/");
|
||||
@ -18,6 +24,7 @@ void httpReboot(AsyncWebServerRequest *request) {
|
||||
}
|
||||
|
||||
void httpSetup() {
|
||||
fsMount();
|
||||
ws.onEvent([](AsyncWebSocket *socket, AsyncWebSocketClient *client, AwsEventType type, void *arg, unsigned char *message, unsigned length) {
|
||||
const char *t;
|
||||
switch (type) {
|
||||
@ -45,14 +52,29 @@ void httpSetup() {
|
||||
});
|
||||
server.addHandler(&ws);
|
||||
|
||||
server.serveStatic("/", LittleFS, "/http/");
|
||||
server.on("/reboot", HTTP_GET, httpReboot);
|
||||
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() {
|
||||
if (!httpSetUp && isWiFiConnected()) {
|
||||
httpSetup();
|
||||
}
|
||||
if (httpSetUp) {
|
||||
ws.cleanupClients();
|
||||
}
|
||||
}
|
||||
|
||||
void websocketSendAll(const String& payload) {
|
||||
if (httpSetUp) {
|
||||
ws.textAll(payload);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
#ifndef PATRIX_HTTP_H
|
||||
#define PATRIX_HTTP_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
void httpSetup();
|
||||
extern AsyncWebServer server;
|
||||
|
||||
void httpLoop();
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <patrix/core/log.h>
|
||||
#include <patrix/core/mqtt.h>
|
||||
#include <patrix/core/wifi.h>
|
||||
#include <patrix/node/PatrixNode.h>
|
||||
|
||||
#define MQTT_RETRY_DELAY_MILLIS 3000
|
||||
|
||||
@ -32,14 +33,25 @@ void mqttLoop() {
|
||||
mqtt.setKeepAlive(10);
|
||||
mqtt.setBufferSize(512);
|
||||
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")) {
|
||||
info("mqtt connected");
|
||||
info("MQTT connected");
|
||||
yield();
|
||||
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();
|
||||
} 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) {
|
||||
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());
|
||||
}
|
||||
|
||||
@ -11,4 +11,8 @@ void mqttLoop();
|
||||
|
||||
bool mqttPublish(const String& topic, const String& payload, Retain retain);
|
||||
|
||||
void mqttSubscribe(const String& topic);
|
||||
|
||||
void mqttUnsubscribe(const String& topic);
|
||||
|
||||
#endif
|
||||
|
||||
@ -18,7 +18,7 @@ auto wifiHost = WIFI_HOST;
|
||||
|
||||
void wifiSetupOTA() {
|
||||
ArduinoOTA.onStart([] {
|
||||
info("beginning ota update...");
|
||||
info("Beginning ota update...");
|
||||
Serial.print("OTA-UPDATE: 0%");
|
||||
});
|
||||
ArduinoOTA.onProgress([](const unsigned done, const unsigned total) {
|
||||
@ -54,7 +54,7 @@ void wifiSetupOTA() {
|
||||
}
|
||||
|
||||
void wifiOff() {
|
||||
info("wifi disabled");
|
||||
info("WiFi disabled");
|
||||
wifiEnabled = false;
|
||||
WiFi.disconnect();
|
||||
}
|
||||
@ -64,10 +64,10 @@ void wifiLoop() {
|
||||
if (wifiConnected != currentState) {
|
||||
wifiConnected = currentState;
|
||||
if (wifiConnected) {
|
||||
info("wifi connected as %s", WiFi.localIP().toString().c_str());
|
||||
info("WiFi connected as %s", WiFi.localIP().toString().c_str());
|
||||
wifiSetupOTA();
|
||||
} else {
|
||||
warn("wifi disconnected");
|
||||
warn("WiFi disconnected");
|
||||
ArduinoOTA.end();
|
||||
WiFi.disconnect();
|
||||
}
|
||||
|
||||
314
src/patrix/display/Display.h
Normal file
314
src/patrix/display/Display.h
Normal 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
|
||||
@ -1,103 +1,32 @@
|
||||
#ifndef DISPLAY_MATRIX_H
|
||||
#define DISPLAY_MATRIX_H
|
||||
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
|
||||
#include "DisplayMatrix_FontCommon.h"
|
||||
#include "Display.h"
|
||||
|
||||
template<int W, int H>
|
||||
class DisplayMatrix {
|
||||
|
||||
Adafruit_NeoPixel leds;
|
||||
class DisplayMatrix final : public Display {
|
||||
|
||||
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:
|
||||
|
||||
RGBA foreground = White;
|
||||
|
||||
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) {
|
||||
explicit DisplayMatrix(const int16_t pin)
|
||||
: Display(pin, W, H) {
|
||||
//
|
||||
}
|
||||
|
||||
void setup() {
|
||||
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);
|
||||
~DisplayMatrix() override = default;
|
||||
|
||||
};
|
||||
|
||||
// ReSharper disable once CppUnusedIncludeDirective
|
||||
#include <patrix/display/DisplayMatrix_draw.h>
|
||||
|
||||
// ReSharper disable once CppUnusedIncludeDirective
|
||||
#include <patrix/display/DisplayMatrix_print.h>
|
||||
|
||||
#endif
|
||||
|
||||
@ -83,7 +83,7 @@ Symbol5 FONT_LOWER[] = {
|
||||
{X,_,_,_,_},
|
||||
{X,_,_,_,_},
|
||||
{X,_,_,_,_},
|
||||
{_,X,_,_,_},
|
||||
{X,X,_,_,_},
|
||||
},
|
||||
{
|
||||
{_,_,_,_,_},
|
||||
|
||||
@ -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,_},
|
||||
},
|
||||
};
|
||||
@ -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
|
||||
74
src/patrix/display/DisplayMatrix_FontNumberSegments.cpp
Normal file
74
src/patrix/display/DisplayMatrix_FontNumberSegments.cpp
Normal 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},
|
||||
},
|
||||
};
|
||||
8
src/patrix/display/DisplayMatrix_FontNumberSegments.h
Normal file
8
src/patrix/display/DisplayMatrix_FontNumberSegments.h
Normal 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
|
||||
@ -1,13 +1,5 @@
|
||||
#include "DisplayMatrix_FontSpecial.h"
|
||||
|
||||
Symbol3 FONT_CHAR_SPACE = {
|
||||
{_,_,_},
|
||||
{_,_,_},
|
||||
{_,_,_},
|
||||
{_,_,_},
|
||||
{_,_,_},
|
||||
};
|
||||
|
||||
Symbol3 FONT_CHAR_MINUS = {
|
||||
{_,_,_},
|
||||
{_,_,_},
|
||||
@ -237,11 +229,11 @@ Symbol4 FONT_ERROR_ = {
|
||||
};
|
||||
|
||||
Symbol5 FONT_CHAR_PERCENT = {
|
||||
{X,_,_,_,X},
|
||||
{_,_,_,X,_},
|
||||
{_,_,X,_,_},
|
||||
{_,X,_,_,_},
|
||||
{X,_,_,_,X},
|
||||
{X,_,_},
|
||||
{_,_,X},
|
||||
{_,X,_},
|
||||
{X,_,_},
|
||||
{_,_,X},
|
||||
};
|
||||
|
||||
//@formatter:off
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
|
||||
#include "DisplayMatrix_FontCommon.h"
|
||||
|
||||
extern Symbol3 FONT_CHAR_SPACE;
|
||||
extern Symbol3 FONT_CHAR_MINUS;
|
||||
extern Symbol3 FONT_CHAR_PLUS;
|
||||
extern Symbol3 FONT_CHAR_UNDERLINE;
|
||||
|
||||
@ -2,11 +2,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,_,_},
|
||||
@ -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,_},
|
||||
@ -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,_,_},
|
||||
@ -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,_,_},
|
||||
@ -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,_,_},
|
||||
},
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
#include "DisplayMatrix_common.h"
|
||||
|
||||
// ReSharper disable CppVariableCanBeMadeConstexpr
|
||||
// @formatter:off
|
||||
const RGBA Transparent { 0, 0, 0, 0};
|
||||
const RGBA Black { 0, 0, 0, 255};
|
||||
const RGBA Red {255, 0, 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 Cyan { 0, 255, 255, 255};
|
||||
const RGBA Blue { 0, 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 White {255, 255, 255, 255};
|
||||
// @formatter:on
|
||||
// ReSharper restore CppVariableCanBeMadeConstexpr
|
||||
|
||||
@ -13,6 +13,38 @@ struct RGBA {
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
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 {
|
||||
@ -20,18 +52,26 @@ struct RGB {
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
|
||||
void operator =(const RGBA& color) {
|
||||
blend(&this->r, color.r, color.a);
|
||||
blend(&this->g, color.g, color.a);
|
||||
blend(&this->b, color.b, color.a);
|
||||
RGB &operator =(const RGBA& color) {
|
||||
r = color.r;
|
||||
g = color.g;
|
||||
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) {
|
||||
const auto factor = alpha / 255.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 Red;
|
||||
extern const RGBA Yellow;
|
||||
extern const RGBA Orange;
|
||||
extern const RGBA Green;
|
||||
extern const RGBA Cyan;
|
||||
extern const RGBA Blue;
|
||||
extern const RGBA Magenta;
|
||||
extern const RGBA Violet;
|
||||
extern const RGBA Gray;
|
||||
extern const RGBA White;
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -1,4 +1,4 @@
|
||||
#include <patrix/node/PatrixNode.h>
|
||||
|
||||
// ReSharper disable once CppUseAuto
|
||||
PatrixNode patrixNode = patrixGetNode();
|
||||
PatrixNode &patrixNode = patrixGetNode();
|
||||
|
||||
@ -22,15 +22,17 @@ public:
|
||||
|
||||
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) {}
|
||||
|
||||
};
|
||||
|
||||
extern PatrixNode patrixNode;
|
||||
extern PatrixNode& patrixNode;
|
||||
|
||||
PatrixNode &patrixGetNode();
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user