From a5cab3d4ac56d308a9774733b9133a2be0fc1bb1 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 20:07:24 +0100 Subject: [PATCH 01/30] Use compressed icons --- src/icons/icons.cpp | 591 ++++----------------------------------- src/icons/icons.h | 13 +- src/lib/config.cpp | 34 ++- src/lib/epd.cpp | 63 +++-- src/lib/epd.hpp | 1 + src/lib/icon_manager.cpp | 59 ++++ src/lib/icon_manager.hpp | 22 ++ 7 files changed, 204 insertions(+), 579 deletions(-) create mode 100644 src/lib/icon_manager.cpp create mode 100644 src/lib/icon_manager.hpp diff --git a/src/icons/icons.cpp b/src/icons/icons.cpp index d9ae65c..2a4ff93 100644 --- a/src/icons/icons.cpp +++ b/src/icons/icons.cpp @@ -1,542 +1,65 @@ #include "icons.h" -// 'lightning-bolt', 122x122px -const unsigned char epd_icons_lightning_bolt [] PROGMEM = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 -}; -// 'rocket-launch', 122x122px -const unsigned char epd_icons_rocket_launch [] PROGMEM = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfe, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf1, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc1, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x03, 0xe0, 0x00, 0x00, 0x1f, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x03, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x07, 0xc0, 0x00, 0x00, 0x07, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x0f, 0x80, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xfe, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf8, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x80, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x80, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf8, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x7e, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0xff, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x81, 0xff, 0x80, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x03, 0xff, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xfe, 0x07, 0xe7, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xfc, 0x0f, 0xc3, 0xf0, 0x00, 0x00, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf8, 0x1f, 0x81, 0xf8, 0x00, 0x03, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf0, 0x3f, 0x00, 0xfc, 0x00, 0x0f, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x7e, 0x00, 0x7e, 0x00, 0x3f, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xc0, 0xfc, 0x00, 0x3f, 0x80, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x81, 0xf8, 0x00, 0x7f, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x03, 0xf0, 0x00, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x07, 0xe0, 0x01, 0xf3, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x0f, 0xc0, 0x03, 0xe0, 0xff, 0x80, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x1f, 0x80, 0x07, 0xc0, 0xff, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x3f, 0x00, 0x0f, 0x81, 0xff, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x7e, 0x00, 0x1f, 0x03, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xfc, 0x00, 0x3e, 0x07, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf8, 0x00, 0x7c, 0x0f, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf0, 0x00, 0xf8, 0x1f, 0xff, 0xc0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x01, 0xf0, 0x3f, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xc0, 0x03, 0xe0, 0x7f, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x80, 0x07, 0xc0, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x00, 0x0f, 0x81, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x00, 0x1f, 0x03, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x00, 0x3e, 0x07, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x00, 0x7c, 0x0f, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x00, 0xf8, 0x1f, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x01, 0xf0, 0x3f, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 -}; -// 'pickaxe', 122x122px -const unsigned char epd_icons_pickaxe [] PROGMEM = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x31, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x7f, 0xf8, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xfd, 0x80, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xfe, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xc0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0x80, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 +const unsigned char epd_icons_lightning_bolt[] PROGMEM = { + 0x1f,0x8b,0x08,0x00,0xa5,0xc7,0x7a,0x67,0x02,0xff,0xbd,0xd5,0xb1,0x0d,0xc3,0x20,0x10,0x40,0x51,0x23,0x17,0x2e,0x19,0x81,0x51,0x18,0x0d,0x6f,0x16,0x46,0x61,0x04,0x97,0x14,0x08,0x92,0x60,0x25,0x24,0xd6,0x1d,0xbf,0xf3,0x75,0x4f,0x3a,0x8a,0xdf,0x70,0xad,0xfd,0x4d,0x6c,0x37,0xf9,0x71,0xb1,0x9f,0xbb,0x82,0x0b,0x38,0x83,0x0f,0x70,0x02,0x47,0xf0,0x0e,0x5e,0xe6,0xae,0xe0,0x02,0xce,0xe0,0x03,0x9c,0xc0,0x11,0xbc,0x83,0x97,0xb9,0x2b,0xb8,0xb0,0x5f,0xe3,0xbe,0xce,0x17,0x1f,0xdd,0x5e,0x75,0xea,0x0e,0xaa,0x63,0x77,0x53,0xbd,0xbf,0x69,0x74,0xf7,0xf5,0x55,0x75,0xed,0xde,0x54,0x9f,0x79,0x56,0xf5,0xc8,0x95,0x3d,0x72,0x65,0x8f,0x5c,0xd9,0x23,0x57,0xf6,0xc8,0x95,0x3d,0x72,0x45,0xff,0xe4,0x8a,0x3e,0x9f,0x58,0xb0,0x03,0x7b,0x70,0x00,0xb7,0xb9,0x0d,0x78,0x05,0x6f,0x60,0x0b,0x76,0x60,0xfa,0x8f,0x02,0xb8,0xcd,0x6d,0xc0,0x2b,0x78,0x03,0x5b,0xb0,0x03,0x7b,0x70,0xb8,0xfb,0x3e,0x7e,0xfc,0x04,0x18,0x55,0x3a,0x4a,0xa0,0x07,0x00,0x00 }; -const unsigned char epd_icons_bitaxe_logo [] PROGMEM = { - // 'bitaxe_dark copy', 88x220px - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3e, 0xff, 0xff, 0xfd, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfe, - 0x00, 0xff, 0xff, 0xfe, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xff, 0x00, - 0x07, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xff, 0xff, - 0xf8, 0x00, 0x3f, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff, - 0xf0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x0f, 0xff, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, - 0xff, 0xf0, 0x00, 0x07, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x03, 0xff, - 0xff, 0xfc, 0x00, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, - 0xff, 0xff, 0xe0, 0x07, 0xf8, 0x7f, 0xff, 0xfd, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xfe, - 0x3f, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0x1f, 0xff, 0xff, 0xf8, 0x07, - 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xcf, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0x80, 0x0f, - 0xff, 0xe7, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xf3, 0xff, 0xff, 0xf0, - 0x01, 0xff, 0xff, 0xff, 0xc0, 0x1f, 0xff, 0xf3, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xff, 0xf0, - 0x1f, 0xff, 0xf9, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xfd, 0xff, 0xff, - 0xe0, 0x07, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xfd, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, - 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, - 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, - 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xfc, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xfe, - 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xfe, 0x7f, - 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xfc, 0x7f, 0xff, 0xff, - 0xff, 0xfc, 0x00, 0x00, 0x7f, 0x3f, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x7e, - 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x0f, 0xff, - 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, - 0x3c, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, - 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0f, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, - 0x1f, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xf0, 0x03, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x07, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, - 0x0f, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xfc, 0xff, 0xff, - 0xff, 0xff, 0xfc, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, - 0x00, 0x3f, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, - 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, - 0x01, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, - 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xf8, 0x00, - 0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, - 0xe0, 0x0f, 0xff, 0xf1, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xf9, 0xfc, - 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0x3f, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, - 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xfd, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xfe, 0x3f, - 0xff, 0xf8, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x1f, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xfc, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, - 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, - 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xf7, 0xff, 0xff, 0xfc, 0x3f, 0xff, - 0xff, 0xff, 0xe0, 0x07, 0xff, 0xf3, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, - 0xf3, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xf3, 0xff, 0xff, 0xf8, 0x01, - 0xff, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xf9, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xff, 0x80, 0x1f, - 0xff, 0xf9, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x1f, 0xff, 0xfc, 0xff, 0xff, 0xf0, - 0x03, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xfe, 0x3f, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xff, 0xf8, - 0x3f, 0xff, 0x7e, 0x1f, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xfc, 0xff, 0x03, 0xff, - 0xc0, 0x07, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, - 0xff, 0x80, 0x01, 0xff, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x03, 0xff, 0xe0, - 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xff, 0xff, - 0xff, 0xff, 0xe0, 0x1f, 0xff, 0xf0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xff, - 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xff, - 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, - 0xff, 0xfc, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x7f, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, - 0xff, 0x83, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xf8, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xf8, 0x7f, - 0xff, 0xf9, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xf0, 0xff, 0xf8, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xe0, 0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x07, 0xff, 0xc0, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x80, 0x1f, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x80, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x03, 0xff, 0xc0, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xe0, 0x3f, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x7f, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1f, 0xff, 0xf9, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, - 0xff, 0xf0, 0x1f, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xfd, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff, - 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, - 0xef, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xfc, 0x00, - 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, - 0x00, 0x07, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xf8, - 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x60, 0x00, - 0x00, 0x00, 0x03, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, - 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, - 0xff, 0xe0, 0x07, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xe0, 0x07, 0xff, 0xff, - 0x80, 0x1f, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xc0, 0x07, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xf8, - 0x0f, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x07, 0xff, 0x80, 0x0f, 0xff, - 0xff, 0x00, 0x3f, 0xff, 0xff, 0xf8, 0x03, 0xff, 0x80, 0x1f, 0xff, 0xff, 0x80, 0x3f, 0xff, 0xff, - 0xf0, 0x01, 0xff, 0xc0, 0x1f, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xf0, 0x1f, - 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xf8, 0x7f, 0xff, - 0xff, 0xe0, 0x03, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xfe, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, - 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, - 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xe0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7f, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xfe, 0x00, 0x7f, 0xff, 0x80, 0x03, 0xff, - 0xff, 0xff, 0xf8, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xfe, - 0x00, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xe0, 0x1f, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xfe, 0x01, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xfc, - 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xfe, 0x03, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, - 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, - 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, - 0xbf, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0x9f, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xe7, 0xff, 0xff, 0xe7, 0xfd, 0xff, 0xff, 0xff, 0xff, - 0xfe, 0x00, 0x1f, 0xff, 0xff, 0xfc, 0x73, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x7f, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff +const unsigned char epd_icons_rocket_launch[] PROGMEM = { + 0x1f,0x8b,0x08,0x00,0xa5,0xc7,0x7a,0x67,0x02,0xff,0xed,0xd4,0xb1,0x6e,0xc4,0x20,0x0c,0x06,0x60,0x47,0x0c,0x8c,0x3c,0x02,0x2f,0x52,0x5d,0x5e,0xab,0xc3,0xe9,0xc2,0xbd,0x19,0x55,0x5f,0x84,0xad,0x2b,0x55,0x17,0x2a,0x51,0x5c,0x43,0x0e,0x0c,0x5e,0x3a,0xde,0xd2,0x6c,0x9f,0x94,0xf8,0xb7,0xc1,0x0a,0xe2,0xf2,0x78,0x7c,0x96,0xa3,0x59,0x9c,0x01,0x8e,0xd9,0x1e,0x60,0x9f,0x0d,0xab,0xb3,0x70,0x14,0xf6,0xc2,0x8e,0x6c,0xd9,0x05,0x56,0x67,0xe1,0x28,0x1c,0x84,0xbd,0xb0,0x13,0x86,0xd5,0x45,0x38,0x0b,0x27,0xd8,0xd2,0xdc,0x6f,0x04,0x5d,0x66,0x07,0x30,0x38,0xdb,0xd3,0xb7,0x6e,0xb2,0x13,0xae,0xef,0x7a,0x3e,0xcf,0xf2,0x87,0xf3,0xe9,0x7e,0x1f,0x25,0xad,0xfe,0x8c,0xcd,0x5b,0xf7,0x9b,0xb0,0x0a,0x75,0x16,0xd7,0x9d,0x15,0x9d,0x0e,0xf5,0xab,0x1e,0x8e,0x9a,0x4c,0xf3,0x74,0x3b,0x43,0xa7,0xa5,0x32,0xe8,0xd3,0x05,0x4c,0x3b,0x3d,0x78,0xec,0x43,0x02,0x0b,0xb3,0x43,0xb7,0x3d,0xed,0xe0,0x72,0x7a,0xef,0x7e,0x59,0x1c,0xe0,0x7a,0xfa,0xe8,0xf5,0x1e,0xee,0xfb,0x07,0xaf,0x8d,0x5b,0xb7,0x4f,0xab,0x63,0xbb,0xfc,0xb3,0xdd,0x36,0xdf,0x69,0x3d,0x1c,0xb8,0xbd,0xc9,0x56,0x78,0x17,0x3e,0x84,0x71,0xf5,0x86,0x6b,0x9e,0x1a,0xce,0x91,0xe3,0xaa,0x53,0xe2,0xb8,0xd6,0xef,0x95,0xe3,0xaa,0xc3,0x8d,0xcb,0xb7,0xf9,0x90,0xcb,0x57,0xdf,0xeb,0xb6,0x6a,0xb6,0x42,0x3f,0xca,0xd7,0xfb,0xd0,0x1f,0x61,0x94,0x23,0x67,0xf3,0x4e,0x0d,0xe0,0xd1,0x9d,0xec,0x3d,0x81,0x2a,0xc3,0x71,0x87,0x0c,0x26,0x8d,0xf7,0x29,0xfe,0x06,0x7b,0x18,0xf6,0x19,0x76,0x47,0xa1,0xdd,0xf4,0xf5,0x81,0x85,0xfb,0x51,0x91,0x7a,0x4d,0x6c,0x1d,0xb6,0x2f,0x0c,0x6a,0xd8,0x78,0x15,0xd0,0xb1,0xad,0xd3,0x34,0x11,0xf7,0xbf,0x83,0xb9,0xcf,0xbe,0x81,0x55,0xb4,0x63,0xdc,0x3f,0x5c,0xf4,0x6c,0xda,0x07,0x83,0xce,0x0e,0x47,0x48,0x16,0x3d,0x3b,0x6c,0x91,0x96,0x9b,0xe7,0xa7,0xf8,0x03,0x03,0xbb,0xc6,0xcf,0xae,0xf1,0x18,0x0f,0x36,0xc5,0x2f,0xa6,0x78,0x8c,0xfc,0xff,0xab,0xf1,0x98,0x26,0x53,0x3c,0x7e,0xb3,0x6b,0x3c,0xfe,0x3c,0xff,0xff,0xfc,0xef,0xa7,0xf8,0x17,0xa1,0x5d,0x0c,0xd4,0xa0,0x07,0x00,0x00 +}; + +const unsigned char epd_icons_pickaxe[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xed, 0xd3, + 0xb1, 0x71, 0xc5, 0x30, 0x08, 0x06, 0x60, 0x7c, 0x2e, 0x5c, 0x7a, 0x04, + 0xcf, 0x90, 0x09, 0x3c, 0x9a, 0x35, 0x1a, 0xa9, 0xb2, 0x86, 0xba, 0x94, + 0x51, 0xa9, 0x5c, 0x14, 0x2b, 0x08, 0x64, 0x09, 0xe8, 0x53, 0x25, 0xaf, + 0xfb, 0xce, 0x98, 0x1f, 0xfc, 0xa4, 0x5a, 0xcd, 0x0f, 0xeb, 0x6f, 0xf8, + 0xdd, 0xf9, 0xd5, 0x19, 0x4f, 0xe7, 0xdd, 0x3a, 0x6c, 0xce, 0x8b, 0x35, + 0xc0, 0x65, 0x9f, 0xc3, 0x61, 0x1c, 0x61, 0x37, 0x4e, 0xb0, 0x1a, 0x17, + 0x58, 0x8c, 0xa9, 0xe1, 0x97, 0x71, 0x80, 0x97, 0x0f, 0xed, 0x08, 0xf0, + 0xa6, 0x9d, 0xa0, 0x77, 0xec, 0xce, 0xd0, 0x3b, 0x76, 0xdf, 0x00, 0xa0, + 0x4d, 0x01, 0x32, 0xf2, 0xe3, 0xe0, 0x8c, 0xae, 0x3e, 0xd9, 0x7e, 0x6d, + 0x80, 0xea, 0xea, 0xfd, 0xfb, 0xa7, 0xab, 0x37, 0xcf, 0x93, 0x7b, 0x3f, + 0x3b, 0x17, 0x57, 0x7f, 0xbb, 0x7e, 0xd5, 0xbb, 0x7f, 0xf2, 0x61, 0x94, + 0x4f, 0xfe, 0xec, 0xff, 0xfc, 0x05, 0xdd, 0xe5, 0xca, 0xb0, 0x29, 0xe7, + 0xf3, 0x3b, 0xac, 0xca, 0xe9, 0xa8, 0xa8, 0x1d, 0xf7, 0x1a, 0x17, 0x65, + 0xdc, 0x68, 0x03, 0x65, 0x3a, 0x20, 0x59, 0x9b, 0xc2, 0x32, 0x2f, 0x80, + 0x7d, 0xfa, 0x83, 0x36, 0x9a, 0x2e, 0x34, 0xfc, 0xcd, 0x0b, 0x60, 0xdf, + 0xfe, 0x32, 0xe6, 0xde, 0xbc, 0x50, 0x3f, 0x3f, 0x2d, 0x3b, 0x4c, 0xf3, + 0x6c, 0x61, 0x1f, 0xe6, 0xf3, 0x89, 0xd3, 0xbc, 0x6b, 0x1c, 0xbe, 0xb9, + 0x55, 0xdc, 0x1e, 0x17, 0x8e, 0x4a, 0xc3, 0x32, 0x6a, 0x1e, 0xf5, 0xb2, + 0x5a, 0x1e, 0xf3, 0xc8, 0xea, 0x65, 0xec, 0x87, 0xcf, 0xe9, 0xed, 0x9e, + 0xd7, 0x43, 0x3c, 0x4e, 0xbb, 0xf8, 0x1e, 0xb7, 0x41, 0x2c, 0xf1, 0xd3, + 0x79, 0xdc, 0x1e, 0xb1, 0xc4, 0x4f, 0xc7, 0xc5, 0x5a, 0xc5, 0xb3, 0x55, + 0x3c, 0x5b, 0xc5, 0x37, 0xeb, 0xf8, 0x66, 0x1d, 0xdf, 0xac, 0xe3, 0x9b, + 0x75, 0x7c, 0xb3, 0x8e, 0x6f, 0xd6, 0xf1, 0xcd, 0x3a, 0xbe, 0x59, 0xc7, + 0x93, 0x4d, 0x3c, 0xd9, 0xc4, 0x93, 0x4d, 0x3c, 0x39, 0xe9, 0x38, 0x72, + 0x5c, 0xad, 0x71, 0xb3, 0x0e, 0xbb, 0xf3, 0xe1, 0xea, 0x4f, 0xeb, 0x78, + 0x59, 0xa7, 0x6a, 0xfd, 0xe9, 0x5c, 0xff, 0xfd, 0x97, 0xfd, 0x03, 0x1c, + 0xb6, 0xaf, 0x78, 0xa0, 0x07, 0x00, 0x00 +}; + +const unsigned char epd_icons_bitaxe_logo[] PROGMEM = { + 0x1f,0x8b,0x08,0x00,0xa5,0xc7,0x7a,0x67,0x02,0xff,0xa5,0x96,0x4d,0x6a,0xdc,0x40,0x10,0x85,0x7b,0xd0,0x62,0x36,0x81,0xbe,0x40,0xa0,0x73,0x90,0xe0,0xbe,0x4a,0x8e,0x10,0xc8,0xde,0x52,0xf0,0x22,0xcb,0x1c,0x21,0x47,0x89,0x42,0x0e,0x90,0x2b,0x68,0xe7,0xa5,0x65,0x66,0xe1,0x16,0xb4,0x54,0xae,0xdf,0x2e,0x8d,0x70,0x88,0x4d,0x04,0xf6,0x7c,0x8c,0xa4,0x56,0xbf,0x7a,0xf5,0x4a,0x03,0xf0,0xcf,0xa3,0x3a,0x6e,0xbd,0xe3,0x0d,0xc0,0xaa,0x18,0xf0,0xa2,0x10,0x0d,0x37,0xc1,0x1a,0xf0,0xe2,0x70,0x16,0xcc,0x00,0x23,0x63,0x21,0x9c,0x14,0x13,0xc0,0xdc,0x11,0xce,0x74,0x4b,0x51,0xc4,0x93,0xe5,0x24,0xd8,0xd1,0xbd,0x84,0x53,0x08,0xba,0x22,0xde,0x5c,0x7a,0x58,0x6b,0x66,0xdc,0xf0,0xa3,0xe2,0x32,0x30,0x46,0xc0,0x8f,0x72,0x16,0xfc,0xa3,0x4b,0x0c,0x11,0xee,0x0d,0x13,0x5c,0x70,0x49,0xc2,0x51,0x90,0x1e,0x37,0x27,0x58,0x14,0x4b,0x26,0x3d,0xbc,0xb5,0xcd,0x11,0x02,0x1f,0x8c,0x03,0x63,0x3c,0xe0,0xc8,0x98,0x0e,0x38,0x1d,0x31,0x1f,0x70,0x3e,0x62,0xdf,0xaa,0x6d,0x55,0x5e,0xaf,0x0a,0x0e,0x19,0x5e,0x7d,0xac,0x7e,0x7b,0x35,0x0b,0x33,0xd4,0x9e,0xbd,0x08,0x7d,0x26,0xb1,0x64,0x40,0xb8,0xd5,0x4d,0x2a,0x46,0xd9,0xcd,0x8d,0x2a,0x46,0xfc,0x88,0x78,0x92,0x9d,0xbf,0x47,0xec,0x44,0xe5,0x3b,0xbd,0x40,0xca,0xc0,0xb7,0x8d,0x8e,0xa2,0x52,0xd6,0x4d,0xac,0x8e,0x9e,0x86,0x36,0x89,0xd9,0xbc,0x17,0x29,0x2e,0x1f,0x3b,0x1c,0x77,0x18,0x5f,0x42,0x34,0xd4,0xb4,0xa1,0xa1,0xa6,0x0d,0x1f,0x65,0xda,0x82,0x6b,0x0b,0xae,0x2d,0xb8,0xb6,0xa0,0x2a,0x08,0x3f,0x35,0x6d,0xe1,0x73,0xd3,0x16,0x66,0xd7,0x56,0x9a,0xb6,0x53,0x91,0xc5,0xa6,0x08,0x8f,0x45,0x04,0xa1,0xb6,0xa5,0x8a,0x6f,0x95,0xdb,0xc8,0xb4,0xad,0xf0,0xa6,0x83,0x2f,0xdf,0x3c,0x6a,0x68,0x1a,0x7e,0xc5,0x82,0xb0,0x11,0x0b,0x88,0xa0,0xcd,0x05,0x55,0x17,0x54,0xbd,0x3d,0x15,0x4f,0xae,0xb8,0x73,0x3c,0xbb,0xf8,0x5d,0x1d,0x92,0x63,0x76,0xdf,0x24,0x64,0xf0,0x84,0x4b,0x4a,0xc8,0x28,0x2d,0x16,0xb2,0x8b,0x87,0xec,0xe2,0x21,0x5b,0x34,0x59,0x83,0xc7,0x69,0x64,0x7b,0x2c,0x64,0x28,0x64,0xd2,0x90,0xdd,0x26,0x0b,0x59,0x85,0x4e,0x1d,0xc7,0x74,0x0f,0x96,0xac,0x13,0x95,0x5b,0xb0,0xa3,0x2d,0x45,0x6d,0x81,0xc9,0xe2,0xc4,0x2d,0x25,0x98,0xa1,0x18,0xf6,0x50,0x2c,0x4e,0x3b,0x94,0x7e,0x3f,0x20,0x17,0x7a,0x3f,0xca,0xde,0x1a,0xb2,0x9f,0x8e,0xdf,0xdd,0xc2,0x6f,0x6e,0xe1,0x1d,0x34,0x0b,0x11,0x0b,0x57,0x52,0x0d,0xe4,0xfd,0x6a,0x6e,0x48,0x9b,0x16,0x9d,0xa7,0x5e,0x68,0x16,0x56,0x47,0xbb,0xaf,0x6b,0xc3,0x88,0xdd,0xf4,0x15,0x6c,0xb5,0xe4,0x98,0xdb,0x83,0xc1,0xb7,0xc3,0xc7,0x6f,0xdf,0xfa,0x05,0xfe,0xe3,0x58,0xdb,0xbf,0x96,0x42,0x74,0x1e,0x27,0xf3,0x62,0x7b,0xcf,0x58,0xf2,0x16,0xbd,0xa9,0x6f,0x8d,0x38,0xe6,0xd9,0xb4,0x0d,0x69,0x87,0x93,0xc9,0x1c,0xf3,0x68,0xd7,0x4e,0xb9,0x4d,0x98,0xb9,0x6f,0x32,0x17,0x1b,0x36,0x59,0x9a,0xcb,0x64,0x62,0x20,0x4d,0xe6,0xf6,0xea,0x61,0xf9,0x97,0xb7,0x5b,0x6e,0xf5,0x21,0xd4,0x27,0x6c,0x49,0x4d,0x55,0xc4,0x33,0x0f,0x6c,0x50,0x57,0xc4,0x8a,0x2a,0x83,0x82,0x0c,0xba,0x46,0x1a,0x2a,0x66,0xf1,0x2c,0x66,0x16,0x19,0x40,0x86,0x1f,0xc4,0xed,0xd9,0xba,0xe4,0x0a,0xad,0x49,0xb0,0x7f,0x29,0x3b,0x43,0xe4,0x87,0x33,0x26,0xce,0xe6,0xa8,0x58,0x22,0x4f,0xcb,0x20,0xef,0x42,0xba,0x8e,0x8a,0x84,0x6f,0x56,0x3a,0x39,0x64,0x8e,0x29,0x26,0x13,0x6b,0xcc,0x38,0x27,0x8e,0x0b,0xc5,0x94,0x86,0x0c,0xda,0x47,0x31,0x25,0x5d,0x55,0x92,0xdf,0x9a,0xf6,0xdc,0x3a,0x2e,0xec,0xbe,0x8d,0xad,0x51,0xf7,0x98,0x5a,0x27,0x87,0xe4,0x2a,0xb2,0xab,0xc8,0x66,0x20,0x09,0x1a,0x02,0x27,0x79,0x23,0xe4,0x11,0xc1,0x3f,0x22,0x78,0x2e,0xac,0x82,0xb1,0xe1,0x94,0xa4,0xf8,0x54,0xa8,0xdc,0xb0,0xf4,0x0d,0xab,0xa3,0xba,0xb9,0x75,0x6e,0x6c,0xe7,0x01,0x27,0xfc,0xe1,0xf8,0x20,0xf8,0x15,0xff,0x9e,0x1c,0x17,0x89,0xfa,0x2f,0x69,0x13,0xc2,0x89,0x4e,0xc9,0x6a,0x54,0x37,0x5d,0xa2,0x64,0xfc,0x95,0x71,0x2f,0x73,0x9c,0x4a,0x5f,0xbf,0xc8,0x7b,0xab,0x7f,0xb9,0xe5,0x9e,0x01,0x52,0xde,0x2e,0xf4,0x74,0x09,0x00,0x00 +}; + +const size_t epd_icons_compressed_sizes[epd_icons_allArray_LEN] = { + 331, + 381, + 183, + 733 +}; + +const size_t epd_icons_original_sizes[epd_icons_allArray_LEN] = { + 1952, // 122x122 pixels / 8 bits per byte + padding + 1952, + 1952, + 2420 // 88x220 pixels / 8 bits per byte + padding }; -// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 8032) -const int epd_icons_allArray_LEN = 4; const unsigned char* epd_icons_allArray[epd_icons_allArray_LEN] = { - epd_icons_pickaxe, - epd_icons_rocket_launch, - epd_icons_lightning_bolt, - epd_icons_bitaxe_logo + epd_icons_pickaxe, + epd_icons_rocket_launch, + epd_icons_lightning_bolt, + epd_icons_bitaxe_logo }; diff --git a/src/icons/icons.h b/src/icons/icons.h index 136b4cc..7313d7b 100644 --- a/src/icons/icons.h +++ b/src/icons/icons.h @@ -1,10 +1,13 @@ #pragma once -#ifndef ICONS_H -#define ICONS_H - #include -extern const unsigned char* epd_icons_allArray[]; +extern const unsigned char epd_icons_pickaxe[] PROGMEM; +extern const unsigned char epd_icons_rocket_launch[] PROGMEM; +extern const unsigned char epd_icons_lightning_bolt[] PROGMEM; +extern const unsigned char epd_icons_bitaxe_logo[] PROGMEM; -#endif // ICONS_H +constexpr int epd_icons_allArray_LEN = 4; +extern const unsigned char* epd_icons_allArray[epd_icons_allArray_LEN]; +extern const size_t epd_icons_original_sizes[epd_icons_allArray_LEN]; +extern const size_t epd_icons_compressed_sizes[epd_icons_allArray_LEN]; \ No newline at end of file diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 765bedd..57c1c28 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -1,4 +1,6 @@ #include "config.hpp" +#include "icon_manager.hpp" +#include "../icons/icons.h" #define MAX_ATTEMPTS_WIFI_CONNECTION 20 @@ -378,6 +380,21 @@ void setupPreferences() addScreenMapping(SCREEN_MINING_POOL_STATS_EARNINGS, "Mining Pool Earnings"); } } + + // Load icons based on preferences + if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) { + IconManager::loadIcon("bitaxe", epd_icons_bitaxe_logo, epd_icons_compressed_sizes[3], epd_icons_original_sizes[3], 88, 220); + IconManager::loadIcon("rocket", epd_icons_rocket_launch, epd_icons_compressed_sizes[1], epd_icons_original_sizes[1], 122, 122); + IconManager::loadIcon("pickaxe", epd_icons_pickaxe, epd_icons_compressed_sizes[0], epd_icons_original_sizes[0], 122, 122); + } + + if (preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED)) { + IconManager::loadIcon("lightning", epd_icons_lightning_bolt, epd_icons_compressed_sizes[2], epd_icons_original_sizes[2], 122, 122); + } + + if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED)) { + IconManager::loadIcon("pickaxe", epd_icons_pickaxe, epd_icons_compressed_sizes[0], epd_icons_original_sizes[0], 122, 122); + } } String replaceAmbiguousChars(String input) @@ -475,21 +492,6 @@ void setupHardware() Serial.println(F("Error loading WebUI")); } - // { - // File f = LittleFS.open("/qr.txt", "w"); - - // if(f) { - // if (f.print("Hello")) { - // Serial.println(F("Written QR to FS")); - // Serial.printf("\nLittleFS free: %zu\n", LittleFS.totalBytes() - LittleFS.usedBytes()); - // } - // } else { - // Serial.println(F("Can't write QR to FS")); - // } - - // f.close(); - // } - setupLeds(); WiFi.setHostname(getMyHostname().c_str()); @@ -502,6 +504,8 @@ void setupHardware() Wire.begin(I2C_SDA_PIN, I2C_SCK_PIN, 400000); + + if (!mcp1.begin()) { Serial.println(F("Error MCP23017 1")); } else { diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index d9b5303..097268f 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -581,27 +581,10 @@ bool renderIcon(const uint dispNum, const String &text, bool partial) displays[dispNum].fillScreen(getBgColor()); displays[dispNum].setTextColor(getFgColor()); - uint iconIndex = 0; - uint width = 122; - uint height = 122; - if (text.endsWith("rocket")) - { - iconIndex = 1; - } - else if (text.endsWith("lnbolt")) - { - iconIndex = 2; - } - else if (text.endsWith("bitaxe")) - { - width = 88; - height = 220; - iconIndex = 3; - } - else if (text.endsWith("miningpool")) + // Handle mining pool logo separately + if (text.endsWith("miningpool")) { LogoData logo = getMiningPoolLogo(); - if (logo.size == 0) { Serial.println(F("No logo found")); @@ -610,19 +593,49 @@ bool renderIcon(const uint dispNum, const String &text, bool partial) int x_offset = (displays[dispNum].width() - logo.width) / 2; int y_offset = (displays[dispNum].height() - logo.height) / 2; - // Close the file - displays[dispNum].drawInvertedBitmap(x_offset, y_offset, logo.data, logo.width, logo.height, getFgColor()); + + if (!partial) + { + displays[dispNum].display(); + } return true; } - int x_offset = (displays[dispNum].width() - width) / 2; - int y_offset = (displays[dispNum].height() - height) / 2; + const char* iconName = nullptr; + if (text.endsWith("rocket")) { + iconName = "rocket"; + } else if (text.endsWith("lnbolt")) { + iconName = "lightning"; + } else if (text.endsWith("bitaxe")) { + iconName = "bitaxe"; + } else if (text.endsWith("pickaxe")) { + iconName = "pickaxe"; + } - displays[dispNum].drawInvertedBitmap(x_offset, y_offset, epd_icons_allArray[iconIndex], width, height, getFgColor()); + if (!iconName) { + return false; + } + + IconData* icon = IconManager::getIcon(iconName); + if (!icon) { + Serial.printf("Icon %s not loaded\n", iconName); + return false; + } + + // Calculate center position + int x_offset = (displays[dispNum].width() - icon->width) / 2; + int y_offset = (displays[dispNum].height() - icon->height) / 2; + + // Draw the icon + displays[dispNum].drawInvertedBitmap(x_offset, y_offset, icon->data, icon->width, icon->height, getFgColor()); + + if (!partial) + { + displays[dispNum].display(); + } return true; - // displays[dispNum].drawInvertedBitmap(0,0, getOceanIcon(), 122, 250, getFgColor()); } void renderQr(const uint dispNum, const String &text, bool partial) diff --git a/src/lib/epd.hpp b/src/lib/epd.hpp index 4796776..035806c 100644 --- a/src/lib/epd.hpp +++ b/src/lib/epd.hpp @@ -15,6 +15,7 @@ #include "lib/shared.hpp" #include "icons/icons.h" #include "mining_pool_stats_fetch.hpp" +#include "icon_manager.hpp" // Font includes #include "../fonts/antonio-semibold20.h" diff --git a/src/lib/icon_manager.cpp b/src/lib/icon_manager.cpp new file mode 100644 index 0000000..6c3532a --- /dev/null +++ b/src/lib/icon_manager.cpp @@ -0,0 +1,59 @@ +#include "icon_manager.hpp" +#include "gzip_decompressor.hpp" + +std::map IconManager::loadedIcons; + +void IconManager::loadIcon(const char* name, const unsigned char* compressedData, size_t compressedSize, size_t originalSize, uint width, uint height) { + // Check if icon is already loaded + if (loadedIcons.find(name) != loadedIcons.end()) { + return; + } + + // Allocate memory for decompressed data + uint8_t* decompressedData = new uint8_t[originalSize]; + if (!decompressedData) { + Serial.printf("Failed to allocate memory for icon %s\n", name); + return; + } + + // Decompress the icon + size_t decompressedSize = originalSize; + if (!GzipDecompressor::decompressData(compressedData, compressedSize, decompressedData, &decompressedSize)) { + Serial.printf("Failed to decompress icon %s\n", name); + delete[] decompressedData; + return; + } + + Serial.printf("Decompressed icon %s to %zu bytes\n", name, decompressedSize); + + // Store the icon data + IconData iconData = { + .data = decompressedData, + .width = width, + .height = height + }; + loadedIcons[name] = iconData; +} + +void IconManager::unloadIcon(const char* name) { + auto it = loadedIcons.find(name); + if (it != loadedIcons.end()) { + delete[] it->second.data; + loadedIcons.erase(it); + } +} + +IconData* IconManager::getIcon(const char* name) { + auto it = loadedIcons.find(name); + if (it != loadedIcons.end()) { + return &it->second; + } + return nullptr; +} + +void IconManager::cleanup() { + for (auto& pair : loadedIcons) { + delete[] pair.second.data; + } + loadedIcons.clear(); +} \ No newline at end of file diff --git a/src/lib/icon_manager.hpp b/src/lib/icon_manager.hpp new file mode 100644 index 0000000..30cc29d --- /dev/null +++ b/src/lib/icon_manager.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +struct IconData { + uint8_t* data; + uint width; + uint height; +}; + +class IconManager { +public: + static void loadIcon(const char* name, const unsigned char* compressedData, size_t compressedSize, size_t originalSize, uint width, uint height); + static void unloadIcon(const char* name); + static IconData* getIcon(const char* name); + static void cleanup(); + +private: + static std::map loadedIcons; +}; \ No newline at end of file From fa15e46d349a5a4ffeb028085b1250c776a1c88c Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 20:07:56 +0100 Subject: [PATCH 02/30] Add compressed sat symbol font file --- src/fonts/sats-symbol.h | 424 ++++++++++++++++++++++------------------ 1 file changed, 229 insertions(+), 195 deletions(-) diff --git a/src/fonts/sats-symbol.h b/src/fonts/sats-symbol.h index d21a9a5..182c6ea 100644 --- a/src/fonts/sats-symbol.h +++ b/src/fonts/sats-symbol.h @@ -1,201 +1,235 @@ -const uint8_t Satoshi_Symbol90pt7bBitmaps[] PROGMEM = { - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0 }; +#pragma once + +#include +#include +#include "fonts.hpp" + +const uint8_t Satoshi_Symbol90pt7bBitmaps_Gzip[] = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x63, 0x60, + 0x60, 0x60, 0xe0, 0xff, 0xc7, 0x00, 0x05, 0xcc, 0xff, 0x1b, 0x60, 0xcc, + 0xff, 0x0f, 0x60, 0x2c, 0xfb, 0x1f, 0x30, 0xd6, 0x60, 0x53, 0x38, 0x28, + 0x81, 0xfd, 0x7f, 0x3a, 0x83, 0x81, 0xf6, 0x30, 0x1a, 0xe0, 0xa7, 0xb7, + 0xff, 0x0f, 0x0c, 0xb4, 0x8f, 0x51, 0x01, 0x33, 0xbd, 0xfd, 0xff, 0x61, + 0xa0, 0x7d, 0x8c, 0x17, 0x0c, 0x8e, 0x92, 0x82, 0x44, 0x85, 0x54, 0x8f, + 0x23, 0x88, 0xe1, 0xec, 0xff, 0xa1, 0x56, 0xf2, 0xff, 0xff, 0x03, 0x61, + 0xc8, 0xff, 0x87, 0x3a, 0xc7, 0x1e, 0x16, 0x8d, 0xf5, 0x30, 0xa7, 0xc2, + 0x12, 0x36, 0xe3, 0x7f, 0xa8, 0xeb, 0x98, 0x47, 0x8d, 0x19, 0x35, 0x66, + 0x78, 0x1b, 0x43, 0x65, 0xf0, 0x00, 0x00, 0xc7, 0x63, 0x9f, 0x4b, 0xde, + 0x08, 0x00, 0x00 +}; +// unsigned int satoshi_bin_gz_len = 123; + + +// const uint8_t Satoshi_Symbol90pt7bBitmaps[] PROGMEM = { +// 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, +// 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, +// 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, +// 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, +// 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, +// 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, +// 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, +// 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, +// 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +// 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, +// 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, +// 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, +// 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, +// 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, +// 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, +// 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, +// 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, +// 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, +// 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, +// 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, +// 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xE0 }; const GFXglyph Satoshi_Symbol90pt7bGlyphs[] PROGMEM = { { 0, 82, 127, 99, 8, -126 }, { 1302, 71, 109, 93, 0, -117 } }; // 0x53 'S' -const GFXfont Satoshi_Symbol90pt7b PROGMEM = { - (uint8_t *)Satoshi_Symbol90pt7bBitmaps, - (GFXglyph *)Satoshi_Symbol90pt7bGlyphs, - 0x53, 0x53, 192 }; +// const GFXfont Satoshi_Symbol90pt7b PROGMEM = { +// (uint8_t *)Satoshi_Symbol90pt7bBitmaps, +// (GFXglyph *)Satoshi_Symbol90pt7bGlyphs, +// 0x53, 0x53, 192 }; + + // Font properties +static constexpr FontData Satoshi_Symbol90pt7b_Properties = { + Satoshi_Symbol90pt7bBitmaps_Gzip, + Satoshi_Symbol90pt7bGlyphs, + sizeof(Satoshi_Symbol90pt7bBitmaps_Gzip), + 2270, // Original size + 0x53, // First char + 0x53, // Last char + 192 // yAdvance +}; + // Approx. 2284 bytes From c91428dd5f872dc0613ce55c5e2ebcfe031d051b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 20:14:55 +0100 Subject: [PATCH 03/30] Refactor BitAxeFetch to a class --- src/lib/bitaxe_fetch.cpp | 53 ++++++++++++++------------------------ src/lib/bitaxe_fetch.hpp | 28 ++++++++++++++++---- src/lib/config.cpp | 4 +-- src/lib/screen_handler.cpp | 4 +-- src/lib/timers.cpp | 5 ++-- 5 files changed, 50 insertions(+), 44 deletions(-) diff --git a/src/lib/bitaxe_fetch.cpp b/src/lib/bitaxe_fetch.cpp index d2dae13..8fa1db0 100644 --- a/src/lib/bitaxe_fetch.cpp +++ b/src/lib/bitaxe_fetch.cpp @@ -1,24 +1,19 @@ #include "bitaxe_fetch.hpp" -TaskHandle_t bitaxeFetchTaskHandle; - -uint64_t bitaxeHashrate; -uint64_t bitaxeBestDiff; - -uint64_t getBitAxeHashRate() -{ - return bitaxeHashrate; +void BitAxeFetch::taskWrapper(void* pvParameters) { + BitAxeFetch::getInstance().task(); } -uint64_t getBitaxeBestDiff() -{ - return bitaxeBestDiff; +uint64_t BitAxeFetch::getHashRate() const { + return hashrate; } -void taskBitaxeFetch(void *pvParameters) -{ - for (;;) - { +uint64_t BitAxeFetch::getBestDiff() const { + return bestDiff; +} + +void BitAxeFetch::task() { + for (;;) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); HTTPClient http; @@ -28,46 +23,38 @@ void taskBitaxeFetch(void *pvParameters) int httpCode = http.GET(); - if (httpCode == 200) - { + if (httpCode == 200) { String payload = http.getString(); JsonDocument doc; deserializeJson(doc, payload); // Convert GH/s to H/s (multiply by 10^9) float hashRateGH = doc["hashRate"].as(); - bitaxeHashrate = static_cast(std::round(hashRateGH * std::pow(10, getHashrateMultiplier('G')))); + hashrate = static_cast(std::round(hashRateGH * std::pow(10, getHashrateMultiplier('G')))); // Parse difficulty string and convert to uint64_t std::string diffStr = doc["bestDiff"].as(); char diffUnit = diffStr[diffStr.length() - 1]; if (std::isalpha(diffUnit)) { float diffValue = std::stof(diffStr.substr(0, diffStr.length() - 1)); - bitaxeBestDiff = static_cast(std::round(diffValue * std::pow(10, getDifficultyMultiplier(diffUnit)))); + bestDiff = static_cast(std::round(diffValue * std::pow(10, getDifficultyMultiplier(diffUnit)))); } else { - bitaxeBestDiff = std::stoull(diffStr); + bestDiff = std::stoull(diffStr); } - if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_HASHRATE || ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_BESTDIFF)) - { + if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_HASHRATE || ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_BESTDIFF)) { WorkItem priceUpdate = {TASK_BITAXE_UPDATE, 0}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); } - } - else - { - Serial.print( - F("Error retrieving BitAxe data. HTTP status code: ")); + } else { + Serial.print(F("Error retrieving BitAxe data. HTTP status code: ")); Serial.println(httpCode); Serial.println(bitaxeApiUrl); } } } -void setupBitaxeFetchTask() -{ - xTaskCreate(taskBitaxeFetch, "bitaxeFetch", (3 * 1024), NULL, tskIDLE_PRIORITY, - &bitaxeFetchTaskHandle); - - xTaskNotifyGive(bitaxeFetchTaskHandle); +void BitAxeFetch::setup() { + xTaskCreate(taskWrapper, "bitaxeFetch", (3 * 1024), NULL, tskIDLE_PRIORITY, &taskHandle); + xTaskNotifyGive(taskHandle); } \ No newline at end of file diff --git a/src/lib/bitaxe_fetch.hpp b/src/lib/bitaxe_fetch.hpp index 8e1da37..0dd98d9 100644 --- a/src/lib/bitaxe_fetch.hpp +++ b/src/lib/bitaxe_fetch.hpp @@ -7,10 +7,28 @@ #include "lib/config.hpp" #include "lib/shared.hpp" -extern TaskHandle_t bitaxeFetchTaskHandle; +class BitAxeFetch { +public: + static BitAxeFetch& getInstance() { + static BitAxeFetch instance; + return instance; + } -void setupBitaxeFetchTask(); -void taskBitaxeFetch(void *pvParameters); + void setup(); + uint64_t getHashRate() const; + uint64_t getBestDiff() const; + static void taskWrapper(void* pvParameters); + TaskHandle_t getTaskHandle() const { return taskHandle; } -uint64_t getBitAxeHashRate(); -uint64_t getBitaxeBestDiff(); \ No newline at end of file +private: + BitAxeFetch() = default; + ~BitAxeFetch() = default; + BitAxeFetch(const BitAxeFetch&) = delete; + BitAxeFetch& operator=(const BitAxeFetch&) = delete; + + void task(); + + TaskHandle_t taskHandle = nullptr; + uint64_t hashrate = 0; + uint64_t bestDiff = 0; +}; \ No newline at end of file diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 765bedd..2dce75e 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -100,7 +100,7 @@ void setup() if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) { - setupBitaxeFetchTask(); + BitAxeFetch::getInstance().setup(); } if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED)) @@ -221,7 +221,7 @@ void setupWifi() // waitUntilNoneBusy(); // std::array epdContent = {"Welcome!", - // "Bienvenidos!", "Use\r\nweb-interface\r\nto configure", "Use\r\nla + // "Bienvenidos!", "Use\r\nweb-interface\r\npara configurar", "Use\r\nla // interfaz web\r\npara configurar", "Or // restart\r\nwhile\r\nholding\r\n2nd button\r\r\nto start\r\n QR-config", // "O reinicie\r\nmientras\r\n mantiene presionado\r\nel segundo diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 4d8d1b9..27d83b9 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -220,8 +220,8 @@ void workerTask(void *pvParameters) { currentScreenValue != SCREEN_BITAXE_BESTDIFF) break; taskEpdContent = (currentScreenValue == SCREEN_BITAXE_HASHRATE) ? - parseBitaxeHashRate(getBitAxeHashRate()) : - parseBitaxeBestDiff(getBitaxeBestDiff()); + parseBitaxeHashRate(BitAxeFetch::getInstance().getHashRate()) : + parseBitaxeBestDiff(BitAxeFetch::getInstance().getBestDiff()); setEpdContent(taskEpdContent); break; } diff --git a/src/lib/timers.cpp b/src/lib/timers.cpp index 2fdb71c..3779c2d 100644 --- a/src/lib/timers.cpp +++ b/src/lib/timers.cpp @@ -68,8 +68,9 @@ void IRAM_ATTR minuteTimerISR(void *arg) { WorkItem timeUpdate = {TASK_TIME_UPDATE, 0}; xQueueSendFromISR(workQueue, &timeUpdate, &xHigherPriorityTaskWoken); - if (bitaxeFetchTaskHandle != NULL) { - vTaskNotifyGiveFromISR(bitaxeFetchTaskHandle, &xHigherPriorityTaskWoken); + TaskHandle_t bitaxeHandle = BitAxeFetch::getInstance().getTaskHandle(); + if (bitaxeHandle != NULL) { + vTaskNotifyGiveFromISR(bitaxeHandle, &xHigherPriorityTaskWoken); } if (miningPoolStatsFetchTaskHandle != NULL) { From ac130988240d41c02b663608b6df13b9445309e7 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 20:24:13 +0100 Subject: [PATCH 04/30] Refactor mining pool stats fetch to a class --- src/lib/config.cpp | 4 +- src/lib/epd.cpp | 2 +- src/lib/mining_pool_stats_fetch.cpp | 128 ++++++++++++---------------- src/lib/mining_pool_stats_fetch.hpp | 42 +++++++-- src/lib/screen_handler.cpp | 7 +- src/lib/timers.cpp | 5 +- 6 files changed, 100 insertions(+), 88 deletions(-) diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 2dce75e..f0db973 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -105,7 +105,7 @@ void setup() if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED)) { - setupMiningPoolStatsFetchTask(); + MiningPoolStatsFetch::getInstance().setup(); } ButtonHandler::setup(); @@ -374,7 +374,7 @@ void setupPreferences() if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED)) { addScreenMapping(SCREEN_MINING_POOL_STATS_HASHRATE, "Mining Pool Hashrate"); - if (getMiningPool()->supportsDailyEarnings()) { + if (MiningPoolStatsFetch::getInstance().getPool()->supportsDailyEarnings()) { addScreenMapping(SCREEN_MINING_POOL_STATS_EARNINGS, "Mining Pool Earnings"); } } diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index d9b5303..04d3c5c 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -600,7 +600,7 @@ bool renderIcon(const uint dispNum, const String &text, bool partial) } else if (text.endsWith("miningpool")) { - LogoData logo = getMiningPoolLogo(); + LogoData logo = MiningPoolStatsFetch::getInstance().getLogo(); if (logo.size == 0) { diff --git a/src/lib/mining_pool_stats_fetch.cpp b/src/lib/mining_pool_stats_fetch.cpp index a35a00e..e04f208 100644 --- a/src/lib/mining_pool_stats_fetch.cpp +++ b/src/lib/mining_pool_stats_fetch.cpp @@ -1,95 +1,98 @@ #include "mining_pool_stats_fetch.hpp" -TaskHandle_t miningPoolStatsFetchTaskHandle; - -std::string miningPoolName; -std::string miningPoolStatsHashrate; -int miningPoolStatsDailyEarnings; - -std::string getMiningPoolStatsHashRate() -{ - return miningPoolStatsHashrate; +void MiningPoolStatsFetch::taskWrapper(void* pvParameters) { + MiningPoolStatsFetch::getInstance().task(); } -int getMiningPoolStatsDailyEarnings() -{ - return miningPoolStatsDailyEarnings; +void MiningPoolStatsFetch::downloadLogoTaskWrapper(void* pvParameters) { + MiningPoolStatsFetch::getInstance().downloadLogoTask(); } -void taskMiningPoolStatsFetch(void *pvParameters) -{ +std::string MiningPoolStatsFetch::getHashRate() const { + return hashrate; +} + +int MiningPoolStatsFetch::getDailyEarnings() const { + return dailyEarnings; +} + +MiningPoolInterface* MiningPoolStatsFetch::getPool() { + if (!currentPool) { + std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(); + currentPool = PoolFactory::createPool(poolName); + } + return currentPool.get(); +} + +const MiningPoolInterface* MiningPoolStatsFetch::getPool() const { + return currentPool.get(); +} + +LogoData MiningPoolStatsFetch::getLogo() const { + if (const auto* pool = getPool()) { + return pool->getLogo(); + } + return LogoData{}; +} + +void MiningPoolStatsFetch::task() { std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(); - auto poolInterface = PoolFactory::createPool(poolName); + auto* poolInterface = getPool(); + if (!poolInterface) return; std::string poolUser = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER).c_str(); // Main stats fetching loop - for (;;) - { + for (;;) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); HTTPClient http; http.setUserAgent(USER_AGENT); - - - + poolInterface->setPoolUser(poolUser); std::string apiUrl = poolInterface->getApiUrl(); http.begin(apiUrl.c_str()); - if (debugLogEnabled()) - { + if (debugLogEnabled()) { Serial.printf("Fetching mining pool stats from %s\r\n", apiUrl.c_str()); } poolInterface->prepareRequest(http); int httpCode = http.GET(); - if (httpCode == 200) - { + if (httpCode == 200) { String payload = http.getString(); JsonDocument doc; deserializeJson(doc, payload); - if (debugLogEnabled()) - { + if (debugLogEnabled()) { Serial.printf("Mining pool stats response: %s\r\n", payload.c_str()); } PoolStats stats = poolInterface->parseResponse(doc); + hashrate = stats.hashrate; - miningPoolStatsHashrate = stats.hashrate; - - if (debugLogEnabled()) - { + if (debugLogEnabled()) { Serial.printf("Mining pool stats parsed hashrate: %s\r\n", stats.hashrate.c_str()); } - if (stats.dailyEarnings) - { - miningPoolStatsDailyEarnings = *stats.dailyEarnings; - } - else - { - miningPoolStatsDailyEarnings = 0; // or any other default value - } + dailyEarnings = stats.dailyEarnings ? *stats.dailyEarnings : 0; - if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE || ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS)) - { + if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE || + ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS)) { WorkItem priceUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); } - } - else - { - Serial.print( - F("Error retrieving mining pool data. HTTP status code: ")); + } else { + Serial.print(F("Error retrieving mining pool data. HTTP status code: ")); Serial.println(httpCode); } } } -void downloadMiningPoolLogoTask(void *pvParameters) { +void MiningPoolStatsFetch::downloadLogoTask() { std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(); - auto poolInterface = PoolFactory::createPool(poolName); - PoolFactory::downloadPoolLogo(poolName, poolInterface.get()); + auto* poolInterface = getPool(); + if (!poolInterface) return; + + PoolFactory::downloadPoolLogo(poolName, poolInterface); // If we're on the mining pool stats screen, trigger a display update if (ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE) { @@ -97,41 +100,22 @@ void downloadMiningPoolLogoTask(void *pvParameters) { xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); } - xTaskNotifyGive(miningPoolStatsFetchTaskHandle); + xTaskNotifyGive(taskHandle); vTaskDelete(NULL); } -void setupMiningPoolStatsFetchTask() -{ - xTaskCreate(downloadMiningPoolLogoTask, +void MiningPoolStatsFetch::setup() { + xTaskCreate(downloadLogoTaskWrapper, "logoDownload", (6 * 1024), NULL, tskIDLE_PRIORITY, NULL); - xTaskCreate(taskMiningPoolStatsFetch, + xTaskCreate(taskWrapper, "miningPoolStatsFetch", (6 * 1024), NULL, tskIDLE_PRIORITY, - &miningPoolStatsFetchTaskHandle); -} - -std::unique_ptr& getMiningPool() -{ - static std::unique_ptr currentMiningPool; - - if (!currentMiningPool) { - std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(); - currentMiningPool = PoolFactory::createPool(poolName); - } - - return currentMiningPool; -} - -LogoData getMiningPoolLogo() -{ - LogoData logo = getMiningPool()->getLogo(); - return logo; + &taskHandle); } diff --git a/src/lib/mining_pool_stats_fetch.hpp b/src/lib/mining_pool_stats_fetch.hpp index 7a84454..9ea351a 100644 --- a/src/lib/mining_pool_stats_fetch.hpp +++ b/src/lib/mining_pool_stats_fetch.hpp @@ -2,18 +2,44 @@ #include #include -#include "mining_pool/pool_factory.hpp" +#include +#include #include "lib/config.hpp" #include "lib/shared.hpp" +#include "lib/mining_pool/mining_pool_interface.hpp" +#include "mining_pool/pool_factory.hpp" -extern TaskHandle_t miningPoolStatsFetchTaskHandle; +class MiningPoolStatsFetch { +public: + static MiningPoolStatsFetch& getInstance() { + static MiningPoolStatsFetch instance; + return instance; + } -void setupMiningPoolStatsFetchTask(); -void taskMiningPoolStatsFetch(void *pvParameters); + void setup(); + std::string getHashRate() const; + int getDailyEarnings() const; + TaskHandle_t getTaskHandle() const { return taskHandle; } + static void taskWrapper(void* pvParameters); + static void downloadLogoTaskWrapper(void* pvParameters); + + // Pool interface methods + MiningPoolInterface* getPool(); + const MiningPoolInterface* getPool() const; + LogoData getLogo() const; -std::string getMiningPoolStatsHashRate(); -int getMiningPoolStatsDailyEarnings(); +private: + MiningPoolStatsFetch() = default; + ~MiningPoolStatsFetch() = default; + MiningPoolStatsFetch(const MiningPoolStatsFetch&) = delete; + MiningPoolStatsFetch& operator=(const MiningPoolStatsFetch&) = delete; -std::unique_ptr& getMiningPool(); -LogoData getMiningPoolLogo(); \ No newline at end of file + void task(); + void downloadLogoTask(); + + TaskHandle_t taskHandle = nullptr; + std::string hashrate; + int dailyEarnings = 0; + std::unique_ptr currentPool; +}; \ No newline at end of file diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 27d83b9..0692c1d 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -231,9 +231,10 @@ void workerTask(void *pvParameters) { currentScreenValue != SCREEN_MINING_POOL_STATS_EARNINGS) break; taskEpdContent = (currentScreenValue == SCREEN_MINING_POOL_STATS_HASHRATE) ? - parseMiningPoolStatsHashRate(getMiningPoolStatsHashRate(), *getMiningPool()) : - parseMiningPoolStatsDailyEarnings(getMiningPoolStatsDailyEarnings(), - getMiningPool()->getDailyEarningsLabel(), *getMiningPool()); + parseMiningPoolStatsHashRate(MiningPoolStatsFetch::getInstance().getHashRate(), *MiningPoolStatsFetch::getInstance().getPool()) : + parseMiningPoolStatsDailyEarnings(MiningPoolStatsFetch::getInstance().getDailyEarnings(), + MiningPoolStatsFetch::getInstance().getPool()->getDailyEarningsLabel(), + *MiningPoolStatsFetch::getInstance().getPool()); setEpdContent(taskEpdContent); break; } diff --git a/src/lib/timers.cpp b/src/lib/timers.cpp index 3779c2d..4e67951 100644 --- a/src/lib/timers.cpp +++ b/src/lib/timers.cpp @@ -73,8 +73,9 @@ void IRAM_ATTR minuteTimerISR(void *arg) { vTaskNotifyGiveFromISR(bitaxeHandle, &xHigherPriorityTaskWoken); } - if (miningPoolStatsFetchTaskHandle != NULL) { - vTaskNotifyGiveFromISR(miningPoolStatsFetchTaskHandle, &xHigherPriorityTaskWoken); + TaskHandle_t miningPoolHandle = MiningPoolStatsFetch::getInstance().getTaskHandle(); + if (miningPoolHandle != NULL) { + vTaskNotifyGiveFromISR(miningPoolHandle, &xHigherPriorityTaskWoken); } if (xHigherPriorityTaskWoken == pdTRUE) { From d023643090370e32408b129ba4167fa971431e5b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 21:19:28 +0100 Subject: [PATCH 05/30] Refactor LedHandler to a class --- src/lib/block_notify.cpp | 3 +- src/lib/config.cpp | 78 +-- src/lib/config.hpp | 8 +- src/lib/epd.cpp | 50 +- src/lib/led_handler.cpp | 1324 +++++++++++++++++--------------------- src/lib/led_handler.hpp | 142 ++-- src/lib/nostr_notify.cpp | 3 +- src/lib/ota.cpp | 10 +- src/lib/timers.cpp | 5 +- src/lib/webserver.cpp | 228 +++++-- src/main.cpp | 15 +- 11 files changed, 931 insertions(+), 935 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index ec72138..111a7a7 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -1,4 +1,5 @@ #include "block_notify.hpp" +#include "led_handler.hpp" char *wsServer; esp_websocket_client_handle_t blockNotifyClient = NULL; @@ -217,7 +218,7 @@ void processNewBlock(uint32_t newBlockHeight) { if (preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD)) { vTaskDelay(pdMS_TO_TICKS(250)); // Wait until screens are updated - queueLedEffect(LED_FLASH_BLOCK_NOTIFY); + getLedHandler().queueEffect(LED_FLASH_BLOCK_NOTIFY); } } } diff --git a/src/lib/config.cpp b/src/lib/config.cpp index f0db973..5a3fa7d 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -1,4 +1,5 @@ #include "config.hpp" +#include "led_handler.hpp" #define MAX_ATTEMPTS_WIFI_CONNECTION 20 @@ -50,7 +51,8 @@ void setup() setupDisplays(); if (preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER)) { - queueLedEffect(LED_POWER_TEST); + auto& ledHandler = getLedHandler(); + ledHandler.queueEffect(LED_POWER_TEST); } { std::lock_guard lockMcp(mcpMutex); @@ -60,7 +62,8 @@ void setup() preferences.remove("txPower"); WiFi.eraseAP(); - queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); + auto& ledHandler = getLedHandler(); + ledHandler.queueEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); } } @@ -76,7 +79,8 @@ void setup() else if (mcp1.read1(1) == LOW) { preferences.clear(); - queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); + auto& ledHandler = getLedHandler(); + ledHandler.queueEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); nvs_flash_erase(); delay(1000); @@ -116,13 +120,13 @@ void setup() #ifdef HAS_FRONTLIGHT if (!preferences.getBool("flAlwaysOn", DEFAULT_FL_ALWAYS_ON)) { - frontlightFadeOutAll(preferences.getUInt("flEffectDelay"), true); + auto& ledHandler = getLedHandler(); + ledHandler.frontlightFadeOutAll(preferences.getUInt("flEffectDelay"), true); flArray.allOFF(); } #endif forceFullRefresh(); - } void setupWifi() @@ -144,7 +148,8 @@ void setupWifi() // if (!preferences.getBool("wifiConfigured", DEFAULT_WIFI_CONFIGURED) { - queueLedEffect(LED_EFFECT_WIFI_WAIT_FOR_CONFIG); + auto& ledHandler = getLedHandler(); + ledHandler.queueEffect(LED_EFFECT_WIFI_WAIT_FOR_CONFIG); bool buttonPress = false; { @@ -279,7 +284,8 @@ void syncTime() while (!getLocalTime(&timeinfo)) { - queueLedEffect(LED_EFFECT_CONFIGURING); + auto& ledHandler = getLedHandler(); + ledHandler.queueEffect(LED_EFFECT_CONFIGURING); configTime(preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS), 0, NTP_SERVER); delay(500); @@ -420,13 +426,14 @@ void setupTimers() void finishSetup() { + auto& ledHandler = getLedHandler(); if (preferences.getBool("ledStatus", DEFAULT_LED_STATUS)) { - restoreLedState(); + ledHandler.restoreLedState(); } else { - clearLeds(); + ledHandler.clear(); } } @@ -475,22 +482,9 @@ void setupHardware() Serial.println(F("Error loading WebUI")); } - // { - // File f = LittleFS.open("/qr.txt", "w"); - - // if(f) { - // if (f.print("Hello")) { - // Serial.println(F("Written QR to FS")); - // Serial.printf("\nLittleFS free: %zu\n", LittleFS.totalBytes() - LittleFS.usedBytes()); - // } - // } else { - // Serial.println(F("Can't write QR to FS")); - // } - - // f.close(); - // } - - setupLeds(); + // Initialize LED handler + auto& ledHandler = getLedHandler(); + ledHandler.setup(); WiFi.setHostname(getMyHostname().c_str()); if (!psramInit()) @@ -548,7 +542,8 @@ void setupHardware() #endif #ifdef HAS_FRONTLIGHT - setupFrontlight(); + // Initialize frontlight through LedHandler + ledHandler.initializeFrontlight(); Wire.beginTransmission(0x5C); byte error = Wire.endTransmission(); @@ -570,6 +565,7 @@ void setupHardware() void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { static bool first_connect = true; + auto& ledHandler = getLedHandler(); // Get ledHandler reference once at the start Serial.printf("[WiFi-event] event: %d\n", event); @@ -595,7 +591,7 @@ void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) if (!first_connect) { Serial.println(F("Disconnected from WiFi access point")); - queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR); + ledHandler.queueEffect(LED_EFFECT_WIFI_CONNECT_ERROR); uint8_t reason = info.wifi_sta_disconnected.reason; if (reason) Serial.printf("Disconnect reason: %s, ", @@ -611,13 +607,13 @@ void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) Serial.print("Obtained IP address: "); Serial.println(WiFi.localIP()); if (!first_connect) - queueLedEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS); + ledHandler.queueEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS); first_connect = false; break; } case ARDUINO_EVENT_WIFI_STA_LOST_IP: Serial.println(F("Lost IP address and IP address is reset to 0")); - queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR); + ledHandler.queueEffect(LED_EFFECT_WIFI_CONNECT_ERROR); WiFi.reconnect(); break; case ARDUINO_EVENT_WIFI_AP_START: @@ -667,30 +663,6 @@ uint getLastTimeSync() } #ifdef HAS_FRONTLIGHT -void setupFrontlight() -{ - if (!flArray.begin(PCA9685_MODE1_AUTOINCR | PCA9685_MODE1_ALLCALL, PCA9685_MODE2_TOTEMPOLE)) - { - Serial.println(F("FL driver error")); - return; - } - flArray.setFrequency(200); - Serial.println(F("FL driver active")); - - if (!preferences.isKey("flMaxBrightness")) - { - preferences.putUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS); - } - if (!preferences.isKey("flEffectDelay")) - { - preferences.putUInt("flEffectDelay", DEFAULT_FL_EFFECT_DELAY); - } - - if (!preferences.isKey("flFlashOnUpd")) - { - preferences.putBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE); - } -} float getLightLevel() { diff --git a/src/lib/config.hpp b/src/lib/config.hpp index d8e27b6..95c877d 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -52,10 +52,10 @@ void setupTimers(); void finishSetup(); void setupMcp(); #ifdef HAS_FRONTLIGHT -void setupFrontlight(); +extern BH1750 bh1750; +extern bool hasLuxSensor; float getLightLevel(); bool hasLightLevel(); -extern PCA9685 flArray; #endif String getMyHostname(); @@ -98,6 +98,10 @@ extern MCP23017 mcp1; extern MCP23017 mcp2; #endif +#ifdef HAS_FRONTLIGHT +extern PCA9685 flArray; +#endif + // Expose DataSourceType enum extern DataSourceType getDataSource(); extern void setDataSource(DataSourceType source); \ No newline at end of file diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 04d3c5c..9808aa7 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -140,8 +140,6 @@ const GFXfont *FONT_SATSYMBOL; std::mutex epdUpdateMutex; std::mutex epdMutex[NUM_SCREENS]; -uint8_t qrcode[800]; - #ifdef IS_BTCLOCK_V8 #define EPD_TASK_STACK_SIZE 4096 #else @@ -159,8 +157,6 @@ void forceFullRefresh() } } -GFXfont font90; - void loadFonts(const String& fontName) { if (fontName == FontNames::ANTONIO) { // Load Antonio fonts @@ -628,35 +624,45 @@ bool renderIcon(const uint dispNum, const String &text, bool partial) void renderQr(const uint dispNum, const String &text, bool partial) { #ifdef USE_QR + // Dynamically allocate QR buffer + uint8_t* qrcode = (uint8_t*)malloc(qrcodegen_BUFFER_LEN_MAX); + if (!qrcode) { + log_e("Failed to allocate QR buffer"); + return; + } uint8_t tempBuffer[800]; bool ok = qrcodegen_encodeText( text.substring(2).c_str(), tempBuffer, qrcode, qrcodegen_Ecc_LOW, qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true); - const int size = qrcodegen_getSize(qrcode); + if (ok) { + const int size = qrcodegen_getSize(qrcode); + const int padding = floor(float(displays[dispNum].width() - (size * 4)) / 2); + const int paddingY = + floor(float(displays[dispNum].height() - (size * 4)) / 2); + displays[dispNum].setRotation(2); - const int padding = floor(float(displays[dispNum].width() - (size * 4)) / 2); - const int paddingY = - floor(float(displays[dispNum].height() - (size * 4)) / 2); - displays[dispNum].setRotation(2); + displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), + displays[dispNum].height()); + displays[dispNum].fillScreen(GxEPD_WHITE); + const int border = 0; - displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), - displays[dispNum].height()); - displays[dispNum].fillScreen(GxEPD_WHITE); - const int border = 0; - - for (int y = -border; y < size * 4 + border; y++) - { - for (int x = -border; x < size * 4 + border; x++) + for (int y = -border; y < size * 4 + border; y++) { - displays[dispNum].drawPixel( - padding + x, paddingY + y, - qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4)) - ? GxEPD_BLACK - : GxEPD_WHITE); + for (int x = -border; x < size * 4 + border; x++) + { + displays[dispNum].drawPixel( + padding + x, paddingY + y, + qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4)) + ? GxEPD_BLACK + : GxEPD_WHITE); + } } } + + // Free the buffer after we're done + free(qrcode); #endif } diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 806ee9c..3cdb953 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -1,227 +1,431 @@ #include "led_handler.hpp" -TaskHandle_t ledTaskHandle = NULL; -QueueHandle_t ledTaskQueue = NULL; -Adafruit_NeoPixel pixels(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800); -uint ledTaskParams; +// Singleton instance +LedHandler& LedHandler::getInstance() { + static LedHandler instance; + return instance; +} + +LedHandler::LedHandler() + : pixels(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800) + , ledTaskHandle(nullptr) + , ledTaskQueue(nullptr) + , ledTaskParams(0) + , dndEnabled(false) + , dndTimeBasedEnabled(false) + , dndTimeRange{23, 0, 7, 0} // Default: 23:00 to 07:00 +#ifdef HAS_FRONTLIGHT + , frontlightOn(false) + , flInTransition(false) +#endif +{ +} + +void LedHandler::setup() { + loadDNDSettings(); + pixels.begin(); + pixels.setBrightness(preferences.getUInt("ledBrightness", DEFAULT_LED_BRIGHTNESS)); + pixels.clear(); + pixels.show(); + setupTask(); + + if (preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER)) { + while (!ledTaskQueue) { + delay(1); + } + queueEffect(LED_POWER_TEST); + } +} + +void LedHandler::setupTask() { + ledTaskQueue = xQueueCreate(5, sizeof(uint)); + xTaskCreate(ledTask, "LedTask", 2048, this, 10, &ledTaskHandle); +} + +void LedHandler::ledTask(void* pvParameters) { + auto* handler = static_cast(pvParameters); + while (true) { + if (handler->ledTaskQueue != nullptr) { + if (xQueueReceive(handler->ledTaskQueue, &handler->ledTaskParams, portMAX_DELAY) == pdPASS) { + if (preferences.getBool("disableLeds", DEFAULT_DISABLE_LEDS)) { + continue; + } + + std::array oldLights; + for (int i = 0; i < NEOPIXEL_COUNT; i++) { + oldLights[i] = handler->pixels.getPixelColor(i); + } #ifdef HAS_FRONTLIGHT -constexpr uint16_t FL_FADE_STEP = 25; - -bool frontlightOn = false; -bool flInTransition = false; - -void frontlightFlash(int flDelayTime) -{ - if (preferences.getBool("flDisable")) - return; - - if (frontlightOn) - { - frontlightFadeOutAll(flDelayTime, true); - frontlightFadeInAll(flDelayTime, true); - } - else - { - frontlightFadeInAll(flDelayTime, true); - frontlightFadeOutAll(flDelayTime, true); - } -} - -void frontlightFadeInAll() -{ - frontlightFadeInAll(preferences.getUInt("flEffectDelay")); -} - -void frontlightFadeOutAll() -{ - frontlightFadeOutAll(preferences.getUInt("flEffectDelay")); -} - -void frontlightFadeIn(uint num) -{ - frontlightFadeIn(num, preferences.getUInt("flEffectDelay")); -} - -void frontlightFadeOut(uint num) -{ - frontlightFadeOut(num, preferences.getUInt("flEffectDelay")); -} - -void frontlightSetBrightness(uint brightness) -{ - if (isDNDActive()) { - return; // Don't change brightness during DND mode - } - if (brightness > 4096) - { - return; - } - - for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) - { - flArray.setPWM(ledPin, 0, brightness); - } -} - -void frontlightFadeInAll(int flDelayTime) -{ - frontlightFadeInAll(flDelayTime, false); -} - -void frontlightFadeInAll(int flDelayTime, bool staggered) -{ - if (preferences.getBool("flDisable") || frontlightIsOn() || flInTransition) - return; - - flInTransition = true; - - const int maxBrightness = preferences.getUInt("flMaxBrightness"); - - if (staggered) - { - int step = FL_FADE_STEP; - int staggerDelay = flDelayTime / NUM_SCREENS; - - for (int dutyCycle = 0; dutyCycle <= maxBrightness + (NUM_SCREENS - 1) * maxBrightness / NUM_SCREENS; dutyCycle += step) - { - for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) - { - int ledBrightness = dutyCycle - ledPin * maxBrightness / NUM_SCREENS; - if (ledBrightness < 0) - ledBrightness = 0; - else if (ledBrightness > maxBrightness) - ledBrightness = maxBrightness; - - flArray.setPWM(ledPin + 1, 0, ledBrightness); - } - vTaskDelay(pdMS_TO_TICKS(staggerDelay)); - } - } - else - { - for (int dutyCycle = 0; dutyCycle <= maxBrightness; dutyCycle += FL_FADE_STEP) - { - for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) - { - flArray.setPWM(ledPin, 0, dutyCycle); - } - vTaskDelay(pdMS_TO_TICKS(flDelayTime)); - } - } - frontlightOn = true; - flInTransition = false; -} - -void frontlightFadeOutAll(int flDelayTime) -{ - frontlightFadeOutAll(flDelayTime, false); -} - -void frontlightFadeOutAll(int flDelayTime, bool staggered) -{ - if (preferences.getBool("flDisable")) - return; - if (!frontlightIsOn()) - return; - if (flInTransition) - return; - flInTransition = true; - - if (staggered) - { - int maxBrightness = preferences.getUInt("flMaxBrightness"); - int step = FL_FADE_STEP; - int staggerDelay = flDelayTime / NUM_SCREENS; - - for (int dutyCycle = maxBrightness; dutyCycle >= 0; dutyCycle -= step) - { - for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) - { - int ledBrightness = dutyCycle - (NUM_SCREENS - 1 - ledPin) * maxBrightness / NUM_SCREENS; - if (ledBrightness < 0) - ledBrightness = 0; - else if (ledBrightness > maxBrightness) - ledBrightness = maxBrightness; - - flArray.setPWM(ledPin + 1, 0, ledBrightness); - } - vTaskDelay(pdMS_TO_TICKS(staggerDelay)); - } - } - else - { - for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= FL_FADE_STEP) - { - for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) - { - flArray.setPWM(ledPin, 0, dutyCycle); - } - vTaskDelay(pdMS_TO_TICKS(flDelayTime)); - } - } - - flArray.allOFF(); - frontlightOn = false; - flInTransition = false; -} - -std::vector frontlightGetStatus() -{ - std::vector statuses; - for (int ledPin = 1; ledPin <= NUM_SCREENS; ledPin++) - { - uint16_t a = 0, b = 0; - flArray.getPWM(ledPin, &a, &b); - statuses.push_back(round(b - a / 4096)); - } - - return statuses; -} - -bool frontlightIsOn() -{ - return frontlightOn; -} - -void frontlightFadeIn(uint num, int flDelayTime) -{ - if (isDNDActive()) { - return; // Don't change brightness during DND mode - } - if (preferences.getBool("flDisable")) - return; - for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) - { - flArray.setPWM(num, 0, dutyCycle); - vTaskDelay(pdMS_TO_TICKS(flDelayTime)); - } -} - -void frontlightFadeOut(uint num, int flDelayTime) -{ - if (isDNDActive()) { - return; // Don't change brightness during DND mode - } - if (preferences.getBool("flDisable")) - return; - if (!frontlightIsOn()) - return; - - for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) - { - flArray.setPWM(num, 0, dutyCycle); - vTaskDelay(pdMS_TO_TICKS(flDelayTime)); - } -} + uint flDelayTime = preferences.getUInt("flEffectDelay"); #endif -// Do Not Disturb mode variables -bool dndEnabled = false; -bool dndTimeBasedEnabled = false; -DNDTimeRange dndTimeRange = {23, 0, 7, 0}; // Default: 23:00 to 07:00 + switch (handler->ledTaskParams) { + case LED_POWER_TEST: +#ifdef HAS_FRONTLIGHT + handler->frontlightFadeInAll(preferences.getUInt("flEffectDelay"), true); +#endif + handler->rainbow(20); + handler->pixels.clear(); + break; -void loadDNDSettings() { + case LED_EFFECT_WIFI_CONNECT_ERROR: + handler->blinkDelayTwoColor(100, 3, handler->pixels.Color(8, 161, 236), + handler->pixels.Color(255, 0, 0)); + break; + + case LED_EFFECT_CONFIGURING: + for (int i = NEOPIXEL_COUNT; i--; i > 0) { + for (int j = NEOPIXEL_COUNT; j--; j > 0) { + uint32_t c = handler->pixels.Color(0, 0, 0); + if (i == j) + c = handler->pixels.Color(0, 0, 255); + handler->pixels.setPixelColor(j, c); + } + handler->pixels.show(); + delay(100); + } + handler->pixels.clear(); + handler->pixels.show(); + break; + + case LED_FLASH_ERROR: + handler->blinkDelayColor(250, 3, 255, 0, 0); + break; + + case LED_EFFECT_HEARTBEAT: + handler->blinkDelayColor(150, 2, 0, 0, 255); + break; + + case LED_DATA_BLOCK_ERROR: + handler->blinkDelayColor(150, 2, 128, 0, 128); + break; + + case LED_DATA_PRICE_ERROR: + handler->blinkDelayColor(150, 2, 177, 90, 31); + break; + + case LED_FLASH_IDENTIFY: + handler->blinkDelayTwoColor(100, 2, handler->pixels.Color(255, 0, 0), + handler->pixels.Color(0, 255, 255)); + handler->blinkDelayTwoColor(100, 2, handler->pixels.Color(0, 255, 0), + handler->pixels.Color(0, 0, 255)); + break; + + case LED_EFFECT_WIFI_CONNECT_SUCCESS: + case LED_FLASH_SUCCESS: + handler->blinkDelayColor(150, 3, 0, 255, 0); + break; + + case LED_PROGRESS_100: + handler->pixels.setPixelColor(0, handler->pixels.Color(0, 255, 0)); + [[fallthrough]]; + case LED_PROGRESS_75: + handler->pixels.setPixelColor(1, handler->pixels.Color(0, 255, 0)); + [[fallthrough]]; + case LED_PROGRESS_50: + handler->pixels.setPixelColor(2, handler->pixels.Color(0, 255, 0)); + [[fallthrough]]; + case LED_PROGRESS_25: + handler->pixels.setPixelColor(3, handler->pixels.Color(0, 255, 0)); + handler->pixels.show(); + break; + + case LED_EFFECT_NOSTR_ZAP: + { +#ifdef HAS_FRONTLIGHT + bool frontlightWasOn = false; + if (preferences.getBool("flFlashOnZap", DEFAULT_FL_FLASH_ON_ZAP)) { + if (handler->frontlightOn) { + frontlightWasOn = true; + handler->frontlightFadeOutAll(flDelayTime, true); + } else { + handler->frontlightFadeInAll(flDelayTime, true); + } + } +#endif + for (int flash = 0; flash < random(7, 10); flash++) { + handler->lightningStrike(); + delay(random(50, 150)); + } +#ifdef HAS_FRONTLIGHT + if (preferences.getBool("flFlashOnZap", DEFAULT_FL_FLASH_ON_ZAP)) { + vTaskDelay(pdMS_TO_TICKS(10)); + if (frontlightWasOn) { + handler->frontlightFadeInAll(flDelayTime, true); + } else { + handler->frontlightFadeOutAll(flDelayTime, true); + } + } +#endif + break; + } + + case LED_FLASH_UPDATE: + handler->blinkDelayTwoColor(250, 3, handler->pixels.Color(0, 230, 0), + handler->pixels.Color(230, 230, 0)); + break; + + case LED_FLASH_BLOCK_NOTIFY: + { +#ifdef HAS_FRONTLIGHT + bool frontlightWasOn = false; + if (preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE)) { + if (handler->frontlightOn) { + frontlightWasOn = true; + handler->frontlightFadeOutAll(flDelayTime, true); + } else { + handler->frontlightFadeInAll(flDelayTime, true); + } + } +#endif + handler->blinkDelayTwoColor(250, 3, handler->pixels.Color(224, 67, 0), + handler->pixels.Color(8, 2, 0)); +#ifdef HAS_FRONTLIGHT + if (preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE)) { + vTaskDelay(pdMS_TO_TICKS(10)); + if (frontlightWasOn) { + handler->frontlightFadeInAll(flDelayTime, true); + } else { + handler->frontlightFadeOutAll(flDelayTime, true); + } + } +#endif + break; + } + + case LED_EFFECT_WIFI_WAIT_FOR_CONFIG: + handler->blinkDelayTwoColor(100, 1, handler->pixels.Color(8, 161, 236), + handler->pixels.Color(156, 225, 240)); + break; + + case LED_EFFECT_WIFI_ERASE_SETTINGS: + handler->blinkDelay(100, 3); + break; + + case LED_EFFECT_WIFI_CONNECTING: + for (int i = NEOPIXEL_COUNT; i >= 0; i--) { + for (int j = NEOPIXEL_COUNT; j >= 0; j--) { + if (j == i) { + handler->pixels.setPixelColor(i, handler->pixels.Color(16, 197, 236)); + } else { + handler->pixels.setPixelColor(j, handler->pixels.Color(0, 0, 0)); + } + } + handler->pixels.show(); + vTaskDelay(pdMS_TO_TICKS(100)); + } + break; + + case LED_EFFECT_PAUSE_TIMER: + for (int i = NEOPIXEL_COUNT; i >= 0; i--) { + for (int j = NEOPIXEL_COUNT; j >= 0; j--) { + uint32_t c = handler->pixels.Color(0, 0, 0); + if (i == j) + c = handler->pixels.Color(0, 255, 0); + handler->pixels.setPixelColor(j, c); + } + handler->pixels.show(); + delay(100); + } + handler->pixels.setPixelColor(0, handler->pixels.Color(255, 0, 0)); + handler->pixels.show(); + delay(900); + handler->pixels.clear(); + handler->pixels.show(); + break; + + case LED_EFFECT_START_TIMER: + handler->pixels.clear(); + handler->pixels.setPixelColor((NEOPIXEL_COUNT - 1), handler->pixels.Color(255, 0, 0)); + handler->pixels.show(); + delay(900); + for (int i = NEOPIXEL_COUNT; i--; i > 0) { + for (int j = NEOPIXEL_COUNT; j--; j > 0) { + uint32_t c = handler->pixels.Color(0, 0, 0); + if (i == j) + c = handler->pixels.Color(0, 255, 0); + handler->pixels.setPixelColor(j, c); + } + handler->pixels.show(); + delay(100); + } + handler->pixels.clear(); + handler->pixels.show(); + break; + } + + // Restore previous state unless power test + for (int i = 0; i < NEOPIXEL_COUNT; i++) { + handler->pixels.setPixelColor(i, oldLights[i]); + } + handler->pixels.show(); + } + } + } +} + +bool LedHandler::queueEffect(uint effect) { + if (isDNDActive()) { + return false; + } + if (ledTaskQueue == nullptr) { + return false; + } + xQueueSend(ledTaskQueue, &effect, portMAX_DELAY); + return true; +} + +void LedHandler::clear() { + preferences.putBool("ledStatus", false); + pixels.clear(); + pixels.show(); +} + +void LedHandler::setLights(int r, int g, int b) { + setLights(pixels.Color(r, g, b)); +} + +void LedHandler::setLights(uint32_t color) { + bool ledStatus = true; + for (int i = 0; i < NEOPIXEL_COUNT; i++) { + pixels.setPixelColor(i, color); + } + pixels.show(); + + if (color == pixels.Color(0, 0, 0)) { + ledStatus = false; + } else { + saveLedState(); + } + preferences.putBool("ledStatus", ledStatus); +} + +void LedHandler::saveLedState() { + for (int i = 0; i < pixels.numPixels(); i++) { + int pixelColor = pixels.getPixelColor(i); + char key[12]; + snprintf(key, 12, "%s%d", "ledColor_", i); + preferences.putUInt(key, pixelColor); + } + xTaskNotifyGive(eventSourceTaskHandle); +} + +void LedHandler::restoreLedState() { + for (int i = 0; i < pixels.numPixels(); i++) { + char key[12]; + snprintf(key, 12, "%s%d", "ledColor_", i); + uint pixelColor = preferences.getUInt(key, pixels.Color(0, 0, 0)); + pixels.setPixelColor(i, pixelColor); + } + pixels.show(); +} + +void LedHandler::rainbow(int wait) { + for (long firstPixelHue = 0; firstPixelHue < 5 * 65536; firstPixelHue += 256) { + pixels.rainbow(firstPixelHue); + pixels.show(); + delayMicroseconds(wait); + } +} + +void LedHandler::theaterChase(uint32_t color, int wait) { + for (int a = 0; a < 10; a++) { + for (int b = 0; b < 3; b++) { + pixels.clear(); + for (int c = b; c < pixels.numPixels(); c += 3) { + pixels.setPixelColor(c, color); + } + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(wait)); + } + } +} + +void LedHandler::theaterChaseRainbow(int wait) { + int firstPixelHue = 0; + for (int a = 0; a < 30; a++) { + for (int b = 0; b < 3; b++) { + pixels.clear(); + for (int c = b; c < pixels.numPixels(); c += 3) { + int hue = firstPixelHue + c * 65536L / pixels.numPixels(); + uint32_t color = pixels.gamma32(pixels.ColorHSV(hue)); + pixels.setPixelColor(c, color); + } + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(wait)); + firstPixelHue += 65536 / 90; + } + } +} + +void LedHandler::lightningStrike() { + uint32_t PURPLE = pixels.Color(128, 0, 128); + uint32_t YELLOW = pixels.Color(255, 226, 41); + + for (int i = 0; i < pixels.numPixels(); i++) { + pixels.setPixelColor(i, random(2) == 0 ? YELLOW : PURPLE); + } + pixels.show(); + delay(random(10, 50)); +} + +void LedHandler::blinkDelay(int d, int times) { + for (int j = 0; j < times; j++) { + pixels.setPixelColor(0, pixels.Color(255, 0, 0)); + pixels.setPixelColor(1, pixels.Color(0, 255, 0)); + pixels.setPixelColor(2, pixels.Color(255, 0, 0)); + pixels.setPixelColor(3, pixels.Color(0, 255, 0)); + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(d)); + + pixels.setPixelColor(0, pixels.Color(255, 255, 0)); + pixels.setPixelColor(1, pixels.Color(0, 255, 255)); + pixels.setPixelColor(2, pixels.Color(255, 255, 0)); + pixels.setPixelColor(3, pixels.Color(0, 255, 255)); + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(d)); + } + pixels.clear(); + pixels.show(); +} + +void LedHandler::blinkDelayColor(int d, int times, uint r, uint g, uint b) { + for (int j = 0; j < times; j++) { + for (int i = 0; i < NEOPIXEL_COUNT; i++) { + pixels.setPixelColor(i, pixels.Color(r, g, b)); + } + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(d)); + + pixels.clear(); + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(d)); + } + pixels.clear(); + pixels.show(); +} + +void LedHandler::blinkDelayTwoColor(int d, int times, const uint32_t& c1, const uint32_t& c2) { + for (int j = 0; j < times; j++) { + for (int i = 0; i < NEOPIXEL_COUNT; i++) { + pixels.setPixelColor(i, c1); + } + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(d)); + + for (int i = 0; i < NEOPIXEL_COUNT; i++) { + pixels.setPixelColor(i, c2); + } + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(d)); + } + pixels.clear(); + pixels.show(); +} + +// DND Implementation +void LedHandler::loadDNDSettings() { dndEnabled = preferences.getBool("dndEnabled", false); dndTimeBasedEnabled = preferences.getBool("dndTimeEnabled", false); @@ -231,29 +435,29 @@ void loadDNDSettings() { dndTimeRange.endMinute = preferences.getUChar("dndEndMin", 0); } -void setDNDEnabled(bool enabled) { +void LedHandler::setDNDEnabled(bool enabled) { dndEnabled = enabled; preferences.putBool("dndEnabled", enabled); if (enabled && isDNDActive()) { - clearLeds(); - #ifdef HAS_FRONTLIGHT + clear(); +#ifdef HAS_FRONTLIGHT frontlightFadeOutAll(); - #endif +#endif } } -void setDNDTimeBasedEnabled(bool enabled) { +void LedHandler::setDNDTimeBasedEnabled(bool enabled) { dndTimeBasedEnabled = enabled; preferences.putBool("dndTimeEnabled", enabled); if (enabled && isDNDActive()) { - clearLeds(); - #ifdef HAS_FRONTLIGHT + clear(); +#ifdef HAS_FRONTLIGHT frontlightFadeOutAll(); - #endif +#endif } } -void setDNDTimeRange(uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute) { +void LedHandler::setDNDTimeRange(uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute) { dndTimeRange.startHour = startHour; dndTimeRange.startMinute = startMinute; dndTimeRange.endHour = endHour; @@ -265,21 +469,19 @@ void setDNDTimeRange(uint8_t startHour, uint8_t startMinute, uint8_t endHour, ui preferences.putUChar("dndEndMin", endMinute); } -bool isTimeInDNDRange(uint8_t hour, uint8_t minute) { +bool LedHandler::isTimeInDNDRange(uint8_t hour, uint8_t minute) const { uint16_t currentTime = hour * 60 + minute; uint16_t startTime = dndTimeRange.startHour * 60 + dndTimeRange.startMinute; uint16_t endTime = dndTimeRange.endHour * 60 + dndTimeRange.endMinute; if (startTime <= endTime) { - // Simple case: start time is before end time (e.g., 09:00 to 17:00) return currentTime >= startTime && currentTime < endTime; } else { - // Complex case: start time is after end time (e.g., 23:00 to 07:00) return currentTime >= startTime || currentTime < endTime; } } -bool isDNDActive() { +bool LedHandler::isDNDActive() const { if (dndEnabled) { return true; } @@ -295,518 +497,174 @@ bool isDNDActive() { return false; } -void ledTask(void *parameter) -{ - while (1) - { - if (ledTaskQueue != NULL) - { - if (xQueueReceive(ledTaskQueue, &ledTaskParams, portMAX_DELAY) == - pdPASS) - { - - if (preferences.getBool("disableLeds", DEFAULT_DISABLE_LEDS)) - { - continue; - } - - std::array oldLights; - - // get current state - for (int i = 0; i < NEOPIXEL_COUNT; i++) - { - oldLights[i] = pixels.getPixelColor(i); - } #ifdef HAS_FRONTLIGHT - uint flDelayTime = preferences.getUInt("flEffectDelay"); -#endif - switch (ledTaskParams) - { - case LED_POWER_TEST: -#ifdef HAS_FRONTLIGHT - frontlightFadeInAll(preferences.getUInt("flEffectDelay"), true); -#endif - ledRainbow(20); - pixels.clear(); - break; - case LED_EFFECT_WIFI_CONNECT_ERROR: - blinkDelayTwoColor(100, 3, pixels.Color(8, 161, 236), - pixels.Color(255, 0, 0)); - break; - case LED_EFFECT_CONFIGURING: - for (int i = NEOPIXEL_COUNT; i--; i > 0) - { - for (int j = NEOPIXEL_COUNT; j--; j > 0) - { - uint32_t c = pixels.Color(0, 0, 0); - if (i == j) - c = pixels.Color(0, 0, 255); +// Frontlight implementation +void LedHandler::frontlightFlash(int flDelayTime) { + if (preferences.getBool("flDisable")) { + return; + } - pixels.setPixelColor(j, c); - } + if (frontlightOn) { + frontlightFadeOutAll(flDelayTime, true); + frontlightFadeInAll(flDelayTime, true); + } else { + frontlightFadeInAll(flDelayTime, true); + frontlightFadeOutAll(flDelayTime, true); + } +} - pixels.show(); +void LedHandler::frontlightFadeInAll() { + frontlightFadeInAll(preferences.getUInt("flEffectDelay")); +} - delay(100); - } +void LedHandler::frontlightFadeOutAll() { + frontlightFadeOutAll(preferences.getUInt("flEffectDelay")); +} - pixels.clear(); - pixels.show(); - break; - case LED_FLASH_ERROR: - blinkDelayColor(250, 3, 255, 0, 0); - break; - case LED_EFFECT_HEARTBEAT: - blinkDelayColor(150, 2, 0, 0, 255); - break; - case LED_DATA_BLOCK_ERROR: - blinkDelayColor(150, 2, 128, 0, 128); - break; - case LED_DATA_PRICE_ERROR: - blinkDelayColor(150, 2, 177, 90, 31); - break; - case LED_FLASH_IDENTIFY: - blinkDelayTwoColor(100, 2, pixels.Color(255, 0, 0), - pixels.Color(0, 255, 255)); - blinkDelayTwoColor(100, 2, pixels.Color(0, 255, 0), - pixels.Color(0, 0, 255)); - break; - case LED_EFFECT_WIFI_CONNECT_SUCCESS: - case LED_FLASH_SUCCESS: - blinkDelayColor(150, 3, 0, 255, 0); - break; - case LED_PROGRESS_100: - pixels.setPixelColor(0, pixels.Color(0, 255, 0)); - case LED_PROGRESS_75: - pixels.setPixelColor(1, pixels.Color(0, 255, 0)); - case LED_PROGRESS_50: - pixels.setPixelColor(2, pixels.Color(0, 255, 0)); - case LED_PROGRESS_25: - pixels.setPixelColor(3, pixels.Color(0, 255, 0)); - pixels.show(); - break; - case LED_EFFECT_NOSTR_ZAP: - { -#ifdef HAS_FRONTLIGHT - bool frontlightWasOn = false; +void LedHandler::frontlightFadeIn(uint num) { + frontlightFadeIn(num, preferences.getUInt("flEffectDelay")); +} - if (preferences.getBool("flFlashOnZap", DEFAULT_FL_FLASH_ON_ZAP)) - { - if (frontlightOn) - { - frontlightWasOn = true; - frontlightFadeOutAll(flDelayTime, true); +void LedHandler::frontlightFadeOut(uint num) { + frontlightFadeOut(num, preferences.getUInt("flEffectDelay")); +} + +void LedHandler::frontlightSetBrightness(uint brightness) { + if (isDNDActive() || brightness > 4096) { + return; + } + + for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { + flArray.setPWM(ledPin, 0, brightness); + } +} + +std::vector LedHandler::frontlightGetStatus() { + std::vector statuses; + for (int ledPin = 1; ledPin <= NUM_SCREENS; ledPin++) { + uint16_t a = 0, b = 0; + flArray.getPWM(ledPin, &a, &b); + statuses.push_back(round(b - a / 4096)); + } + return statuses; +} + +void LedHandler::frontlightFadeInAll(int flDelayTime, bool staggered) { + if (preferences.getBool("flDisable") || frontlightIsOn() || flInTransition) { + return; + } + + flInTransition = true; + const int maxBrightness = preferences.getUInt("flMaxBrightness"); + + if (staggered) { + int step = FL_FADE_STEP; + int staggerDelay = flDelayTime / NUM_SCREENS; + + for (int dutyCycle = 0; dutyCycle <= maxBrightness + (NUM_SCREENS - 1) * maxBrightness / NUM_SCREENS; dutyCycle += step) { + for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { + int ledBrightness = dutyCycle - ledPin * maxBrightness / NUM_SCREENS; + if (ledBrightness < 0) { + ledBrightness = 0; + } else if (ledBrightness > maxBrightness) { + ledBrightness = maxBrightness; + } + flArray.setPWM(ledPin + 1, 0, ledBrightness); } - else - { - frontlightFadeInAll(flDelayTime, true); - } - } -#endif - for (int flash = 0; flash < random(7, 10); flash++) - { - lightningStrike(); - delay(random(50, 150)); - } - // blinkDelayColor(250, 3, 142, 48, 235); - // blinkDelayTwoColor(250, 3, pixels.Color(142, 48, 235), - // pixels.Color(169, 21, 255)); -#ifdef HAS_FRONTLIGHT - if (preferences.getBool("flFlashOnZap", DEFAULT_FL_FLASH_ON_ZAP)) - { - vTaskDelay(pdMS_TO_TICKS(10)); - if (frontlightWasOn) - { - frontlightFadeInAll(flDelayTime, true); - } - else - { - frontlightFadeOutAll(flDelayTime, true); - } - } -#endif - break; + vTaskDelay(pdMS_TO_TICKS(staggerDelay)); } - case LED_FLASH_UPDATE: - blinkDelayTwoColor(250, 3, pixels.Color(0, 230, 0), - pixels.Color(230, 230, 0)); - break; - case LED_FLASH_BLOCK_NOTIFY: - { -#ifdef HAS_FRONTLIGHT - bool frontlightWasOn = false; + } else { + for (int dutyCycle = 0; dutyCycle <= maxBrightness; dutyCycle += FL_FADE_STEP) { + for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { + flArray.setPWM(ledPin, 0, dutyCycle); + } + vTaskDelay(pdMS_TO_TICKS(flDelayTime)); + } + } + frontlightOn = true; + flInTransition = false; +} - if (preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE)) - { - if (frontlightOn) - { - frontlightWasOn = true; - frontlightFadeOutAll(flDelayTime, true); +void LedHandler::frontlightFadeOutAll(int flDelayTime, bool staggered) { + if (preferences.getBool("flDisable") || !frontlightIsOn() || flInTransition) { + return; + } + + flInTransition = true; + if (staggered) { + int maxBrightness = preferences.getUInt("flMaxBrightness"); + int step = FL_FADE_STEP; + int staggerDelay = flDelayTime / NUM_SCREENS; + + for (int dutyCycle = maxBrightness; dutyCycle >= 0; dutyCycle -= step) { + for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { + int ledBrightness = dutyCycle - (NUM_SCREENS - 1 - ledPin) * maxBrightness / NUM_SCREENS; + if (ledBrightness < 0) { + ledBrightness = 0; + } else if (ledBrightness > maxBrightness) { + ledBrightness = maxBrightness; + } + flArray.setPWM(ledPin + 1, 0, ledBrightness); } - else - { - frontlightFadeInAll(flDelayTime, true); + vTaskDelay(pdMS_TO_TICKS(staggerDelay)); + } + } else { + for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= FL_FADE_STEP) { + for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { + flArray.setPWM(ledPin, 0, dutyCycle); } - } + vTaskDelay(pdMS_TO_TICKS(flDelayTime)); + } + } + + flArray.allOFF(); + frontlightOn = false; + flInTransition = false; +} + +void LedHandler::frontlightFadeIn(uint num, int flDelayTime) { + if (isDNDActive() || preferences.getBool("flDisable")) { + return; + } + + for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) { + flArray.setPWM(num, 0, dutyCycle); + vTaskDelay(pdMS_TO_TICKS(flDelayTime)); + } +} + +void LedHandler::frontlightFadeOut(uint num, int flDelayTime) { + if (isDNDActive() || preferences.getBool("flDisable") || !frontlightIsOn()) { + return; + } + + for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) { + flArray.setPWM(num, 0, dutyCycle); + vTaskDelay(pdMS_TO_TICKS(flDelayTime)); + } +} + +void LedHandler::initializeFrontlight() { + if (!flArray.begin(PCA9685_MODE1_AUTOINCR | PCA9685_MODE1_ALLCALL, PCA9685_MODE2_TOTEMPOLE)) + { + Serial.println(F("FL driver error")); + return; + } + flArray.setFrequency(200); + Serial.println(F("FL driver active")); + + if (!preferences.isKey("flMaxBrightness")) + { + preferences.putUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS); + } + if (!preferences.isKey("flEffectDelay")) + { + preferences.putUInt("flEffectDelay", DEFAULT_FL_EFFECT_DELAY); + } + + if (!preferences.isKey("flFlashOnUpd")) + { + preferences.putBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE); + } +} #endif - blinkDelayTwoColor(250, 3, pixels.Color(224, 67, 0), - pixels.Color(8, 2, 0)); -#ifdef HAS_FRONTLIGHT - if (preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE)) - { - vTaskDelay(pdMS_TO_TICKS(10)); - if (frontlightWasOn) - { - frontlightFadeInAll(flDelayTime, true); - } - else - { - frontlightFadeOutAll(flDelayTime, true); - } - } -#endif - break; - } - case LED_EFFECT_WIFI_WAIT_FOR_CONFIG: - blinkDelayTwoColor(100, 1, pixels.Color(8, 161, 236), - pixels.Color(156, 225, 240)); - break; - case LED_EFFECT_WIFI_ERASE_SETTINGS: - blinkDelay(100, 3); - break; - case LED_EFFECT_WIFI_CONNECTING: - for (int i = NEOPIXEL_COUNT; i >= 0; i--) - { - for (int j = NEOPIXEL_COUNT; j >= 0; j--) - { - if (j == i) - { - pixels.setPixelColor(i, pixels.Color(16, 197, 236)); - } - else - { - pixels.setPixelColor(j, pixels.Color(0, 0, 0)); - } - } - pixels.show(); - vTaskDelay(pdMS_TO_TICKS(100)); - } - break; - case LED_EFFECT_PAUSE_TIMER: - for (int i = NEOPIXEL_COUNT; i >= 0; i--) - { - for (int j = NEOPIXEL_COUNT; j >= 0; j--) - { - uint32_t c = pixels.Color(0, 0, 0); - if (i == j) - c = pixels.Color(0, 255, 0); - pixels.setPixelColor(j, c); - } - - pixels.show(); - - delay(100); - } - pixels.setPixelColor(0, pixels.Color(255, 0, 0)); - pixels.show(); - - delay(900); - - pixels.clear(); - pixels.show(); - break; - case LED_EFFECT_START_TIMER: - pixels.clear(); - pixels.setPixelColor((NEOPIXEL_COUNT - 1), pixels.Color(255, 0, 0)); - pixels.show(); - - delay(900); - - for (int i = NEOPIXEL_COUNT; i--; i > 0) - { - for (int j = NEOPIXEL_COUNT; j--; j > 0) - { - uint32_t c = pixels.Color(0, 0, 0); - if (i == j) - c = pixels.Color(0, 255, 0); - - pixels.setPixelColor(j, c); - } - - pixels.show(); - - delay(100); - } - - pixels.clear(); - pixels.show(); - break; - } - - // revert to previous state unless power test - - for (int i = 0; i < NEOPIXEL_COUNT; i++) - { - pixels.setPixelColor(i, oldLights[i]); - } - - pixels.show(); - } - } - } -} - -void setupLeds() -{ - loadDNDSettings(); - pixels.begin(); - pixels.setBrightness(preferences.getUInt("ledBrightness", DEFAULT_LED_BRIGHTNESS)); - pixels.clear(); - pixels.show(); - setupLedTask(); - if (preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER)) - { - while (!ledTaskQueue) - { - delay(1); - // wait until queue is available - } - queueLedEffect(LED_POWER_TEST); - } -} - -void setupLedTask() -{ - ledTaskQueue = xQueueCreate(5, sizeof(uint)); - - xTaskCreate(ledTask, "LedTask", 2048, NULL, 10, &ledTaskHandle); -} - -void blinkDelay(int d, int times) -{ - for (int j = 0; j < times; j++) - { - pixels.setPixelColor(0, pixels.Color(255, 0, 0)); - pixels.setPixelColor(1, pixels.Color(0, 255, 0)); - pixels.setPixelColor(2, pixels.Color(255, 0, 0)); - pixels.setPixelColor(3, pixels.Color(0, 255, 0)); - pixels.show(); - vTaskDelay(pdMS_TO_TICKS(d)); - - pixels.setPixelColor(0, pixels.Color(255, 255, 0)); - pixels.setPixelColor(1, pixels.Color(0, 255, 255)); - pixels.setPixelColor(2, pixels.Color(255, 255, 0)); - pixels.setPixelColor(3, pixels.Color(0, 255, 255)); - pixels.show(); - vTaskDelay(pdMS_TO_TICKS(d)); - } - pixels.clear(); - pixels.show(); -} - -void blinkDelayColor(int d, int times, uint r, uint g, uint b) -{ - for (int j = 0; j < times; j++) - { - for (int i = 0; i < NEOPIXEL_COUNT; i++) - { - pixels.setPixelColor(i, pixels.Color(r, g, b)); - } - - pixels.show(); - vTaskDelay(pdMS_TO_TICKS(d)); - - pixels.clear(); - pixels.show(); - vTaskDelay(pdMS_TO_TICKS(d)); - } - pixels.clear(); - pixels.show(); -} - -void blinkDelayTwoColor(int d, int times, const uint32_t& c1, const uint32_t& c2) -{ - for (int j = 0; j < times; j++) - { - for (int i = 0; i < NEOPIXEL_COUNT; i++) - { - pixels.setPixelColor(i, c1); - } - pixels.show(); - vTaskDelay(pdMS_TO_TICKS(d)); - - for (int i = 0; i < NEOPIXEL_COUNT; i++) - { - pixels.setPixelColor(i, c2); - } - pixels.show(); - vTaskDelay(pdMS_TO_TICKS(d)); - } - pixels.clear(); - pixels.show(); -} - -void clearLeds() -{ - preferences.putBool("ledStatus", false); - pixels.clear(); - pixels.show(); -} - -void setLights(int r, int g, int b) { setLights(pixels.Color(r, g, b)); } - -void setLights(uint32_t color) -{ - bool ledStatus = true; - - for (int i = 0; i < NEOPIXEL_COUNT; i++) - { - pixels.setPixelColor(i, color); - } - pixels.show(); - - if (color == pixels.Color(0, 0, 0)) - { - ledStatus = false; - } - else - { - saveLedState(); - } - preferences.putBool("ledStatus", ledStatus); -} - -void saveLedState() -{ - for (int i = 0; i < pixels.numPixels(); i++) - { - int pixelColor = pixels.getPixelColor(i); - char key[12]; - snprintf(key, 12, "%s%d", "ledColor_", i); - preferences.putUInt(key, pixelColor); - } - - xTaskNotifyGive(eventSourceTaskHandle); -} - -void restoreLedState() -{ - for (int i = 0; i < pixels.numPixels(); i++) - { - char key[12]; - snprintf(key, 12, "%s%d", "ledColor_", i); - uint pixelColor = preferences.getUInt(key, pixels.Color(0, 0, 0)); - pixels.setPixelColor(i, pixelColor); - } - - pixels.show(); -} - -QueueHandle_t getLedTaskQueue() { return ledTaskQueue; } - -bool queueLedEffect(uint effect) { - if (isDNDActive()) { - return false; // Don't queue any effects during DND mode - } - if (ledTaskQueue == NULL) - { - return false; - } - - uint flashType = effect; - xQueueSend(ledTaskQueue, &flashType, portMAX_DELAY); - return true; -} - -void ledRainbow(int wait) -{ - // Hue of first pixel runs 5 complete loops through the color wheel. - // Color wheel has a range of 65536 but it's OK if we roll over, so - // just count from 0 to 5*65536. Adding 256 to firstPixelHue each time - // means we'll make 5*65536/256 = 1280 passes through this loop: - for (long firstPixelHue = 0; firstPixelHue < 5 * 65536; - firstPixelHue += 256) - { - // strip.rainbow() can take a single argument (first pixel hue) or - // optionally a few extras: number of rainbow repetitions (default 1), - // saturation and value (brightness) (both 0-255, similar to the - // ColorHSV() function, default 255), and a true/false flag for whether - // to apply gamma correction to provide 'truer' colors (default true). - pixels.rainbow(firstPixelHue); - // Above line is equivalent to: - // strip.rainbow(firstPixelHue, 1, 255, 255, true); - pixels.show(); // Update strip with new contents - delayMicroseconds(wait); - // vTaskDelay(pdMS_TO_TICKS(wait)); // Pause for a moment - } -} - -void ledTheaterChase(uint32_t color, int wait) -{ - for (int a = 0; a < 10; a++) - { // Repeat 10 times... - for (int b = 0; b < 3; b++) - { // 'b' counts from 0 to 2... - pixels.clear(); // Set all pixels in RAM to 0 (off) - // 'c' counts up from 'b' to end of strip in steps of 3... - for (int c = b; c < pixels.numPixels(); c += 3) - { - pixels.setPixelColor(c, color); // Set pixel 'c' to value 'color' - } - pixels.show(); // Update strip with new contents - vTaskDelay(pdMS_TO_TICKS(wait)); // Pause for a moment - } - } -} - -void ledTheaterChaseRainbow(int wait) -{ - int firstPixelHue = 0; // First pixel starts at red (hue 0) - for (int a = 0; a < 30; a++) - { // Repeat 30 times... - for (int b = 0; b < 3; b++) - { // 'b' counts from 0 to 2... - pixels.clear(); // Set all pixels in RAM to 0 (off) - // 'c' counts up from 'b' to end of strip in increments of 3... - for (int c = b; c < pixels.numPixels(); c += 3) - { - // hue of pixel 'c' is offset by an amount to make one full - // revolution of the color wheel (range 65536) along the length - // of the strip (strip.numPixels() steps): - int hue = firstPixelHue + c * 65536L / pixels.numPixels(); - uint32_t color = pixels.gamma32(pixels.ColorHSV(hue)); // hue -> RGB - pixels.setPixelColor(c, color); // Set pixel 'c' to value 'color' - } - pixels.show(); // Update strip with new contents - vTaskDelay(pdMS_TO_TICKS(wait)); // Pause for a moment - firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames - } - } -} - -void lightningStrike() -{ - uint32_t PURPLE = pixels.Color(128, 0, 128); - uint32_t YELLOW = pixels.Color(255, 226, 41); - - // Randomly choose which LEDs to light up - for (int i = 0; i < pixels.numPixels(); i++) - { - if (random(2) == 0) - { // 50% chance for each LED - pixels.setPixelColor(i, YELLOW); - } - else - { - pixels.setPixelColor(i, PURPLE); - } - } - pixels.show(); - - delay(random(10, 50)); // Flash duration - - // Return to purple background - // setAllPixels(PURPLE); -} - -Adafruit_NeoPixel getPixels() { return pixels; } diff --git a/src/lib/led_handler.hpp b/src/lib/led_handler.hpp index fe72ffa..e599e2a 100644 --- a/src/lib/led_handler.hpp +++ b/src/lib/led_handler.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "lib/shared.hpp" #include "lib/webserver.hpp" @@ -15,12 +16,11 @@ #define NEOPIXEL_COUNT 4 #endif +// LED effect constants const int LED_FLASH_ERROR = 0; const int LED_FLASH_SUCCESS = 1; const int LED_FLASH_UPDATE = 2; - const int LED_EFFECT_CONFIGURING = 10; - const int LED_FLASH_BLOCK_NOTIFY = 4; const int LED_EFFECT_START_TIMER = 5; const int LED_EFFECT_PAUSE_TIMER = 6; @@ -30,61 +30,15 @@ const int LED_EFFECT_WIFI_CONNECTING = 101; const int LED_EFFECT_WIFI_CONNECT_ERROR = 102; const int LED_EFFECT_WIFI_CONNECT_SUCCESS = 103; const int LED_EFFECT_WIFI_ERASE_SETTINGS = 104; - const int LED_PROGRESS_25 = 200; const int LED_PROGRESS_50 = 201; const int LED_PROGRESS_75 = 202; const int LED_PROGRESS_100 = 203; - const int LED_DATA_PRICE_ERROR = 300; const int LED_DATA_BLOCK_ERROR = 301; - const int LED_EFFECT_NOSTR_ZAP = 400; - const int LED_FLASH_IDENTIFY = 990; const int LED_POWER_TEST = 999; -extern TaskHandle_t ledTaskHandle; -extern Adafruit_NeoPixel pixels; - -void ledTask(void *pvParameters); -void setupLeds(); -void setupLedTask(); -void blinkDelay(int d, int times); -void blinkDelayColor(int d, int times, uint r, uint g, uint b); -void blinkDelayTwoColor(int d, int times, const uint32_t& c1, const uint32_t& c2); -void clearLeds(); -void saveLedState(); -void restoreLedState(); -QueueHandle_t getLedTaskQueue(); -bool queueLedEffect(uint effect); -void setLights(int r, int g, int b); -void setLights(uint32_t color); -void ledRainbow(int wait); -void ledTheaterChaseRainbow(int wait); -void ledTheaterChase(uint32_t color, int wait); -Adafruit_NeoPixel getPixels(); -void lightningStrike(); - -#ifdef HAS_FRONTLIGHT -void frontlightFlash(int flDelayTime); -void frontlightFadeInAll(); -void frontlightFadeOutAll(); -void frontlightFadeIn(uint num); -void frontlightFadeOut(uint num); - -std::vector frontlightGetStatus(); - -void frontlightSetBrightness(uint brightness); -bool frontlightIsOn(); - -void frontlightFadeInAll(int flDelayTime); -void frontlightFadeInAll(int flDelayTime, bool staggered); -void frontlightFadeOutAll(int flDelayTime); -void frontlightFadeOutAll(int flDelayTime, bool staggered); - -void frontlightFadeIn(uint num, int flDelayTime); -void frontlightFadeOut(uint num, int flDelayTime); -#endif // Do Not Disturb mode settings struct DNDTimeRange { @@ -94,12 +48,88 @@ struct DNDTimeRange { uint8_t endMinute; }; -extern bool dndEnabled; -extern bool dndTimeBasedEnabled; -extern DNDTimeRange dndTimeRange; +class LedHandler { +public: + static LedHandler& getInstance(); + + // Delete copy constructor and assignment operator + LedHandler(const LedHandler&) = delete; + LedHandler& operator=(const LedHandler&) = delete; -void setDNDEnabled(bool enabled); -void setDNDTimeBasedEnabled(bool enabled); -void setDNDTimeRange(uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute); -bool isDNDActive(); -bool isTimeInDNDRange(uint8_t hour, uint8_t minute); \ No newline at end of file + void setup(); + void setupTask(); + bool queueEffect(uint effect); + void clear(); + void setLights(int r, int g, int b); + void setLights(uint32_t color); + void saveLedState(); + void restoreLedState(); + QueueHandle_t getTaskQueue() const { return ledTaskQueue; } + Adafruit_NeoPixel& getPixels() { return pixels; } + + // DND methods + void setDNDEnabled(bool enabled); + void setDNDTimeBasedEnabled(bool enabled); + void setDNDTimeRange(uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute); + bool isDNDActive() const; + bool isTimeInDNDRange(uint8_t hour, uint8_t minute) const; + + // DND getters + bool isDNDEnabled() const { return dndEnabled; } + bool isDNDTimeBasedEnabled() const { return dndTimeBasedEnabled; } + uint8_t getDNDStartHour() const { return dndTimeRange.startHour; } + uint8_t getDNDStartMinute() const { return dndTimeRange.startMinute; } + uint8_t getDNDEndHour() const { return dndTimeRange.endHour; } + uint8_t getDNDEndMinute() const { return dndTimeRange.endMinute; } + + // Effect methods + void rainbow(int wait); + void theaterChase(uint32_t color, int wait); + void theaterChaseRainbow(int wait); + void lightningStrike(); + void blinkDelay(int d, int times); + void blinkDelayColor(int d, int times, uint r, uint g, uint b); + void blinkDelayTwoColor(int d, int times, const uint32_t& c1, const uint32_t& c2); + +#ifdef HAS_FRONTLIGHT + void frontlightFlash(int flDelayTime); + void frontlightFadeInAll(); + void frontlightFadeOutAll(); + void frontlightFadeIn(uint num); + void frontlightFadeOut(uint num); + std::vector frontlightGetStatus(); + void frontlightSetBrightness(uint brightness); + bool frontlightIsOn() const { return frontlightOn; } + void frontlightFadeInAll(int flDelayTime, bool staggered = false); + void frontlightFadeOutAll(int flDelayTime, bool staggered = false); + void frontlightFadeIn(uint num, int flDelayTime); + void frontlightFadeOut(uint num, int flDelayTime); + void initializeFrontlight(); +#endif + +private: + LedHandler(); // Private constructor for singleton + void loadDNDSettings(); + static void ledTask(void* pvParameters); + + Adafruit_NeoPixel pixels; + TaskHandle_t ledTaskHandle; + QueueHandle_t ledTaskQueue; + uint ledTaskParams; + + // DND members + bool dndEnabled; + bool dndTimeBasedEnabled; + DNDTimeRange dndTimeRange; + +#ifdef HAS_FRONTLIGHT + static constexpr uint16_t FL_FADE_STEP = 25; + bool frontlightOn; + bool flInTransition; +#endif +}; + +// Global accessor function +inline LedHandler& getLedHandler() { + return LedHandler::getInstance(); +} \ No newline at end of file diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index b9a7a46..3c996a2 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -1,4 +1,5 @@ #include "nostr_notify.hpp" +#include "led_handler.hpp" std::vector pools; nostr::Transport *transport; @@ -286,7 +287,7 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) vTaskDelay(pdMS_TO_TICKS(315 * NUM_SCREENS) + pdMS_TO_TICKS(250)); if (preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP)) { - queueLedEffect(LED_EFFECT_NOSTR_ZAP); + getLedHandler().queueEffect(LED_EFFECT_NOSTR_ZAP); } if (timerPeriod > 0) { diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index c481b72..b42a505 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -1,4 +1,5 @@ #include "ota.hpp" +#include "led_handler.hpp" TaskHandle_t taskOtaHandle = NULL; bool isOtaUpdating = false; @@ -31,6 +32,9 @@ void setupOTA() void onOTAProgress(unsigned int progress, unsigned int total) { uint percentage = progress / (total / 100); + auto& ledHandler = getLedHandler(); + auto& pixels = ledHandler.getPixels(); + pixels.fill(pixels.Color(0, 255, 0)); if (percentage < 100) { @@ -84,15 +88,15 @@ void handleOTATask(void *parameter) { if (msg.updateType == UPDATE_ALL) { isOtaUpdating = true; - queueLedEffect(LED_FLASH_UPDATE); + getLedHandler().queueEffect(LED_FLASH_UPDATE); int resultWebUi = downloadUpdateHandler(UPDATE_WEBUI); - queueLedEffect(LED_FLASH_UPDATE); + getLedHandler().queueEffect(LED_FLASH_UPDATE); int resultFw = downloadUpdateHandler(UPDATE_FIRMWARE); if (resultWebUi == 0 && resultFw == 0) { ESP.restart(); } else { - queueLedEffect(LED_FLASH_ERROR); + getLedHandler().queueEffect(LED_FLASH_ERROR); vTaskDelay(pdMS_TO_TICKS(3000)); ESP.restart(); } diff --git a/src/lib/timers.cpp b/src/lib/timers.cpp index 4e67951..850ead3 100644 --- a/src/lib/timers.cpp +++ b/src/lib/timers.cpp @@ -1,4 +1,5 @@ #include "timers.hpp" +#include "led_handler.hpp" esp_timer_handle_t screenRotateTimer; esp_timer_handle_t minuteTimer; @@ -49,11 +50,11 @@ void setTimerActive(bool status) { if (status) { esp_timer_start_periodic(screenRotateTimer, getTimerSeconds() * usPerSecond); - queueLedEffect(LED_EFFECT_START_TIMER); + getLedHandler().queueEffect(LED_EFFECT_START_TIMER); preferences.putBool("timerActive", true); } else { esp_timer_stop(screenRotateTimer); - queueLedEffect(LED_EFFECT_PAUSE_TIMER); + getLedHandler().queueEffect(LED_EFFECT_PAUSE_TIMER); preferences.putBool("timerActive", false); } diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 2ba0a4d..4897811 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -1,4 +1,6 @@ #include "webserver.hpp" +#include "lib/led_handler.hpp" +#include "lib/shared.hpp" static const char* JSON_CONTENT = "application/json"; @@ -231,6 +233,7 @@ void asyncFirmwareUpdateHandler(AsyncWebServerRequest *request, String filename, JsonDocument getStatusObject() { + auto& ledHandler = getLedHandler(); JsonDocument root; root["currentScreen"] = ScreenHandler::getCurrentScreen(); @@ -238,25 +241,21 @@ JsonDocument getStatusObject() root["timerRunning"] = isTimerActive(); root["isOTAUpdating"] = getIsOTAUpdating(); root["espUptime"] = esp_timer_get_time() / 1000000; - // root["currentPrice"] = getPrice(); - // root["currentBlockHeight"] = getBlockHeight(); root["espFreeHeap"] = ESP.getFreeHeap(); root["espHeapSize"] = ESP.getHeapSize(); - // root["espFreePsram"] = ESP.getFreePsram(); - // root["espPsramSize"] = ESP.getPsramSize(); JsonObject conStatus = root["connectionStatus"].to(); conStatus["price"] = isPriceNotifyConnected(); conStatus["blocks"] = isBlockNotifyConnected(); conStatus["V2"] = V2Notify::isV2NotifyConnected(); - conStatus["nostr"] = nostrConnected(); root["rssi"] = WiFi.RSSI(); root["currency"] = getCurrencyCode(ScreenHandler::getCurrentCurrency()); + #ifdef HAS_FRONTLIGHT - std::vector statuses = frontlightGetStatus(); + std::vector statuses = ledHandler.frontlightGetStatus(); uint16_t arr[NUM_SCREENS]; std::copy(statuses.begin(), statuses.end(), arr); @@ -270,22 +269,24 @@ JsonDocument getStatusObject() #endif // Add DND status - root["dnd"]["enabled"] = dndEnabled; - root["dnd"]["timeBasedEnabled"] = dndTimeBasedEnabled; - root["dnd"]["startTime"] = String(dndTimeRange.startHour) + ":" + - (dndTimeRange.startMinute < 10 ? "0" : "") + String(dndTimeRange.startMinute); - root["dnd"]["endTime"] = String(dndTimeRange.endHour) + ":" + - (dndTimeRange.endMinute < 10 ? "0" : "") + String(dndTimeRange.endMinute); - root["dnd"]["active"] = isDNDActive(); + root["dnd"]["enabled"] = ledHandler.isDNDEnabled(); + root["dnd"]["timeBasedEnabled"] = ledHandler.isDNDTimeBasedEnabled(); + root["dnd"]["startTime"] = String(ledHandler.getDNDStartHour()) + ":" + + (ledHandler.getDNDStartMinute() < 10 ? "0" : "") + String(ledHandler.getDNDStartMinute()); + root["dnd"]["endTime"] = String(ledHandler.getDNDEndHour()) + ":" + + (ledHandler.getDNDEndMinute() < 10 ? "0" : "") + String(ledHandler.getDNDEndMinute()); + root["dnd"]["active"] = ledHandler.isDNDActive(); return root; } JsonDocument getLedStatusObject() { + auto& ledHandler = getLedHandler(); + auto& pixels = ledHandler.getPixels(); + JsonDocument root; JsonArray colors = root["data"].to(); - // Adafruit_NeoPixel pix = getPixels(); for (uint i = 0; i < pixels.numPixels(); i++) { @@ -295,13 +296,7 @@ JsonDocument getLedStatusObject() uint blue = pixColor & 0xFF; char hexColor[8]; snprintf(hexColor, sizeof(hexColor), "#%02X%02X%02X", red, green, blue); - - - JsonObject object = colors.add(); - object["red"] = red; - object["green"] = green; - object["blue"] = blue; - object["hex"] = hexColor; + colors.add(hexColor); } return root; @@ -621,24 +616,26 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) // Handle DND settings if (settings.containsKey("dnd")) { JsonObject dndObj = settings["dnd"]; + auto& ledHandler = getLedHandler(); + if (dndObj.containsKey("timeBasedEnabled")) { - setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as()); + ledHandler.setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as()); } if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") && dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) { - setDNDTimeRange( - dndObj["startHour"].as(), - dndObj["startMinute"].as(), - dndObj["endHour"].as(), - dndObj["endMinute"].as() - ); + ledHandler.setDNDTimeRange( + dndObj["startHour"].as(), + dndObj["startMinute"].as(), + dndObj["endHour"].as(), + dndObj["endMinute"].as()); } } request->send(HTTP_OK); if (settingsChanged) { - queueLedEffect(LED_FLASH_SUCCESS); + auto& ledHandler = getLedHandler(); + ledHandler.queueEffect(LED_FLASH_SUCCESS); } } @@ -659,7 +656,8 @@ void onApiRestart(AsyncWebServerRequest *request) void onApiIdentify(AsyncWebServerRequest *request) { - queueLedEffect(LED_FLASH_IDENTIFY); + auto& ledHandler = getLedHandler(); + ledHandler.queueEffect(LED_FLASH_IDENTIFY); request->send(HTTP_OK); } @@ -797,12 +795,13 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["ceDisableSSL"] = preferences.getBool("ceDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL); // Add DND settings - root["dnd"]["enabled"] = dndEnabled; - root["dnd"]["timeBasedEnabled"] = dndTimeBasedEnabled; - root["dnd"]["startHour"] = dndTimeRange.startHour; - root["dnd"]["startMinute"] = dndTimeRange.startMinute; - root["dnd"]["endHour"] = dndTimeRange.endHour; - root["dnd"]["endMinute"] = dndTimeRange.endMinute; + auto& ledHandler = getLedHandler(); + root["dnd"]["enabled"] = ledHandler.isDNDEnabled(); + root["dnd"]["timeBasedEnabled"] = ledHandler.isDNDTimeBasedEnabled(); + root["dnd"]["startHour"] = ledHandler.getDNDStartHour(); + root["dnd"]["startMinute"] = ledHandler.getDNDStartMinute(); + root["dnd"]["endHour"] = ledHandler.getDNDEndHour(); + root["dnd"]["endMinute"] = ledHandler.getDNDEndMinute(); AsyncResponseStream *response = request->beginResponseStream(JSON_CONTENT); @@ -929,7 +928,8 @@ void onApiRestartDataSources(AsyncWebServerRequest *request) void onApiLightsOff(AsyncWebServerRequest *request) { - setLights(0, 0, 0); + auto& ledHandler = getLedHandler(); + ledHandler.setLights(0, 0, 0); request->send(HTTP_OK); } @@ -944,13 +944,15 @@ void onApiLightsSetColor(AsyncWebServerRequest *request) if (rgbColor.compareTo("off") == 0) { - setLights(0, 0, 0); + auto& ledHandler = getLedHandler(); + ledHandler.setLights(0, 0, 0); } else { uint r, g, b; sscanf(rgbColor.c_str(), "%02x%02x%02x", &r, &g, &b); - setLights(r, g, b); + auto& ledHandler = getLedHandler(); + ledHandler.setLights(r, g, b); } JsonDocument doc; @@ -968,6 +970,9 @@ void onApiLightsSetColor(AsyncWebServerRequest *request) void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json) { + auto& ledHandler = getLedHandler(); + auto& pixels = ledHandler.getPixels(); + JsonArray lights = json.as(); if (lights.size() != pixels.numPixels()) @@ -1016,7 +1021,7 @@ void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json) } pixels.show(); - saveLedState(); + ledHandler.saveLedState(); request->send(HTTP_OK); } @@ -1080,19 +1085,21 @@ void onApiShowCurrency(AsyncWebServerRequest *request) #ifdef HAS_FRONTLIGHT void onApiFrontlightOn(AsyncWebServerRequest *request) { - frontlightFadeInAll(); + auto& ledHandler = getLedHandler(); + ledHandler.frontlightFadeInAll(); request->send(HTTP_OK); } void onApiFrontlightStatus(AsyncWebServerRequest *request) { + auto& ledHandler = getLedHandler(); AsyncResponseStream *response = request->beginResponseStream(JSON_CONTENT); JsonDocument root; - std::vector statuses = frontlightGetStatus(); + std::vector statuses = ledHandler.frontlightGetStatus(); uint16_t arr[NUM_SCREENS]; std::copy(statuses.begin(), statuses.end(), arr); @@ -1105,7 +1112,8 @@ void onApiFrontlightStatus(AsyncWebServerRequest *request) void onApiFrontlightFlash(AsyncWebServerRequest *request) { - frontlightFlash(preferences.getUInt("flEffectDelay")); + auto& ledHandler = getLedHandler(); + ledHandler.frontlightFlash(preferences.getUInt("flEffectDelay")); request->send(HTTP_OK); } @@ -1114,7 +1122,8 @@ void onApiFrontlightSetBrightness(AsyncWebServerRequest *request) { if (request->hasParam("b")) { - frontlightSetBrightness(request->getParam("b")->value().toInt()); + auto& ledHandler = getLedHandler(); + ledHandler.frontlightSetBrightness(request->getParam("b")->value().toInt()); request->send(HTTP_OK); } else @@ -1125,21 +1134,51 @@ void onApiFrontlightSetBrightness(AsyncWebServerRequest *request) void onApiFrontlightOff(AsyncWebServerRequest *request) { - frontlightFadeOutAll(); + auto& ledHandler = getLedHandler(); + ledHandler.frontlightFadeOutAll(); request->send(HTTP_OK); } #endif +void onApiDNDTimeBasedEnable(AsyncWebServerRequest *request) { + auto& ledHandler = getLedHandler(); + ledHandler.setDNDTimeBasedEnabled(true); + request->send(200); +} + +void onApiDNDTimeBasedDisable(AsyncWebServerRequest *request) { + auto& ledHandler = getLedHandler(); + ledHandler.setDNDTimeBasedEnabled(false); + request->send(200); +} + +void onApiDNDSetTimeRange(AsyncWebServerRequest *request) { + if (request->hasParam("startHour") && request->hasParam("startMinute") && + request->hasParam("endHour") && request->hasParam("endMinute")) { + auto& ledHandler = getLedHandler(); + uint8_t startHour = request->getParam("startHour")->value().toInt(); + uint8_t startMinute = request->getParam("startMinute")->value().toInt(); + uint8_t endHour = request->getParam("endHour")->value().toInt(); + uint8_t endMinute = request->getParam("endMinute")->value().toInt(); + + ledHandler.setDNDTimeRange(startHour, startMinute, endHour, endMinute); + request->send(200); + } else { + request->send(400); + } +} + void onApiDNDStatus(AsyncWebServerRequest *request) { + auto& ledHandler = getLedHandler(); JsonDocument doc; - doc["enabled"] = dndEnabled; - doc["timeBasedEnabled"] = dndTimeBasedEnabled; - doc["startTime"] = String(dndTimeRange.startHour) + ":" + - (dndTimeRange.startMinute < 10 ? "0" : "") + String(dndTimeRange.startMinute); - doc["endTime"] = String(dndTimeRange.endHour) + ":" + - (dndTimeRange.endMinute < 10 ? "0" : "") + String(dndTimeRange.endMinute); - doc["active"] = isDNDActive(); + doc["enabled"] = ledHandler.isDNDEnabled(); + doc["timeBasedEnabled"] = ledHandler.isDNDTimeBasedEnabled(); + doc["startTime"] = String(ledHandler.getDNDStartHour()) + ":" + + (ledHandler.getDNDStartMinute() < 10 ? "0" : "") + String(ledHandler.getDNDStartMinute()); + doc["endTime"] = String(ledHandler.getDNDEndHour()) + ":" + + (ledHandler.getDNDEndMinute() < 10 ? "0" : "") + String(ledHandler.getDNDEndMinute()); + doc["active"] = ledHandler.isDNDActive(); String response; serializeJson(doc, response); @@ -1147,11 +1186,92 @@ void onApiDNDStatus(AsyncWebServerRequest *request) { } void onApiDNDEnable(AsyncWebServerRequest *request) { - setDNDEnabled(true); + auto& ledHandler = getLedHandler(); + ledHandler.setDNDEnabled(true); request->send(200); } void onApiDNDDisable(AsyncWebServerRequest *request) { - setDNDEnabled(false); + auto& ledHandler = getLedHandler(); + ledHandler.setDNDEnabled(false); request->send(200); +} + +void onApiLightsGet(AsyncWebServerRequest *request) +{ + auto& ledHandler = getLedHandler(); + auto& pixels = ledHandler.getPixels(); + + DynamicJsonDocument doc(1024); + JsonArray lights = doc.createNestedArray("lights"); + + for (uint i = 0; i < pixels.numPixels(); i++) + { + uint32_t pixColor = pixels.getPixelColor(pixels.numPixels() - i - 1); + JsonObject light = lights.createNestedObject(); + light["r"] = (uint8_t)(pixColor >> 16); + light["g"] = (uint8_t)(pixColor >> 8); + light["b"] = (uint8_t)pixColor; + } + + String output; + serializeJson(doc, output); + request->send(200, "application/json", output); +} + +void onApiLightsPost(AsyncWebServerRequest *request, uint8_t *data, size_t len, + size_t index, size_t total) +{ + auto& ledHandler = getLedHandler(); + auto& pixels = ledHandler.getPixels(); + + DynamicJsonDocument doc(1024); + DeserializationError error = deserializeJson(doc, data); + if (error) + { + request->send(400); + return; + } + + JsonArray lights = doc["lights"]; + if (lights.size() != pixels.numPixels()) + { + request->send(400); + return; + } + + for (uint i = 0; i < pixels.numPixels(); i++) + { + JsonObject light = lights[i]; + uint8_t red = light["r"]; + uint8_t green = light["g"]; + uint8_t blue = light["b"]; + + pixels.setPixelColor((pixels.numPixels() - i - 1), + pixels.Color(red, green, blue)); + } + pixels.show(); + + request->send(200); +} + +void onApiSettings(AsyncWebServerRequest *request, JsonVariant &json) +{ + JsonObject settings = json.as(); + auto& ledHandler = getLedHandler(); + + if (settings.containsKey("dnd")) { + JsonObject dndObj = settings["dnd"]; + if (dndObj.containsKey("timeBasedEnabled")) { + ledHandler.setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as()); + } + if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") && + dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) { + ledHandler.setDNDTimeRange( + dndObj["startHour"].as(), + dndObj["startMinute"].as(), + dndObj["endHour"].as(), + dndObj["endMinute"].as()); + } + } } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 4943a51..c892a03 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,6 +18,7 @@ #define WEBSERVER_H #include "ESPAsyncWebServer.h" #include "lib/config.hpp" +#include "lib/led_handler.hpp" uint wifiLostConnection; uint priceNotifyLostConnection = 0; @@ -58,13 +59,14 @@ void handleFrontlight() { if (hasLightLevel() && preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) != 0) { uint lightLevel = getLightLevel(); uint luxThreshold = preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE); + auto& ledHandler = getLedHandler(); if (lightLevel <= 1 && preferences.getBool("flOffWhenDark", DEFAULT_FL_OFF_WHEN_DARK)) { - if (frontlightIsOn()) frontlightFadeOutAll(); - } else if (lightLevel < luxThreshold && !frontlightIsOn()) { - frontlightFadeInAll(); - } else if (frontlightIsOn() && lightLevel > luxThreshold) { - frontlightFadeOutAll(); + if (ledHandler.frontlightIsOn()) ledHandler.frontlightFadeOutAll(); + } else if (lightLevel < luxThreshold && !ledHandler.frontlightIsOn()) { + ledHandler.frontlightFadeInAll(); + } else if (ledHandler.frontlightIsOn() && lightLevel > luxThreshold) { + ledHandler.frontlightFadeOutAll(); } } #endif @@ -100,9 +102,7 @@ void checkMissedBlocks() { } } - void monitorDataConnections() { - // Price notification monitoring if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE) && !isPriceNotifyConnected()) { handlePriceNotifyDisconnection(); @@ -137,7 +137,6 @@ extern "C" void app_main() { bool thirdPartySource = getDataSource() == THIRD_PARTY_SOURCE; - while (true) { if (eventSourceTaskHandle != NULL) { xTaskNotifyGive(eventSourceTaskHandle); From a6a8b5a0710a4555e18201f7c7fc3a3f1e561370 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 22:11:53 +0100 Subject: [PATCH 06/30] Refactor EPD code to EPDManager class --- src/lib/block_notify.cpp | 37 +- src/lib/config.cpp | 60 +-- src/lib/epd.cpp | 867 +++++++++++++++---------------------- src/lib/epd.hpp | 119 +++-- src/lib/nostr_notify.cpp | 20 +- src/lib/ota.cpp | 4 +- src/lib/screen_handler.cpp | 16 +- src/lib/shared.cpp | 3 +- src/lib/shared.hpp | 5 +- src/lib/v2_notify.cpp | 5 + src/lib/webserver.cpp | 34 +- 11 files changed, 532 insertions(+), 638 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 111a7a7..37b5b2f 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -181,45 +181,42 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) } void processNewBlock(uint32_t newBlockHeight) { - if (newBlockHeight < currentBlockHeight) - return; + if (currentBlockHeight == newBlockHeight) + { + return; + } - currentBlockHeight = newBlockHeight; - - // Serial.printf("New block found: %d\r\n", block["height"].as()); - preferences.putUInt("blockHeight", currentBlockHeight); + currentBlockHeight = newBlockHeight; lastBlockUpdate = esp_timer_get_time() / 1000000; if (workQueue != nullptr) { - WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; - xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); - // xTaskNotifyGive(blockUpdateTaskHandle); + WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + } - if (ScreenHandler::getCurrentScreen() != SCREEN_BLOCK_HEIGHT && - preferences.getBool("stealFocus", DEFAULT_STEAL_FOCUS)) - { + if (ScreenHandler::getCurrentScreen() != SCREEN_BLOCK_HEIGHT && + preferences.getBool("stealFocus", DEFAULT_STEAL_FOCUS)) + { uint64_t timerPeriod = 0; if (isTimerActive()) { - // store timer periode before making inactive to prevent artifacts - timerPeriod = getTimerSeconds(); - esp_timer_stop(screenRotateTimer); + timerPeriod = getTimerSeconds(); + esp_timer_stop(screenRotateTimer); } ScreenHandler::setCurrentScreen(SCREEN_BLOCK_HEIGHT); if (timerPeriod > 0) { - esp_timer_start_periodic(screenRotateTimer, + esp_timer_start_periodic(screenRotateTimer, timerPeriod * usPerSecond); } vTaskDelay(pdMS_TO_TICKS(315*NUM_SCREENS)); // Extra delay because of screen switching - } + } - if (preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD)) - { + if (preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD)) + { vTaskDelay(pdMS_TO_TICKS(250)); // Wait until screens are updated getLedHandler().queueEffect(LED_FLASH_BLOCK_NOTIFY); - } } } diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 5a3fa7d..6354a7a 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -48,7 +48,7 @@ void setup() setupPreferences(); setupHardware(); - setupDisplays(); + EPDManager::getInstance().initialize(); if (preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER)) { auto& ledHandler = getLedHandler(); @@ -115,7 +115,7 @@ void setup() ButtonHandler::setup(); setupOTA(); - waitUntilNoneBusy(); + EPDManager::getInstance().waitUntilNoneBusy(); #ifdef HAS_FRONTLIGHT if (!preferences.getBool("flAlwaysOn", DEFAULT_FL_ALWAYS_ON)) @@ -126,7 +126,7 @@ void setup() } #endif - forceFullRefresh(); + EPDManager::getInstance().forceFullRefresh(); } void setupWifi() @@ -181,8 +181,8 @@ void setupWifi() wifiManager->getConfigPortalSSID().c_str(), softAP_password.c_str()); // delay(6000); - setFgColor(GxEPD_BLACK); - setBgColor(GxEPD_WHITE); + EPDManager::getInstance().setForegroundColor(GxEPD_BLACK); + EPDManager::getInstance().setBackgroundColor(GxEPD_WHITE); const String qrText = "qrWIFI:S:" + wifiManager->getConfigPortalSSID() + ";T:WPA;P:" + softAP_password.c_str() + ";;"; const String explainText = "*SSID: *\r\n" + @@ -212,58 +212,22 @@ void setupWifi() #endif "\r\n\r\n*FW build date:*\r\n" + formattedDate, qrText}; - setEpdContent(epdContent); }); + + EPDManager::getInstance().setContent(epdContent); }); wm.setSaveConfigCallback([]() { preferences.putBool("wifiConfigured", true); delay(1000); - // just restart after succes + // just restart after success ESP.restart(); }); bool ac = wm.autoConnect(softAP_SSID.c_str(), softAP_password.c_str()); - - // waitUntilNoneBusy(); - // std::array epdContent = {"Welcome!", - // "Bienvenidos!", "Use\r\nweb-interface\r\npara configurar", "Use\r\nla - // interfaz web\r\npara configurar", "Or - // restart\r\nwhile\r\nholding\r\n2nd button\r\r\nto start\r\n QR-config", - // "O reinicie\r\nmientras\r\n mantiene presionado\r\nel segundo - // botón\r\r\npara iniciar\r\nQR-config", ""}; setEpdContent(epdContent); - // esp_task_wdt_init(30, false); - // uint count = 0; - // while (WiFi.status() != WL_CONNECTED) - // { - // if (Serial.available() > 0) - // { - // uint8_t b = Serial.read(); - - // if (parse_improv_serial_byte(x_position, b, x_buffer, - // onImprovCommandCallback, onImprovErrorCallback)) - // { - // x_buffer[x_position++] = b; - // } - // else - // { - // x_position = 0; - // } - // } - // count++; - - // if (count > 2000000) { - // queueLedEffect(LED_EFFECT_HEARTBEAT); - // count = 0; - // } - // } - // esp_task_wdt_deinit(); - // esp_task_wdt_reset(); } - - - setFgColor(preferences.getUInt("fgColor", isWhiteVersion() ? GxEPD_BLACK : GxEPD_WHITE)); - setBgColor(preferences.getUInt("bgColor", isWhiteVersion() ? GxEPD_WHITE : GxEPD_BLACK)); + EPDManager::getInstance().setForegroundColor(preferences.getUInt("fgColor", isWhiteVersion() ? GxEPD_BLACK : GxEPD_WHITE)); + EPDManager::getInstance().setBackgroundColor(preferences.getUInt("bgColor", isWhiteVersion() ? GxEPD_WHITE : GxEPD_BLACK)); } // else // { @@ -299,8 +263,8 @@ void setupPreferences() { preferences.begin("btclock", false); - setFgColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR)); - setBgColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR)); + EPDManager::getInstance().setForegroundColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR)); + EPDManager::getInstance().setBackgroundColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR)); setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT)); setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD); diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 9808aa7..fa389c6 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -1,455 +1,278 @@ #include "epd.hpp" +// Initialize static members #ifdef IS_BTCLOCK_REV_B -Native_Pin EPD_CS[NUM_SCREENS] = { - Native_Pin(2), - Native_Pin(4), - Native_Pin(6), - Native_Pin(10), - Native_Pin(38), - Native_Pin(21), - Native_Pin(17), +Native_Pin EPDManager::EPD_DC(14); +std::array EPDManager::EPD_CS = { + Native_Pin(2), Native_Pin(4), Native_Pin(6), Native_Pin(10), + Native_Pin(38), Native_Pin(21), Native_Pin(17) }; -Native_Pin EPD_BUSY[NUM_SCREENS] = { - Native_Pin(3), - Native_Pin(5), - Native_Pin(7), - Native_Pin(9), - Native_Pin(37), - Native_Pin(18), - Native_Pin(16), +std::array EPDManager::EPD_BUSY = { + Native_Pin(3), Native_Pin(5), Native_Pin(7), Native_Pin(9), + Native_Pin(37), Native_Pin(18), Native_Pin(16) }; -MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { - MCP23X17_Pin(mcp1, 8), - MCP23X17_Pin(mcp1, 9), - MCP23X17_Pin(mcp1, 10), - MCP23X17_Pin(mcp1, 11), - MCP23X17_Pin(mcp1, 12), - MCP23X17_Pin(mcp1, 13), - MCP23X17_Pin(mcp1, 14), +std::array EPDManager::EPD_RESET = { + MCP23X17_Pin(mcp1, 8), MCP23X17_Pin(mcp1, 9), MCP23X17_Pin(mcp1, 10), + MCP23X17_Pin(mcp1, 11), MCP23X17_Pin(mcp1, 12), MCP23X17_Pin(mcp1, 13), + MCP23X17_Pin(mcp1, 14) }; - -Native_Pin EPD_DC = Native_Pin(14); -#elif IS_BTCLOCK_V8 -Native_Pin EPD_DC = Native_Pin(38); - -MCP23X17_Pin EPD_BUSY[NUM_SCREENS] = { - MCP23X17_Pin(mcp1, 8), - MCP23X17_Pin(mcp1, 9), - MCP23X17_Pin(mcp1, 10), - MCP23X17_Pin(mcp1, 11), - MCP23X17_Pin(mcp1, 12), - MCP23X17_Pin(mcp1, 13), - MCP23X17_Pin(mcp1, 14), - MCP23X17_Pin(mcp1, 4), +#elif defined(IS_BTCLOCK_V8) +Native_Pin EPDManager::EPD_DC(38); +std::array EPDManager::EPD_BUSY = { + MCP23X17_Pin(mcp1, 8), MCP23X17_Pin(mcp1, 9), MCP23X17_Pin(mcp1, 10), + MCP23X17_Pin(mcp1, 11), MCP23X17_Pin(mcp1, 12), MCP23X17_Pin(mcp1, 13), + MCP23X17_Pin(mcp1, 14), MCP23X17_Pin(mcp1, 4) }; - -MCP23X17_Pin EPD_CS[NUM_SCREENS] = { +std::array EPDManager::EPD_CS = { MCP23X17_Pin(mcp2, 8), MCP23X17_Pin(mcp2, 10), MCP23X17_Pin(mcp2, 12), MCP23X17_Pin(mcp2, 14), MCP23X17_Pin(mcp2, 0), MCP23X17_Pin(mcp2, 2), - MCP23X17_Pin(mcp2, 4), MCP23X17_Pin(mcp2, 6)}; - -MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { - MCP23X17_Pin(mcp2, 9), - MCP23X17_Pin(mcp2, 11), - MCP23X17_Pin(mcp2, 13), - MCP23X17_Pin(mcp2, 15), - MCP23X17_Pin(mcp2, 1), - MCP23X17_Pin(mcp2, 3), - MCP23X17_Pin(mcp2, 5), - MCP23X17_Pin(mcp2, 7), + MCP23X17_Pin(mcp2, 4), MCP23X17_Pin(mcp2, 6) +}; +std::array EPDManager::EPD_RESET = { + MCP23X17_Pin(mcp2, 9), MCP23X17_Pin(mcp2, 11), MCP23X17_Pin(mcp2, 13), + MCP23X17_Pin(mcp2, 15), MCP23X17_Pin(mcp2, 1), MCP23X17_Pin(mcp2, 3), + MCP23X17_Pin(mcp2, 5), MCP23X17_Pin(mcp2, 7) }; #else -Native_Pin EPD_CS[NUM_SCREENS] = { - Native_Pin(2), - Native_Pin(4), - Native_Pin(6), - Native_Pin(10), - Native_Pin(33), - Native_Pin(21), - Native_Pin(17), -#if NUM_SCREENS == 9 - // MCP23X17_Pin(mcp2, 7), - Native_Pin(-1), - Native_Pin(-1), -#endif +Native_Pin EPDManager::EPD_DC(14); +std::array EPDManager::EPD_CS = { + Native_Pin(2), Native_Pin(4), Native_Pin(6), Native_Pin(10), + Native_Pin(33), Native_Pin(21), Native_Pin(17) }; -Native_Pin EPD_BUSY[NUM_SCREENS] = { - Native_Pin(3), - Native_Pin(5), - Native_Pin(7), - Native_Pin(9), - Native_Pin(37), - Native_Pin(18), - Native_Pin(16), +std::array EPDManager::EPD_BUSY = { + Native_Pin(3), Native_Pin(5), Native_Pin(7), Native_Pin(9), + Native_Pin(37), Native_Pin(18), Native_Pin(16) }; -MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { - MCP23X17_Pin(mcp1, 8), - MCP23X17_Pin(mcp1, 9), - MCP23X17_Pin(mcp1, 10), - MCP23X17_Pin(mcp1, 11), - MCP23X17_Pin(mcp1, 12), - MCP23X17_Pin(mcp1, 13), - MCP23X17_Pin(mcp1, 14), +std::array EPDManager::EPD_RESET = { + MCP23X17_Pin(mcp1, 8), MCP23X17_Pin(mcp1, 9), MCP23X17_Pin(mcp1, 10), + MCP23X17_Pin(mcp1, 11), MCP23X17_Pin(mcp1, 12), MCP23X17_Pin(mcp1, 13), + MCP23X17_Pin(mcp1, 14) }; - -Native_Pin EPD_DC = Native_Pin(14); #endif -GxEPD2_BW displays[NUM_SCREENS] = { - EPD_CLASS(&EPD_CS[0], &EPD_DC, &EPD_RESET_MPD[0], &EPD_BUSY[0]), - EPD_CLASS(&EPD_CS[1], &EPD_DC, &EPD_RESET_MPD[1], &EPD_BUSY[1]), - EPD_CLASS(&EPD_CS[2], &EPD_DC, &EPD_RESET_MPD[2], &EPD_BUSY[2]), - EPD_CLASS(&EPD_CS[3], &EPD_DC, &EPD_RESET_MPD[3], &EPD_BUSY[3]), - EPD_CLASS(&EPD_CS[4], &EPD_DC, &EPD_RESET_MPD[4], &EPD_BUSY[4]), - EPD_CLASS(&EPD_CS[5], &EPD_DC, &EPD_RESET_MPD[5], &EPD_BUSY[5]), - EPD_CLASS(&EPD_CS[6], &EPD_DC, &EPD_RESET_MPD[6], &EPD_BUSY[6]), -#ifdef IS_BTCLOCK_V8 - EPD_CLASS(&EPD_CS[7], &EPD_DC, &EPD_RESET_MPD[7], &EPD_BUSY[7]), -#endif -}; - -std::array currentEpdContent; -std::array epdContent; -uint32_t lastFullRefresh[NUM_SCREENS]; -TaskHandle_t tasks[NUM_SCREENS]; -// TaskHandle_t epdTaskHandle = NULL; - -#define UPDATE_QUEUE_SIZE 14 -QueueHandle_t updateQueue; - -// SemaphoreHandle_t epdUpdateSemaphore[NUM_SCREENS]; - -int fgColor = GxEPD_WHITE; -int bgColor = GxEPD_BLACK; - -struct FontFamily { - GFXfont* big; - GFXfont* medium; - GFXfont* small; -}; - -FontFamily antonioFonts = {nullptr, nullptr, nullptr}; -FontFamily oswaldFonts = {nullptr, nullptr, nullptr}; - -const GFXfont *FONT_SMALL; -const GFXfont *FONT_BIG; -const GFXfont *FONT_MEDIUM; -const GFXfont *FONT_SATSYMBOL; - -std::mutex epdUpdateMutex; -std::mutex epdMutex[NUM_SCREENS]; - -#ifdef IS_BTCLOCK_V8 -#define EPD_TASK_STACK_SIZE 4096 -#else -#define EPD_TASK_STACK_SIZE 2048 -#endif - -#define BUSY_TIMEOUT_COUNT 200 -#define BUSY_RETRY_DELAY pdMS_TO_TICKS(10) - -void forceFullRefresh() -{ - for (uint i = 0; i < NUM_SCREENS; i++) - { - lastFullRefresh[i] = NULL; - } +EPDManager& EPDManager::getInstance() { + static EPDManager instance; + return instance; } -void loadFonts(const String& fontName) { - if (fontName == FontNames::ANTONIO) { - // Load Antonio fonts - antonioFonts.big = FontLoader::loadCompressedFont(Antonio_SemiBold90pt7b_Properties); - antonioFonts.medium = FontLoader::loadCompressedFont(Antonio_SemiBold40pt7b_Properties); - antonioFonts.small = FontLoader::loadCompressedFont(Antonio_SemiBold20pt7b_Properties); - - FONT_BIG = antonioFonts.big; - FONT_MEDIUM = antonioFonts.medium; - FONT_SMALL = antonioFonts.small; - } else if (fontName == FontNames::OSWALD) { - // Load Oswald fonts - oswaldFonts.big = FontLoader::loadCompressedFont(Oswald_Medium80pt7b_Properties); - oswaldFonts.medium = FontLoader::loadCompressedFont(Oswald_Medium30pt7b_Properties); - oswaldFonts.small = FontLoader::loadCompressedFont(Oswald_Medium20pt7b_Properties); - - FONT_BIG = oswaldFonts.big; - FONT_MEDIUM = oswaldFonts.medium; - FONT_SMALL = oswaldFonts.small; - } - - FONT_SATSYMBOL = FontLoader::loadCompressedFont(Satoshi_Symbol90pt7b_Properties); -} - -void setupDisplays() { - // Load fonts based on preference - String fontName = preferences.getString("fontName", DEFAULT_FONT_NAME); - loadFonts(fontName); - - // Initialize displays - std::lock_guard lockMcp(mcpMutex); - for (uint i = 0; i < NUM_SCREENS; i++) { - displays[i].init(0, true, 30); - } - - // Create update queue and task - updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem)); - xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", EPD_TASK_STACK_SIZE * 2, NULL, 11, NULL); - - // Create display update tasks - for (uint i = 0; i < NUM_SCREENS; i++) { - int *taskParam = new int; - *taskParam = i; - xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), EPD_TASK_STACK_SIZE, taskParam, 11, &tasks[i]); - } - - // Check for storage mode (prevents burn-in) - if (mcp1.read1(0) == LOW) { - setFgColor(GxEPD_BLACK); - setBgColor(GxEPD_WHITE); - epdContent.fill(""); - } else { - // Initialize with custom text or default - String customText = preferences.getString("displayText", DEFAULT_BOOT_TEXT); - std::array newContent; - newContent.fill(" "); - - for (size_t i = 0; i < std::min(customText.length(), (size_t)NUM_SCREENS); i++) { - newContent[i] = String(customText[i]); +EPDManager::EPDManager() + : currentContent{} + , content{} + , lastFullRefresh{} + , tasks{} + , updateQueue{nullptr} + , antonioFonts{nullptr, nullptr, nullptr} + , oswaldFonts{nullptr, nullptr, nullptr} + , fontSmall{nullptr} + , fontBig{nullptr} + , fontMedium{nullptr} + , fontSatsymbol{nullptr} + , bgColor{GxEPD_BLACK} + , fgColor{GxEPD_WHITE} + , displays{ + #ifdef IS_BTCLOCK_V8 + EPD_CLASS(&EPD_CS[0], &EPD_DC, &EPD_RESET[0], &EPD_BUSY[0]), + EPD_CLASS(&EPD_CS[1], &EPD_DC, &EPD_RESET[1], &EPD_BUSY[1]), + EPD_CLASS(&EPD_CS[2], &EPD_DC, &EPD_RESET[2], &EPD_BUSY[2]), + EPD_CLASS(&EPD_CS[3], &EPD_DC, &EPD_RESET[3], &EPD_BUSY[3]), + EPD_CLASS(&EPD_CS[4], &EPD_DC, &EPD_RESET[4], &EPD_BUSY[4]), + EPD_CLASS(&EPD_CS[5], &EPD_DC, &EPD_RESET[5], &EPD_BUSY[5]), + EPD_CLASS(&EPD_CS[6], &EPD_DC, &EPD_RESET[6], &EPD_BUSY[6]), + EPD_CLASS(&EPD_CS[7], &EPD_DC, &EPD_RESET[7], &EPD_BUSY[7]) + #else + EPD_CLASS(&EPD_CS[0], &EPD_DC, &EPD_RESET[0], &EPD_BUSY[0]), + EPD_CLASS(&EPD_CS[1], &EPD_DC, &EPD_RESET[1], &EPD_BUSY[1]), + EPD_CLASS(&EPD_CS[2], &EPD_DC, &EPD_RESET[2], &EPD_BUSY[2]), + EPD_CLASS(&EPD_CS[3], &EPD_DC, &EPD_RESET[3], &EPD_BUSY[3]), + EPD_CLASS(&EPD_CS[4], &EPD_DC, &EPD_RESET[4], &EPD_BUSY[4]), + EPD_CLASS(&EPD_CS[5], &EPD_DC, &EPD_RESET[5], &EPD_BUSY[5]), + EPD_CLASS(&EPD_CS[6], &EPD_DC, &EPD_RESET[6], &EPD_BUSY[6]) + #endif } - - epdContent = newContent; - } - - setEpdContent(epdContent); +{ } -void setEpdContent(std::array newEpdContent) -{ - setEpdContent(newEpdContent, false); -} - -void setEpdContent(std::array newEpdContent) -{ - std::array conv; - - for (size_t i = 0; i < newEpdContent.size(); ++i) - { - conv[i] = String(newEpdContent[i].c_str()); +EPDManager::~EPDManager() { + // Clean up tasks + for (auto& task : tasks) { + if (task != nullptr) { + vTaskDelete(task); + } } - return setEpdContent(conv); + // Clean up queue + if (updateQueue != nullptr) { + vQueueDelete(updateQueue); + } + + // Clean up fonts + delete antonioFonts.big; + delete antonioFonts.medium; + delete antonioFonts.small; + delete oswaldFonts.big; + delete oswaldFonts.medium; + delete oswaldFonts.small; } -void setEpdContent(std::array newEpdContent, - bool forceUpdate) -{ - std::lock_guard lock(epdUpdateMutex); +void EPDManager::initialize() { + // Load fonts based on preference + String fontName = preferences.getString("fontName", DEFAULT_FONT_NAME); + loadFonts(fontName); + // Initialize displays + std::lock_guard lockMcp(mcpMutex); + for (auto& display : displays) { + display.init(0, true, 30); + } + + // Create update queue and task + updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem)); + xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", EPD_TASK_STACK_SIZE * 2, nullptr, 11, nullptr); + + // Create display update tasks + for (size_t i = 0; i < NUM_SCREENS; i++) { + auto* taskParam = new int(i); + xTaskCreate(updateDisplayTask, ("EpdUpd" + String(i)).c_str(), EPD_TASK_STACK_SIZE, + taskParam, 11, &tasks[i]); + } + + // Check for storage mode (prevents burn-in) + if (mcp1.read1(0) == LOW) { + setForegroundColor(GxEPD_BLACK); + setBackgroundColor(GxEPD_WHITE); + content.fill(""); + } else { + // Initialize with custom text or default + String customText = preferences.getString("displayText", DEFAULT_BOOT_TEXT); + std::array newContent; + newContent.fill(" "); + + for (size_t i = 0; i < std::min(customText.length(), (size_t)NUM_SCREENS); i++) { + newContent[i] = String(customText[i]); + } + + content = newContent; + } + + setContent(content); +} + +void EPDManager::loadFonts(const String& fontName) { + if (fontName == FontNames::ANTONIO) { + // Load Antonio fonts + antonioFonts.big = FontLoader::loadCompressedFont(Antonio_SemiBold90pt7b_Properties); + antonioFonts.medium = FontLoader::loadCompressedFont(Antonio_SemiBold40pt7b_Properties); + antonioFonts.small = FontLoader::loadCompressedFont(Antonio_SemiBold20pt7b_Properties); + + fontBig = antonioFonts.big; + fontMedium = antonioFonts.medium; + fontSmall = antonioFonts.small; + } else if (fontName == FontNames::OSWALD) { + // Load Oswald fonts + oswaldFonts.big = FontLoader::loadCompressedFont(Oswald_Medium80pt7b_Properties); + oswaldFonts.medium = FontLoader::loadCompressedFont(Oswald_Medium30pt7b_Properties); + oswaldFonts.small = FontLoader::loadCompressedFont(Oswald_Medium20pt7b_Properties); + + fontBig = oswaldFonts.big; + fontMedium = oswaldFonts.medium; + fontSmall = oswaldFonts.small; + } + + fontSatsymbol = FontLoader::loadCompressedFont(Satoshi_Symbol90pt7b_Properties); +} + +void EPDManager::forceFullRefresh() { + std::fill(lastFullRefresh.begin(), lastFullRefresh.end(), 0); +} + +void EPDManager::setContent(const std::array& newContent, bool forceUpdate) { + std::lock_guard lock(updateMutex); waitUntilNoneBusy(); - for (uint i = 0; i < NUM_SCREENS; i++) - { - if (newEpdContent[i].compareTo(currentEpdContent[i]) != 0 || forceUpdate) - { - epdContent[i] = newEpdContent[i]; - UpdateDisplayTaskItem dispUpdate = {i}; + for (size_t i = 0; i < NUM_SCREENS; i++) { + if (newContent[i].compareTo(currentContent[i]) != 0 || forceUpdate) { + content[i] = newContent[i]; + UpdateDisplayTaskItem dispUpdate{static_cast(i)}; xQueueSend(updateQueue, &dispUpdate, portMAX_DELAY); } } } -void prepareDisplayUpdateTask(void *pvParameters) -{ - UpdateDisplayTaskItem receivedItem; - - while (1) - { - // Wait for a work item to be available in the queue - if (xQueueReceive(updateQueue, &receivedItem, portMAX_DELAY)) - { - uint epdIndex = receivedItem.dispNum; - std::lock_guard lock(epdMutex[epdIndex]); - // displays[epdIndex].init(0, false); // Little longer reset duration - // because of MCP - - bool updatePartial = true; - - if (epdContent[epdIndex].length() > 1 && strstr(epdContent[epdIndex].c_str(), "/") != NULL) - { - String top = epdContent[epdIndex].substring( - 0, epdContent[epdIndex].indexOf("/")); - String bottom = epdContent[epdIndex].substring( - epdContent[epdIndex].indexOf("/") + 1); - splitText(epdIndex, top, bottom, updatePartial); - } - else if (epdContent[epdIndex].startsWith(F("qr"))) - { - renderQr(epdIndex, epdContent[epdIndex], updatePartial); - } - else if (epdContent[epdIndex].startsWith(F("mdi"))) - { - bool updated = renderIcon(epdIndex, epdContent[epdIndex], updatePartial); - if (!updated) - { - continue; - } - } - else if (epdContent[epdIndex].length() > 5) - { - renderText(epdIndex, epdContent[epdIndex], updatePartial); - } - else - { - if (epdContent[epdIndex].length() == 2) - { - showChars(epdIndex, epdContent[epdIndex], updatePartial, FONT_BIG); - } - else if (epdContent[epdIndex].length() > 1 && epdContent[epdIndex].indexOf(".") == -1) - { - if (epdContent[epdIndex].equals("STS")) - { - showDigit(epdIndex, 'S', updatePartial, - FONT_SATSYMBOL); - } - else - { - showChars(epdIndex, epdContent[epdIndex], updatePartial, - FONT_MEDIUM); - } - } - else - { - - showDigit(epdIndex, epdContent[epdIndex].c_str()[0], updatePartial, - FONT_BIG); - } - } - - xTaskNotifyGive(tasks[epdIndex]); - } +void EPDManager::setContent(const std::array& newContent) { + std::array conv; + for (size_t i = 0; i < newContent.size(); ++i) { + conv[i] = String(newContent[i].c_str()); } + setContent(conv); } -extern "C" void updateDisplay(void *pvParameters) noexcept -{ - const int epdIndex = *(int *)pvParameters; - delete (int *)pvParameters; +std::array EPDManager::getCurrentContent() const { + return currentContent; +} - for (;;) - { - // Wait for the task notification - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - - std::lock_guard lock(epdMutex[epdIndex]); - - { - std::lock_guard lockMcp(mcpMutex); - - displays[epdIndex].init(0, false, 40); - } - uint count = 0; - while (EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10) - { - vTaskDelay(pdMS_TO_TICKS(100)); +void EPDManager::waitUntilNoneBusy() { + for (size_t i = 0; i < NUM_SCREENS; i++) { + uint32_t count = 0; + while (EPD_BUSY[i].digitalRead()) { count++; - } - - bool updatePartial = true; - - // Full Refresh every x minutes - if (!lastFullRefresh[epdIndex] || - (millis() - lastFullRefresh[epdIndex]) > - (preferences.getUInt("fullRefreshMin", - DEFAULT_MINUTES_FULL_REFRESH) * - 60 * 1000)) - { - updatePartial = false; - } - - char tries = 0; - while (tries < 3) - { - if (displays[epdIndex].displayWithReturn(updatePartial)) - { - displays[epdIndex].powerOff(); - currentEpdContent[epdIndex] = epdContent[epdIndex]; - if (!updatePartial) - lastFullRefresh[epdIndex] = millis(); - - if (eventSourceTaskHandle != NULL) - xTaskNotifyGive(eventSourceTaskHandle); + vTaskDelay(BUSY_RETRY_DELAY); + if (count == BUSY_TIMEOUT_COUNT) { + vTaskDelay(pdMS_TO_TICKS(100)); + } else if (count > BUSY_TIMEOUT_COUNT + 5) { + log_e("Display %d busy timeout", i); break; } - - vTaskDelay(pdMS_TO_TICKS(100)); - tries++; } } } -void splitText(const uint dispNum, const String &top, const String &bottom, - bool partial) -{ - if (preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0) - { +void EPDManager::setupDisplay(uint dispNum, const GFXfont* font) { + displays[dispNum].setRotation(2); + displays[dispNum].setFont(font); + displays[dispNum].setTextColor(fgColor); + displays[dispNum].fillScreen(bgColor); +} + +void EPDManager::splitText(uint dispNum, const String& top, const String& bottom, bool partial) { + if (preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0) { displays[dispNum].setRotation(1); - } - else - { + } else { displays[dispNum].setRotation(2); } - displays[dispNum].setFont(FONT_SMALL); - displays[dispNum].setTextColor(getFgColor()); + displays[dispNum].setFont(fontSmall); + displays[dispNum].setTextColor(fgColor); // Top text int16_t ttbx, ttby; uint16_t ttbw, ttbh; displays[dispNum].getTextBounds(top, 0, 0, &ttbx, &ttby, &ttbw, &ttbh); uint16_t tx = ((displays[dispNum].width() - ttbw) / 2) - ttbx; - uint16_t ty = - ((displays[dispNum].height() - ttbh) / 2) - ttby - ttbh / 2 - 12; + uint16_t ty = ((displays[dispNum].height() - ttbh) / 2) - ttby - ttbh / 2 - 12; // Bottom text int16_t tbbx, tbby; uint16_t tbbw, tbbh; displays[dispNum].getTextBounds(bottom, 0, 0, &tbbx, &tbby, &tbbw, &tbbh); uint16_t bx = ((displays[dispNum].width() - tbbw) / 2) - tbbx; - uint16_t by = - ((displays[dispNum].height() - tbbh) / 2) - tbby + tbbh / 2 + 12; + uint16_t by = ((displays[dispNum].height() - tbbh) / 2) - tbby + tbbh / 2 + 12; - // Make separator as wide as the shortest text. - uint16_t lineWidth, lineX; - if (tbbw < ttbh) - lineWidth = tbbw; - else - lineWidth = ttbw; - lineX = round((displays[dispNum].width() - lineWidth) / 2); + // Make separator as wide as the shortest text + uint16_t lineWidth = (tbbw < ttbh) ? tbbw : ttbw; + uint16_t lineX = round((displays[dispNum].width() - lineWidth) / 2); - displays[dispNum].fillScreen(getBgColor()); + displays[dispNum].fillScreen(bgColor); displays[dispNum].setCursor(tx, ty); displays[dispNum].print(top); displays[dispNum].fillRoundRect(lineX, displays[dispNum].height() / 2 - 3, - lineWidth, 6, 3, getFgColor()); + lineWidth, 6, 3, fgColor); displays[dispNum].setCursor(bx, by); displays[dispNum].print(bottom); } -// Consolidate common display setup code into a helper function -void setupDisplay(const uint dispNum, const GFXfont *font) -{ - displays[dispNum].setRotation(2); - displays[dispNum].setFont(font); - displays[dispNum].setTextColor(getFgColor()); - displays[dispNum].fillScreen(getBgColor()); -} - -void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font) -{ +void EPDManager::showDigit(uint dispNum, char chr, bool partial, const GFXfont* font) { String str(chr); - if (chr == '.') - { + if (chr == '.') { str = "!"; } @@ -465,56 +288,34 @@ void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font) displays[dispNum].setCursor(x, y); displays[dispNum].print(str); - if (chr == '.') - { + if (chr == '.') { displays[dispNum].fillRect(0, 0, displays[dispNum].width(), - round(displays[dispNum].height() * 0.67), getBgColor()); + round(displays[dispNum].height() * 0.67), bgColor); } } -int16_t calculateDescent(const GFXfont *font) -{ - int16_t maxDescent = 0; - for (uint16_t i = font->first; i <= font->last; i++) - { - GFXglyph *glyph = &font->glyph[i - font->first]; - int16_t descent = glyph->yOffset; - if (descent > maxDescent) - { - maxDescent = descent; - } - } - return maxDescent; -} - -void showChars(const uint dispNum, const String &chars, bool partial, - const GFXfont *font) -{ +void EPDManager::showChars(uint dispNum, const String& chars, bool partial, const GFXfont* font) { setupDisplay(dispNum, font); int16_t tbx, tby; uint16_t tbw, tbh; displays[dispNum].getTextBounds(chars, 0, 0, &tbx, &tby, &tbw, &tbh); - // center the bounding box by transposition of the origin: + // Center the bounding box by transposition of the origin uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx; uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby; - for (int i = 0; i < chars.length(); i++) - { + for (size_t i = 0; i < chars.length(); i++) { char c = chars[i]; - if (c == '.' || c == ',') - { + if (c == '.' || c == ',') { // For the dot, calculate its specific descent - GFXglyph *dotGlyph = &font->glyph[c - font->first]; + GFXglyph* dotGlyph = &font->glyph[c - font->first]; int16_t dotDescent = dotGlyph->yOffset; // Draw the dot with adjusted y-position displays[dispNum].setCursor(x, y + dotDescent + dotGlyph->height + 8); displays[dispNum].print(c); - } - else - { + } else { // For other characters, use the original y-position displays[dispNum].setCursor(x, y); displays[dispNum].print(c); @@ -525,104 +326,70 @@ void showChars(const uint dispNum, const String &chars, bool partial, } } -int getBgColor() { return bgColor; } - -int getFgColor() { return fgColor; } - -void setBgColor(int color) { bgColor = color; } - -void setFgColor(int color) { fgColor = color; } - -std::array getCurrentEpdContent() -{ - return currentEpdContent; -} -void renderText(const uint dispNum, const String &text, bool partial) -{ +bool EPDManager::renderIcon(uint dispNum, const String& text, bool partial) { displays[dispNum].setRotation(2); displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), - displays[dispNum].height()); - displays[dispNum].fillScreen(GxEPD_WHITE); - displays[dispNum].setTextColor(GxEPD_BLACK); - displays[dispNum].setCursor(0, 50); - - std::stringstream ss; - ss.str(text.c_str()); - - std::string line; - - while (std::getline(ss, line, '\n')) - { - if (line.rfind("*", 0) == 0) - { - line.erase(std::remove(line.begin(), line.end(), '*'), line.end()); - - displays[dispNum].setFont(&FreeSansBold9pt7b); - displays[dispNum].println(line.c_str()); - } - else - { - displays[dispNum].setFont(&FreeSans9pt7b); - displays[dispNum].println(line.c_str()); - } - } -} - -bool renderIcon(const uint dispNum, const String &text, bool partial) -{ - displays[dispNum].setRotation(2); - - displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), - displays[dispNum].height()); - displays[dispNum].fillScreen(getBgColor()); - displays[dispNum].setTextColor(getFgColor()); + displays[dispNum].height()); + displays[dispNum].fillScreen(bgColor); + displays[dispNum].setTextColor(fgColor); uint iconIndex = 0; uint width = 122; uint height = 122; - if (text.endsWith("rocket")) - { + + if (text.endsWith("rocket")) { iconIndex = 1; - } - else if (text.endsWith("lnbolt")) - { + } else if (text.endsWith("lnbolt")) { iconIndex = 2; - } - else if (text.endsWith("bitaxe")) - { + } else if (text.endsWith("bitaxe")) { width = 88; height = 220; iconIndex = 3; - } - else if (text.endsWith("miningpool")) - { + } else if (text.endsWith("miningpool")) { LogoData logo = MiningPoolStatsFetch::getInstance().getLogo(); - - if (logo.size == 0) - { + if (logo.size == 0) { Serial.println(F("No logo found")); return false; } int x_offset = (displays[dispNum].width() - logo.width) / 2; int y_offset = (displays[dispNum].height() - logo.height) / 2; - // Close the file - - displays[dispNum].drawInvertedBitmap(x_offset, y_offset, logo.data, logo.width, logo.height, getFgColor()); + displays[dispNum].drawInvertedBitmap(x_offset, y_offset, logo.data, + logo.width, logo.height, fgColor); return true; } int x_offset = (displays[dispNum].width() - width) / 2; int y_offset = (displays[dispNum].height() - height) / 2; - - displays[dispNum].drawInvertedBitmap(x_offset, y_offset, epd_icons_allArray[iconIndex], width, height, getFgColor()); - + displays[dispNum].drawInvertedBitmap(x_offset, y_offset, epd_icons_allArray[iconIndex], + width, height, fgColor); return true; - // displays[dispNum].drawInvertedBitmap(0,0, getOceanIcon(), 122, 250, getFgColor()); } -void renderQr(const uint dispNum, const String &text, bool partial) -{ +void EPDManager::renderText(uint dispNum, const String& text, bool partial) { + displays[dispNum].setRotation(2); + displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), + displays[dispNum].height()); + displays[dispNum].fillScreen(GxEPD_WHITE); + displays[dispNum].setTextColor(GxEPD_BLACK); + displays[dispNum].setCursor(0, 50); + + std::stringstream ss; + ss.str(text.c_str()); + std::string line; + + while (std::getline(ss, line, '\n')) { + if (line.rfind("*", 0) == 0) { + line.erase(std::remove(line.begin(), line.end(), '*'), line.end()); + displays[dispNum].setFont(&FreeSansBold9pt7b); + } else { + displays[dispNum].setFont(&FreeSans9pt7b); + } + displays[dispNum].println(line.c_str()); + } +} + +void EPDManager::renderQr(uint dispNum, const String& text, bool partial) { #ifdef USE_QR // Dynamically allocate QR buffer uint8_t* qrcode = (uint8_t*)malloc(qrcodegen_BUFFER_LEN_MAX); @@ -639,19 +406,15 @@ void renderQr(const uint dispNum, const String &text, bool partial) if (ok) { const int size = qrcodegen_getSize(qrcode); const int padding = floor(float(displays[dispNum].width() - (size * 4)) / 2); - const int paddingY = - floor(float(displays[dispNum].height() - (size * 4)) / 2); + const int paddingY = floor(float(displays[dispNum].height() - (size * 4)) / 2); + displays[dispNum].setRotation(2); - displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), - displays[dispNum].height()); + displays[dispNum].height()); displays[dispNum].fillScreen(GxEPD_WHITE); - const int border = 0; - for (int y = -border; y < size * 4 + border; y++) - { - for (int x = -border; x < size * 4 + border; x++) - { + for (int y = 0; y < size * 4; y++) { + for (int x = 0; x < size * 4; x++) { displays[dispNum].drawPixel( padding + x, paddingY + y, qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4)) @@ -661,30 +424,114 @@ void renderQr(const uint dispNum, const String &text, bool partial) } } - // Free the buffer after we're done free(qrcode); #endif } -void waitUntilNoneBusy() -{ - for (int i = 0; i < NUM_SCREENS; i++) - { - uint count = 0; - while (EPD_BUSY[i].digitalRead()) - { - count++; - vTaskDelay(BUSY_RETRY_DELAY); +int16_t EPDManager::calculateDescent(const GFXfont* font) { + int16_t maxDescent = 0; + for (uint16_t i = font->first; i <= font->last; i++) { + GFXglyph* glyph = &font->glyph[i - font->first]; + int16_t descent = glyph->yOffset; + if (descent > maxDescent) { + maxDescent = descent; + } + } + return maxDescent; +} - if (count == BUSY_TIMEOUT_COUNT) - { - vTaskDelay(pdMS_TO_TICKS(100)); - } - else if (count > BUSY_TIMEOUT_COUNT + 5) - { - log_e("Display %d busy timeout", i); +void EPDManager::updateDisplayTask(void* pvParameters) noexcept { + auto& instance = EPDManager::getInstance(); + const int epdIndex = *(int*)pvParameters; + delete (int*)pvParameters; + + for (;;) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + std::lock_guard lock(instance.displayMutexes[epdIndex]); + { + std::lock_guard lockMcp(mcpMutex); + instance.displays[epdIndex].init(0, false, 40); + } + + uint32_t count = 0; + while (instance.EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10) { + vTaskDelay(pdMS_TO_TICKS(100)); + count++; + } + + bool updatePartial = true; + if (!instance.lastFullRefresh[epdIndex] || + (millis() - instance.lastFullRefresh[epdIndex]) > + (preferences.getUInt("fullRefreshMin", DEFAULT_MINUTES_FULL_REFRESH) * 60 * 1000)) { + updatePartial = false; + } + + char tries = 0; + while (tries < 3) { + if (instance.displays[epdIndex].displayWithReturn(updatePartial)) { + instance.displays[epdIndex].powerOff(); + instance.currentContent[epdIndex] = instance.content[epdIndex]; + if (!updatePartial) { + instance.lastFullRefresh[epdIndex] = millis(); + } + + if (eventSourceTaskHandle != nullptr) { + xTaskNotifyGive(eventSourceTaskHandle); + } break; } + + vTaskDelay(pdMS_TO_TICKS(100)); + tries++; + } + } +} + +void EPDManager::prepareDisplayUpdateTask(void* pvParameters) { + auto& instance = EPDManager::getInstance(); + UpdateDisplayTaskItem receivedItem; + + for (;;) { + if (xQueueReceive(instance.updateQueue, &receivedItem, portMAX_DELAY)) { + uint epdIndex = receivedItem.dispNum; + std::lock_guard lock(instance.displayMutexes[epdIndex]); + + bool updatePartial = true; + + if (instance.content[epdIndex].length() > 1 && + strstr(instance.content[epdIndex].c_str(), "/") != nullptr) { + String top = instance.content[epdIndex].substring( + 0, instance.content[epdIndex].indexOf("/")); + String bottom = instance.content[epdIndex].substring( + instance.content[epdIndex].indexOf("/") + 1); + instance.splitText(epdIndex, top, bottom, updatePartial); + } else if (instance.content[epdIndex].startsWith(F("qr"))) { + instance.renderQr(epdIndex, instance.content[epdIndex], updatePartial); + } else if (instance.content[epdIndex].startsWith(F("mdi"))) { + if (!instance.renderIcon(epdIndex, instance.content[epdIndex], updatePartial)) { + continue; + } + } else if (instance.content[epdIndex].length() > 5) { + instance.renderText(epdIndex, instance.content[epdIndex], updatePartial); + } else { + if (instance.content[epdIndex].length() == 2) { + instance.showChars(epdIndex, instance.content[epdIndex], updatePartial, instance.fontBig); + } else if (instance.content[epdIndex].length() > 1 && + instance.content[epdIndex].indexOf(".") == -1) { + if (instance.content[epdIndex].equals("STS")) { + instance.showDigit(epdIndex, 'S', updatePartial, instance.fontSatsymbol); + } else { + instance.showChars(epdIndex, instance.content[epdIndex], updatePartial, + instance.fontMedium); + } + } else { + instance.showDigit(epdIndex, instance.content[epdIndex].c_str()[0], + updatePartial, instance.fontBig); + } + } + + xTaskNotifyGive(instance.tasks[epdIndex]); } } } \ No newline at end of file diff --git a/src/lib/epd.hpp b/src/lib/epd.hpp index 4796776..80bed83 100644 --- a/src/lib/epd.hpp +++ b/src/lib/epd.hpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include "fonts/fonts.hpp" #include "lib/config.hpp" @@ -32,39 +34,102 @@ #include "qrcodegen.h" #endif -typedef struct { - char dispNum; -} UpdateDisplayTaskItem; +struct UpdateDisplayTaskItem { + char dispNum; +}; -void forceFullRefresh(); -void setupDisplays(); -void loadFonts(const String& fontName); +struct FontFamily { + GFXfont* big; + GFXfont* medium; + GFXfont* small; +}; -void splitText(const uint dispNum, const String &top, const String &bottom, - bool partial); +class EPDManager { +public: + static EPDManager& getInstance(); -void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font); -void showChars(const uint dispNum, const String &chars, bool partial, - const GFXfont *font); + // Delete copy constructor and assignment operator + EPDManager(const EPDManager&) = delete; + EPDManager& operator=(const EPDManager&) = delete; -extern "C" void updateDisplay(void *pvParameters) noexcept; -void updateDisplayAlt(int epdIndex); -void prepareDisplayUpdateTask(void *pvParameters); + void initialize(); + void forceFullRefresh(); + void loadFonts(const String& fontName); + void setContent(const std::array& newContent, bool forceUpdate = false); + void setContent(const std::array& newContent); + std::array getCurrentContent() const; -int getBgColor(); -int getFgColor(); -void setBgColor(int color); -void setFgColor(int color); + int getBackgroundColor() const { return bgColor; } + int getForegroundColor() const { return fgColor; } + void setBackgroundColor(int color) { bgColor = color; } + void setForegroundColor(int color) { fgColor = color; } + void waitUntilNoneBusy(); -bool renderIcon(const uint dispNum, const String &text, bool partial); -void renderText(const uint dispNum, const String &text, bool partial); -void renderQr(const uint dispNum, const String &text, bool partial); +private: + EPDManager(); // Private constructor for singleton + ~EPDManager(); // Private destructor -void setEpdContent(std::array newEpdContent, - bool forceUpdate); -void setEpdContent(std::array newEpdContent); + void setupDisplay(uint dispNum, const GFXfont* font); + void splitText(uint dispNum, const String& top, const String& bottom, bool partial); + void showDigit(uint dispNum, char chr, bool partial, const GFXfont* font); + void showChars(uint dispNum, const String& chars, bool partial, const GFXfont* font); + bool renderIcon(uint dispNum, const String& text, bool partial); + void renderText(uint dispNum, const String& text, bool partial); + void renderQr(uint dispNum, const String& text, bool partial); + int16_t calculateDescent(const GFXfont* font); -void setEpdContent(std::array newEpdContent); + static void updateDisplayTask(void* pvParameters) noexcept; + static void prepareDisplayUpdateTask(void* pvParameters); -std::array getCurrentEpdContent(); -void waitUntilNoneBusy(); \ No newline at end of file + // Member variables + std::array currentContent; + std::array content; + std::array lastFullRefresh; + std::array tasks; + QueueHandle_t updateQueue; + + FontFamily antonioFonts; + FontFamily oswaldFonts; + const GFXfont* fontSmall; + const GFXfont* fontBig; + const GFXfont* fontMedium; + const GFXfont* fontSatsymbol; + + int bgColor; + int fgColor; + + std::mutex updateMutex; + std::array displayMutexes; + + // Pin configurations based on board version + #ifdef IS_BTCLOCK_REV_B + static Native_Pin EPD_DC; + static std::array EPD_CS; + static std::array EPD_BUSY; + static std::array EPD_RESET; + #elif defined(IS_BTCLOCK_V8) + static Native_Pin EPD_DC; + static std::array EPD_BUSY; + static std::array EPD_CS; + static std::array EPD_RESET; + #else + static Native_Pin EPD_DC; + static std::array EPD_CS; + static std::array EPD_BUSY; + static std::array EPD_RESET; + #endif + + // Display array + std::array, NUM_SCREENS> displays; + + static constexpr size_t UPDATE_QUEUE_SIZE = 14; + static constexpr uint32_t BUSY_TIMEOUT_COUNT = 200; + static constexpr TickType_t BUSY_RETRY_DELAY = pdMS_TO_TICKS(10); + static constexpr size_t EPD_TASK_STACK_SIZE = + #ifdef IS_BTCLOCK_V8 + 4096 + #else + 2048 + #endif + ; +}; \ No newline at end of file diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index 3c996a2..6d9cce9 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -283,7 +283,7 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) } ScreenHandler::setCurrentScreen(SCREEN_CUSTOM); - setEpdContent(textEpdContent); + EPDManager::getInstance().setContent(textEpdContent); vTaskDelay(pdMS_TO_TICKS(315 * NUM_SCREENS) + pdMS_TO_TICKS(250)); if (preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP)) { @@ -294,4 +294,20 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) esp_timer_start_periodic(screenRotateTimer, timerPeriod * usPerSecond); } -} \ No newline at end of file +} + +// void onNostrEvent(const String &subId, const nostr::Event &event) { +// // This is the callback that will be called when a new event is received +// if (event.kind == 9735) { +// // Parse the zap amount from the event +// uint16_t amount = parseZapAmount(event); +// if (amount > 0) { +// std::array zapContent = parseZapNotify(amount, true); +// EPDManager::getInstance().setContent(zapContent); + +// if (preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD)) { +// getLedHandler().queueEffect(LED_FLASH_BLOCK_NOTIFY); +// } +// } +// } +// } \ No newline at end of file diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index b42a505..59497c7 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -57,10 +57,10 @@ void onOTAProgress(unsigned int progress, unsigned int total) void onOTAStart() { - forceFullRefresh(); + EPDManager::getInstance().forceFullRefresh(); std::array epdContent = {"U", "P", "D", "A", "T", "E", "!"}; - setEpdContent(epdContent); + EPDManager::getInstance().setContent(epdContent); // Stop all timers esp_timer_stop(screenRotateTimer); esp_timer_stop(minuteTimer); diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 0692c1d..6b3241e 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -203,7 +203,7 @@ void ScreenHandler::showSystemStatusScreen() { String((int)round(ESP.getFreeHeap() / 1024)) + "/" + (int)round(ESP.getHeapSize() / 1024); setCurrentScreen(SCREEN_CUSTOM); - setEpdContent(sysStatusEpdContent); + EPDManager::getInstance().setContent(sysStatusEpdContent); } // Keep these as free functions @@ -222,7 +222,7 @@ void workerTask(void *pvParameters) { taskEpdContent = (currentScreenValue == SCREEN_BITAXE_HASHRATE) ? parseBitaxeHashRate(BitAxeFetch::getInstance().getHashRate()) : parseBitaxeBestDiff(BitAxeFetch::getInstance().getBestDiff()); - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); break; } @@ -235,7 +235,7 @@ void workerTask(void *pvParameters) { parseMiningPoolStatsDailyEarnings(MiningPoolStatsFetch::getInstance().getDailyEarnings(), MiningPoolStatsFetch::getInstance().getPool()->getDailyEarningsLabel(), *MiningPoolStatsFetch::getInstance().getPool()); - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); break; } @@ -256,13 +256,13 @@ void workerTask(void *pvParameters) { preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR)); } - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); break; } case TASK_FEE_UPDATE: { if (currentScreenValue == SCREEN_BLOCK_FEE_RATE) { taskEpdContent = parseBlockFees(static_cast(getBlockMedianFee())); - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); } break; } @@ -275,7 +275,7 @@ void workerTask(void *pvParameters) { if (currentScreenValue == SCREEN_HALVING_COUNTDOWN || currentScreenValue == SCREEN_BLOCK_HEIGHT) { - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); } break; } @@ -302,7 +302,7 @@ void workerTask(void *pvParameters) { for (uint i = 1; i < NUM_SCREENS; i++) { taskEpdContent[i] = timeString[i]; } - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); } break; @@ -330,8 +330,6 @@ void setupTasks() { xTaskCreate(taskScreenRotate, "rotateScreen", 4096, NULL, tskIDLE_PRIORITY, &taskScreenRotateTaskHandle); - waitUntilNoneBusy(); - if (findScreenIndexByValue(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)) != -1) ScreenHandler::setCurrentScreen(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)); } diff --git a/src/lib/shared.cpp b/src/lib/shared.cpp index a776750..aa67768 100644 --- a/src/lib/shared.cpp +++ b/src/lib/shared.cpp @@ -179,4 +179,5 @@ void HttpHelper::end(HTTPClient* http) { http->end(); delete http; } -} \ No newline at end of file +} + diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 78fb9ea..479a52f 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -16,6 +16,8 @@ #include #include +#include +#include #include "defaults.hpp" @@ -118,4 +120,5 @@ private: static WiFiClientSecure secureClient; static bool certBundleSet; static WiFiClient insecureClient; -}; \ No newline at end of file +}; + diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index 469e6e1..c0007dd 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -106,6 +106,11 @@ namespace V2Notify JsonDocument doc; DeserializationError error = deserializeMsgPack(doc, payload, length); + if (error) { + Serial.println(F("Error deserializing message")); + break; + } + V2Notify::handleV2Message(doc); break; } diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 4897811..382faa9 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -309,7 +309,7 @@ void eventSourceUpdate() { doc["leds"] = getLedStatusObject()["data"]; // Get current EPD content directly as array - std::array epdContent = getCurrentEpdContent(); + std::array epdContent = EPDManager::getInstance().getCurrentContent(); // Add EPD content arrays JsonArray data = doc["data"].to(); @@ -336,7 +336,7 @@ void onApiStatus(AsyncWebServerRequest *request) JsonDocument root = getStatusObject(); // Get current EPD content directly as array - std::array epdContent = getCurrentEpdContent(); + std::array epdContent = EPDManager::getInstance().getCurrentContent(); // Add EPD content arrays JsonArray data = root["data"].to(); @@ -378,11 +378,9 @@ void onApiActionTimerRestart(AsyncWebServerRequest *request) */ void onApiFullRefresh(AsyncWebServerRequest *request) { - forceFullRefresh(); - std::array newEpdContent = getCurrentEpdContent(); - - setEpdContent(newEpdContent, true); - + EPDManager::getInstance().forceFullRefresh(); + std::array newEpdContent = EPDManager::getInstance().getCurrentContent(); + EPDManager::getInstance().setContent(newEpdContent, true); request->send(HTTP_OK); } @@ -429,7 +427,7 @@ void onApiShowText(AsyncWebServerRequest *request) textEpdContent[i] = t[i]; } - setEpdContent(textEpdContent); + EPDManager::getInstance().setContent(textEpdContent); } ScreenHandler::setCurrentScreen(SCREEN_CUSTOM); request->send(HTTP_OK); @@ -447,7 +445,7 @@ void onApiShowTextAdvanced(AsyncWebServerRequest *request, JsonVariant &json) i++; } - setEpdContent(epdContent); + EPDManager::getInstance().setContent(epdContent); ScreenHandler::setCurrentScreen(SCREEN_CUSTOM); request->send(HTTP_OK); @@ -475,13 +473,13 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) if (inverted) { preferences.putUInt("fgColor", GxEPD_WHITE); preferences.putUInt("bgColor", GxEPD_BLACK); - setFgColor(GxEPD_WHITE); - setBgColor(GxEPD_BLACK); + EPDManager::getInstance().setForegroundColor(GxEPD_WHITE); + EPDManager::getInstance().setBackgroundColor(GxEPD_BLACK); } else { preferences.putUInt("fgColor", GxEPD_BLACK); preferences.putUInt("bgColor", GxEPD_WHITE); - setFgColor(GxEPD_BLACK); - setBgColor(GxEPD_WHITE); + EPDManager::getInstance().setForegroundColor(GxEPD_BLACK); + EPDManager::getInstance().setBackgroundColor(GxEPD_WHITE); } Serial.printf("Setting invertedColor to %d\r\n", inverted); settingsChanged = true; @@ -680,7 +678,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) JsonDocument root; root["numScreens"] = NUM_SCREENS; - root["invertedColor"] = preferences.getBool("invertedColor", getFgColor() == GxEPD_WHITE); + root["invertedColor"] = preferences.getBool("invertedColor", EPDManager::getInstance().getForegroundColor() == GxEPD_WHITE); root["timerSeconds"] = getTimerSeconds(); root["timerRunning"] = isTimerActive(); root["minSecPriceUpd"] = preferences.getUInt( @@ -818,7 +816,7 @@ bool processEpdColorSettings(AsyncWebServerRequest *request) const AsyncWebParameter *fgColor = request->getParam("fgColor", true); uint32_t color = strtol(fgColor->value().c_str(), NULL, 16); preferences.putUInt("fgColor", color); - setFgColor(color); + EPDManager::getInstance().setForegroundColor(color); // Serial.print(F("Setting foreground color to ")); // Serial.println(fgColor->value().c_str()); settingsChanged = true; @@ -829,7 +827,7 @@ bool processEpdColorSettings(AsyncWebServerRequest *request) uint32_t color = strtol(bgColor->value().c_str(), NULL, 16); preferences.putUInt("bgColor", color); - setBgColor(color); + EPDManager::getInstance().setBackgroundColor(color); // Serial.print(F("Setting background color to ")); // Serial.println(bgColor->value().c_str()); settingsChanged = true; @@ -1202,7 +1200,7 @@ void onApiLightsGet(AsyncWebServerRequest *request) auto& ledHandler = getLedHandler(); auto& pixels = ledHandler.getPixels(); - DynamicJsonDocument doc(1024); + JsonDocument doc; JsonArray lights = doc.createNestedArray("lights"); for (uint i = 0; i < pixels.numPixels(); i++) @@ -1225,7 +1223,7 @@ void onApiLightsPost(AsyncWebServerRequest *request, uint8_t *data, size_t len, auto& ledHandler = getLedHandler(); auto& pixels = ledHandler.getPixels(); - DynamicJsonDocument doc(1024); + JsonDocument doc; DeserializationError error = deserializeJson(doc, data); if (error) { From b435552c92685ad4525c7538df36138bffb199fb Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 22:12:39 +0100 Subject: [PATCH 07/30] fix: verify block update --- src/lib/block_notify.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 37b5b2f..0dab659 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -181,7 +181,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) } void processNewBlock(uint32_t newBlockHeight) { - if (currentBlockHeight == newBlockHeight) + if (currentBlockHeight <= newBlockHeight) { return; } From 178748b94d5019a6702497749d032e3ebac537ca Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 22:47:13 +0100 Subject: [PATCH 08/30] WebUI update --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 033fe09..732dd26 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 033fe098295ab6da6568d6298b4380e51bec0b98 +Subproject commit 732dd260ea708841f0e15ee1ee64a3d5115cd475 From 0999dd08ad30bc91efe9cacebcc79f4c2f0893fe Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 23:13:05 +0100 Subject: [PATCH 09/30] Remove deprecated ArduinoJson methods --- src/lib/block_notify.cpp | 6 +++--- src/lib/v2_notify.cpp | 6 +++--- src/lib/webserver.cpp | 29 ++++------------------------- 3 files changed, 10 insertions(+), 31 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 0dab659..60b3034 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -158,7 +158,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) // return; // } - if (doc.containsKey("block")) + if (doc["block"].is()) { JsonObject block = doc["block"]; @@ -168,7 +168,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) processNewBlock(block["height"].as()); } - else if (doc.containsKey("mempool-blocks")) + else if (doc["mempool-blocks"].is()) { JsonArray blockInfo = doc["mempool-blocks"].as(); @@ -181,7 +181,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) } void processNewBlock(uint32_t newBlockHeight) { - if (currentBlockHeight <= newBlockHeight) + if (newBlockHeight <= currentBlockHeight) { return; } diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index c0007dd..560b39d 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -127,7 +127,7 @@ namespace V2Notify void handleV2Message(JsonDocument doc) { - if (doc.containsKey("blockheight")) + if (doc["blockheight"].is()) { uint newBlockHeight = doc["blockheight"].as(); @@ -138,13 +138,13 @@ namespace V2Notify processNewBlock(newBlockHeight); } - else if (doc.containsKey("blockfee")) + else if (doc["blockfee"].is()) { uint medianFee = doc["blockfee"].as(); processNewBlockFee(medianFee); } - else if (doc.containsKey("price")) + else if (doc["price"].is()) { // Iterate through the key-value pairs of the "price" object diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 382faa9..1d6a878 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -612,15 +612,15 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) } // Handle DND settings - if (settings.containsKey("dnd")) { + if (settings["dnd"].is()) { JsonObject dndObj = settings["dnd"]; auto& ledHandler = getLedHandler(); - if (dndObj.containsKey("timeBasedEnabled")) { + if (dndObj["timeBasedEnabled"].is()) { ledHandler.setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as()); } - if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") && - dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) { + if (dndObj["startHour"].is() && dndObj["startMinute"].is() && + dndObj["endHour"].is() && dndObj["endMinute"].is()) { ledHandler.setDNDTimeRange( dndObj["startHour"].as(), dndObj["startMinute"].as(), @@ -1251,25 +1251,4 @@ void onApiLightsPost(AsyncWebServerRequest *request, uint8_t *data, size_t len, pixels.show(); request->send(200); -} - -void onApiSettings(AsyncWebServerRequest *request, JsonVariant &json) -{ - JsonObject settings = json.as(); - auto& ledHandler = getLedHandler(); - - if (settings.containsKey("dnd")) { - JsonObject dndObj = settings["dnd"]; - if (dndObj.containsKey("timeBasedEnabled")) { - ledHandler.setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as()); - } - if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") && - dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) { - ledHandler.setDNDTimeRange( - dndObj["startHour"].as(), - dndObj["startMinute"].as(), - dndObj["endHour"].as(), - dndObj["endMinute"].as()); - } - } } \ No newline at end of file From 7195b7d3432bf844798ffd58f7764b13afbe73dd Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 23:13:25 +0100 Subject: [PATCH 10/30] Rewrite price notify to websocketsclient --- src/lib/price_notify.cpp | 165 +++++++++++++++------------------------ src/lib/price_notify.hpp | 14 ++-- 2 files changed, 71 insertions(+), 108 deletions(-) diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index 2c6fe94..6ac2f3b 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -2,103 +2,64 @@ const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; -// WebsocketsClient client; -esp_websocket_client_handle_t clientPrice = NULL; -esp_websocket_client_config_t config; +WebSocketsClient webSocket; uint currentPrice = 90000; unsigned long int lastPriceUpdate; bool priceNotifyInit = false; std::map currencyMap; std::map lastUpdateMap; -WebSocketsClient priceNotifyWs; +TaskHandle_t priceNotifyTaskHandle; + +void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length); void setupPriceNotify() { - config = {.uri = wsServerPrice, - .user_agent = USER_AGENT}; - config.cert_pem = isrg_root_x1cert; + webSocket.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin"); + webSocket.onEvent([](WStype_t type, uint8_t * payload, size_t length) { + onWebsocketPriceEvent(type, payload, length); + }); + webSocket.setReconnectInterval(5000); + webSocket.enableHeartbeat(15000, 3000, 2); - config.task_stack = (6*1024); - - - clientPrice = esp_websocket_client_init(&config); - esp_websocket_register_events(clientPrice, WEBSOCKET_EVENT_ANY, - onWebsocketPriceEvent, clientPrice); - esp_websocket_client_start(clientPrice); - - // priceNotifyWs.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin"); - // priceNotifyWs.onEvent(onWebsocketPriceEvent); - // priceNotifyWs.setReconnectInterval(5000); - // priceNotifyWs.enableHeartbeat(15000, 3000, 2); + setupPriceNotifyTask(); } +void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) { + switch(type) { + case WStype_DISCONNECTED: + Serial.println(F("Price WS Connection Closed")); + break; + case WStype_CONNECTED: + { + Serial.println("Connected to " + String(wsServerPrice)); + priceNotifyInit = true; + break; + } + case WStype_TEXT: + { + JsonDocument doc; + deserializeJson(doc, (char *)payload); -// void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) { -// switch(type) { -// case WStype_DISCONNECTED: -// Serial.printf("[WSc] Disconnected!\n"); -// break; -// case WStype_CONNECTED: -// { -// Serial.printf("[WSc] Connected to url: %s\n", payload); - - -// break; -// } -// case WStype_TEXT: -// String message = String((char*)payload); -// onWebsocketPriceMessage(message); -// break; -// case WStype_BIN: -// break; -// case WStype_ERROR: -// case WStype_FRAGMENT_TEXT_START: -// case WStype_FRAGMENT_BIN_START: -// case WStype_FRAGMENT: -// case WStype_PING: -// case WStype_PONG: -// case WStype_FRAGMENT_FIN: -// break; -// } -// } - -void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, - int32_t event_id, void *event_data) -{ - esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; - - switch (event_id) - { - case WEBSOCKET_EVENT_CONNECTED: - Serial.println("Connected to " + String(config.uri) + " WebSocket"); - priceNotifyInit = true; - - break; - case WEBSOCKET_EVENT_DATA: - onWebsocketPriceMessage(data); - break; - case WEBSOCKET_EVENT_ERROR: - Serial.println(F("Price WS Connnection error")); - break; - case WEBSOCKET_EVENT_DISCONNECTED: - Serial.println(F("Price WS Connnection Closed")); - break; - } -} - -void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data) -{ - JsonDocument doc; - - deserializeJson(doc, (char *)event_data->data_ptr); - - if (doc.containsKey("bitcoin")) - { - if (currentPrice != doc["bitcoin"].as()) - { - processNewPrice(doc["bitcoin"].as(), CURRENCY_USD); + if (doc["bitcoin"].is()) + { + if (currentPrice != doc["bitcoin"].as()) + { + processNewPrice(doc["bitcoin"].as(), CURRENCY_USD); + } + } + break; + } + case WStype_BIN: + break; + case WStype_ERROR: + case WStype_FRAGMENT_TEXT_START: + case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT: + case WStype_PING: + case WStype_PONG: + case WStype_FRAGMENT_FIN: + break; } - } } void processNewPrice(uint newPrice, char currency) @@ -175,9 +136,7 @@ void setPrice(uint newPrice, char currency) bool isPriceNotifyConnected() { - if (clientPrice == NULL) - return false; - return esp_websocket_client_is_connected(clientPrice); + return webSocket.isConnected(); } bool getPriceNotifyInit() @@ -187,24 +146,30 @@ bool getPriceNotifyInit() void stopPriceNotify() { - if (clientPrice == NULL) - return; - esp_websocket_client_close(clientPrice, pdMS_TO_TICKS(5000)); - esp_websocket_client_stop(clientPrice); - esp_websocket_client_destroy(clientPrice); - - clientPrice = NULL; + webSocket.disconnect(); + if (priceNotifyTaskHandle != NULL) { + vTaskDelete(priceNotifyTaskHandle); + priceNotifyTaskHandle = NULL; + } } void restartPriceNotify() { stopPriceNotify(); - if (clientPrice == NULL) + setupPriceNotify(); +} + +void taskPriceNotify(void *pvParameters) +{ + for (;;) { - setupPriceNotify(); - return; + webSocket.loop(); + vTaskDelay(10 / portTICK_PERIOD_MS); } - // esp_websocket_client_close(clientPrice, pdMS_TO_TICKS(5000)); - // esp_websocket_client_stop(clientPrice); - // esp_websocket_client_start(clientPrice); +} + +void setupPriceNotifyTask() +{ + xTaskCreate(taskPriceNotify, "priceNotify", (6 * 1024), NULL, tskIDLE_PRIORITY, + &priceNotifyTaskHandle); } \ No newline at end of file diff --git a/src/lib/price_notify.hpp b/src/lib/price_notify.hpp index 3591d40..6c8c6df 100644 --- a/src/lib/price_notify.hpp +++ b/src/lib/price_notify.hpp @@ -2,24 +2,22 @@ #include #include -#include -#include "block_notify.hpp" +#include #include #include "lib/screen_handler.hpp" +extern TaskHandle_t priceNotifyTaskHandle; + void setupPriceNotify(); +void setupPriceNotifyTask(); +void taskPriceNotify(void *pvParameters); -void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, - int32_t event_id, void *event_data); -//void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length); - -void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data); +void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length); uint getPrice(char currency); void setPrice(uint newPrice, char currency); -//void processNewPrice(uint newPrice); void processNewPrice(uint newPrice, char currency); bool isPriceNotifyConnected(); From e19cad05bced9337c727e23a33d35fba65bc257e Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 23:15:34 +0100 Subject: [PATCH 11/30] Update ESPAsyncWebserver --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index b65defd..d46c153 100644 --- a/platformio.ini +++ b/platformio.ini @@ -38,7 +38,7 @@ build_unflags = lib_deps = https://github.com/joltwallet/esp_littlefs.git bblanchon/ArduinoJson@^7.2.1 - mathieucarbou/ESPAsyncWebServer @ 3.3.23 + mathieucarbou/ESPAsyncWebServer @ 3.4.5 robtillaart/MCP23017@^0.8.0 adafruit/Adafruit NeoPixel@^1.12.3 https://github.com/dsbaars/universal_pin#feature/mcp23017_rt From ebbec75e6bc9e51a973d13ad78f277f7be2c2865 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 6 Jan 2025 00:01:34 +0100 Subject: [PATCH 12/30] Fix V2 message parsing --- src/lib/v2_notify.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index 560b39d..6d9fc61 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -127,7 +127,7 @@ namespace V2Notify void handleV2Message(JsonDocument doc) { - if (doc["blockheight"].is()) + if (doc["blockheight"].is()) { uint newBlockHeight = doc["blockheight"].as(); @@ -136,16 +136,29 @@ namespace V2Notify return; } + if (debugLogEnabled()) { + Serial.print(F("processNewBlock ")); + Serial.println(newBlockHeight); + } processNewBlock(newBlockHeight); } - else if (doc["blockfee"].is()) + else if (doc["blockfee"].is()) { uint medianFee = doc["blockfee"].as(); + if (debugLogEnabled()) { + Serial.print(F("processNewBlockFee ")); + Serial.println(medianFee); + } + processNewBlockFee(medianFee); } else if (doc["price"].is()) { + if (debugLogEnabled()) { + Serial.print(F("processNewPrice ")); + Serial.println(doc["price"].as().size()); + } // Iterate through the key-value pairs of the "price" object for (JsonPair kv : doc["price"].as()) From e330984ba23504f1a214c93d48101d4dc3630bf9 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 6 Jan 2025 00:43:31 +0100 Subject: [PATCH 13/30] Refactor BlockNotify to a class, use websocketsClient --- src/lib/block_notify.cpp | 363 ++++++++++++++++--------------------- src/lib/block_notify.hpp | 65 +++++-- src/lib/config.cpp | 4 +- src/lib/nostr_notify.cpp | 11 +- src/lib/ota.cpp | 4 +- src/lib/screen_handler.cpp | 14 +- src/lib/v2_notify.cpp | 6 +- src/lib/webserver.cpp | 9 +- src/main.cpp | 20 +- 9 files changed, 239 insertions(+), 257 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 60b3034..4c32a2a 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -1,186 +1,146 @@ #include "block_notify.hpp" -#include "led_handler.hpp" -char *wsServer; -esp_websocket_client_handle_t blockNotifyClient = NULL; -uint32_t currentBlockHeight = 873400; -uint16_t blockMedianFee = 1; -bool blockNotifyInit = false; -unsigned long int lastBlockUpdate; +// Initialize static members +WebSocketsClient BlockNotify::webSocket; +uint32_t BlockNotify::currentBlockHeight = 878000; +uint16_t BlockNotify::blockMedianFee = 1; +bool BlockNotify::notifyInit = false; +unsigned long int BlockNotify::lastBlockUpdate = 0; +TaskHandle_t BlockNotify::taskHandle = nullptr; -const char *mempoolWsCert = R"EOF( ------BEGIN CERTIFICATE----- -MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB -iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl -cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV -BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw -MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV -BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU -aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy -dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B -3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY -tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ -Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 -VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT -79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 -c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT -Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l -c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee -UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE -Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd -BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G -A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF -Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO -VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 -ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs -8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR -iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze -Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ -XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ -qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB -VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB -L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG -jjxDah2nGN59PRbxYvnKkKj9 ------END CERTIFICATE----- -)EOF"; - -void setupBlockNotify() +void BlockNotify::taskNotify(void* pvParameters) { - IPAddress result; - - int dnsErr = -1; - String mempoolInstance = - preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); - - while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':')) - { - dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result); - - if (dnsErr != 1) + for (;;) { - Serial.print(mempoolInstance); - Serial.println(F("mempool DNS could not be resolved")); - WiFi.reconnect(); - vTaskDelay(pdMS_TO_TICKS(1000)); + webSocket.loop(); + vTaskDelay(10 / portTICK_PERIOD_MS); } - } - - // Get current block height through regular API - int blockFetch = getBlockFetch(); - - if (blockFetch > currentBlockHeight) - currentBlockHeight = blockFetch; - - if (currentBlockHeight != -1) - { - lastBlockUpdate = esp_timer_get_time() / 1000000; - } - - if (workQueue != nullptr) - { - WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; - xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); - } - - // std::strcpy(wsServer, String("wss://" + mempoolInstance + - // "/api/v1/ws").c_str()); - - const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "wss" : "ws"; - - String mempoolUri = protocol + "://" + preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE) + "/api/v1/ws"; - - esp_websocket_client_config_t config = { - // .uri = "wss://mempool.space/api/v1/ws", - .task_stack = (6*1024), - .user_agent = USER_AGENT - }; - - if (preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE)) { - config.cert_pem = mempoolWsCert; - } - - config.uri = mempoolUri.c_str(); - - Serial.printf("Connecting to %s\r\n", preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE)); - - blockNotifyClient = esp_websocket_client_init(&config); - esp_websocket_register_events(blockNotifyClient, WEBSOCKET_EVENT_ANY, - onWebsocketBlockEvent, blockNotifyClient); - esp_websocket_client_start(blockNotifyClient); } -void onWebsocketBlockEvent(void *handler_args, esp_event_base_t base, - int32_t event_id, void *event_data) +void BlockNotify::setupTask() { - esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; - const String sub = "{\"action\": \"want\", \"data\":[\"blocks\", \"mempool-blocks\"]}"; - switch (event_id) - { - case WEBSOCKET_EVENT_CONNECTED: - blockNotifyInit = true; + xTaskCreate(taskNotify, "blockNotify", (6 * 1024), NULL, tskIDLE_PRIORITY, + &taskHandle); +} - Serial.println(F("Connected to Mempool.space WebSocket")); +void BlockNotify::onWebsocketEvent(WStype_t type, uint8_t* payload, size_t length) { + switch(type) { + case WStype_DISCONNECTED: { + Serial.println(F("Mempool.space WS Connection Closed")); + break; + } + case WStype_CONNECTED: { + notifyInit = true; + Serial.print(F("Connected to ")); + Serial.println(preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE)); - Serial.println(sub); - if (esp_websocket_client_send_text(blockNotifyClient, sub.c_str(), - sub.length(), portMAX_DELAY) == -1) + JsonDocument doc; + doc["action"] = "want"; + JsonArray data = doc.createNestedArray("data"); + data.add("blocks"); + data.add("mempool-blocks"); + + String sub; + serializeJson(doc, sub); + Serial.println(sub); + webSocket.sendTXT(sub.c_str()); + break; + } + case WStype_TEXT: { + JsonDocument doc; + JsonDocument filter; + filter["block"]["height"] = true; + filter["mempool-blocks"][0]["medianFee"] = true; + + deserializeJson(doc, (char*)payload, DeserializationOption::Filter(filter)); + + if (debugLogEnabled()) { + Serial.println(doc.as()); + } + + if (doc["block"].is()) + { + JsonObject block = doc["block"]; + if (block["height"].as() != currentBlockHeight) { + BlockNotify::getInstance().processNewBlock(block["height"].as()); + } + } + else if (doc["mempool-blocks"].is()) + { + JsonArray blockInfo = doc["mempool-blocks"].as(); + uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); + BlockNotify::getInstance().processNewBlockFee(medianFee); + } + break; + } + case WStype_BIN: + case WStype_ERROR: + case WStype_FRAGMENT_TEXT_START: + case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT: + case WStype_PING: + case WStype_PONG: + case WStype_FRAGMENT_FIN: { + break; + } + } +} + +void BlockNotify::setup() +{ + IPAddress result; + int dnsErr = -1; + String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); + + while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':')) { - Serial.println(F("Mempool.space WS Block Subscribe Error")); + dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result); + + if (dnsErr != 1) + { + Serial.print(mempoolInstance); + Serial.println(F("mempool DNS could not be resolved")); + WiFi.reconnect(); + vTaskDelay(pdMS_TO_TICKS(1000)); + } } - break; - case WEBSOCKET_EVENT_DATA: - onWebsocketBlockMessage(data); - break; - case WEBSOCKET_EVENT_ERROR: - Serial.println(F("Mempool.space WS Connnection error")); - break; - case WEBSOCKET_EVENT_DISCONNECTED: - Serial.println(F("Mempool.space WS Connnection Closed")); - break; - } -} + // Get current block height through regular API + int blockFetch = fetchLatestBlock(); -void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) -{ - JsonDocument doc; + if (blockFetch > currentBlockHeight) + currentBlockHeight = blockFetch; - JsonDocument filter; - filter["block"]["height"] = true; - filter["mempool-blocks"][0]["medianFee"] = true; - - deserializeJson(doc, (char *)event_data->data_ptr, DeserializationOption::Filter(filter)); - - // if (error) { - // Serial.print("deserializeJson() failed: "); - // Serial.println(error.c_str()); - // return; - // } - - if (doc["block"].is()) - { - JsonObject block = doc["block"]; - - if (block["height"].as() == currentBlockHeight) { - return; + if (currentBlockHeight != -1) + { + lastBlockUpdate = esp_timer_get_time() / 1000000; } - processNewBlock(block["height"].as()); - } - else if (doc["mempool-blocks"].is()) - { - JsonArray blockInfo = doc["mempool-blocks"].as(); + if (workQueue != nullptr) + { + WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + } - uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); + const bool useSSL = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE); + const int port = useSSL ? 443 : 80; - processNewBlockFee(medianFee); - } + if (useSSL) { + webSocket.beginSSL(mempoolInstance.c_str(), port, "/api/v1/ws"); +// webSocket.beginSSL("ws.btclock.dev", port, "/api/v1/ws"); - doc.clear(); + } else { + webSocket.begin(mempoolInstance.c_str(), port, "/api/v1/ws"); + } + + webSocket.onEvent(onWebsocketEvent); + webSocket.setReconnectInterval(5000); + webSocket.enableHeartbeat(15000, 3000, 2); + + setupTask(); } -void processNewBlock(uint32_t newBlockHeight) { +void BlockNotify::processNewBlock(uint32_t newBlockHeight) { if (newBlockHeight <= currentBlockHeight) { return; @@ -220,76 +180,65 @@ void processNewBlock(uint32_t newBlockHeight) { } } -void processNewBlockFee(uint16_t newBlockFee) { - if (blockMedianFee == newBlockFee) +void BlockNotify::processNewBlockFee(uint16_t newBlockFee) { + if (blockMedianFee == newBlockFee) { - return; + return; } - // Serial.printf("New median fee: %d\r\n", medianFee); blockMedianFee = newBlockFee; if (workQueue != nullptr) { - WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; - xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); } } -uint32_t getBlockHeight() { return currentBlockHeight; } - -void setBlockHeight(uint32_t newBlockHeight) -{ - currentBlockHeight = newBlockHeight; +uint32_t BlockNotify::getBlockHeight() const { + return currentBlockHeight; } -uint16_t getBlockMedianFee() { return blockMedianFee; } - -void setBlockMedianFee(uint16_t newBlockMedianFee) +void BlockNotify::setBlockHeight(uint32_t newBlockHeight) { - blockMedianFee = newBlockMedianFee; + currentBlockHeight = newBlockHeight; } -bool isBlockNotifyConnected() -{ - if (blockNotifyClient == NULL) - return false; - return esp_websocket_client_is_connected(blockNotifyClient); +uint16_t BlockNotify::getBlockMedianFee() const { + return blockMedianFee; } -bool getBlockNotifyInit() +void BlockNotify::setBlockMedianFee(uint16_t newBlockMedianFee) { - return blockNotifyInit; + blockMedianFee = newBlockMedianFee; } -void stopBlockNotify() +bool BlockNotify::isConnected() const { - if (blockNotifyClient == NULL) - return; - - esp_websocket_client_close(blockNotifyClient, pdMS_TO_TICKS(5000)); - esp_websocket_client_stop(blockNotifyClient); - esp_websocket_client_destroy(blockNotifyClient); - - blockNotifyClient = NULL; + return webSocket.isConnected(); } -void restartBlockNotify() +bool BlockNotify::isInitialized() const { - stopBlockNotify(); - - if (blockNotifyClient == NULL) { - setupBlockNotify(); - return; - } - - // esp_websocket_client_close(blockNotifyClient, pdMS_TO_TICKS(5000)); - // esp_websocket_client_stop(blockNotifyClient); - // esp_websocket_client_start(blockNotifyClient); + return notifyInit; } +void BlockNotify::stop() +{ + webSocket.disconnect(); + if (taskHandle != NULL) { + vTaskDelete(taskHandle); + taskHandle = NULL; + } +} -int getBlockFetch() { +void BlockNotify::restart() +{ + stop(); + setup(); +} + +int BlockNotify::fetchLatestBlock() { try { String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "https" : "http"; @@ -312,12 +261,12 @@ int getBlockFetch() { return 2203; // B-T-C } -uint getLastBlockUpdate() +uint BlockNotify::getLastBlockUpdate() const { - return lastBlockUpdate; + return lastBlockUpdate; } -void setLastBlockUpdate(uint lastUpdate) +void BlockNotify::setLastBlockUpdate(uint lastUpdate) { - lastBlockUpdate = lastUpdate; + lastBlockUpdate = lastUpdate; } \ No newline at end of file diff --git a/src/lib/block_notify.hpp b/src/lib/block_notify.hpp index 9c41bf0..aa99f2b 100644 --- a/src/lib/block_notify.hpp +++ b/src/lib/block_notify.hpp @@ -4,8 +4,7 @@ #include #include #include -#include - +#include #include #include @@ -14,28 +13,54 @@ #include "lib/timers.hpp" #include "lib/shared.hpp" -// using namespace websockets; +class BlockNotify { +public: + static BlockNotify& getInstance() { + static BlockNotify instance; + return instance; + } -void setupBlockNotify(); + // Delete copy constructor and assignment operator + BlockNotify(const BlockNotify&) = delete; + void operator=(const BlockNotify&) = delete; -void onWebsocketBlockEvent(void *handler_args, esp_event_base_t base, - int32_t event_id, void *event_data); -void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data); + // Block notification setup and control + void setup(); + void stop(); + void restart(); + bool isConnected() const; + bool isInitialized() const; -void setBlockHeight(uint32_t newBlockHeight); -uint32_t getBlockHeight(); + // Block height management + void setBlockHeight(uint32_t newBlockHeight); + uint32_t getBlockHeight() const; -void setBlockMedianFee(uint16_t blockMedianFee); -uint16_t getBlockMedianFee(); + // Block fee management + void setBlockMedianFee(uint16_t blockMedianFee); + uint16_t getBlockMedianFee() const; -bool isBlockNotifyConnected(); -void stopBlockNotify(); -void restartBlockNotify(); + // Block processing + void processNewBlock(uint32_t newBlockHeight); + void processNewBlockFee(uint16_t newBlockFee); -void processNewBlock(uint32_t newBlockHeight); -void processNewBlockFee(uint16_t newBlockFee); + // Block fetch and update tracking + int fetchLatestBlock(); + uint getLastBlockUpdate() const; + void setLastBlockUpdate(uint lastUpdate); -bool getBlockNotifyInit(); -uint32_t getLastBlockUpdate(); -int getBlockFetch(); -void setLastBlockUpdate(uint32_t lastUpdate); + // Task handling + static void taskNotify(void* pvParameters); + +private: + BlockNotify() = default; // Private constructor for singleton + + void setupTask(); + static void onWebsocketEvent(WStype_t type, uint8_t* payload, size_t length); + + static WebSocketsClient webSocket; + static uint32_t currentBlockHeight; + static uint16_t blockMedianFee; + static bool notifyInit; + static unsigned long int lastBlockUpdate; + static TaskHandle_t taskHandle; +}; \ No newline at end of file diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 6354a7a..def33ea 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -265,7 +265,7 @@ void setupPreferences() EPDManager::getInstance().setForegroundColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR)); EPDManager::getInstance().setBackgroundColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR)); - setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT)); + BlockNotify::getInstance().setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT)); setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD); if (!preferences.isKey("enableDebugLog")) { @@ -373,7 +373,7 @@ void setupWebsocketClients(void *pvParameters) } else if (dataSource == THIRD_PARTY_SOURCE) { - setupBlockNotify(); + BlockNotify::getInstance().setup(); setupPriceNotify(); } diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index 6d9cce9..7df5ea1 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -79,8 +79,9 @@ void nostrTask(void *pvParameters) { DataSourceType dataSource = getDataSource(); if(dataSource == NOSTR_SOURCE) { - int blockFetch = getBlockFetch(); - processNewBlock(blockFetch); + auto& blockNotify = BlockNotify::getInstance(); + int blockFetch = blockNotify.fetchLatestBlock(); + blockNotify.processNewBlock(blockFetch); } while (1) @@ -174,11 +175,13 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even processNewPrice(obj["content"].as(), CURRENCY_USD); } else if (typeValue == "blockHeight") { - processNewBlock(obj["content"].as()); + auto& blockNotify = BlockNotify::getInstance(); + blockNotify.processNewBlock(obj["content"].as()); } if (medianFee != 0) { - processNewBlockFee(medianFee); + auto& blockNotify = BlockNotify::getInstance(); + blockNotify.processNewBlockFee(medianFee); } } } diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index 59497c7..2d94d48 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -74,8 +74,8 @@ void onOTAStart() ButtonHandler::suspendTask(); // stopWebServer(); - stopBlockNotify(); - stopPriceNotify(); + auto& blockNotify = BlockNotify::getInstance(); + blockNotify.stop(); } void handleOTATask(void *parameter) diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 6b3241e..75e59aa 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -251,9 +251,8 @@ void workerTask(void *pvParameters) { } else if (currentScreenValue == SCREEN_SATS_PER_CURRENCY) { taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); } else { - taskEpdContent = - parseMarketCap(getBlockHeight(), price, currency, - preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR)); + auto& blockNotify = BlockNotify::getInstance(); + taskEpdContent = parseMarketCap(blockNotify.getBlockHeight(), price, currency, preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR)); } EPDManager::getInstance().setContent(taskEpdContent); @@ -261,16 +260,19 @@ void workerTask(void *pvParameters) { } case TASK_FEE_UPDATE: { if (currentScreenValue == SCREEN_BLOCK_FEE_RATE) { - taskEpdContent = parseBlockFees(static_cast(getBlockMedianFee())); + auto& blockNotify = BlockNotify::getInstance(); + taskEpdContent = parseBlockFees(static_cast(blockNotify.getBlockMedianFee())); EPDManager::getInstance().setContent(taskEpdContent); } break; } case TASK_BLOCK_UPDATE: { if (currentScreenValue != SCREEN_HALVING_COUNTDOWN) { - taskEpdContent = parseBlockHeight(getBlockHeight()); + auto& blockNotify = BlockNotify::getInstance(); + taskEpdContent = parseBlockHeight(blockNotify.getBlockHeight()); } else { - taskEpdContent = parseHalvingCountdown(getBlockHeight(), preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN)); + auto& blockNotify = BlockNotify::getInstance(); + taskEpdContent = parseHalvingCountdown(blockNotify.getBlockHeight(), preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN)); } if (currentScreenValue == SCREEN_HALVING_COUNTDOWN || diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index 6d9fc61..a8174a1 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -131,7 +131,7 @@ namespace V2Notify { uint newBlockHeight = doc["blockheight"].as(); - if (newBlockHeight == getBlockHeight()) + if (newBlockHeight == BlockNotify::getInstance().getBlockHeight()) { return; } @@ -140,7 +140,7 @@ namespace V2Notify Serial.print(F("processNewBlock ")); Serial.println(newBlockHeight); } - processNewBlock(newBlockHeight); + BlockNotify::getInstance().processNewBlock(newBlockHeight); } else if (doc["blockfee"].is()) { @@ -151,7 +151,7 @@ namespace V2Notify Serial.println(medianFee); } - processNewBlockFee(medianFee); + BlockNotify::getInstance().processNewBlockFee(medianFee); } else if (doc["price"].is()) { diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 1d6a878..430828d 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -247,7 +247,8 @@ JsonDocument getStatusObject() JsonObject conStatus = root["connectionStatus"].to(); conStatus["price"] = isPriceNotifyConnected(); - conStatus["blocks"] = isBlockNotifyConnected(); + auto& blockNotify = BlockNotify::getInstance(); + conStatus["blocks"] = blockNotify.isConnected(); conStatus["V2"] = V2Notify::isV2NotifyConnected(); conStatus["nostr"] = nostrConnected(); @@ -906,7 +907,7 @@ void onApiStopDataSources(AsyncWebServerRequest *request) request->beginResponseStream(JSON_CONTENT); stopPriceNotify(); - stopBlockNotify(); + BlockNotify::getInstance().stop(); request->send(response); } @@ -917,9 +918,7 @@ void onApiRestartDataSources(AsyncWebServerRequest *request) request->beginResponseStream(JSON_CONTENT); restartPriceNotify(); - restartBlockNotify(); - // setupPriceNotify(); - // setupBlockNotify(); + BlockNotify::getInstance().restart(); request->send(response); } diff --git a/src/main.cpp b/src/main.cpp index c892a03..08d38ad 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #include "ESPAsyncWebServer.h" #include "lib/config.hpp" #include "lib/led_handler.hpp" +#include "lib/block_notify.hpp" uint wifiLostConnection; uint priceNotifyLostConnection = 0; @@ -49,7 +50,8 @@ void handleBlockNotifyDisconnection() { if ((getUptime() - blockNotifyLostConnection) > 300) { // 5 minutes timeout Serial.println(F("Block notification connection lost for 5 minutes, restarting handler...")); - restartBlockNotify(); + auto& blockNotify = BlockNotify::getInstance(); + blockNotify.restart(); blockNotifyLostConnection = 0; } } @@ -92,13 +94,14 @@ void checkWiFiConnection() { void checkMissedBlocks() { Serial.println(F("Long time (45 min) since last block, checking if I missed anything...")); - int currentBlock = getBlockFetch(); + auto& blockNotify = BlockNotify::getInstance(); + int currentBlock = blockNotify.fetchLatestBlock(); if (currentBlock != -1) { - if (currentBlock != getBlockHeight()) { + if (currentBlock != blockNotify.getBlockHeight()) { Serial.println(F("Detected stuck block height... restarting block handler.")); - restartBlockNotify(); + blockNotify.restart(); } - setLastBlockUpdate(getUptime()); + blockNotify.setLastBlockUpdate(getUptime()); } } @@ -111,9 +114,10 @@ void monitorDataConnections() { } // Block notification monitoring - if (getBlockNotifyInit() && !isBlockNotifyConnected()) { + auto& blockNotify = BlockNotify::getInstance(); + if (blockNotify.isInitialized() && !blockNotify.isConnected()) { handleBlockNotifyDisconnection(); - } else if (blockNotifyLostConnection > 0 && isBlockNotifyConnected()) { + } else if (blockNotifyLostConnection > 0 && blockNotify.isConnected()) { blockNotifyLostConnection = 0; } @@ -125,7 +129,7 @@ void monitorDataConnections() { } // Check for missed blocks - if ((getLastBlockUpdate() - getUptime()) > 45 * 60) { + if ((blockNotify.getLastBlockUpdate() - getUptime()) > 45 * 60) { checkMissedBlocks(); } } From 1d61453563f62430d441630e642737bb8aad5406 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 6 Jan 2025 01:13:09 +0100 Subject: [PATCH 14/30] Revert to esp websocket client because websocketsClient does not work --- src/lib/block_notify.cpp | 195 ++++++++++++++++++++++----------------- src/lib/block_notify.hpp | 11 +-- src/lib/v2_notify.cpp | 2 +- 3 files changed, 114 insertions(+), 94 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 4c32a2a..8502e3d 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -1,103 +1,116 @@ #include "block_notify.hpp" // Initialize static members -WebSocketsClient BlockNotify::webSocket; +esp_websocket_client_handle_t BlockNotify::wsClient = nullptr; uint32_t BlockNotify::currentBlockHeight = 878000; uint16_t BlockNotify::blockMedianFee = 1; bool BlockNotify::notifyInit = false; unsigned long int BlockNotify::lastBlockUpdate = 0; TaskHandle_t BlockNotify::taskHandle = nullptr; -void BlockNotify::taskNotify(void* pvParameters) -{ - for (;;) - { - webSocket.loop(); - vTaskDelay(10 / portTICK_PERIOD_MS); - } -} +const char* BlockNotify::mempoolWsCert = R"EOF( +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- +)EOF"; -void BlockNotify::setupTask() -{ - xTaskCreate(taskNotify, "blockNotify", (6 * 1024), NULL, tskIDLE_PRIORITY, - &taskHandle); -} +void BlockNotify::onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { + esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; + BlockNotify& instance = BlockNotify::getInstance(); -void BlockNotify::onWebsocketEvent(WStype_t type, uint8_t* payload, size_t length) { - switch(type) { - case WStype_DISCONNECTED: { - Serial.println(F("Mempool.space WS Connection Closed")); - break; - } - case WStype_CONNECTED: { + switch (event_id) { + case WEBSOCKET_EVENT_CONNECTED: + { notifyInit = true; Serial.print(F("Connected to ")); Serial.println(preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE)); JsonDocument doc; doc["action"] = "want"; - JsonArray data = doc.createNestedArray("data"); - data.add("blocks"); - data.add("mempool-blocks"); + JsonArray dataArray = doc.createNestedArray("data"); + dataArray.add("blocks"); + dataArray.add("mempool-blocks"); String sub; serializeJson(doc, sub); - Serial.println(sub); - webSocket.sendTXT(sub.c_str()); + esp_websocket_client_send_text(wsClient, sub.c_str(), sub.length(), portMAX_DELAY); break; } - case WStype_TEXT: { - JsonDocument doc; - JsonDocument filter; - filter["block"]["height"] = true; - filter["mempool-blocks"][0]["medianFee"] = true; - - deserializeJson(doc, (char*)payload, DeserializationOption::Filter(filter)); - - if (debugLogEnabled()) { - Serial.println(doc.as()); - } - - if (doc["block"].is()) - { - JsonObject block = doc["block"]; - if (block["height"].as() != currentBlockHeight) { - BlockNotify::getInstance().processNewBlock(block["height"].as()); - } - } - else if (doc["mempool-blocks"].is()) - { - JsonArray blockInfo = doc["mempool-blocks"].as(); - uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); - BlockNotify::getInstance().processNewBlockFee(medianFee); - } + case WEBSOCKET_EVENT_DATA: + instance.onWebsocketMessage(data); break; - } - case WStype_BIN: - case WStype_ERROR: - case WStype_FRAGMENT_TEXT_START: - case WStype_FRAGMENT_BIN_START: - case WStype_FRAGMENT: - case WStype_PING: - case WStype_PONG: - case WStype_FRAGMENT_FIN: { + + case WEBSOCKET_EVENT_DISCONNECTED: + Serial.println(F("Mempool.space WS Connection Closed")); + break; + + case WEBSOCKET_EVENT_ERROR: + Serial.println(F("Mempool.space WS Connection Error")); break; - } } } -void BlockNotify::setup() -{ +void BlockNotify::onWebsocketMessage(esp_websocket_event_data_t *data) { + JsonDocument doc; + JsonDocument filter; + filter["block"]["height"] = true; + filter["mempool-blocks"][0]["medianFee"] = true; + + deserializeJson(doc, (char*)data->data_ptr, DeserializationOption::Filter(filter)); + + if (doc["block"].is()) { + JsonObject block = doc["block"]; + if (block["height"].as() != currentBlockHeight) { + processNewBlock(block["height"].as()); + } + } + else if (doc["mempool-blocks"].is()) { + JsonArray blockInfo = doc["mempool-blocks"].as(); + uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); + processNewBlockFee(medianFee); + } +} + +void BlockNotify::setup() { IPAddress result; int dnsErr = -1; String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); - while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':')) - { + while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':')) { dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result); - if (dnsErr != 1) - { + if (dnsErr != 1) { Serial.print(mempoolInstance); Serial.println(F("mempool DNS could not be resolved")); WiFi.reconnect(); @@ -111,35 +124,39 @@ void BlockNotify::setup() if (blockFetch > currentBlockHeight) currentBlockHeight = blockFetch; - if (currentBlockHeight != -1) - { + if (currentBlockHeight != -1) { lastBlockUpdate = esp_timer_get_time() / 1000000; } - if (workQueue != nullptr) - { + if (workQueue != nullptr) { WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); } const bool useSSL = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE); - const int port = useSSL ? 443 : 80; + const String protocol = useSSL ? "wss" : "ws"; + String wsUri = protocol + "://" + mempoolInstance + "/api/v1/ws"; + + esp_websocket_client_config_t config = { + .task_stack = (6*1024), + .user_agent = USER_AGENT + }; if (useSSL) { - webSocket.beginSSL(mempoolInstance.c_str(), port, "/api/v1/ws"); -// webSocket.beginSSL("ws.btclock.dev", port, "/api/v1/ws"); - - } else { - webSocket.begin(mempoolInstance.c_str(), port, "/api/v1/ws"); + config.cert_pem = mempoolWsCert; } - webSocket.onEvent(onWebsocketEvent); - webSocket.setReconnectInterval(5000); - webSocket.enableHeartbeat(15000, 3000, 2); + config.uri = wsUri.c_str(); - setupTask(); + Serial.printf("Connecting to %s\r\n", mempoolInstance.c_str()); + + wsClient = esp_websocket_client_init(&config); + esp_websocket_register_events(wsClient, WEBSOCKET_EVENT_ANY, onWebsocketEvent, wsClient); + esp_websocket_client_start(wsClient); } + + void BlockNotify::processNewBlock(uint32_t newBlockHeight) { if (newBlockHeight <= currentBlockHeight) { @@ -215,7 +232,9 @@ void BlockNotify::setBlockMedianFee(uint16_t newBlockMedianFee) bool BlockNotify::isConnected() const { - return webSocket.isConnected(); + if (wsClient == NULL) + return false; + return esp_websocket_client_is_connected(wsClient); } bool BlockNotify::isInitialized() const @@ -225,11 +244,13 @@ bool BlockNotify::isInitialized() const void BlockNotify::stop() { - webSocket.disconnect(); - if (taskHandle != NULL) { - vTaskDelete(taskHandle); - taskHandle = NULL; - } + if (wsClient == NULL) + return; + + esp_websocket_client_close(wsClient, portMAX_DELAY); + esp_websocket_client_stop(wsClient); + esp_websocket_client_destroy(wsClient); + wsClient = NULL; } void BlockNotify::restart() diff --git a/src/lib/block_notify.hpp b/src/lib/block_notify.hpp index aa99f2b..15aabee 100644 --- a/src/lib/block_notify.hpp +++ b/src/lib/block_notify.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include @@ -48,16 +48,15 @@ public: uint getLastBlockUpdate() const; void setLastBlockUpdate(uint lastUpdate); - // Task handling - static void taskNotify(void* pvParameters); - private: BlockNotify() = default; // Private constructor for singleton void setupTask(); - static void onWebsocketEvent(WStype_t type, uint8_t* payload, size_t length); + static void onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data); + void onWebsocketMessage(esp_websocket_event_data_t *data); - static WebSocketsClient webSocket; + static const char* mempoolWsCert; + static esp_websocket_client_handle_t wsClient; static uint32_t currentBlockHeight; static uint16_t blockMedianFee; static bool notifyInit; diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index a8174a1..4d23f3a 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -176,7 +176,7 @@ namespace V2Notify for (;;) { webSocket.loop(); - vTaskDelay(10 / portTICK_PERIOD_MS); + vTaskDelay(pdMS_TO_TICKS(10)); } } From bf64b2f64f26ca189ade22a40c0928d0b36b3b9e Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 6 Jan 2025 01:27:13 +0100 Subject: [PATCH 15/30] Merge root certificates --- src/lib/block_notify.cpp | 32 +++++++++++++++++++ src/lib/shared.cpp | 66 ++++++++++++++++++++-------------------- src/lib/shared.hpp | 2 +- 3 files changed, 66 insertions(+), 34 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 8502e3d..20e6267 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -43,6 +43,38 @@ VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG jjxDah2nGN59PRbxYvnKkKj9 -----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- )EOF"; void BlockNotify::onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { diff --git a/src/lib/shared.cpp b/src/lib/shared.cpp index aa67768..b8efc0c 100644 --- a/src/lib/shared.cpp +++ b/src/lib/shared.cpp @@ -40,39 +40,39 @@ // "MrY=\n" // "-----END CERTIFICATE-----\n"; -const char* isrg_root_x1cert = R"EOF( ------BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- -)EOF"; +// const char* isrg_root_x1cert = R"EOF( +// -----BEGIN CERTIFICATE----- +// MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +// TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +// cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +// WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +// ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +// MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +// h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +// 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +// A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +// T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +// B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +// B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +// KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +// OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +// jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +// qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +// rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +// HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +// hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +// ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +// 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +// NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +// ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +// TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +// jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +// oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +// 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +// mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +// emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +// -----END CERTIFICATE----- +// )EOF"; #ifdef TEST_SCREENS diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 479a52f..54d48aa 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -68,7 +68,7 @@ const int usPerSecond = 1000000; const int usPerMinute = 60 * usPerSecond; // extern const char *github_root_ca; -extern const char *isrg_root_x1cert; +// extern const char *isrg_root_x1cert; extern const uint8_t rootca_crt_bundle_start[] asm("_binary_x509_crt_bundle_start"); // extern const uint8_t ocean_logo_comp[] asm("_binary_ocean_gz_start"); From 963f3b10b7a42ebfb50aba1e71b20d6fe8557dcc Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 6 Jan 2025 01:30:46 +0100 Subject: [PATCH 16/30] Update WebUI --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 732dd26..91e60d2 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 732dd260ea708841f0e15ee1ee64a3d5115cd475 +Subproject commit 91e60d2f4cd7437075a6e10ea0f395b2650ed531 From 1083a3222bf71ab96d27196efa6666e4093bbb3b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 8 Jan 2025 02:14:33 +0100 Subject: [PATCH 17/30] Add local public pool support --- data | 2 +- dependencies.lock | 2 +- src/lib/defaults.hpp | 1 + src/lib/mining_pool/pool_factory.cpp | 2 ++ src/lib/mining_pool/pool_factory.hpp | 3 +++ src/lib/mining_pool/public_pool/local_public_pool.cpp | 11 +++++++++++ src/lib/mining_pool/public_pool/local_public_pool.hpp | 11 +++++++++++ src/lib/v2_notify.cpp | 4 ---- src/lib/webserver.cpp | 5 ++++- 9 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 src/lib/mining_pool/public_pool/local_public_pool.cpp create mode 100644 src/lib/mining_pool/public_pool/local_public_pool.hpp diff --git a/data b/data index 91e60d2..e0d539a 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 91e60d2f4cd7437075a6e10ea0f395b2650ed531 +Subproject commit e0d539a8a3d29669ccc116311870f317b0d9dfcc diff --git a/dependencies.lock b/dependencies.lock index c338e6c..f52d5f2 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -4,6 +4,6 @@ dependencies: source: type: idf version: 4.4.7 -manifest_hash: cd2f3ee15e776d949eb4ea4eddc8f39b30c2a7905050850eed01ab4928143cff +manifest_hash: 04e75badb795f8a851d8b088baff06d145ecf7a66457d960c2f4ede17b45ef05 target: esp32s3 version: 1.0.0 diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 29d7eee..a662344 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -60,6 +60,7 @@ #define DEFAULT_MINING_POOL_STATS_ENABLED false #define DEFAULT_MINING_POOL_NAME "ocean" #define DEFAULT_MINING_POOL_USER "38Qkkei3SuF1Eo45BaYmRHUneRD54yyTFy" // Random actual Ocean hasher +#define DEFAULT_LOCAL_POOL_ENDPOINT "umbrel.local:2019" #define DEFAULT_ZAP_NOTIFY_ENABLED false #define DEFAULT_ZAP_NOTIFY_PUBKEY "b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422" diff --git a/src/lib/mining_pool/pool_factory.cpp b/src/lib/mining_pool/pool_factory.cpp index c353996..45bc6ce 100644 --- a/src/lib/mining_pool/pool_factory.cpp +++ b/src/lib/mining_pool/pool_factory.cpp @@ -5,6 +5,7 @@ const char* PoolFactory::MINING_POOL_NAME_NODERUNNERS = "noderunners"; const char* PoolFactory::MINING_POOL_NAME_BRAIINS = "braiins"; const char* PoolFactory::MINING_POOL_NAME_SATOSHI_RADIO = "satoshi_radio"; const char* PoolFactory::MINING_POOL_NAME_PUBLIC_POOL = "public_pool"; +const char* PoolFactory::MINING_POOL_NAME_LOCAL_PUBLIC_POOL = "local_public_pool"; const char* PoolFactory::MINING_POOL_NAME_GOBRRR_POOL = "gobrrr_pool"; const char* PoolFactory::MINING_POOL_NAME_CKPOOL = "ckpool"; const char* PoolFactory::MINING_POOL_NAME_EU_CKPOOL = "eu_ckpool"; @@ -17,6 +18,7 @@ std::unique_ptr PoolFactory::createPool(const std::string& {MINING_POOL_NAME_BRAIINS, []() { return std::make_unique(); }}, {MINING_POOL_NAME_SATOSHI_RADIO, []() { return std::make_unique(); }}, {MINING_POOL_NAME_PUBLIC_POOL, []() { return std::make_unique(); }}, + {MINING_POOL_NAME_LOCAL_PUBLIC_POOL, []() { return std::make_unique(); }}, {MINING_POOL_NAME_GOBRRR_POOL, []() { return std::make_unique(); }}, {MINING_POOL_NAME_CKPOOL, []() { return std::make_unique(); }}, {MINING_POOL_NAME_EU_CKPOOL, []() { return std::make_unique(); }} diff --git a/src/lib/mining_pool/pool_factory.hpp b/src/lib/mining_pool/pool_factory.hpp index 951dbe5..9885d74 100644 --- a/src/lib/mining_pool/pool_factory.hpp +++ b/src/lib/mining_pool/pool_factory.hpp @@ -10,6 +10,7 @@ #include "ocean/ocean_pool.hpp" #include "satoshi_radio/satoshi_radio_pool.hpp" #include "public_pool/public_pool.hpp" +#include "public_pool/local_public_pool.hpp" #include "gobrrr_pool/gobrrr_pool.hpp" #include "ckpool/ckpool.hpp" #include "ckpool/eu_ckpool.hpp" @@ -28,6 +29,7 @@ class PoolFactory { MINING_POOL_NAME_SATOSHI_RADIO, MINING_POOL_NAME_BRAIINS, MINING_POOL_NAME_PUBLIC_POOL, + MINING_POOL_NAME_LOCAL_PUBLIC_POOL, MINING_POOL_NAME_GOBRRR_POOL, MINING_POOL_NAME_CKPOOL, MINING_POOL_NAME_EU_CKPOOL @@ -55,6 +57,7 @@ class PoolFactory { static const char* MINING_POOL_NAME_BRAIINS; static const char* MINING_POOL_NAME_SATOSHI_RADIO; static const char* MINING_POOL_NAME_PUBLIC_POOL; + static const char* MINING_POOL_NAME_LOCAL_PUBLIC_POOL; static const char* MINING_POOL_NAME_GOBRRR_POOL; static const char* MINING_POOL_NAME_CKPOOL; static const char* MINING_POOL_NAME_EU_CKPOOL; diff --git a/src/lib/mining_pool/public_pool/local_public_pool.cpp b/src/lib/mining_pool/public_pool/local_public_pool.cpp new file mode 100644 index 0000000..048197c --- /dev/null +++ b/src/lib/mining_pool/public_pool/local_public_pool.cpp @@ -0,0 +1,11 @@ +#include "local_public_pool.hpp" +#include "lib/shared.hpp" +#include "lib/defaults.hpp" + +std::string LocalPublicPool::getEndpoint() const { + return preferences.getString("localPoolEndpoint", DEFAULT_LOCAL_POOL_ENDPOINT).c_str(); +} + +std::string LocalPublicPool::getApiUrl() const { + return "http://" + getEndpoint() + "/api/client/" + poolUser; +} \ No newline at end of file diff --git a/src/lib/mining_pool/public_pool/local_public_pool.hpp b/src/lib/mining_pool/public_pool/local_public_pool.hpp new file mode 100644 index 0000000..a9e37ad --- /dev/null +++ b/src/lib/mining_pool/public_pool/local_public_pool.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "public_pool.hpp" + +class LocalPublicPool : public PublicPool { +public: + std::string getApiUrl() const override; + std::string getDisplayLabel() const override { return "LOCAL/POOL"; } +private: + std::string getEndpoint() const; +}; \ No newline at end of file diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index 4d23f3a..b915518 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -155,10 +155,6 @@ namespace V2Notify } else if (doc["price"].is()) { - if (debugLogEnabled()) { - Serial.print(F("processNewPrice ")); - Serial.println(doc["price"].as().size()); - } // Iterate through the key-value pairs of the "price" object for (JsonPair kv : doc["price"].as()) diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 430828d..fef1634 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -5,7 +5,7 @@ static const char* JSON_CONTENT = "application/json"; static const char *const PROGMEM strSettings[] = { - "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName"}; + "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName", "localPoolEndpoint"}; static const char *const PROGMEM uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout"}; @@ -696,6 +696,9 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["mempoolInstance"] = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); root["mempoolSecure"] = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE); + // Local pool settings + root["localPoolEndpoint"] = preferences.getString("localPoolEndpoint", DEFAULT_LOCAL_POOL_ENDPOINT); + // Nostr settings (used for NOSTR_SOURCE or when zapNotify is enabled) root["nostrPubKey"] = preferences.getString("nostrPubKey", DEFAULT_NOSTR_NPUB); root["nostrRelay"] = preferences.getString("nostrRelay", DEFAULT_NOSTR_RELAY); From b01003f07549cbd26e89b871bba4004a0356813b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 15 Jan 2025 22:09:05 +0100 Subject: [PATCH 18/30] fix: Never write to LED0 --- src/lib/led_handler.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 3cdb953..453b067 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -535,7 +535,7 @@ void LedHandler::frontlightSetBrightness(uint brightness) { } for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { - flArray.setPWM(ledPin, 0, brightness); + flArray.setPWM(ledPin + 1, 0, brightness); } } @@ -543,7 +543,7 @@ std::vector LedHandler::frontlightGetStatus() { std::vector statuses; for (int ledPin = 1; ledPin <= NUM_SCREENS; ledPin++) { uint16_t a = 0, b = 0; - flArray.getPWM(ledPin, &a, &b); + flArray.getPWM(ledPin + 1, &a, &b); statuses.push_back(round(b - a / 4096)); } return statuses; @@ -576,7 +576,7 @@ void LedHandler::frontlightFadeInAll(int flDelayTime, bool staggered) { } else { for (int dutyCycle = 0; dutyCycle <= maxBrightness; dutyCycle += FL_FADE_STEP) { for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { - flArray.setPWM(ledPin, 0, dutyCycle); + flArray.setPWM(ledPin + 1, 0, dutyCycle); } vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } @@ -611,7 +611,7 @@ void LedHandler::frontlightFadeOutAll(int flDelayTime, bool staggered) { } else { for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= FL_FADE_STEP) { for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { - flArray.setPWM(ledPin, 0, dutyCycle); + flArray.setPWM(ledPin + 1, 0, dutyCycle); } vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } @@ -628,7 +628,7 @@ void LedHandler::frontlightFadeIn(uint num, int flDelayTime) { } for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) { - flArray.setPWM(num, 0, dutyCycle); + flArray.setPWM(num + 1, 0, dutyCycle); vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } } @@ -639,7 +639,7 @@ void LedHandler::frontlightFadeOut(uint num, int flDelayTime) { } for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) { - flArray.setPWM(num, 0, dutyCycle); + flArray.setPWM(num + 1, 0, dutyCycle); vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } } From 9ea021086490c53845508e2a6eac27b6f016b63e Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 16 Jan 2025 00:30:40 +0100 Subject: [PATCH 19/30] fix: set better defaults for frontlight enabled devices --- data | 2 +- src/lib/defaults.hpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data b/data index e0d539a..68207a7 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit e0d539a8a3d29669ccc116311870f317b0d9dfcc +Subproject commit 68207a7d95b6cb16df5c991b84ff40c25c23755b diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index a662344..55da6e3 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -46,8 +46,8 @@ #define DEFAULT_LUX_LIGHT_TOGGLE 128 #define DEFAULT_FL_OFF_WHEN_DARK true -#define DEFAULT_FL_ALWAYS_ON false -#define DEFAULT_FL_FLASH_ON_UPDATE false +#define DEFAULT_FL_ALWAYS_ON true +#define DEFAULT_FL_FLASH_ON_UPDATE true #define DEFAULT_LED_STATUS false #define DEFAULT_TIMER_ACTIVE true From 678a4ba0998121d78fa582c2353d44a92866bafc Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 19 Jan 2025 22:32:04 +0100 Subject: [PATCH 20/30] fix: Set WiFi country to NL for scanning --- src/lib/config.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/lib/config.cpp b/src/lib/config.cpp index def33ea..16cb80a 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -132,9 +132,25 @@ void setup() void setupWifi() { WiFi.onEvent(WiFiEvent); + + // wifi_country_t country = { + // .cc = "NL", + // .schan = 1, + // .nchan = 13, + // .policy = WIFI_COUNTRY_POLICY_MANUAL + // }; + + // esp_err_t err = esp_wifi_set_country(&country); + // if (err != ESP_OK) { + // Serial.printf("Failed to set country: %d\n", err); + // } + WiFi.setAutoConnect(true); WiFi.setAutoReconnect(true); WiFi.begin(); + + + if (preferences.getInt("txPower", DEFAULT_TX_POWER)) { if (WiFi.setTxPower( @@ -172,6 +188,7 @@ void setupWifi() wm.setConfigPortalTimeout(preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT)); wm.setWiFiAutoReconnect(false); wm.setDebugOutput(false); + wm.setCountry("NL"); wm.setConfigPortalBlocking(true); wm.setAPCallback([&](WiFiManager *wifiManager) From 3265eec30886a6ff3b6b4f54eb96b36773133cbc Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 20 Jan 2025 12:05:48 +0100 Subject: [PATCH 21/30] chore: update dependencies and make eventsource use static jsondocument --- platformio.ini | 13 ++++++++----- src/lib/webserver.cpp | 15 ++++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/platformio.ini b/platformio.ini index d46c153..1eb178a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,17 +30,17 @@ build_flags = -DLAST_BUILD_TIME=$UNIX_TIME -DARDUINO_USB_CDC_ON_BOOT -DCORE_DEBUG_LEVEL=0 - -D DEFAULT_BOOT_TEXT=\"BTCLOCK\" + -D CONFIG_ASYNC_TCP_STACK_SIZE=16384 -fexceptions build_unflags = -Werror=all -fno-exceptions lib_deps = https://github.com/joltwallet/esp_littlefs.git - bblanchon/ArduinoJson@^7.2.1 - mathieucarbou/ESPAsyncWebServer @ 3.4.5 + bblanchon/ArduinoJson@^7.3.0 + mathieucarbou/ESPAsyncWebServer @ 3.6.0 robtillaart/MCP23017@^0.8.0 - adafruit/Adafruit NeoPixel@^1.12.3 + adafruit/Adafruit NeoPixel@^1.12.4 https://github.com/dsbaars/universal_pin#feature/mcp23017_rt https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 @@ -79,9 +79,10 @@ build_flags = -D I2C_SDA_PIN=35 -D I2C_SCK_PIN=36 -D HAS_FRONTLIGHT - -D PCA_OE_PIN=45 + -D PCA_OE_PIN=48 -D PCA_I2C_ADDR=0x42 -D IS_HW_REV_B + lib_deps = ${btclock_base.lib_deps} robtillaart/PCA9685@^0.7.1 @@ -100,6 +101,7 @@ build_flags = -D USE_QR -D VERSION_EPD_2_13 -D HW_REV=\"REV_A_EPD_2_13\" + -D CONFIG_ARDUINO_MAIN_TASK_STACK_SIZE=16384 platform_packages = platformio/tool-mklittlefs@^1.203.210628 earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 @@ -112,6 +114,7 @@ build_flags = -D USE_QR -D VERSION_EPD_2_13 -D HW_REV=\"REV_B_EPD_2_13\" + -D CONFIG_ARDUINO_MAIN_TASK_STACK_SIZE=16384 platform_packages = platformio/tool-mklittlefs@^1.203.210628 earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index fef1634..cfb5d62 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -30,7 +30,8 @@ TaskHandle_t eventSourceTaskHandle; void setupWebserver() { events.onConnect([](AsyncEventSourceClient *client) - { client->send("welcome", NULL, millis(), 1000); }); + { client->send("welcome", NULL, millis(), 1000); + }); server.addHandler(&events); AsyncStaticWebHandler &staticHandler = server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html"); @@ -306,14 +307,18 @@ JsonDocument getLedStatusObject() void eventSourceUpdate() { if (!events.count()) return; - JsonDocument doc = getStatusObject(); - doc["leds"] = getLedStatusObject()["data"]; + static JsonDocument doc; + doc.clear(); + + JsonDocument root = getStatusObject(); + + root["leds"] = getLedStatusObject()["data"]; // Get current EPD content directly as array std::array epdContent = EPDManager::getInstance().getCurrentContent(); // Add EPD content arrays - JsonArray data = doc["data"].to(); + JsonArray data = root["data"].to(); // Copy array elements directly for(const auto& content : epdContent) { @@ -321,7 +326,7 @@ void eventSourceUpdate() { } String buffer; - serializeJson(doc, buffer); + serializeJson(root, buffer); events.send(buffer.c_str(), "status"); } From 0b1a362b53d44aa9c31b1bfd13a1781f92a585c4 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 19 Feb 2025 14:12:16 +0100 Subject: [PATCH 22/30] chore: dependency updates --- platformio.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index 1eb178a..f9dc11e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,7 +15,7 @@ default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd, btclock_rev_b_213epd, [env] [btclock_base] -platform = espressif32 @ ^6.9.0 +platform = espressif32 @ ^6.10.0 framework = arduino, espidf monitor_speed = 115200 monitor_filters = esp32_exception_decoder, colorize @@ -38,8 +38,8 @@ build_unflags = lib_deps = https://github.com/joltwallet/esp_littlefs.git bblanchon/ArduinoJson@^7.3.0 - mathieucarbou/ESPAsyncWebServer @ 3.6.0 - robtillaart/MCP23017@^0.8.0 + esp32async/ESPAsyncWebServer @ 3.7.0 + robtillaart/MCP23017@^0.9.0 adafruit/Adafruit NeoPixel@^1.12.4 https://github.com/dsbaars/universal_pin#feature/mcp23017_rt https://github.com/dsbaars/GxEPD2#universal_pin From e4ac3c5c948372f6cc58048d24656b9ec0f0b187 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 19 Feb 2025 15:15:53 +0100 Subject: [PATCH 23/30] feat: switch to replaceable events for nostr source --- data | 2 +- dependencies.lock | 2 +- src/lib/nostr_notify.cpp | 12 +++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/data b/data index 68207a7..0116cd6 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 68207a7d95b6cb16df5c991b84ff40c25c23755b +Subproject commit 0116cd68cdfdf383823f74e0f9665a1700cf0500 diff --git a/dependencies.lock b/dependencies.lock index f52d5f2..c338e6c 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -4,6 +4,6 @@ dependencies: source: type: idf version: 4.4.7 -manifest_hash: 04e75badb795f8a851d8b088baff06d145ecf7a66457d960c2f4ede17b45ef05 +manifest_hash: cd2f3ee15e776d949eb4ea4eddc8f39b30c2a7905050850eed01ab4928143cff target: esp32s3 version: 1.0.0 diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index 7df5ea1..dda24e1 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -41,7 +41,7 @@ void setupNostrNotify(bool asDatasource, bool zapNotify) {relay}, {// First filter { - {"kinds", {"1"}}, + {"kinds", {"12203"}}, {"since", {String(getMinutesAgo(60))}}, {"authors", {pubKey}}, }}, @@ -146,6 +146,7 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even // Use direct value access instead of multiple comparisons String typeValue; uint medianFee = 0; + uint blockHeight = 0; for (JsonArray tag : tags) { if (tag.size() != 2) continue; @@ -166,6 +167,11 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even medianFee = tag[1].as(); } break; + case 'b': // blockHeight + if (strcmp(key, "block") == 0) { + blockHeight = tag[1].as(); + } + break; } } @@ -173,6 +179,10 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even if (!typeValue.isEmpty()) { if (typeValue == "priceUsd") { processNewPrice(obj["content"].as(), CURRENCY_USD); + if (blockHeight != 0) { + auto& blockNotify = BlockNotify::getInstance(); + blockNotify.processNewBlock(blockHeight); + } } else if (typeValue == "blockHeight") { auto& blockNotify = BlockNotify::getInstance(); From dc8e348aa349e9fcf3c0f7b12733b4f2306a995f Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 19 Feb 2025 15:38:54 +0100 Subject: [PATCH 24/30] fix: Set explicit littlefs version tag --- dependencies.lock | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies.lock b/dependencies.lock index c338e6c..2274b85 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -4,6 +4,6 @@ dependencies: source: type: idf version: 4.4.7 -manifest_hash: cd2f3ee15e776d949eb4ea4eddc8f39b30c2a7905050850eed01ab4928143cff +manifest_hash: 1d4ef353a86901733b106a1897b186dbf9fc091a4981f0560ea2f6899b7a3d44 target: esp32s3 version: 1.0.0 diff --git a/platformio.ini b/platformio.ini index f9dc11e..37e99da 100644 --- a/platformio.ini +++ b/platformio.ini @@ -36,7 +36,7 @@ build_unflags = -Werror=all -fno-exceptions lib_deps = - https://github.com/joltwallet/esp_littlefs.git + https://github.com/joltwallet/esp_littlefs.git#v1.16.4 bblanchon/ArduinoJson@^7.3.0 esp32async/ESPAsyncWebServer @ 3.7.0 robtillaart/MCP23017@^0.9.0 From 7266a51a92c776350bd7cb3b5ff409695478313f Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 5 Apr 2025 22:35:40 +0200 Subject: [PATCH 25/30] chore: update dependencies --- platformio.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platformio.ini b/platformio.ini index 37e99da..140946d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -37,10 +37,10 @@ build_unflags = -fno-exceptions lib_deps = https://github.com/joltwallet/esp_littlefs.git#v1.16.4 - bblanchon/ArduinoJson@^7.3.0 - esp32async/ESPAsyncWebServer @ 3.7.0 - robtillaart/MCP23017@^0.9.0 - adafruit/Adafruit NeoPixel@^1.12.4 + bblanchon/ArduinoJson@^7.3.1 + esp32async/ESPAsyncWebServer @ 3.7.4 + robtillaart/MCP23017@^0.9.1 + adafruit/Adafruit NeoPixel@^1.12.5 https://github.com/dsbaars/universal_pin#feature/mcp23017_rt https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 From c62563c3c2a9886e09a617aa136c94901acebb50 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 5 Apr 2025 22:40:12 +0200 Subject: [PATCH 26/30] feat: Replace timezone offset with timezone selector --- include/timezone_data.hpp | 721 ++++++++++++++++++++++++++++++++++++++ src/lib/config.cpp | 13 +- src/lib/config.hpp | 3 +- src/lib/defaults.hpp | 1 + src/lib/webserver.cpp | 7 +- 5 files changed, 739 insertions(+), 6 deletions(-) create mode 100644 include/timezone_data.hpp diff --git a/include/timezone_data.hpp b/include/timezone_data.hpp new file mode 100644 index 0000000..1ebb5db --- /dev/null +++ b/include/timezone_data.hpp @@ -0,0 +1,721 @@ +#pragma once + +#include +#include +#include + +namespace timezone_data { + +// Enum for unique timezone strings +enum class TimezoneValue : uint8_t { + plus000plus02_2M350_1M1050_3, + plus01_1, + plus02_2, + plus0330_330, + plus03_3, + plus0430_430, + plus04_4, + plus0530_530, + plus0545_545, + plus05_5, + plus0630_630, + plus06_6, + plus07_7, + plus0845_845, + plus08_8, + plus09_9, + plus1030_1030plus11_11M1010M410, + plus10_10, + plus11_11, + plus11_11plus12M1010M410_3, + plus1245_1245plus1345M950_245M410_345, + plus12_12, + plus13_13, + plus14_14, + _011, + _011plus00M350_0M1050_1, + _022, + _022_01M350__1M1050_0, + _033, + _033_02M320M1110, + _044, + _044_03M1010_0M340_0, + _044_03M916_24M416_24, + _055, + _066, + _066_05M916_22M416_22, + _077, + _088, + _0930930, + _099, + _1010, + _1111, + _1212, + ACST_930, + ACST_930ACDTM1010M410_3, + AEST_10, + AEST_10AEDTM1010M410_3, + AKST9AKDTM320M1110, + AST4, + AST4ADTM320M1110, + AWST_8, + CAT_2, + CET_1, + CET_1CESTM350M1050_3, + CST_8, + CST5CDTM320_0M1110_1, + CST6, + CST6CDTM320M1110, + ChST_10, + EAT_3, + EET_2, + EET_2EESTM344_50M1044_50, + EET_2EESTM350M1050_3, + EET_2EESTM350_0M1050_0, + EET_2EESTM350_3M1050_4, + EET_2EESTM455_0M1054_24, + EST5, + EST5EDTM320M1110, + GMT0, + GMT0BSTM350_1M1050, + HKT_8, + HST10, + HST10HDTM320M1110, + IST_1GMT0M1050M350_1, + IST_2IDTM344_26M1050, + IST_530, + JST_9, + KST_9, + MSK_3, + MST7, + MST7MDTM320M1110, + NST330NDTM320M1110, + NZST_12NZDTM950M410_3, + PKT_5, + PST_8, + PST8PDTM320M1110, + SAST_2, + SST11, + UTC0, + WAT_1, + WET0WESTM350_1M1050, + WIB_7, + WIT_9, + WITA_8, +}; + +// Key-value pair type +using TimezoneEntry = std::pair; + +// Lookup table +constexpr std::array TIMEZONE_DATA = {{ + {"Africa/Abidjan", TimezoneValue::GMT0}, + {"Africa/Accra", TimezoneValue::GMT0}, + {"Africa/Addis_Ababa", TimezoneValue::EAT_3}, + {"Africa/Algiers", TimezoneValue::CET_1}, + {"Africa/Asmara", TimezoneValue::EAT_3}, + {"Africa/Bamako", TimezoneValue::GMT0}, + {"Africa/Bangui", TimezoneValue::WAT_1}, + {"Africa/Banjul", TimezoneValue::GMT0}, + {"Africa/Bissau", TimezoneValue::GMT0}, + {"Africa/Blantyre", TimezoneValue::CAT_2}, + {"Africa/Brazzaville", TimezoneValue::WAT_1}, + {"Africa/Bujumbura", TimezoneValue::CAT_2}, + {"Africa/Cairo", TimezoneValue::EET_2EESTM455_0M1054_24}, + {"Africa/Casablanca", TimezoneValue::plus01_1}, + {"Africa/Ceuta", TimezoneValue::CET_1CESTM350M1050_3}, + {"Africa/Conakry", TimezoneValue::GMT0}, + {"Africa/Dakar", TimezoneValue::GMT0}, + {"Africa/Dar_es_Salaam", TimezoneValue::EAT_3}, + {"Africa/Djibouti", TimezoneValue::EAT_3}, + {"Africa/Douala", TimezoneValue::WAT_1}, + {"Africa/El_Aaiun", TimezoneValue::plus01_1}, + {"Africa/Freetown", TimezoneValue::GMT0}, + {"Africa/Gaborone", TimezoneValue::CAT_2}, + {"Africa/Harare", TimezoneValue::CAT_2}, + {"Africa/Johannesburg", TimezoneValue::SAST_2}, + {"Africa/Juba", TimezoneValue::CAT_2}, + {"Africa/Kampala", TimezoneValue::EAT_3}, + {"Africa/Khartoum", TimezoneValue::CAT_2}, + {"Africa/Kigali", TimezoneValue::CAT_2}, + {"Africa/Kinshasa", TimezoneValue::WAT_1}, + {"Africa/Lagos", TimezoneValue::WAT_1}, + {"Africa/Libreville", TimezoneValue::WAT_1}, + {"Africa/Lome", TimezoneValue::GMT0}, + {"Africa/Luanda", TimezoneValue::WAT_1}, + {"Africa/Lubumbashi", TimezoneValue::CAT_2}, + {"Africa/Lusaka", TimezoneValue::CAT_2}, + {"Africa/Malabo", TimezoneValue::WAT_1}, + {"Africa/Maputo", TimezoneValue::CAT_2}, + {"Africa/Maseru", TimezoneValue::SAST_2}, + {"Africa/Mbabane", TimezoneValue::SAST_2}, + {"Africa/Mogadishu", TimezoneValue::EAT_3}, + {"Africa/Monrovia", TimezoneValue::GMT0}, + {"Africa/Nairobi", TimezoneValue::EAT_3}, + {"Africa/Ndjamena", TimezoneValue::WAT_1}, + {"Africa/Niamey", TimezoneValue::WAT_1}, + {"Africa/Nouakchott", TimezoneValue::GMT0}, + {"Africa/Ouagadougou", TimezoneValue::GMT0}, + {"Africa/Porto-Novo", TimezoneValue::WAT_1}, + {"Africa/Sao_Tome", TimezoneValue::GMT0}, + {"Africa/Tripoli", TimezoneValue::EET_2}, + {"Africa/Tunis", TimezoneValue::CET_1}, + {"Africa/Windhoek", TimezoneValue::CAT_2}, + {"America/Adak", TimezoneValue::HST10HDTM320M1110}, + {"America/Anchorage", TimezoneValue::AKST9AKDTM320M1110}, + {"America/Anguilla", TimezoneValue::AST4}, + {"America/Antigua", TimezoneValue::AST4}, + {"America/Araguaina", TimezoneValue::_033}, + {"America/Argentina/Buenos_Aires", TimezoneValue::_033}, + {"America/Argentina/Catamarca", TimezoneValue::_033}, + {"America/Argentina/Cordoba", TimezoneValue::_033}, + {"America/Argentina/Jujuy", TimezoneValue::_033}, + {"America/Argentina/La_Rioja", TimezoneValue::_033}, + {"America/Argentina/Mendoza", TimezoneValue::_033}, + {"America/Argentina/Rio_Gallegos", TimezoneValue::_033}, + {"America/Argentina/Salta", TimezoneValue::_033}, + {"America/Argentina/San_Juan", TimezoneValue::_033}, + {"America/Argentina/San_Luis", TimezoneValue::_033}, + {"America/Argentina/Tucuman", TimezoneValue::_033}, + {"America/Argentina/Ushuaia", TimezoneValue::_033}, + {"America/Aruba", TimezoneValue::AST4}, + {"America/Asuncion", TimezoneValue::_044_03M1010_0M340_0}, + {"America/Atikokan", TimezoneValue::EST5}, + {"America/Bahia", TimezoneValue::_033}, + {"America/Bahia_Banderas", TimezoneValue::CST6}, + {"America/Barbados", TimezoneValue::AST4}, + {"America/Belem", TimezoneValue::_033}, + {"America/Belize", TimezoneValue::CST6}, + {"America/Blanc-Sablon", TimezoneValue::AST4}, + {"America/Boa_Vista", TimezoneValue::_044}, + {"America/Bogota", TimezoneValue::_055}, + {"America/Boise", TimezoneValue::MST7MDTM320M1110}, + {"America/Cambridge_Bay", TimezoneValue::MST7MDTM320M1110}, + {"America/Campo_Grande", TimezoneValue::_044}, + {"America/Cancun", TimezoneValue::EST5}, + {"America/Caracas", TimezoneValue::_044}, + {"America/Cayenne", TimezoneValue::_033}, + {"America/Cayman", TimezoneValue::EST5}, + {"America/Chicago", TimezoneValue::CST6CDTM320M1110}, + {"America/Chihuahua", TimezoneValue::CST6}, + {"America/Costa_Rica", TimezoneValue::CST6}, + {"America/Creston", TimezoneValue::MST7}, + {"America/Cuiaba", TimezoneValue::_044}, + {"America/Curacao", TimezoneValue::AST4}, + {"America/Danmarkshavn", TimezoneValue::GMT0}, + {"America/Dawson", TimezoneValue::MST7}, + {"America/Dawson_Creek", TimezoneValue::MST7}, + {"America/Denver", TimezoneValue::MST7MDTM320M1110}, + {"America/Detroit", TimezoneValue::EST5EDTM320M1110}, + {"America/Dominica", TimezoneValue::AST4}, + {"America/Edmonton", TimezoneValue::MST7MDTM320M1110}, + {"America/Eirunepe", TimezoneValue::_055}, + {"America/El_Salvador", TimezoneValue::CST6}, + {"America/Fort_Nelson", TimezoneValue::MST7}, + {"America/Fortaleza", TimezoneValue::_033}, + {"America/Glace_Bay", TimezoneValue::AST4ADTM320M1110}, + {"America/Godthab", TimezoneValue::_022_01M350__1M1050_0}, + {"America/Goose_Bay", TimezoneValue::AST4ADTM320M1110}, + {"America/Grand_Turk", TimezoneValue::EST5EDTM320M1110}, + {"America/Grenada", TimezoneValue::AST4}, + {"America/Guadeloupe", TimezoneValue::AST4}, + {"America/Guatemala", TimezoneValue::CST6}, + {"America/Guayaquil", TimezoneValue::_055}, + {"America/Guyana", TimezoneValue::_044}, + {"America/Halifax", TimezoneValue::AST4ADTM320M1110}, + {"America/Havana", TimezoneValue::CST5CDTM320_0M1110_1}, + {"America/Hermosillo", TimezoneValue::MST7}, + {"America/Indiana/Indianapolis", TimezoneValue::EST5EDTM320M1110}, + {"America/Indiana/Knox", TimezoneValue::CST6CDTM320M1110}, + {"America/Indiana/Marengo", TimezoneValue::EST5EDTM320M1110}, + {"America/Indiana/Petersburg", TimezoneValue::EST5EDTM320M1110}, + {"America/Indiana/Tell_City", TimezoneValue::CST6CDTM320M1110}, + {"America/Indiana/Vevay", TimezoneValue::EST5EDTM320M1110}, + {"America/Indiana/Vincennes", TimezoneValue::EST5EDTM320M1110}, + {"America/Indiana/Winamac", TimezoneValue::EST5EDTM320M1110}, + {"America/Inuvik", TimezoneValue::MST7MDTM320M1110}, + {"America/Iqaluit", TimezoneValue::EST5EDTM320M1110}, + {"America/Jamaica", TimezoneValue::EST5}, + {"America/Juneau", TimezoneValue::AKST9AKDTM320M1110}, + {"America/Kentucky/Louisville", TimezoneValue::EST5EDTM320M1110}, + {"America/Kentucky/Monticello", TimezoneValue::EST5EDTM320M1110}, + {"America/Kralendijk", TimezoneValue::AST4}, + {"America/La_Paz", TimezoneValue::_044}, + {"America/Lima", TimezoneValue::_055}, + {"America/Los_Angeles", TimezoneValue::PST8PDTM320M1110}, + {"America/Lower_Princes", TimezoneValue::AST4}, + {"America/Maceio", TimezoneValue::_033}, + {"America/Managua", TimezoneValue::CST6}, + {"America/Manaus", TimezoneValue::_044}, + {"America/Marigot", TimezoneValue::AST4}, + {"America/Martinique", TimezoneValue::AST4}, + {"America/Matamoros", TimezoneValue::CST6CDTM320M1110}, + {"America/Mazatlan", TimezoneValue::MST7}, + {"America/Menominee", TimezoneValue::CST6CDTM320M1110}, + {"America/Merida", TimezoneValue::CST6}, + {"America/Metlakatla", TimezoneValue::AKST9AKDTM320M1110}, + {"America/Mexico_City", TimezoneValue::CST6}, + {"America/Miquelon", TimezoneValue::_033_02M320M1110}, + {"America/Moncton", TimezoneValue::AST4ADTM320M1110}, + {"America/Monterrey", TimezoneValue::CST6}, + {"America/Montevideo", TimezoneValue::_033}, + {"America/Montreal", TimezoneValue::EST5EDTM320M1110}, + {"America/Montserrat", TimezoneValue::AST4}, + {"America/Nassau", TimezoneValue::EST5EDTM320M1110}, + {"America/New_York", TimezoneValue::EST5EDTM320M1110}, + {"America/Nipigon", TimezoneValue::EST5EDTM320M1110}, + {"America/Nome", TimezoneValue::AKST9AKDTM320M1110}, + {"America/Noronha", TimezoneValue::_022}, + {"America/North_Dakota/Beulah", TimezoneValue::CST6CDTM320M1110}, + {"America/North_Dakota/Center", TimezoneValue::CST6CDTM320M1110}, + {"America/North_Dakota/New_Salem", TimezoneValue::CST6CDTM320M1110}, + {"America/Nuuk", TimezoneValue::_022_01M350__1M1050_0}, + {"America/Ojinaga", TimezoneValue::CST6CDTM320M1110}, + {"America/Panama", TimezoneValue::EST5}, + {"America/Pangnirtung", TimezoneValue::EST5EDTM320M1110}, + {"America/Paramaribo", TimezoneValue::_033}, + {"America/Phoenix", TimezoneValue::MST7}, + {"America/Port-au-Prince", TimezoneValue::EST5EDTM320M1110}, + {"America/Port_of_Spain", TimezoneValue::AST4}, + {"America/Porto_Velho", TimezoneValue::_044}, + {"America/Puerto_Rico", TimezoneValue::AST4}, + {"America/Punta_Arenas", TimezoneValue::_033}, + {"America/Rainy_River", TimezoneValue::CST6CDTM320M1110}, + {"America/Rankin_Inlet", TimezoneValue::CST6CDTM320M1110}, + {"America/Recife", TimezoneValue::_033}, + {"America/Regina", TimezoneValue::CST6}, + {"America/Resolute", TimezoneValue::CST6CDTM320M1110}, + {"America/Rio_Branco", TimezoneValue::_055}, + {"America/Santarem", TimezoneValue::_033}, + {"America/Santiago", TimezoneValue::_044_03M916_24M416_24}, + {"America/Santo_Domingo", TimezoneValue::AST4}, + {"America/Sao_Paulo", TimezoneValue::_033}, + {"America/Scoresbysund", TimezoneValue::_022_01M350__1M1050_0}, + {"America/Sitka", TimezoneValue::AKST9AKDTM320M1110}, + {"America/St_Barthelemy", TimezoneValue::AST4}, + {"America/St_Johns", TimezoneValue::NST330NDTM320M1110}, + {"America/St_Kitts", TimezoneValue::AST4}, + {"America/St_Lucia", TimezoneValue::AST4}, + {"America/St_Thomas", TimezoneValue::AST4}, + {"America/St_Vincent", TimezoneValue::AST4}, + {"America/Swift_Current", TimezoneValue::CST6}, + {"America/Tegucigalpa", TimezoneValue::CST6}, + {"America/Thule", TimezoneValue::AST4ADTM320M1110}, + {"America/Thunder_Bay", TimezoneValue::EST5EDTM320M1110}, + {"America/Tijuana", TimezoneValue::PST8PDTM320M1110}, + {"America/Toronto", TimezoneValue::EST5EDTM320M1110}, + {"America/Tortola", TimezoneValue::AST4}, + {"America/Vancouver", TimezoneValue::PST8PDTM320M1110}, + {"America/Whitehorse", TimezoneValue::MST7}, + {"America/Winnipeg", TimezoneValue::CST6CDTM320M1110}, + {"America/Yakutat", TimezoneValue::AKST9AKDTM320M1110}, + {"America/Yellowknife", TimezoneValue::MST7MDTM320M1110}, + {"Antarctica/Casey", TimezoneValue::plus08_8}, + {"Antarctica/Davis", TimezoneValue::plus07_7}, + {"Antarctica/DumontDUrville", TimezoneValue::plus10_10}, + {"Antarctica/Macquarie", TimezoneValue::AEST_10AEDTM1010M410_3}, + {"Antarctica/Mawson", TimezoneValue::plus05_5}, + {"Antarctica/McMurdo", TimezoneValue::NZST_12NZDTM950M410_3}, + {"Antarctica/Palmer", TimezoneValue::_033}, + {"Antarctica/Rothera", TimezoneValue::_033}, + {"Antarctica/Syowa", TimezoneValue::plus03_3}, + {"Antarctica/Troll", TimezoneValue::plus000plus02_2M350_1M1050_3}, + {"Antarctica/Vostok", TimezoneValue::plus05_5}, + {"Arctic/Longyearbyen", TimezoneValue::CET_1CESTM350M1050_3}, + {"Asia/Aden", TimezoneValue::plus03_3}, + {"Asia/Almaty", TimezoneValue::plus05_5}, + {"Asia/Amman", TimezoneValue::plus03_3}, + {"Asia/Anadyr", TimezoneValue::plus12_12}, + {"Asia/Aqtau", TimezoneValue::plus05_5}, + {"Asia/Aqtobe", TimezoneValue::plus05_5}, + {"Asia/Ashgabat", TimezoneValue::plus05_5}, + {"Asia/Atyrau", TimezoneValue::plus05_5}, + {"Asia/Baghdad", TimezoneValue::plus03_3}, + {"Asia/Bahrain", TimezoneValue::plus03_3}, + {"Asia/Baku", TimezoneValue::plus04_4}, + {"Asia/Bangkok", TimezoneValue::plus07_7}, + {"Asia/Barnaul", TimezoneValue::plus07_7}, + {"Asia/Beirut", TimezoneValue::EET_2EESTM350_0M1050_0}, + {"Asia/Bishkek", TimezoneValue::plus06_6}, + {"Asia/Brunei", TimezoneValue::plus08_8}, + {"Asia/Chita", TimezoneValue::plus09_9}, + {"Asia/Choibalsan", TimezoneValue::plus08_8}, + {"Asia/Colombo", TimezoneValue::plus0530_530}, + {"Asia/Damascus", TimezoneValue::plus03_3}, + {"Asia/Dhaka", TimezoneValue::plus06_6}, + {"Asia/Dili", TimezoneValue::plus09_9}, + {"Asia/Dubai", TimezoneValue::plus04_4}, + {"Asia/Dushanbe", TimezoneValue::plus05_5}, + {"Asia/Famagusta", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Asia/Gaza", TimezoneValue::EET_2EESTM344_50M1044_50}, + {"Asia/Hebron", TimezoneValue::EET_2EESTM344_50M1044_50}, + {"Asia/Ho_Chi_Minh", TimezoneValue::plus07_7}, + {"Asia/Hong_Kong", TimezoneValue::HKT_8}, + {"Asia/Hovd", TimezoneValue::plus07_7}, + {"Asia/Irkutsk", TimezoneValue::plus08_8}, + {"Asia/Jakarta", TimezoneValue::WIB_7}, + {"Asia/Jayapura", TimezoneValue::WIT_9}, + {"Asia/Jerusalem", TimezoneValue::IST_2IDTM344_26M1050}, + {"Asia/Kabul", TimezoneValue::plus0430_430}, + {"Asia/Kamchatka", TimezoneValue::plus12_12}, + {"Asia/Karachi", TimezoneValue::PKT_5}, + {"Asia/Kathmandu", TimezoneValue::plus0545_545}, + {"Asia/Khandyga", TimezoneValue::plus09_9}, + {"Asia/Kolkata", TimezoneValue::IST_530}, + {"Asia/Krasnoyarsk", TimezoneValue::plus07_7}, + {"Asia/Kuala_Lumpur", TimezoneValue::plus08_8}, + {"Asia/Kuching", TimezoneValue::plus08_8}, + {"Asia/Kuwait", TimezoneValue::plus03_3}, + {"Asia/Macau", TimezoneValue::CST_8}, + {"Asia/Magadan", TimezoneValue::plus11_11}, + {"Asia/Makassar", TimezoneValue::WITA_8}, + {"Asia/Manila", TimezoneValue::PST_8}, + {"Asia/Muscat", TimezoneValue::plus04_4}, + {"Asia/Nicosia", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Asia/Novokuznetsk", TimezoneValue::plus07_7}, + {"Asia/Novosibirsk", TimezoneValue::plus07_7}, + {"Asia/Omsk", TimezoneValue::plus06_6}, + {"Asia/Oral", TimezoneValue::plus05_5}, + {"Asia/Phnom_Penh", TimezoneValue::plus07_7}, + {"Asia/Pontianak", TimezoneValue::WIB_7}, + {"Asia/Pyongyang", TimezoneValue::KST_9}, + {"Asia/Qatar", TimezoneValue::plus03_3}, + {"Asia/Qyzylorda", TimezoneValue::plus05_5}, + {"Asia/Riyadh", TimezoneValue::plus03_3}, + {"Asia/Sakhalin", TimezoneValue::plus11_11}, + {"Asia/Samarkand", TimezoneValue::plus05_5}, + {"Asia/Seoul", TimezoneValue::KST_9}, + {"Asia/Shanghai", TimezoneValue::CST_8}, + {"Asia/Singapore", TimezoneValue::plus08_8}, + {"Asia/Srednekolymsk", TimezoneValue::plus11_11}, + {"Asia/Taipei", TimezoneValue::CST_8}, + {"Asia/Tashkent", TimezoneValue::plus05_5}, + {"Asia/Tbilisi", TimezoneValue::plus04_4}, + {"Asia/Tehran", TimezoneValue::plus0330_330}, + {"Asia/Thimphu", TimezoneValue::plus06_6}, + {"Asia/Tokyo", TimezoneValue::JST_9}, + {"Asia/Tomsk", TimezoneValue::plus07_7}, + {"Asia/Ulaanbaatar", TimezoneValue::plus08_8}, + {"Asia/Urumqi", TimezoneValue::plus06_6}, + {"Asia/Ust-Nera", TimezoneValue::plus10_10}, + {"Asia/Vientiane", TimezoneValue::plus07_7}, + {"Asia/Vladivostok", TimezoneValue::plus10_10}, + {"Asia/Yakutsk", TimezoneValue::plus09_9}, + {"Asia/Yangon", TimezoneValue::plus0630_630}, + {"Asia/Yekaterinburg", TimezoneValue::plus05_5}, + {"Asia/Yerevan", TimezoneValue::plus04_4}, + {"Atlantic/Azores", TimezoneValue::_011plus00M350_0M1050_1}, + {"Atlantic/Bermuda", TimezoneValue::AST4ADTM320M1110}, + {"Atlantic/Canary", TimezoneValue::WET0WESTM350_1M1050}, + {"Atlantic/Cape_Verde", TimezoneValue::_011}, + {"Atlantic/Faroe", TimezoneValue::WET0WESTM350_1M1050}, + {"Atlantic/Madeira", TimezoneValue::WET0WESTM350_1M1050}, + {"Atlantic/Reykjavik", TimezoneValue::GMT0}, + {"Atlantic/South_Georgia", TimezoneValue::_022}, + {"Atlantic/St_Helena", TimezoneValue::GMT0}, + {"Atlantic/Stanley", TimezoneValue::_033}, + {"Australia/Adelaide", TimezoneValue::ACST_930ACDTM1010M410_3}, + {"Australia/Brisbane", TimezoneValue::AEST_10}, + {"Australia/Broken_Hill", TimezoneValue::ACST_930ACDTM1010M410_3}, + {"Australia/Currie", TimezoneValue::AEST_10AEDTM1010M410_3}, + {"Australia/Darwin", TimezoneValue::ACST_930}, + {"Australia/Eucla", TimezoneValue::plus0845_845}, + {"Australia/Hobart", TimezoneValue::AEST_10AEDTM1010M410_3}, + {"Australia/Lindeman", TimezoneValue::AEST_10}, + {"Australia/Lord_Howe", TimezoneValue::plus1030_1030plus11_11M1010M410}, + {"Australia/Melbourne", TimezoneValue::AEST_10AEDTM1010M410_3}, + {"Australia/Perth", TimezoneValue::AWST_8}, + {"Australia/Sydney", TimezoneValue::AEST_10AEDTM1010M410_3}, + {"Etc/GMT", TimezoneValue::GMT0}, + {"Etc/GMT+0", TimezoneValue::GMT0}, + {"Etc/GMT+1", TimezoneValue::_011}, + {"Etc/GMT+10", TimezoneValue::_1010}, + {"Etc/GMT+11", TimezoneValue::_1111}, + {"Etc/GMT+12", TimezoneValue::_1212}, + {"Etc/GMT+2", TimezoneValue::_022}, + {"Etc/GMT+3", TimezoneValue::_033}, + {"Etc/GMT+4", TimezoneValue::_044}, + {"Etc/GMT+5", TimezoneValue::_055}, + {"Etc/GMT+6", TimezoneValue::_066}, + {"Etc/GMT+7", TimezoneValue::_077}, + {"Etc/GMT+8", TimezoneValue::_088}, + {"Etc/GMT+9", TimezoneValue::_099}, + {"Etc/GMT-0", TimezoneValue::GMT0}, + {"Etc/GMT-1", TimezoneValue::plus01_1}, + {"Etc/GMT-10", TimezoneValue::plus10_10}, + {"Etc/GMT-11", TimezoneValue::plus11_11}, + {"Etc/GMT-12", TimezoneValue::plus12_12}, + {"Etc/GMT-13", TimezoneValue::plus13_13}, + {"Etc/GMT-14", TimezoneValue::plus14_14}, + {"Etc/GMT-2", TimezoneValue::plus02_2}, + {"Etc/GMT-3", TimezoneValue::plus03_3}, + {"Etc/GMT-4", TimezoneValue::plus04_4}, + {"Etc/GMT-5", TimezoneValue::plus05_5}, + {"Etc/GMT-6", TimezoneValue::plus06_6}, + {"Etc/GMT-7", TimezoneValue::plus07_7}, + {"Etc/GMT-8", TimezoneValue::plus08_8}, + {"Etc/GMT-9", TimezoneValue::plus09_9}, + {"Etc/GMT0", TimezoneValue::GMT0}, + {"Etc/Greenwich", TimezoneValue::GMT0}, + {"Etc/UCT", TimezoneValue::UTC0}, + {"Etc/UTC", TimezoneValue::UTC0}, + {"Etc/Universal", TimezoneValue::UTC0}, + {"Etc/Zulu", TimezoneValue::UTC0}, + {"Europe/Amsterdam", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Andorra", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Astrakhan", TimezoneValue::plus04_4}, + {"Europe/Athens", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Belgrade", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Berlin", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Bratislava", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Brussels", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Bucharest", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Budapest", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Busingen", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Chisinau", TimezoneValue::EET_2EESTM350M1050_3}, + {"Europe/Copenhagen", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Dublin", TimezoneValue::IST_1GMT0M1050M350_1}, + {"Europe/Gibraltar", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Guernsey", TimezoneValue::GMT0BSTM350_1M1050}, + {"Europe/Helsinki", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Isle_of_Man", TimezoneValue::GMT0BSTM350_1M1050}, + {"Europe/Istanbul", TimezoneValue::plus03_3}, + {"Europe/Jersey", TimezoneValue::GMT0BSTM350_1M1050}, + {"Europe/Kaliningrad", TimezoneValue::EET_2}, + {"Europe/Kiev", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Kirov", TimezoneValue::MSK_3}, + {"Europe/Lisbon", TimezoneValue::WET0WESTM350_1M1050}, + {"Europe/Ljubljana", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/London", TimezoneValue::GMT0BSTM350_1M1050}, + {"Europe/Luxembourg", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Madrid", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Malta", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Mariehamn", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Minsk", TimezoneValue::plus03_3}, + {"Europe/Monaco", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Moscow", TimezoneValue::MSK_3}, + {"Europe/Oslo", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Paris", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Podgorica", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Prague", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Riga", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Rome", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Samara", TimezoneValue::plus04_4}, + {"Europe/San_Marino", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Sarajevo", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Saratov", TimezoneValue::plus04_4}, + {"Europe/Simferopol", TimezoneValue::MSK_3}, + {"Europe/Skopje", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Sofia", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Stockholm", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Tallinn", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Tirane", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Ulyanovsk", TimezoneValue::plus04_4}, + {"Europe/Uzhgorod", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Vaduz", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Vatican", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Vienna", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Vilnius", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Volgograd", TimezoneValue::MSK_3}, + {"Europe/Warsaw", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Zagreb", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Zaporozhye", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Zurich", TimezoneValue::CET_1CESTM350M1050_3}, + {"Indian/Antananarivo", TimezoneValue::EAT_3}, + {"Indian/Chagos", TimezoneValue::plus06_6}, + {"Indian/Christmas", TimezoneValue::plus07_7}, + {"Indian/Cocos", TimezoneValue::plus0630_630}, + {"Indian/Comoro", TimezoneValue::EAT_3}, + {"Indian/Kerguelen", TimezoneValue::plus05_5}, + {"Indian/Mahe", TimezoneValue::plus04_4}, + {"Indian/Maldives", TimezoneValue::plus05_5}, + {"Indian/Mauritius", TimezoneValue::plus04_4}, + {"Indian/Mayotte", TimezoneValue::EAT_3}, + {"Indian/Reunion", TimezoneValue::plus04_4}, + {"Pacific/Apia", TimezoneValue::plus13_13}, + {"Pacific/Auckland", TimezoneValue::NZST_12NZDTM950M410_3}, + {"Pacific/Bougainville", TimezoneValue::plus11_11}, + {"Pacific/Chatham", TimezoneValue::plus1245_1245plus1345M950_245M410_345}, + {"Pacific/Chuuk", TimezoneValue::plus10_10}, + {"Pacific/Easter", TimezoneValue::_066_05M916_22M416_22}, + {"Pacific/Efate", TimezoneValue::plus11_11}, + {"Pacific/Enderbury", TimezoneValue::plus13_13}, + {"Pacific/Fakaofo", TimezoneValue::plus13_13}, + {"Pacific/Fiji", TimezoneValue::plus12_12}, + {"Pacific/Funafuti", TimezoneValue::plus12_12}, + {"Pacific/Galapagos", TimezoneValue::_066}, + {"Pacific/Gambier", TimezoneValue::_099}, + {"Pacific/Guadalcanal", TimezoneValue::plus11_11}, + {"Pacific/Guam", TimezoneValue::ChST_10}, + {"Pacific/Honolulu", TimezoneValue::HST10}, + {"Pacific/Kiritimati", TimezoneValue::plus14_14}, + {"Pacific/Kosrae", TimezoneValue::plus11_11}, + {"Pacific/Kwajalein", TimezoneValue::plus12_12}, + {"Pacific/Majuro", TimezoneValue::plus12_12}, + {"Pacific/Marquesas", TimezoneValue::_0930930}, + {"Pacific/Midway", TimezoneValue::SST11}, + {"Pacific/Nauru", TimezoneValue::plus12_12}, + {"Pacific/Niue", TimezoneValue::_1111}, + {"Pacific/Norfolk", TimezoneValue::plus11_11plus12M1010M410_3}, + {"Pacific/Noumea", TimezoneValue::plus11_11}, + {"Pacific/Pago_Pago", TimezoneValue::SST11}, + {"Pacific/Palau", TimezoneValue::plus09_9}, + {"Pacific/Pitcairn", TimezoneValue::_088}, + {"Pacific/Pohnpei", TimezoneValue::plus11_11}, + {"Pacific/Port_Moresby", TimezoneValue::plus10_10}, + {"Pacific/Rarotonga", TimezoneValue::_1010}, + {"Pacific/Saipan", TimezoneValue::ChST_10}, + {"Pacific/Tahiti", TimezoneValue::_1010}, + {"Pacific/Tarawa", TimezoneValue::plus12_12}, + {"Pacific/Tongatapu", TimezoneValue::plus13_13}, + {"Pacific/Wake", TimezoneValue::plus12_12}, + {"Pacific/Wallis", TimezoneValue::plus12_12}, +}}; + +// Helper function to find timezone value +inline TimezoneValue find_timezone_value(std::string_view key) { + for (const auto& entry : TIMEZONE_DATA) { + if (entry.first == key) { + return entry.second; + } + } + return TimezoneValue::plus000plus02_2M350_1M1050_3; // Default fallback +} + +// Overload for String +inline TimezoneValue find_timezone_value(const String& key) { + return find_timezone_value(std::string_view(key.c_str())); +} + +// Overload for std::string +inline TimezoneValue find_timezone_value(const std::string& key) { + return find_timezone_value(std::string_view(key)); +} + +// Helper function to convert TimezoneValue to string representation +inline String get_timezone_string(TimezoneValue value) { + for (const auto& entry : TIMEZONE_DATA) { + if (entry.second == value) { + return String(entry.first.data(), entry.first.length()); + } + } + return String("GMT0"); // Default fallback +} + +// Overload for std::string +inline std::string get_timezone_string_std(TimezoneValue value) { + for (const auto& entry : TIMEZONE_DATA) { + if (entry.second == value) { + return std::string(entry.first.data(), entry.first.length()); + } + } + return "GMT0"; // Default fallback +} + +// Helper function to convert TimezoneValue enum to its string representation +inline String get_timezone_value_string(TimezoneValue value) { + switch (value) { + case TimezoneValue::plus000plus02_2M350_1M1050_3: return String("+00:00+02:00,M3.5.0/1,M10.5.0/3"); + case TimezoneValue::plus01_1: return String("+01:00"); + case TimezoneValue::plus02_2: return String("+02:00"); + case TimezoneValue::plus0330_330: return String("+03:30"); + case TimezoneValue::plus03_3: return String("+03:00"); + case TimezoneValue::plus0430_430: return String("+04:30"); + case TimezoneValue::plus04_4: return String("+04:00"); + case TimezoneValue::plus0530_530: return String("+05:30"); + case TimezoneValue::plus0545_545: return String("+05:45"); + case TimezoneValue::plus05_5: return String("+05:00"); + case TimezoneValue::plus0630_630: return String("+06:30"); + case TimezoneValue::plus06_6: return String("+06:00"); + case TimezoneValue::plus07_7: return String("+07:00"); + case TimezoneValue::plus0845_845: return String("+08:45"); + case TimezoneValue::plus08_8: return String("+08:00"); + case TimezoneValue::plus09_9: return String("+09:00"); + case TimezoneValue::plus1030_1030plus11_11M1010M410: return String("+10:30+11:00,M10.1.0,M4.1.0"); + case TimezoneValue::plus10_10: return String("+10:00"); + case TimezoneValue::plus11_11: return String("+11:00"); + case TimezoneValue::plus11_11plus12M1010M410_3: return String("+11:00+12:00,M10.1.0,M4.1.0/3"); + case TimezoneValue::plus1245_1245plus1345M950_245M410_345: return String("+12:45+13:45,M9.5.0/2:45,M4.1.0/3:45"); + case TimezoneValue::plus12_12: return String("+12:00"); + case TimezoneValue::plus13_13: return String("+13:00"); + case TimezoneValue::plus14_14: return String("+14:00"); + case TimezoneValue::_011: return String("-01:00"); + case TimezoneValue::_011plus00M350_0M1050_1: return String("-01:00+00:00,M3.5.0/0,M10.5.0/1"); + case TimezoneValue::_022: return String("-02:00"); + case TimezoneValue::_022_01M350__1M1050_0: return String("-02:00-01:00,M3.5.0/-1,M10.5.0/0"); + case TimezoneValue::_033: return String("-03:00"); + case TimezoneValue::_033_02M320M1110: return String("-03:00-02:00,M3.2.0,M11.1.0"); + case TimezoneValue::_044: return String("-04:00"); + case TimezoneValue::_044_03M1010_0M340_0: return String("-04:00-03:00,M10.1.0/0,M3.4.0/0"); + case TimezoneValue::_044_03M916_24M416_24: return String("-04:00-03:00,M9.1.6/24,M4.1.6/24"); + case TimezoneValue::_055: return String("-05:00"); + case TimezoneValue::_066: return String("-06:00"); + case TimezoneValue::_066_05M916_22M416_22: return String("-06:00-05:00,M9.1.6/22,M4.1.6/22"); + case TimezoneValue::_077: return String("-07:00"); + case TimezoneValue::_088: return String("-08:00"); + case TimezoneValue::_0930930: return String("-09:30"); + case TimezoneValue::_099: return String("-09:00"); + case TimezoneValue::_1010: return String("-10:00"); + case TimezoneValue::_1111: return String("-11:00"); + case TimezoneValue::_1212: return String("-12:00"); + case TimezoneValue::ACST_930: return String("ACST-9:30"); + case TimezoneValue::ACST_930ACDTM1010M410_3: return String("ACST-9:30ACDT,M10.1.0,M4.1.0/3"); + case TimezoneValue::AEST_10: return String("AEST-10"); + case TimezoneValue::AEST_10AEDTM1010M410_3: return String("AEST-10AEDT,M10.1.0,M4.1.0/3"); + case TimezoneValue::AKST9AKDTM320M1110: return String("AKST9AKDT,M3.2.0,M11.1.0"); + case TimezoneValue::AST4: return String("AST4"); + case TimezoneValue::AST4ADTM320M1110: return String("AST4ADT,M3.2.0,M11.1.0"); + case TimezoneValue::AWST_8: return String("AWST-8"); + case TimezoneValue::CAT_2: return String("CAT-2"); + case TimezoneValue::CET_1: return String("CET-1"); + case TimezoneValue::CET_1CESTM350M1050_3: return String("CET-1CEST,M3.5.0,M10.5.0/3"); + case TimezoneValue::CST_8: return String("CST-8"); + case TimezoneValue::CST5CDTM320_0M1110_1: return String("CST5CDT,M3.2.0/0,M11.1.0/1"); + case TimezoneValue::CST6: return String("CST6"); + case TimezoneValue::CST6CDTM320M1110: return String("CST6CDT,M3.2.0,M11.1.0"); + case TimezoneValue::ChST_10: return String("ChST-10"); + case TimezoneValue::EAT_3: return String("EAT-3"); + case TimezoneValue::EET_2: return String("EET-2"); + case TimezoneValue::EET_2EESTM344_50M1044_50: return String("EET-2EEST,M3.4.4/50,M10.4.4/50"); + case TimezoneValue::EET_2EESTM350M1050_3: return String("EET-2EEST,M3.5.0,M10.5.0/3"); + case TimezoneValue::EET_2EESTM350_0M1050_0: return String("EET-2EEST,M3.5.0/0,M10.5.0/0"); + case TimezoneValue::EET_2EESTM350_3M1050_4: return String("EET-2EEST,M3.5.0/3,M10.5.0/4"); + case TimezoneValue::EET_2EESTM455_0M1054_24: return String("EET-2EEST,M4.5.5/0,M10.5.4/24"); + case TimezoneValue::EST5: return String("EST5"); + case TimezoneValue::EST5EDTM320M1110: return String("EST5EDT,M3.2.0,M11.1.0"); + case TimezoneValue::GMT0: return String("GMT0"); + case TimezoneValue::GMT0BSTM350_1M1050: return String("GMT0BST,M3.5.0/1,M10.5.0"); + case TimezoneValue::HKT_8: return String("HKT-8"); + case TimezoneValue::HST10: return String("HST10"); + case TimezoneValue::HST10HDTM320M1110: return String("HST10HDT,M3.2.0,M11.1.0"); + case TimezoneValue::IST_1GMT0M1050M350_1: return String("IST-1GMT0,M10.5.0,M3.5.0/1"); + case TimezoneValue::IST_2IDTM344_26M1050: return String("IST-2IDT,M3.4.4/26,M10.5.0"); + case TimezoneValue::IST_530: return String("IST-5:30"); + case TimezoneValue::JST_9: return String("JST-9"); + case TimezoneValue::KST_9: return String("KST-9"); + case TimezoneValue::MSK_3: return String("MSK-3"); + case TimezoneValue::MST7: return String("MST7"); + case TimezoneValue::MST7MDTM320M1110: return String("MST7MDT,M3.2.0,M11.1.0"); + case TimezoneValue::NST330NDTM320M1110: return String("NST3:30NDT,M3.2.0,M11.1.0"); + case TimezoneValue::NZST_12NZDTM950M410_3: return String("NZST-12NZDT,M9.5.0,M4.1.0/3"); + case TimezoneValue::PKT_5: return String("PKT-5"); + case TimezoneValue::PST_8: return String("PST-8"); + case TimezoneValue::PST8PDTM320M1110: return String("PST8PDT,M3.2.0,M11.1.0"); + case TimezoneValue::SAST_2: return String("SAST-2"); + case TimezoneValue::SST11: return String("SST11"); + case TimezoneValue::UTC0: return String("UTC0"); + case TimezoneValue::WAT_1: return String("WAT-1"); + case TimezoneValue::WET0WESTM350_1M1050: return String("WET0WEST,M3.5.0/1,M10.5.0"); + case TimezoneValue::WIB_7: return String("WIB-7"); + case TimezoneValue::WIT_9: return String("WIT-9"); + case TimezoneValue::WITA_8: return String("WITA-8"); + default: return String("GMT0"); // Default fallback + } +} + +// Overload for std::string +inline std::string get_timezone_value_string_std(TimezoneValue value) { + return std::string(get_timezone_value_string(value).c_str()); +} + +} // namespace timezone_data diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 16cb80a..908a5a6 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -259,7 +259,7 @@ void setupWifi() void syncTime() { - configTime(preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS), 0, + configTime(0, 0, NTP_SERVER); struct tm timeinfo; @@ -267,15 +267,24 @@ void syncTime() { auto& ledHandler = getLedHandler(); ledHandler.queueEffect(LED_EFFECT_CONFIGURING); - configTime(preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS), 0, + configTime(0, 0, NTP_SERVER); delay(500); Serial.println(F("Retry set time")); } + setTimezone(get_timezone_value_string(timezone_data::find_timezone_value(preferences.getString("tzString", DEFAULT_TZ_STRING)))); + lastTimeSync = esp_timer_get_time() / 1000000; } +void setTimezone(String timezone) { + Serial.printf(" Setting Timezone to %s\n",timezone.c_str()); + setenv("TZ",timezone.c_str(),1); // Now adjust the TZ. Clock settings are adjusted to show the new local time + tzset(); +} + + void setupPreferences() { preferences.begin("btclock", false); diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 95c877d..c8d5336 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -33,7 +33,7 @@ #include "shared.hpp" #include "defaults.hpp" - +#include "timezone_data.hpp" #define NTP_SERVER "pool.ntp.org" #define DEFAULT_TIME_OFFSET_SECONDS 3600 #ifndef MCP_DEV_ADDR @@ -43,6 +43,7 @@ void setup(); void syncTime(); +void setTimezone(String timezone); uint getLastTimeSync(); void setupPreferences(); void setupWebsocketClients(void *pvParameters); diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 55da6e3..00d8bc0 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -24,6 +24,7 @@ #define DEFAULT_V2_SOURCE_CURRENCY CURRENCY_USD #define DEFAULT_TIME_OFFSET_SECONDS 3600 +#define DEFAULT_TZ_STRING "Europe/Amsterdam" #define DEFAULT_HOSTNAME_PREFIX "btclock" #define DEFAULT_MEMPOOL_INSTANCE "mempool.space" diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index cfb5d62..8313b76 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -5,7 +5,7 @@ static const char* JSON_CONTENT = "application/json"; static const char *const PROGMEM strSettings[] = { - "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName", "localPoolEndpoint"}; + "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName", "localPoolEndpoint", "tzString"}; static const char *const PROGMEM uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout"}; @@ -692,8 +692,9 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["fullRefreshMin"] = preferences.getUInt("fullRefreshMin", DEFAULT_MINUTES_FULL_REFRESH); root["wpTimeout"] = preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT); - root["tzOffset"] = preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS) / 60; - + //root["tzOffset"] = preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS) / 60; + root["tzString"] = preferences.getString("tzString", DEFAULT_TZ_STRING); + // Add data source settings root["dataSource"] = preferences.getUChar("dataSource", DEFAULT_DATA_SOURCE); From 3e54343da8b995aae663ce972631561b6a950e7b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 5 Apr 2025 22:40:52 +0200 Subject: [PATCH 27/30] chore: Update WebUI --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 0116cd6..0e278d1 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 0116cd68cdfdf383823f74e0f9665a1700cf0500 +Subproject commit 0e278d1be4160ab382213e80ec0716f8aad14a65 From d648551835d3db3ad80921ee652f4c518aae53dd Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 2 May 2025 22:28:46 +0200 Subject: [PATCH 28/30] fix: Fix LED status object --- src/lib/webserver.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 8313b76..6ee62f9 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -298,7 +298,13 @@ JsonDocument getLedStatusObject() uint blue = pixColor & 0xFF; char hexColor[8]; snprintf(hexColor, sizeof(hexColor), "#%02X%02X%02X", red, green, blue); - colors.add(hexColor); + // colors.add(hexColor); + + JsonObject object = colors.add(); + object["red"] = red; + object["green"] = green; + object["blue"] = blue; + object["hex"] = hexColor; } return root; From 064fe8fe6ce83b04bfdc28529bf6685ffda095ca Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 3 May 2025 00:03:01 +0200 Subject: [PATCH 29/30] chore: Update WebUI and dependencies --- data | 2 +- platformio.ini | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data b/data index 0e278d1..8389ed8 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 0e278d1be4160ab382213e80ec0716f8aad14a65 +Subproject commit 8389ed8e36a9a1a7a39ca31f53324a0949aedb1d diff --git a/platformio.ini b/platformio.ini index 140946d..98192fe 100644 --- a/platformio.ini +++ b/platformio.ini @@ -37,8 +37,8 @@ build_unflags = -fno-exceptions lib_deps = https://github.com/joltwallet/esp_littlefs.git#v1.16.4 - bblanchon/ArduinoJson@^7.3.1 - esp32async/ESPAsyncWebServer @ 3.7.4 + bblanchon/ArduinoJson@^7.4.1 + esp32async/ESPAsyncWebServer @ 3.7.7 robtillaart/MCP23017@^0.9.1 adafruit/Adafruit NeoPixel@^1.12.5 https://github.com/dsbaars/universal_pin#feature/mcp23017_rt From 2ac2a62c617ca0469cba1027c70c0ddd9ac3ae76 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 3 May 2025 00:03:19 +0200 Subject: [PATCH 30/30] feat: change 3rd party price source to kraken --- src/lib/price_notify.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index 6ac2f3b..c851e85 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -1,6 +1,6 @@ #include "price_notify.hpp" -const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; +const char *wsServerPrice = "wss://ws.kraken.com/v2"; WebSocketsClient webSocket; uint currentPrice = 90000; @@ -14,7 +14,7 @@ void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length); void setupPriceNotify() { - webSocket.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin"); + webSocket.beginSSL("ws.kraken.com", 443, "/v2"); webSocket.onEvent([](WStype_t type, uint8_t * payload, size_t length) { onWebsocketPriceEvent(type, payload, length); }); @@ -32,7 +32,14 @@ void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) { case WStype_CONNECTED: { Serial.println("Connected to " + String(wsServerPrice)); - priceNotifyInit = true; + + JsonDocument doc; + doc["method"] = "subscribe"; + JsonObject params = doc["params"].to(); + params["channel"] = "ticker"; + params["symbol"][0] = "BTC/USD"; + + webSocket.sendTXT(doc.as().c_str()); break; } case WStype_TEXT: @@ -40,13 +47,15 @@ void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) { JsonDocument doc; deserializeJson(doc, (char *)payload); - if (doc["bitcoin"].is()) + if (doc["data"][0].is()) { - if (currentPrice != doc["bitcoin"].as()) + float price = doc["data"][0]["last"].as(); + uint roundedPrice = round(price); + if (currentPrice != roundedPrice) { - processNewPrice(doc["bitcoin"].as(), CURRENCY_USD); + processNewPrice(roundedPrice, CURRENCY_USD); } - } + } break; } case WStype_BIN: