splittet Display into combinable parts

This commit is contained in:
Patrick Haßel 2025-01-13 16:34:47 +01:00
parent 3fad49cfb4
commit 85e635169d
20 changed files with 1040 additions and 392 deletions

29
data/http/index.css Normal file
View File

@ -0,0 +1,29 @@
body {
font-family: sans-serif;
font-size: 8vw;
margin: 0;
}
button {
width: 33vmin;
font-size: 9vw;
}
button.cancel {
width: 15vmin;
height: 15vmin;
}
table {
border-collapse: collapse;
}
td {
text-align: center;
}
svg {
border: 1px solid black;
width: 100%;
height: calc(11 / 27 * 100%);
}

View File

@ -5,41 +5,11 @@
<meta name="viewport" content="width=device-width, user-scalable=no"> <meta name="viewport" content="width=device-width, user-scalable=no">
<title>Sporttafel</title> <title>Sporttafel</title>
<link rel="icon" href="icon.svg"> <link rel="icon" href="icon.svg">
<style> <script src="index.js"></script>
body { <link rel="stylesheet" href="index.css">
font-family: sans-serif;
font-size: 8vw;
margin: 0;
}
button {
width: 33vmin;
font-size: 9vw;
}
button.cancel {
width: 15vmin;
height: 15vmin;
}
table {
border-collapse: collapse;
}
td {
text-align: center;
}
svg {
border: 1px solid black;
width: 100%;
height: calc(11 / 27 * 100%);
}
</style>
</head> </head>
<body> <body>
<svg width="800" height="325" id="display" style="background-color: black"> <svg width="800" height="325" id="display" style="background-color: black"></svg>
</svg>
<table> <table>
<tr> <tr>
<td> <td>
@ -68,130 +38,24 @@
</td> </td>
<td>&nbsp;</td> <td>&nbsp;</td>
</tr> </tr>
<tr>
<script> <td>
<button onclick="get('/action/left/up')">&uarr;</button>
const DEV_HOST = "10.0.0.119"; </td>
<td>&nbsp;</td>
const S = 100 / 27; <td>
<button onclick="get('/action/right/up')">&uarr;</button>
const display = document.getElementById("display"); </td>
</tr>
const segments = []; <tr>
<td>
function url(protocol, path) { <button onclick="get('/action/left/down')">&darr;</button>
const hostPart = location.host.substring(0, location.host.indexOf(":")) </td>
const isLocal = hostPart === "" || hostPart === "localhost" || hostPart === "0"; <td>&nbsp;</td>
const host = isLocal ? DEV_HOST : hostPart; <td>
const port = isLocal ? "80" : location.port; <button onclick="get('/action/right/down')">&darr;</button>
return `${protocol}://${host}:${port}${path}`; </td>
} </tr>
function get(path) {
const request = new XMLHttpRequest();
request.open("GET", url("http", path), true);
request.send();
}
function drawDigit(x, y) {
drawPixel(x, y, 0, 3);
drawPixel(x, y, 0, 2);
drawPixel(x, y, 0, 1);
drawPixel(x, y, 1, 0);
drawPixel(x, y, 2, 0);
drawPixel(x, y, 3, 0);
drawPixel(x, y, 4, 1);
drawPixel(x, y, 4, 2);
drawPixel(x, y, 4, 3);
drawPixel(x, y, 4, 5);
drawPixel(x, y, 4, 6);
drawPixel(x, y, 4, 7);
drawPixel(x, y, 3, 8);
drawPixel(x, y, 2, 8);
drawPixel(x, y, 1, 8);
drawPixel(x, y, 0, 7);
drawPixel(x, y, 0, 6);
drawPixel(x, y, 0, 5);
drawPixel(x, y, 1, 4);
drawPixel(x, y, 2, 4);
drawPixel(x, y, 3, 4);
}
function drawPixel(offsetRasterX, offsetRasterY, innerRasterX, innerRasterY) {
const segment = document.createElementNS("http://www.w3.org/2000/svg", "rect");
const x = (offsetRasterX + innerRasterX) * S;
const y = (offsetRasterY + innerRasterY) * S;
segment.setAttribute("x", x + "vw");
segment.setAttribute("y", y + "vw");
segment.setAttribute("width", S + "vw");
segment.setAttribute("height", S + "vw");
segment.setAttribute("stroke", "white");
segment.setAttribute("fill", "none");
segment.setAttribute("id", "segment" + segments.length);
display.appendChild(segment);
segments.push(segment);
}
function drawDisplay(rasterX, rasterY) {
drawDigit(rasterX, rasterY);
rasterX += 6;
drawDigit(rasterX, rasterY);
rasterX += 6;
drawPixel(rasterX, rasterY, 0, 0);
drawPixel(rasterX, rasterY + 2, 0, 0);
drawPixel(rasterX, rasterY + 6, 0, 0);
drawPixel(rasterX, rasterY + 8, 0, 0);
rasterX += 2;
drawDigit(rasterX, rasterY);
rasterX += 6;
drawDigit(rasterX, rasterY);
}
function connect() {
console.log("connecting websocket...");
const socket = new WebSocket(url("ws", "/ws"));
socket.timeout = 1;
socket.addEventListener('open', _ => {
console.log('websocket connected');
segments.forEach(segment => {
segment.setAttribute("fill", "none");
segment.setAttribute("stroke", "none");
});
});
socket.addEventListener('message', event => {
const len = event.data.length;
let index = 0;
let start = 0;
while (start < len) {
const end = start + 3;
const color = event.data.substring(start, end);
const segment = segments[index];
segment.setAttribute("fill", "#" + color)
index++;
start = end;
}
});
socket.addEventListener('close', _ => {
console.log('websocket disconnected');
segments.forEach(segment => {
segment.setAttribute("fill", "none");
segment.setAttribute("stroke", "white");
});
setTimeout(connect, 1000);
});
}
drawDisplay(1, 1);
setTimeout(connect, 1000);
</script>
</table> </table>
</body> </body>
</html> </html>

118
data/http/index.js Normal file
View File

@ -0,0 +1,118 @@
const DEV_HOST = "10.0.0.119";
const S = 100 / 27;
const display = document.getElementById("display");
const segments = [];
function url(protocol, path) {
const hostPart = location.host.substring(0, location.host.indexOf(":"))
const isLocal = hostPart === "" || hostPart === "localhost" || hostPart === "0";
const host = isLocal ? DEV_HOST : hostPart;
const port = isLocal ? "80" : location.port;
return `${protocol}://${host}:${port}${path}`;
}
function get(path) {
const request = new XMLHttpRequest();
request.open("GET", url("http", path), true);
request.send();
}
function drawDigit(x, y) {
drawPixel(x, y, 0, 3);
drawPixel(x, y, 0, 2);
drawPixel(x, y, 0, 1);
drawPixel(x, y, 1, 0);
drawPixel(x, y, 2, 0);
drawPixel(x, y, 3, 0);
drawPixel(x, y, 4, 1);
drawPixel(x, y, 4, 2);
drawPixel(x, y, 4, 3);
drawPixel(x, y, 4, 5);
drawPixel(x, y, 4, 6);
drawPixel(x, y, 4, 7);
drawPixel(x, y, 3, 8);
drawPixel(x, y, 2, 8);
drawPixel(x, y, 1, 8);
drawPixel(x, y, 0, 7);
drawPixel(x, y, 0, 6);
drawPixel(x, y, 0, 5);
drawPixel(x, y, 1, 4);
drawPixel(x, y, 2, 4);
drawPixel(x, y, 3, 4);
}
function drawPixel(offsetRasterX, offsetRasterY, innerRasterX, innerRasterY) {
const segment = document.createElementNS("http://www.w3.org/2000/svg", "rect");
const x = (offsetRasterX + innerRasterX) * S;
const y = (offsetRasterY + innerRasterY) * S;
segment.setAttribute("x", x + "vw");
segment.setAttribute("y", y + "vw");
segment.setAttribute("width", S + "vw");
segment.setAttribute("height", S + "vw");
segment.setAttribute("stroke", "white");
segment.setAttribute("fill", "none");
segment.setAttribute("id", "segment" + segments.length);
display.appendChild(segment);
segments.push(segment);
}
function drawDisplay(rasterX, rasterY) {
drawDigit(rasterX, rasterY);
rasterX += 6;
drawDigit(rasterX, rasterY);
rasterX += 6;
drawPixel(rasterX, rasterY, 0, 0);
drawPixel(rasterX, rasterY + 2, 0, 0);
drawPixel(rasterX, rasterY + 6, 0, 0);
drawPixel(rasterX, rasterY + 8, 0, 0);
rasterX += 2;
drawDigit(rasterX, rasterY);
rasterX += 6;
drawDigit(rasterX, rasterY);
}
function connect() {
console.log("connecting websocket...");
const socket = new WebSocket(url("ws", "/ws"));
socket.timeout = 1;
socket.addEventListener('open', _ => {
console.log('websocket connected');
segments.forEach(segment => {
segment.setAttribute("fill", "none");
segment.setAttribute("stroke", "none");
});
});
socket.addEventListener('message', event => {
const len = event.data.length;
let index = 0;
let start = 0;
while (start < len) {
const end = start + 3;
const color = event.data.substring(start, end);
const segment = segments[index];
segment.setAttribute("fill", "#" + color)
index++;
start = end;
}
});
socket.addEventListener('close', _ => {
console.log('websocket disconnected');
segments.forEach(segment => {
segment.setAttribute("fill", "none");
segment.setAttribute("stroke", "white");
});
setTimeout(connect, 1000);
});
}
drawDisplay(1, 1);
setTimeout(connect, 1000);

View File

@ -4,7 +4,7 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <LittleFS.h> #include <LittleFS.h>
#include <core/log.h> #include <core/log.h>
#include <display/Display.h> #include <display/MyDisplay.h>
#define CONFIG_WRITE_DELAY_MILLIS (30 * 1000) #define CONFIG_WRITE_DELAY_MILLIS (30 * 1000)
@ -87,6 +87,22 @@ public:
// //
} }
virtual void leftUp() {
//
}
virtual void leftDown() {
//
}
virtual void rightUp() {
//
}
virtual void rightDown() {
//
}
virtual bool setConfig(const String& key, const String& valueStr) { virtual bool setConfig(const String& key, const String& valueStr) {
return false; return false;
} }

View File

@ -71,7 +71,7 @@ protected:
void draw() override { void draw() override {
display.clear(); display.clear();
display.strokeRect(step, step, DISPLAY_VIRTUAL_WIDTH - 1 - 2 * step, DISPLAY_VIRTUAL_HEIGHT - 1 - 2 * step); display.main.strokeRect(step, step, DISPLAY_VIRTUAL_WIDTH - 1 - 2 * step, DISPLAY_VIRTUAL_HEIGHT - 1 - 2 * step);
} }
}; };

View File

@ -52,6 +52,10 @@ class AppMatch final : public App {
State state = INITIAL; State state = INITIAL;
int pointsLeft = 0;
int pointsRight = 0;
public: public:
explicit AppMatch() explicit AppMatch()
@ -118,12 +122,35 @@ public:
} }
} }
void leftUp() override {
pointsLeft++;
markDirty(true);
}
void leftDown() override {
pointsLeft = max(0, pointsLeft - 1);
markDirty(true);
}
void rightUp() override {
pointsRight++;
markDirty(true);
}
void rightDown() override {
pointsRight = max(0, pointsRight - 1);
markDirty(true);
}
protected: protected:
void _start() override { void _start() override {
configMillis = configGet<unsigned long>(CONFIG_SECONDS_KEY, CONFIG_SECONDS_DEFAULT) * MS_PER_SEC; configMillis = configGet<unsigned long>(CONFIG_SECONDS_KEY, CONFIG_SECONDS_DEFAULT) * MS_PER_SEC;
configCentis = configGet<bool>(CONFIG_CENTIS_KEY, CONFIG_CENTIS_DEFAULT); configCentis = configGet<bool>(CONFIG_CENTIS_KEY, CONFIG_CENTIS_DEFAULT);
pointsLeft = 0;
pointsRight = 0;
info("config:"); info("config:");
info(" seconds = %ld", configMillis / MS_PER_SEC); info(" seconds = %ld", configMillis / MS_PER_SEC);
info(" centis = %s", configCentis ? "true" : "false"); info(" centis = %s", configCentis ? "true" : "false");
@ -177,17 +204,21 @@ protected:
} }
if (blinkIntervalMillis == 0 || blinkState) { if (blinkIntervalMillis == 0 || blinkState) {
if (totalMinutes > 0) { if (totalMinutes > 0) {
display.printf("%2d:%02d", totalMinutes, partSeconds); display.main.printf("%2d:%02d", totalMinutes, partSeconds);
} else if (totalMillis > 0) { } else if (totalMillis > 0) {
if (configCentis) { if (configCentis) {
display.printf("%2d.%02d", partSeconds, partCentis); display.main.printf("%2d.%02d", partSeconds, partCentis);
} else { } else {
display.printf(" %2d", partSeconds); display.main.printf(" %2d", partSeconds);
} }
} else { } else {
display.printf("00:00"); display.main.printf("00:00");
} }
} }
// ReSharper disable once CppExpressionWithoutSideEffects
display.left.printf("%2d", pointsLeft);
// ReSharper disable once CppExpressionWithoutSideEffects
display.right.printf("%2d", pointsRight);
} }
private: private:

12
src/core/BASICS.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef BASICS_H
#define BASICS_H
#include <Arduino.h>
#include "log.h"
#define X true
#define _ false
#define countof(a) (sizeof(a) / sizeof((a)[0]))
#endif

View File

@ -53,6 +53,34 @@ void httpActionConfirm(AsyncWebServerRequest *request) {
request->send(200); request->send(200);
} }
void httpActionLeftUp(AsyncWebServerRequest *request) {
if (app != nullptr) {
app->leftUp();
}
request->send(200);
}
void httpActionLeftDown(AsyncWebServerRequest *request) {
if (app != nullptr) {
app->leftDown();
}
request->send(200);
}
void httpActionRightUp(AsyncWebServerRequest *request) {
if (app != nullptr) {
app->rightUp();
}
request->send(200);
}
void httpActionRightDown(AsyncWebServerRequest *request) {
if (app != nullptr) {
app->rightDown();
}
request->send(200);
}
void httpAppConfig(AsyncWebServerRequest *request) { void httpAppConfig(AsyncWebServerRequest *request) {
if (!request->hasArg("key")) { if (!request->hasArg("key")) {
error("missing parameter 'key'"); error("missing parameter 'key'");
@ -97,6 +125,10 @@ void httpSetup() {
server.on("/action/right", HTTP_GET, httpActionRight); server.on("/action/right", HTTP_GET, httpActionRight);
server.on("/action/cancel", HTTP_GET, httpActionCancel); server.on("/action/cancel", HTTP_GET, httpActionCancel);
server.on("/action/confirm", HTTP_GET, httpActionConfirm); server.on("/action/confirm", HTTP_GET, httpActionConfirm);
server.on("/action/left/up", HTTP_GET, httpActionLeftUp);
server.on("/action/left/down", HTTP_GET, httpActionLeftDown);
server.on("/action/right/up", HTTP_GET, httpActionRightUp);
server.on("/action/right/down", HTTP_GET, httpActionRightDown);
server.on("/app/config", HTTP_GET, httpAppConfig); server.on("/app/config", HTTP_GET, httpAppConfig);
server.serveStatic("/", LittleFS, "/http/", "max-age=86400").setDefaultFile("index.html"); server.serveStatic("/", LittleFS, "/http/", "max-age=86400").setDefaultFile("index.html");
server.onNotFound(httpNotFound); server.onNotFound(httpNotFound);

View File

@ -1,3 +0,0 @@
#include "Display.h"
Display display;

View File

@ -5,53 +5,69 @@
#include <AsyncWebSocket.h> #include <AsyncWebSocket.h>
#include <cstdint> #include <cstdint>
#include <core/log.h>
#include "Color.h" #include "Color.h"
#include "font.h"
#include "core/http.h" #include "core/http.h"
#define PIXELS_PER_SEGMENT 3
#define SEGMENTS_PER_DIGIT 7
#define PIXELS_PER_DOT 1
#define DOT_COUNT 4
#define DIGIT_COUNT 4
#define PIXELS_PER_DIGIT (PIXELS_PER_SEGMENT * SEGMENTS_PER_DIGIT)
#define TOTAL_DOT_PIXEL_COUNT (PIXELS_PER_DOT * DOT_COUNT)
#define TOTAL_SEGMENT_PIXEL_COUNT (DIGIT_COUNT * PIXELS_PER_DIGIT)
#define TOTAL_PIXEL_COUNT (TOTAL_SEGMENT_PIXEL_COUNT + TOTAL_DOT_PIXEL_COUNT)
#define TOTAL_PIXEL_BYTE_COUNT (TOTAL_PIXEL_COUNT * sizeof(Color))
#define HEX_BUFFER_SIZE (TOTAL_PIXEL_BYTE_COUNT + 1)
#define DISPLAY_VIRTUAL_WIDTH 25
#define DISPLAY_VIRTUAL_HEIGHT 9
#define HEX_BUFFER_MIN_WAIT_MS 500 #define HEX_BUFFER_MIN_WAIT_MS 500
class Display { class Display {
// Adafruit_NeoPixel leds; // Adafruit_NeoPixel leds;
Color pixels[TOTAL_PIXEL_COUNT] = {}; size_t totalPixelCount;
size_t pixelByteCount;
Color *pixels;
char *hexBuffer;
Color color = WHITE; Color color = WHITE;
char hexBuffer[HEX_BUFFER_SIZE] = ""; protected:
struct Mapping { explicit Display(const int totalPixelCount) :/* leds(totalPixelCount, GPIO_NUM_13) ,*/ totalPixelCount(totalPixelCount), pixelByteCount(totalPixelCount * sizeof(Color)) {
uint8_t x;
uint8_t y;
};
public:
Display() /* : leds(PIXEL_COUNT, GPIO_NUM_13) */ {
// leds.begin(); // leds.begin();
pixels = static_cast<Color *>(malloc(pixelByteCount));
if (pixels == nullptr) {
error("Failed to allocate memory for Display.pixels");
}
hexBuffer = static_cast<char *>(malloc(pixelByteCount + 1));
if (hexBuffer == nullptr) {
error("Failed to allocate memory for Display.hexBuffer");
} else {
*hexBuffer = 0;
}
setBrightness(6); setBrightness(6);
clear(); clear();
flush(); flush();
} }
~Display() {
if (pixels != nullptr) {
free(pixels);
pixels = nullptr;
}
if (hexBuffer != nullptr) {
free(hexBuffer);
hexBuffer = nullptr;
}
}
public:
void setPixel(const int index) const {
if (pixels == nullptr) {
return;
}
pixels[index] = color;
}
void setColor(const Color newColor) { void setColor(const Color newColor) {
this->color = newColor; this->color = newColor;
} }
@ -60,12 +76,15 @@ public:
// leds.setBrightness(brightness); // leds.setBrightness(brightness);
} }
void clear() { void clear() const {
memset(pixels, 0, TOTAL_PIXEL_BYTE_COUNT); if (pixels == nullptr) {
return;
}
memset(pixels, 0, pixelByteCount);
} }
void flush(const bool forceNextHexBuffer = false) { void flush(const bool forceNextHexBuffer = false) const {
// memcpy(leds.getPixels(), buffer, PIXEL_BYTE_COUNT); // memcpy(leds.getPixels(), buffer, pixelByteCount);
// leds.show(); // leds.show();
const auto now = millis() - HEX_BUFFER_MIN_WAIT_MS; const auto now = millis() - HEX_BUFFER_MIN_WAIT_MS;
@ -73,178 +92,43 @@ public:
if (now - last >= HEX_BUFFER_MIN_WAIT_MS || forceNextHexBuffer) { if (now - last >= HEX_BUFFER_MIN_WAIT_MS || forceNextHexBuffer) {
last = now; last = now;
fillHexBuffer(); fillHexBuffer();
sendHexBufferAll();
}
}
void sendHexBufferAll() const {
if (hexBuffer == nullptr) {
return;
}
if (*hexBuffer == 0) {
return;
}
websocketSendAll(hexBuffer); websocketSendAll(hexBuffer);
} }
}
void printf(const char *format, ...) { void sendHexBuffer(AsyncWebSocketClient *client) const {
char buffer[64]; if (hexBuffer == nullptr) {
return;
va_list args;
va_start(args, format);
vsnprintf(buffer, sizeof buffer, format, args);
va_end(args);
print(buffer);
} }
if (*hexBuffer == 0) {
void print(const char *str) { return;
auto digit = 0;
for (auto c = str; *c != 0 && digit < 4; c++) {
switch (*c) {
case '\'':
case '"':
case '`':
case '.':
printDots(false, false, false, true);
digit = 2;
break;
case ',':
printDots(false, false, true, true);
digit = 2;
break;
case ':':
printDots(false, true, true, false);
digit = 2;
break;
case ';':
printDots(false, true, true, true);
digit = 2;
break;
case '|':
printDots(true, true, true, true);
digit = 2;
break;
default:
printCharacter(digit++, *c);
break;
} }
}
}
void printDots(const bool dot0, const bool dot1, const bool dot2, const bool dot3) {
auto pixel = PIXELS_PER_DIGIT * 2;
if (dot0) {
pixels[pixel] = color;
}
pixel++;
if (dot1) {
pixels[pixel] = color;
}
pixel++;
if (dot2) {
pixels[pixel] = color;
}
pixel++;
if (dot3) {
pixels[pixel] = color;
}
}
void printCharacter(int digit, const char character) {
auto pixel = digit * PIXELS_PER_DIGIT + (digit >= 2 ? TOTAL_DOT_PIXEL_COUNT : 0);
const auto symbol = getSymbol(character);
for (auto s = *symbol; s < *symbol + SYMBOL_SIZE; s++) {
if (*s) {
pixels[pixel++] = color;
pixels[pixel++] = color;
pixels[pixel++] = color;
} else {
pixel += 3;
}
}
}
void sendHexBuffer(AsyncWebSocketClient *client) {
client->text(hexBuffer); client->text(hexBuffer);
} }
void fillRect(const uint8_t x, uint8_t y, uint8_t w, uint8_t h) { private
for (int dx = 0; dx < w; ++dx) { :
for (int dy = 0; dy < h; ++dy) {
drawVirtualPixel(x + dx, y + dy);
}
}
}
void strokeRect(const uint8_t x, uint8_t y, uint8_t w, uint8_t h) { void fillHexBuffer() const {
for (int dx = 0; dx < w; ++dx) { if (pixels == nullptr || hexBuffer == nullptr) {
drawVirtualPixel(x + dx, y); return;
drawVirtualPixel(x + dx, y + h);
} }
for (int dy = 0; dy < h; ++dy) {
drawVirtualPixel(x, y + dy);
drawVirtualPixel(x + w, y + dy);
}
}
void drawVirtualPixel(const uint8_t x, const uint8_t y) {
const auto index = findIndexForVirtualCoordinates(x, y);
if (index >= 0) {
pixels[index] = color;
}
}
private:
Mapping digitMapping[32] = {
// @formatter:off
{0, 3}, {0, 2}, {0, 1}, // top left
{1, 0}, {2, 0}, {3, 0}, // top
{4, 1}, {4, 2}, {4, 3}, // top right
{4, 5}, {4, 6}, {4, 7}, // bottom right
{3, 8}, {2, 8}, {1, 8}, // bottom
{0, 7}, {0, 6}, {0, 5}, // bottom left
{1, 4}, {2, 4}, {3, 4}, // middle
// @formatter:on
};
uint8_t dotMapping[4] = {0, 2, 6, 8};
uint8_t findIndexForVirtualCoordinates(const uint8_t x, const uint8_t y) {
if (x < 0 || x > 24 || y < 0 || y > 8) {
return -1;
} else if (x >= 20) {
return _findIndexForVirtualCoordinates_digitRelative(x - 20, y) + 3 * PIXELS_PER_DIGIT + TOTAL_DOT_PIXEL_COUNT;
} else if (x >= 14) {
return _findIndexForVirtualCoordinates_digitRelative(x - 14, y) + 2 * PIXELS_PER_DIGIT + TOTAL_DOT_PIXEL_COUNT;
} else if (x == 12) {
return _findIndexForVirtualCoordinates_dotRelative(y) + 2 * PIXELS_PER_DIGIT;
} else if (x >= 6) {
return _findIndexForVirtualCoordinates_digitRelative(x - 6, y) + 1 * PIXELS_PER_DIGIT;
} else {
return _findIndexForVirtualCoordinates_digitRelative(x, y) + 0 * PIXELS_PER_DIGIT;;
}
}
uint8_t _findIndexForVirtualCoordinates_digitRelative(const uint8_t x, const uint8_t y) {
for (auto index = 0; index < PIXELS_PER_DIGIT; index++) {
auto item = digitMapping[index];
if (item.x == x && item.y == y) {
return index;
}
}
return -1;
}
uint8_t _findIndexForVirtualCoordinates_dotRelative(const uint8_t y) {
for (auto index = 0; index < DOT_COUNT; index++) {
auto dotY = dotMapping[index];
if (dotY == y) {
return index;
}
}
return -1;
}
void fillHexBuffer() {
auto b = hexBuffer; auto b = hexBuffer;
for (const auto& pixel: pixels) { for (auto pixel = pixels; pixel < pixels + totalPixelCount; ++pixel) {
b += snprintf(b, sizeof hexBuffer - (b - hexBuffer), "%X%X%X", pixel.r / 16, pixel.g / 16, pixel.b / 16); b += snprintf(b, sizeof hexBuffer - (b - hexBuffer), "%X%X%X", pixel->r / 16, pixel->g / 16, pixel->b / 16);
} }
} }
}; };
extern Display display;
#endif #endif

View File

@ -0,0 +1,200 @@
#ifndef DISPLAY_DIGITS4_H
#define DISPLAY_DIGITS4_H
#include "DisplayPart.h"
#include "font.h"
#define PIXELS_PER_SEGMENT 3
#define SEGMENTS_PER_DIGIT 7
#define PIXELS_PER_DOT 1
#define DOT_COUNT 4
#define DIGIT_COUNT 4
#define PIXELS_PER_DIGIT (PIXELS_PER_SEGMENT * SEGMENTS_PER_DIGIT)
#define TOTAL_DOT_PIXEL_COUNT (PIXELS_PER_DOT * DOT_COUNT)
#define TOTAL_SEGMENT_PIXEL_COUNT (DIGIT_COUNT * PIXELS_PER_DIGIT)
#define TOTAL_POINTS_PIXEL_COUNT (4 * 3 * 5)
#define TOTAL_PIXEL_COUNT (TOTAL_SEGMENT_PIXEL_COUNT + TOTAL_DOT_PIXEL_COUNT + TOTAL_POINTS_PIXEL_COUNT)
#define DISPLAY_VIRTUAL_WIDTH 25
#define DISPLAY_VIRTUAL_HEIGHT 9
class DisplayDigits4 final : public DisplayPart {
struct Mapping {
uint8_t x;
uint8_t y;
};
public:
explicit DisplayDigits4(Display *display, const int indexOffset)
: DisplayPart(display, indexOffset,TOTAL_PIXEL_COUNT) {
//
}
void printf(const char *format, ...) const {
char buffer[64];
va_list args;
va_start(args, format);
vsnprintf(buffer, sizeof buffer, format, args);
va_end(args);
print(buffer);
}
void print(const char *str) const {
auto digit = 0;
for (auto c = str; *c != 0 && digit < 4; c++) {
switch (*c) {
case '\'':
case '"':
case '`':
case '.':
printDots(false, false, false, true);
digit = 2;
break;
case ',':
printDots(false, false, true, true);
digit = 2;
break;
case ':':
printDots(false, true, true, false);
digit = 2;
break;
case ';':
printDots(false, true, true, true);
digit = 2;
break;
case '|':
printDots(true, true, true, true);
digit = 2;
break;
default:
printCharacter(digit++, *c);
break;
}
}
}
void printDots(const bool dot0, const bool dot1, const bool dot2, const bool dot3) const {
auto pixel = PIXELS_PER_DIGIT * 2;
if (dot0) {
setIndexPixel(pixel);
}
pixel++;
if (dot1) {
setIndexPixel(pixel);
}
pixel++;
if (dot2) {
setIndexPixel(pixel);
}
pixel++;
if (dot3) {
setIndexPixel(pixel);
}
}
void printCharacter(const int digit, const char character) const {
auto pixel = digit * PIXELS_PER_DIGIT + (digit >= 2 ? TOTAL_DOT_PIXEL_COUNT : 0);
const auto symbol = getSegment7Character(character);
for (auto s = *symbol; s < *symbol + sizeof(Segment7Character); s++) {
if (*s) {
setIndexPixel(pixel++);
setIndexPixel(pixel++);
setIndexPixel(pixel++);
} else {
pixel += 3;
}
}
}
void fillRect(const uint8_t x, const uint8_t y, const uint8_t w, const uint8_t h) const {
for (auto dx = 0; dx < w; ++dx) {
for (auto dy = 0; dy < h; ++dy) {
setRasterPixel(x + dx, y + dy);
}
}
}
void strokeRect(const uint8_t x, const uint8_t y, const uint8_t w, const uint8_t h) const {
for (auto dx = 0; dx < w; ++dx) {
setRasterPixel(x + dx, y);
setRasterPixel(x + dx, y + h);
}
for (auto dy = 0; dy < h; ++dy) {
setRasterPixel(x, y + dy);
setRasterPixel(x + w, y + dy);
}
}
void setRasterPixel(const uint8_t x, const uint8_t y) const override {
const auto index = findIndexForVirtualCoordinates(x, y);
if (index >= 0) {
setIndexPixel(index);
}
}
private:
Mapping digitMapping[32] = {
// @formatter:off
{0, 3}, {0, 2}, {0, 1}, // top left
{1, 0}, {2, 0}, {3, 0}, // top
{4, 1}, {4, 2}, {4, 3}, // top right
{4, 5}, {4, 6}, {4, 7}, // bottom right
{3, 8}, {2, 8}, {1, 8}, // bottom
{0, 7}, {0, 6}, {0, 5}, // bottom left
{1, 4}, {2, 4}, {3, 4}, // middle
// @formatter:on
};
uint8_t dotMapping[4] = {0, 2, 6, 8};
int findIndexForVirtualCoordinates(const uint8_t x, const uint8_t y) const {
if (x < 0 || x > 24 || y < 0 || y > 8) {
return -1;
}
if (x >= 20) {
return _findIndexForVirtualCoordinates_digitRelative(x - 20, y) + 3 * PIXELS_PER_DIGIT + TOTAL_DOT_PIXEL_COUNT;
}
if (x >= 14) {
return _findIndexForVirtualCoordinates_digitRelative(x - 14, y) + 2 * PIXELS_PER_DIGIT + TOTAL_DOT_PIXEL_COUNT;
}
if (x == 12) {
return _findIndexForVirtualCoordinates_dotRelative(y) + 2 * PIXELS_PER_DIGIT;
}
if (x >= 6) {
return _findIndexForVirtualCoordinates_digitRelative(x - 6, y) + 1 * PIXELS_PER_DIGIT;
}
return _findIndexForVirtualCoordinates_digitRelative(x, y) + 0 * PIXELS_PER_DIGIT;
}
int _findIndexForVirtualCoordinates_digitRelative(const uint8_t x, const uint8_t y) const {
for (auto index = 0; index < PIXELS_PER_DIGIT; index++) {
const auto item = digitMapping[index];
if (item.x == x && item.y == y) {
return index;
}
}
return -1;
}
int _findIndexForVirtualCoordinates_dotRelative(const uint8_t y) const {
for (auto index = 0; index < DOT_COUNT; index++) {
const auto dotY = dotMapping[index];
if (dotY == y) {
return index;
}
}
return -1;
}
};
#endif

View File

@ -0,0 +1,66 @@
#ifndef DISPLAY_MATRIX_H
#define DISPLAY_MATRIX_H
#include "DisplayPart.h"
#include "DisplayRasterFont.h"
class DisplayMatrix final : public DisplayPart {
public:
const int rows;
DisplayMatrix(Display *display, const int indexOffset, const int cols, const int rows) : DisplayPart(display, indexOffset, cols * rows), rows(rows) {
//
}
int printf(const char *format, ...) const {
va_list args;
va_start(args, format);
const auto x = printf(0, 0, format, args);
va_end(args);
return x;
}
int printf(int x, const int y, const char *format, ...) const {
va_list args;
va_start(args, format);
x = printf(x, y, format, args);
va_end(args);
return x;
}
int printf(const int x, const int y, const char *format, const va_list args) const {
char buffer[128];
vsnprintf(buffer, sizeof buffer, format, args);
return print(x, y, buffer);
}
int print(int x, const int y, const char *text) const {
for (auto c = text; *c != '\0'; ++c) {
x = print(x, y, *c) + 1;
}
return x;
}
int print(const int x, const int y, const char c) const {
auto maxWidth = 0;
const auto character = getRasterCharacter(c);
for (auto cy = 0; cy < sizeof(RasterCharacter); cy++) {
for (auto cx = 0; cx < sizeof(RasterCharacter[0]); cx++) {
if (character[y][x]) {
setRasterPixel(x + cx, y + cy);
maxWidth = max(maxWidth, x + cx);
}
}
}
return x + maxWidth;
}
void setRasterPixel(const uint8_t x, const uint8_t y) const override {
setIndexPixel(x * rows + y);
}
};
#endif

37
src/display/DisplayPart.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef DISPLAYPART_H
#define DISPLAYPART_H
#include "Display.h"
class DisplayPart {
Display *display;
public:
const int indexOffset;
const int totalPixelCount;
const int nextPixelIndex;
protected:
DisplayPart(Display *display, const int indexOffset, const int totalPixelCount) : display(display), indexOffset(indexOffset), totalPixelCount(totalPixelCount), nextPixelIndex(indexOffset + totalPixelCount) {
//
}
virtual ~DisplayPart() = default;
public:
virtual void setRasterPixel(uint8_t x, uint8_t y) const = 0;
void setIndexPixel(const int index) const {
display->setPixel(indexOffset + index);
}
};
#endif

View File

@ -0,0 +1,325 @@
#include "DisplayRasterFont.h"
#include "../core/BASICS.h"
#define RASTER_FONT_NUMBER 0
#define RASTER_FONT_ALPHA (RASTER_FONT_NUMBER + 10)
#define RASTER_FONT_SPECIAL (RASTER_FONT_ALPHA + 26)
#define RASTER_FONT_COLON (RASTER_FONT_SPECIAL + 1)
#define RASTER_FONT_DASH (RASTER_FONT_COLON + 1)
#define RASTER_FONT_PERCENT (RASTER_FONT_DASH + 1)
#define RASTER_FONT_DEGREE (RASTER_FONT_PERCENT + 1)
#define RASTER_FONT_FALLBACK (RASTER_FONT_DEGREE + 1)
RasterCharacter *getRasterCharacter(const char character) {
if (character >= '0' && character <= '9') {
return &RASTER_FONT[character - '0' + RASTER_FONT_NUMBER];
}
if (character >= 'a' && character <= 'z') {
return &RASTER_FONT[character - 'a' + RASTER_FONT_ALPHA];
}
if (character >= 'A' && character <= 'Z') {
return &RASTER_FONT[character - 'A' + RASTER_FONT_ALPHA];
}
switch (character) {
case ':': return &RASTER_FONT[RASTER_FONT_COLON];
case '-': return &RASTER_FONT[RASTER_FONT_DASH];
case '%': return &RASTER_FONT[RASTER_FONT_PERCENT];
case '^': return &RASTER_FONT[RASTER_FONT_DEGREE];
default: {
error("[ERROR] NO RASTER MAPPING FOR CHARACTER \"%c\" = #%d\n", character, character);
return &RASTER_FONT[RASTER_FONT_FALLBACK];
}
}
}
RasterCharacter RASTER_FONT[] = {
{
{X, X, X},
{X, _, X},
{X, _, X},
{X, _, X},
{X, X, X},
}, // 0
{
{_, _, X},
{_, X, X},
{X, _, X},
{_, _, X},
{_, _, X},
}, // 1
{
{X, X, X},
{_, _, X},
{X, X, X},
{X, _, _},
{X, X, X},
}, // 2
{
{X, X, X},
{_, _, X},
{_, X, X},
{_, _, X},
{X, X, X},
}, // 3
{
{X, _, X},
{X, _, X},
{X, X, X},
{_, _, X},
{_, _, X},
}, // 4
{
{X, X, X},
{X, _, _},
{X, X, X},
{_, _, X},
{X, X, X},
}, // 5
{
{X, X, X},
{X, _, _},
{X, X, X},
{X, _, X},
{X, X, X},
}, // 6
{
{X, X, X},
{_, _, X},
{_, X, _},
{X, _, _},
{X, _, _},
}, // 7
{
{X, X, X},
{X, _, X},
{X, X, X},
{X, _, X},
{X, X, X},
}, // 8
{
{X, X, X},
{X, _, X},
{X, X, X},
{_, _, X},
{X, X, X},
}, // 9
{
{_, X, _},
{X, _, X},
{X, X, X},
{X, _, X},
{X, _, X},
}, // A
{
{X, X, _},
{X, _, X},
{X, X, _},
{X, _, X},
{X, X, _},
}, //
{
{_, X, X},
{X, _, _},
{X, _, _},
{X, _, _},
{_, X, X},
}, //
{
{X, X, _},
{X, _, X},
{X, _, X},
{X, _, X},
{X, X, _},
}, //
{
{X, X, X},
{X, _, _},
{X, X, _},
{X, _, _},
{X, X, X},
}, //
{
{X, X, X},
{X, _, _},
{X, X, _},
{X, _, _},
{X, _, _},
}, //
{
{_, X, X},
{X, _, _},
{X, _, X},
{X, _, X},
{_, X, _},
}, //
{
{X, _, X},
{X, _, X},
{X, X, X},
{X, _, X},
{X, _, X},
}, //
{
{_, X, _},
{_, X, _},
{_, X, _},
{_, X, _},
{_, X, _},
}, //
{
{X, X, X},
{_, _, X},
{_, _, X},
{X, _, X},
{_, X, _},
}, //
{
{X, _, X},
{X, X, _},
{X, X, _},
{X, _, _},
{X, _, X},
}, //
{
{X, _, _},
{X, _, _},
{X, _, _},
{X, _, _},
{X, X, X},
}, //
{
{X, _, X},
{X, X, X},
{X, _, X},
{X, _, X},
{X, _, X},
}, //
{
{_, _, _},
{_, _, _},
{_, X, _},
{X, _, X},
{X, _, X},
}, //
{
{_, _, _},
{_, _, _},
{_, X, _},
{X, _, X},
{_, X, _},
}, //
{
{X, X, _},
{X, _, X},
{X, X, _},
{X, _, _},
{X, _, _},
}, //
{
{_, _, _},
{_, X, X},
{X, _, X},
{_, X, X},
{_, _, X},
}, //
{
{X, X, _},
{X, _, X},
{X, X, _},
{X, X, _},
{X, _, X},
}, //
{
{_, X, X},
{X, _, _},
{_, X, _},
{_, _, X},
{X, X, _},
}, //
{
{X, X, X},
{_, X, _},
{_, X, _},
{_, X, _},
{_, X, _},
}, //
{
{X, _, X},
{X, _, X},
{X, _, X},
{X, _, X},
{_, X, _},
}, // :
{
{_, _, _},
{_, _, _},
{_, _, _},
{X, _, X},
{_, X, _},
}, // :
{
{X, _, X},
{X, _, X},
{X, _, X},
{X, X, X},
{X, _, X},
}, // :
{
{_, _, _},
{_, _, _},
{X, _, X},
{_, X, _},
{X, _, X},
}, // :
{
{X, _, X},
{X, _, X},
{_, X, _},
{_, X, _},
{X, _, _},
}, // :
{
{X, X, X},
{_, X, X},
{_, X, _},
{X, _, _},
{X, X, X},
}, // :
{
{_, _, _},
{_, X, _},
{_, _, _},
{_, X, _},
{_, _, _},
}, // :
{
{_, _, _},
{_, _, _},
{X, X, X},
{_, _, _},
{_, _, _},
}, // -
{
{X, _, _},
{_, _, X},
{_, X, _},
{X, _, _},
{_, _, X},
}, // %
{
{_, X, _},
{X, _, X},
{_, X, _},
{_, _, _},
{_, _, _},
}, // %
{
{X, X, X},
{X, X, X},
{X, X, X},
{X, X, X},
{X, X, X},
}, // FALLBACK
};

View File

@ -0,0 +1,10 @@
#ifndef RASTER_FONT_H
#define RASTER_FONT_H
typedef bool RasterCharacter[5][4];
extern RasterCharacter RASTER_FONT[];
RasterCharacter *getRasterCharacter(char character);
#endif

View File

@ -0,0 +1,3 @@
#include "MyDisplay.h"
MyDisplay display;

32
src/display/MyDisplay.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef MY_DISPLAY_H
#define MY_DISPLAY_H
#include "DisplayDigits4.h"
#include "DisplayMatrix.h"
class MyDisplay : public Display {
public:
DisplayDigits4 main;
DisplayMatrix left;
DisplayMatrix right;
explicit MyDisplay()
: Display(TOTAL_PIXEL_COUNT + 2 * 3 * 5),
main(this, 0),
left(this, main.nextPixelIndex, 3, 5),
right(this, left.nextPixelIndex, 3, 5) {
//
}
~MyDisplay() = default;
};
extern MyDisplay display;
#endif

View File

@ -1,9 +1,30 @@
#include "font.h" #include "font.h"
#include <Arduino.h> #include "../core/BASICS.h"
#include "../core/log.h"
SYMBOL SYMBOLS[][SYMBOL_SIZE] = { Segment7Character *getSegment7Character(const char character) {
if (character >= '0' && character <= '9') {
return &SEGMENT7_FONT[character - '0'];
}
if (character >= 'a' && character <= 'z') {
return &SEGMENT7_FONT[character - 'a' + 10];
}
if (character >= 'A' && character <= 'Z') {
return &SEGMENT7_FONT[character - 'A' + 10];
}
switch (character) {
case '-': return &SEGMENT7_FONT[36];
case '_': return &SEGMENT7_FONT[37];
case '^': return &SEGMENT7_FONT[38];
case ' ': return &SEGMENT7_FONT[39];
default: {
error("[ERROR] NO SEGMENT7 MAPPING FOR CHARACTER \"%c\" = #%d\n", character, character);
return &SEGMENT7_FONT[countof(SEGMENT7_FONT) - 1];
}
}
}
Segment7Character SEGMENT7_FONT[] = {
{X,X,X,X,X,X,_}, // 0 {X,X,X,X,X,X,_}, // 0
{_,_,X,X,_,_,_}, // 1 {_,_,X,X,_,_,_}, // 1
{_,X,X,_,X,X,X}, // 2 {_,X,X,_,X,X,X}, // 2
@ -45,25 +66,3 @@ SYMBOL SYMBOLS[][SYMBOL_SIZE] = {
{X,X,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: {
error("[ERROR] NO SYMBOL MAPPING FOR CHARACTER \"%c\" = #%d\n", character, character);
return SYMBOLS[SYMBOL_SIZE - 1];
}
}
}

View File

@ -1,17 +1,10 @@
#ifndef FONT_H #ifndef FONT_H
#define FONT_H #define FONT_H
#define X true typedef const bool Segment7Character[7];
#define _ false
constexpr auto SYMBOL_COUNT = 39; extern Segment7Character SEGMENT7_FONT[40];
constexpr auto SYMBOL_SIZE = 7; Segment7Character *getSegment7Character(char character);
typedef const bool SYMBOL[SYMBOL_SIZE];
extern SYMBOL SYMBOLS[][SYMBOL_SIZE];
SYMBOL *getSymbol(char character);
#endif #endif

View File

@ -12,7 +12,7 @@ void setup() {
bootDelay(); bootDelay();
fsMount(); fsMount();
httpSetup(); httpSetup();
appStart(APP_DEMO_NAME); appStart(APP_MATCH_NAME);
} }
void loop() { void loop() {