summaryrefslogtreecommitdiff
path: root/src/sketch.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/sketch.cpp')
-rw-r--r--src/sketch.cpp356
1 files changed, 356 insertions, 0 deletions
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);
+}