From f961306d40654ac6a1ab7c262af7af74401dc693 Mon Sep 17 00:00:00 2001 From: kkard2 Date: Wed, 10 Jun 2026 14:06:55 +0200 Subject: init --- src/sketch.cpp | 356 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 src/sketch.cpp (limited to 'src/sketch.cpp') diff --git a/src/sketch.cpp b/src/sketch.cpp new file mode 100644 index 0000000..168136c --- /dev/null +++ b/src/sketch.cpp @@ -0,0 +1,356 @@ +#ifndef ENABLE_EMULATION + #include "Arduino.h" +#endif + +#include "font_atlas.c" + +#include "driver.h" +#ifdef ENABLE_EMULATION + #include "driver_emulator.cpp" +#else + #include "driver.cpp" +#endif + +// === Config ================================================================== + +#define WIFI_SSID "Klatka schodowa (ekran)" +#define WIFI_PASSWORD "12345678" +#define WIFI_TIMEOUT_MS 300000 // 5 minutes +#define SCREEN_TIMEOUT_MS 60000 // 1 minute +#define DISPLAY_MARGIN 4 + +// === Color =================================================================== + +struct Color24 { uint8_t r, g, b; }; + +static uint16_t color565(uint8_t r, uint8_t g, uint8_t b) { + return ((r / 8) << 11) | ((g / 4) << 5) | (b / 8); +} + +static uint16_t color565(Color24 c) { + return color565(c.r, c.g, c.b); +} + +static Color24 color_lerp(Color24 a, Color24 b, float t) { + return { + (uint8_t)(a.r * (1-t) + b.r * t), + (uint8_t)(a.g * (1-t) + b.g * t), + (uint8_t)(a.b * (1-t) + b.b * t), + }; +} + +static uint8_t parse_hex2(const char *s) { + char buf[3] = { s[0], s[1], 0 }; + return (uint8_t)strtol(buf, nullptr, 16); +} + +static Color24 parse_color(const char *hex) { + if (!hex || hex[0] != '#' || strlen(hex) < 7) return {0, 0, 0}; + return { + parse_hex2(hex + 1), + parse_hex2(hex + 3), + parse_hex2(hex + 5), + }; +} + +// === Display primitives ====================================================== + +static void display_progress_bar( + int32_t x, int32_t y, int32_t w, int32_t h, float t, uint16_t fg, uint16_t bg +) { + int32_t fill = (int32_t)(w * t); + for (int32_t py = y; py < y + h; py++) { + for (int32_t px = x; px < x + w; px++) { + display_set_pixel(px, py, px < x + fill ? fg : bg); + } + } +} + +static void display_gradient_rect( + int32_t x, int32_t y, int32_t w, int32_t h, Color24 top, Color24 bot +) { + for (int32_t py = y; py < y + h; py++) { + float t = (float)(py - y) / (float)(h - 1); + uint16_t row_color = color565(color_lerp(top, bot, t)); + for (int32_t px = x; px < x + w; px++) + display_set_pixel(px, py, row_color); + } +} + +// === Text ==================================================================== + +static void display_write_char(int32_t *x, int32_t *y, uint8_t c, uint16_t color, int32_t scale) { + FontGlyph glyph = font_glyphs[c]; + for (int row = 0; row < FONT_CHAR_H; row++) { + for (int col = 0; col < glyph.width; col++) { + if (glyph.data[row] & (1 << col)) { + for (int sy = 0; sy < scale; sy++) { + for (int sx = 0; sx < scale; sx++) { + display_set_pixel(*x + col * scale + sx, *y + row * scale + sy, color); + } + } + } + } + } + *x += (glyph.width + 1) * scale; +} + +static int32_t measure_word(const uint8_t *text) { + int32_t w = 0; + while (*text && *text != ' ' && *text != '\n') { + w += (font_glyphs[*text].width + 1); + text++; + } + return w; +} + +static void display_write_text( + int32_t *x, int32_t *y, uint8_t *text, uint16_t color, int32_t scale, int32_t max_x, bool word_wrap +) { + int32_t x_start = *x; + + while (*text != 0) { + if (*text == '\n') { + *x = x_start; + *y += (FONT_CHAR_H + 1) * scale; + text++; + continue; + } + + if (word_wrap) { + if (*text == ' ') { + if (max_x > 0) { + int32_t word_w = measure_word(text + 1) * scale; + int32_t space_w = (font_glyphs[' '].width + 1) * scale; + if (*x + space_w + word_w > max_x) { + *x = x_start; + *y += (FONT_CHAR_H + 1) * scale; + text++; + continue; + } + } + display_write_char(x, y, ' ', color, scale); + text++; + continue; + } + + if (max_x > 0 && *x > x_start) { + int32_t word_w = measure_word(text) * scale; + int32_t line_w = max_x - x_start; + if (*x + word_w > max_x && word_w <= line_w) { + *x = x_start; + *y += (FONT_CHAR_H + 1) * scale; + } + } + } + + // Per-character wrap (always active; also handles oversized words in word_wrap mode). + if (max_x > 0 && *x + (int32_t)(font_glyphs[*text].width + 1) * scale > max_x) { + *x = x_start; + *y += (FONT_CHAR_H + 1) * scale; + } + + display_write_char(x, y, *text, color, scale); + text++; + } +} + +// === HTTP helpers ============================================================ + +static char *url_decode(const char *src, size_t src_len, char *dst, size_t dst_size) { + size_t i = 0; + while (src_len-- > 0 && *src && i < dst_size - 1) { + if (*src == '+') { + dst[i++] = ' '; src++; + } else if (*src == '%' && src[1] && src[2]) { + char hex[3] = { src[1], src[2], 0 }; + dst[i++] = (char)strtol(hex, nullptr, 16); + src += 3; src_len -= 2; + } else { + dst[i++] = *src++; + } + } + dst[i] = 0; + return dst; +} + +static bool form_get(const char *body, const char *key, char *out, size_t out_size) { + char search[64]; + snprintf(search, sizeof(search), "%s=", key); + const char *p = strstr(body ? body : "", search); + if (!p) return false; + p += strlen(search); + const char *end = strchr(p, '&'); + size_t len = end ? (size_t)(end - p) : strlen(p); + url_decode(p, len, out, out_size); + return true; +} + +// === Display state =========================================================== + +static uint8_t display_text[1024] = ""; +static Color24 display_fg = {255, 255, 255}; +static Color24 display_bg1 = { 0, 0, 0}; +static Color24 display_bg2 = { 0, 0, 0}; +static int32_t display_scale = 2; +static bool display_wrap = true; + +// === App state =============================================================== + +static bool display_active = true; +static bool wifi_active = false; +static uint32_t wifi_on_until = 0; +static uint32_t screen_on_until = 0; + +// === WiFi ==================================================================== + +static WifiResponse on_request(const WifiRequest *req); // forward decl + +static void wifi_start(void) { + wifi_hotspot_start(WIFI_SSID, WIFI_PASSWORD, on_request); + wifi_active = true; + wifi_on_until = millis() + WIFI_TIMEOUT_MS; + display_active = true; + display_on(); +} + +static void wifi_stop(void) { + wifi_hotspot_stop(); + wifi_active = false; + screen_on_until = millis() + SCREEN_TIMEOUT_MS; + display_active = true; + display_on(); +} + +#include "frontend.h" + +static WifiResponse on_request(const WifiRequest *req) { + wifi_on_until = millis() + WIFI_TIMEOUT_MS; + + if (strcmp(req->method, "POST") == 0 && strcmp(req->uri, "/submit") == 0) { + const char *body = req->body ? req->body : ""; + char buf[16] = {}; + + if (!form_get(body, "text", (char *)display_text, sizeof(display_text))) + return { 400, "text/html; charset=utf-8", err_html }; + + if (form_get(body, "fg", buf, sizeof(buf))) display_fg = parse_color(buf); + if (form_get(body, "bg1", buf, sizeof(buf))) display_bg1 = parse_color(buf); + if (form_get(body, "bg2", buf, sizeof(buf))) display_bg2 = parse_color(buf); + if (form_get(body, "scale", buf, sizeof(buf))) { + int32_t s = atoi(buf); + if (s >= 1 && s <= 4) display_scale = s; + } + display_wrap = strstr(body, "wrap=1") != nullptr; + + return { 200, "text/html; charset=utf-8", ok_html }; + } + if (strcmp(req->method, "POST") == 0 && strcmp(req->uri, "/wifi-off") == 0) { + wifi_stop(); + return { 200, "text/html; charset=utf-8", wifi_off_html }; + } + return { 200, "text/html; charset=utf-8", page_html }; +} + +// === Button ================================================================== + +static uint32_t button_held_since = 0; +static bool button_was_pressed = false; +static bool button_hold_fired = false; + +static void button_tick(uint32_t now) { + bool pressed = button_get(); + + if (pressed && !button_was_pressed) { + button_held_since = now; + button_hold_fired = false; + } + + if (pressed && !button_hold_fired && (now - button_held_since) > 2000) { + button_hold_fired = true; + if (wifi_active) wifi_stop(); + else wifi_start(); + } + + if (!pressed && button_was_pressed && !button_hold_fired) { + display_active = !display_active; + if (display_active) { + display_on(); + screen_on_until = now + SCREEN_TIMEOUT_MS; + } else if (!wifi_active) { + display_off(); + } + } + + button_was_pressed = pressed; +} + +// === Drawing ================================================================= + +static void draw_frame(uint32_t now) { + display_begin(); + + if (!display_active && !wifi_active) { + display_end(); + return; + } + + display_clear(color565(0, 0, 0)); + + // progress bar (top 3px) + { + float t = wifi_active + ? (float)(wifi_on_until - now) / (float)WIFI_TIMEOUT_MS + : (float)(screen_on_until - now) / (float)SCREEN_TIMEOUT_MS; + display_progress_bar(0, 0, DISPLAY_WIDTH, 3, t, + color565(display_fg), color565(display_bg1)); + } + + int32_t x = DISPLAY_MARGIN; + int32_t y = DISPLAY_MARGIN + 3; + + // wifi info section + if (wifi_active) { + // "Połącz się z siecią" + display_write_text(&x, &y, (uint8_t *)"Po""\xb3""\xb1""cz si""\xea"" z sieci""\xb1"" i ustaw tekst\n", color565(0, 255, 0), 2, DISPLAY_WIDTH - DISPLAY_MARGIN, false); + display_write_text(&x, &y, (uint8_t *)"SSID: " WIFI_SSID "\n", color565(180, 180, 180), 2, 0, false); + display_write_text(&x, &y, (uint8_t *)"PASS: " WIFI_PASSWORD "\n", color565(180, 180, 180), 2, 0, false); + display_write_text(&x, &y, (uint8_t *)WIFI_HOTSPOT_IP "\n", color565(180, 180, 180), 2, 0, false); + } + + // text section: gradient background, then text on top + display_gradient_rect(0, y - DISPLAY_MARGIN, DISPLAY_WIDTH, DISPLAY_HEIGHT - y + DISPLAY_MARGIN, display_bg1, display_bg2); + display_write_text(&x, &y, display_text, color565(display_fg), display_scale, DISPLAY_WIDTH - DISPLAY_MARGIN, display_wrap); + + display_end(); +} + +// === Entry points ==================================================== + +void setup(void) { + display_setup(); + display_on(); + button_setup(); + wifi_setup(); + wifi_start(); +} + +void loop(void) { + uint32_t now = millis(); + + button_tick(now); + + // wifi timeout + if (wifi_active && now >= wifi_on_until) + wifi_stop(); + + // screen timeout (only when wifi is off) + if (!wifi_active && display_active && now >= screen_on_until) { + display_active = false; + display_off(); + } + + wifi_tick(); + + draw_frame(now); +} -- cgit v1.3.1