Refactor EPD code to EPDManager class

This commit is contained in:
Djuri 2025-01-05 22:11:53 +01:00
parent d023643090
commit a6a8b5a071
Signed by: djuri
GPG key ID: 61B9B2DDE5AA3AC1
11 changed files with 532 additions and 638 deletions

View file

@ -181,20 +181,19 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data)
}
void processNewBlock(uint32_t newBlockHeight) {
if (newBlockHeight < currentBlockHeight)
if (currentBlockHeight == newBlockHeight)
{
return;
}
currentBlockHeight = newBlockHeight;
// Serial.printf("New block found: %d\r\n", block["height"].as<uint>());
preferences.putUInt("blockHeight", currentBlockHeight);
lastBlockUpdate = esp_timer_get_time() / 1000000;
if (workQueue != nullptr)
{
WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0};
xQueueSend(workQueue, &blockUpdate, portMAX_DELAY);
// xTaskNotifyGive(blockUpdateTaskHandle);
}
if (ScreenHandler::getCurrentScreen() != SCREEN_BLOCK_HEIGHT &&
preferences.getBool("stealFocus", DEFAULT_STEAL_FOCUS))
@ -202,7 +201,6 @@ void processNewBlock(uint32_t newBlockHeight) {
uint64_t timerPeriod = 0;
if (isTimerActive())
{
// store timer periode before making inactive to prevent artifacts
timerPeriod = getTimerSeconds();
esp_timer_stop(screenRotateTimer);
}
@ -221,7 +219,6 @@ void processNewBlock(uint32_t newBlockHeight) {
getLedHandler().queueEffect(LED_FLASH_BLOCK_NOTIFY);
}
}
}
void processNewBlockFee(uint16_t newBlockFee) {
if (blockMedianFee == newBlockFee)

View file

@ -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<String, NUM_SCREENS> 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);

View file

@ -1,213 +1,146 @@
#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<Native_Pin, NUM_SCREENS> 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<Native_Pin, NUM_SCREENS> 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<MCP23X17_Pin, NUM_SCREENS> 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<MCP23X17_Pin, NUM_SCREENS> 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<MCP23X17_Pin, NUM_SCREENS> 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<MCP23X17_Pin, NUM_SCREENS> 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<Native_Pin, NUM_SCREENS> 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<Native_Pin, NUM_SCREENS> 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<MCP23X17_Pin, NUM_SCREENS> 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<EPD_CLASS, EPD_CLASS::HEIGHT> 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]),
EPDManager& EPDManager::getInstance() {
static EPDManager instance;
return instance;
}
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[7], &EPD_DC, &EPD_RESET_MPD[7], &EPD_BUSY[7]),
#endif
};
std::array<String, NUM_SCREENS> currentEpdContent;
std::array<String, NUM_SCREENS> 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
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
#define EPD_TASK_STACK_SIZE 2048
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
#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() {
// Clean up tasks
for (auto& task : tasks) {
if (task != nullptr) {
vTaskDelete(task);
}
}
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;
// Clean up queue
if (updateQueue != nullptr) {
vQueueDelete(updateQueue);
}
FONT_SATSYMBOL = FontLoader::loadCompressedFont(Satoshi_Symbol90pt7b_Properties);
// Clean up fonts
delete antonioFonts.big;
delete antonioFonts.medium;
delete antonioFonts.small;
delete oswaldFonts.big;
delete oswaldFonts.medium;
delete oswaldFonts.small;
}
void setupDisplays() {
void EPDManager::initialize() {
// Load fonts based on preference
String fontName = preferences.getString("fontName", DEFAULT_FONT_NAME);
loadFonts(fontName);
// Initialize displays
std::lock_guard<std::mutex> lockMcp(mcpMutex);
for (uint i = 0; i < NUM_SCREENS; i++) {
displays[i].init(0, true, 30);
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, NULL, 11, NULL);
xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", EPD_TASK_STACK_SIZE * 2, nullptr, 11, nullptr);
// 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]);
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) {
setFgColor(GxEPD_BLACK);
setBgColor(GxEPD_WHITE);
epdContent.fill("");
setForegroundColor(GxEPD_BLACK);
setBackgroundColor(GxEPD_WHITE);
content.fill("");
} else {
// Initialize with custom text or default
String customText = preferences.getString("displayText", DEFAULT_BOOT_TEXT);
@ -218,238 +151,128 @@ void setupDisplays() {
newContent[i] = String(customText[i]);
}
epdContent = newContent;
content = newContent;
}
setEpdContent(epdContent);
setContent(content);
}
void setEpdContent(std::array<String, NUM_SCREENS> newEpdContent)
{
setEpdContent(newEpdContent, false);
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;
}
void setEpdContent(std::array<std::string, NUM_SCREENS> newEpdContent)
{
std::array<String, NUM_SCREENS> conv;
for (size_t i = 0; i < newEpdContent.size(); ++i)
{
conv[i] = String(newEpdContent[i].c_str());
fontSatsymbol = FontLoader::loadCompressedFont(Satoshi_Symbol90pt7b_Properties);
}
return setEpdContent(conv);
void EPDManager::forceFullRefresh() {
std::fill(lastFullRefresh.begin(), lastFullRefresh.end(), 0);
}
void setEpdContent(std::array<String, NUM_SCREENS> newEpdContent,
bool forceUpdate)
{
std::lock_guard<std::mutex> lock(epdUpdateMutex);
void EPDManager::setContent(const std::array<String, NUM_SCREENS>& newContent, bool forceUpdate) {
std::lock_guard<std::mutex> 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<char>(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<std::mutex> 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);
void EPDManager::setContent(const std::array<std::string, NUM_SCREENS>& newContent) {
std::array<String, NUM_SCREENS> conv;
for (size_t i = 0; i < newContent.size(); ++i) {
conv[i] = String(newContent[i].c_str());
}
setContent(conv);
}
xTaskNotifyGive(tasks[epdIndex]);
}
}
std::array<String, NUM_SCREENS> EPDManager::getCurrentContent() const {
return currentContent;
}
extern "C" void updateDisplay(void *pvParameters) noexcept
{
const int epdIndex = *(int *)pvParameters;
delete (int *)pvParameters;
for (;;)
{
// Wait for the task notification
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
std::lock_guard<std::mutex> lock(epdMutex[epdIndex]);
{
std::lock_guard<std::mutex> 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,46 +288,26 @@ 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];
int16_t dotDescent = dotGlyph->yOffset;
@ -512,9 +315,7 @@ void showChars(const uint dispNum, const String &chars, bool partial,
// 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,20 +326,47 @@ void showChars(const uint dispNum, const String &chars, bool partial,
}
}
int getBgColor() { return bgColor; }
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(bgColor);
displays[dispNum].setTextColor(fgColor);
int getFgColor() { return fgColor; }
uint iconIndex = 0;
uint width = 122;
uint height = 122;
void setBgColor(int color) { bgColor = color; }
void setFgColor(int color) { fgColor = color; }
std::array<String, NUM_SCREENS> getCurrentEpdContent()
{
return currentEpdContent;
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")) {
LogoData logo = MiningPoolStatsFetch::getInstance().getLogo();
if (logo.size == 0) {
Serial.println(F("No logo found"));
return false;
}
void renderText(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;
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, fgColor);
return true;
}
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());
@ -548,81 +376,20 @@ void renderText(const uint dispNum, const String &text, bool partial)
std::stringstream ss;
ss.str(text.c_str());
std::string line;
while (std::getline(ss, line, '\n'))
{
if (line.rfind("*", 0) == 0)
{
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
{
} 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());
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"))
{
LogoData logo = MiningPoolStatsFetch::getInstance().getLogo();
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());
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());
return true;
// displays[dispNum].drawInvertedBitmap(0,0, getOceanIcon(), 122, 250, getFgColor());
}
void renderQr(const uint dispNum, const String &text, bool partial)
{
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);
displays[dispNum].setRotation(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;
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);
if (count == BUSY_TIMEOUT_COUNT)
{
vTaskDelay(pdMS_TO_TICKS(100));
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;
}
else if (count > BUSY_TIMEOUT_COUNT + 5)
}
return maxDescent;
}
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<std::mutex> lock(instance.displayMutexes[epdIndex]);
{
log_e("Display %d busy timeout", i);
std::lock_guard<std::mutex> 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<std::mutex> 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]);
}
}
}

View file

@ -9,6 +9,8 @@
#include <mutex>
#include <native_pin.hpp>
#include <regex>
#include <array>
#include <memory>
#include "fonts/fonts.hpp"
#include "lib/config.hpp"
@ -32,39 +34,102 @@
#include "qrcodegen.h"
#endif
typedef struct {
struct UpdateDisplayTaskItem {
char dispNum;
} UpdateDisplayTaskItem;
};
struct FontFamily {
GFXfont* big;
GFXfont* medium;
GFXfont* small;
};
class EPDManager {
public:
static EPDManager& getInstance();
// Delete copy constructor and assignment operator
EPDManager(const EPDManager&) = delete;
EPDManager& operator=(const EPDManager&) = delete;
void initialize();
void forceFullRefresh();
void setupDisplays();
void loadFonts(const String& fontName);
void setContent(const std::array<String, NUM_SCREENS>& newContent, bool forceUpdate = false);
void setContent(const std::array<std::string, NUM_SCREENS>& newContent);
std::array<String, NUM_SCREENS> getCurrentContent() const;
void splitText(const uint dispNum, const String &top, const String &bottom,
bool partial);
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);
extern "C" void updateDisplay(void *pvParameters) noexcept;
void updateDisplayAlt(int epdIndex);
void prepareDisplayUpdateTask(void *pvParameters);
int getBgColor();
int getFgColor();
void setBgColor(int color);
void setFgColor(int color);
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);
void setEpdContent(std::array<String, NUM_SCREENS> newEpdContent,
bool forceUpdate);
void setEpdContent(std::array<String, NUM_SCREENS> newEpdContent);
void setEpdContent(std::array<std::string, NUM_SCREENS> newEpdContent);
std::array<String, NUM_SCREENS> getCurrentEpdContent();
int getBackgroundColor() const { return bgColor; }
int getForegroundColor() const { return fgColor; }
void setBackgroundColor(int color) { bgColor = color; }
void setForegroundColor(int color) { fgColor = color; }
void waitUntilNoneBusy();
private:
EPDManager(); // Private constructor for singleton
~EPDManager(); // Private destructor
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);
static void updateDisplayTask(void* pvParameters) noexcept;
static void prepareDisplayUpdateTask(void* pvParameters);
// Member variables
std::array<String, NUM_SCREENS> currentContent;
std::array<String, NUM_SCREENS> content;
std::array<uint32_t, NUM_SCREENS> lastFullRefresh;
std::array<TaskHandle_t, NUM_SCREENS> 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<std::mutex, NUM_SCREENS> displayMutexes;
// Pin configurations based on board version
#ifdef IS_BTCLOCK_REV_B
static Native_Pin EPD_DC;
static std::array<Native_Pin, NUM_SCREENS> EPD_CS;
static std::array<Native_Pin, NUM_SCREENS> EPD_BUSY;
static std::array<MCP23X17_Pin, NUM_SCREENS> EPD_RESET;
#elif defined(IS_BTCLOCK_V8)
static Native_Pin EPD_DC;
static std::array<MCP23X17_Pin, NUM_SCREENS> EPD_BUSY;
static std::array<MCP23X17_Pin, NUM_SCREENS> EPD_CS;
static std::array<MCP23X17_Pin, NUM_SCREENS> EPD_RESET;
#else
static Native_Pin EPD_DC;
static std::array<Native_Pin, NUM_SCREENS> EPD_CS;
static std::array<Native_Pin, NUM_SCREENS> EPD_BUSY;
static std::array<MCP23X17_Pin, NUM_SCREENS> EPD_RESET;
#endif
// Display array
std::array<GxEPD2_BW<EPD_CLASS, EPD_CLASS::HEIGHT>, 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
;
};

View file

@ -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))
{
@ -295,3 +295,19 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event)
timerPeriod * usPerSecond);
}
}
// 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<std::string, NUM_SCREENS> zapContent = parseZapNotify(amount, true);
// EPDManager::getInstance().setContent(zapContent);
// if (preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD)) {
// getLedHandler().queueEffect(LED_FLASH_BLOCK_NOTIFY);
// }
// }
// }
// }

View file

@ -57,10 +57,10 @@ void onOTAProgress(unsigned int progress, unsigned int total)
void onOTAStart()
{
forceFullRefresh();
EPDManager::getInstance().forceFullRefresh();
std::array<String, NUM_SCREENS> epdContent = {"U", "P", "D", "A",
"T", "E", "!"};
setEpdContent(epdContent);
EPDManager::getInstance().setContent(epdContent);
// Stop all timers
esp_timer_stop(screenRotateTimer);
esp_timer_stop(minuteTimer);

View file

@ -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<std::uint16_t>(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));
}

View file

@ -180,3 +180,4 @@ void HttpHelper::end(HTTPClient* http) {
delete http;
}
}

View file

@ -16,6 +16,8 @@
#include <mutex>
#include <utils.hpp>
#include <array>
#include <string>
#include "defaults.hpp"
@ -119,3 +121,4 @@ private:
static bool certBundleSet;
static WiFiClient insecureClient;
};

View file

@ -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;
}

View file

@ -309,7 +309,7 @@ void eventSourceUpdate() {
doc["leds"] = getLedStatusObject()["data"];
// Get current EPD content directly as array
std::array<String, NUM_SCREENS> epdContent = getCurrentEpdContent();
std::array<String, NUM_SCREENS> epdContent = EPDManager::getInstance().getCurrentContent();
// Add EPD content arrays
JsonArray data = doc["data"].to<JsonArray>();
@ -336,7 +336,7 @@ void onApiStatus(AsyncWebServerRequest *request)
JsonDocument root = getStatusObject();
// Get current EPD content directly as array
std::array<String, NUM_SCREENS> epdContent = getCurrentEpdContent();
std::array<String, NUM_SCREENS> epdContent = EPDManager::getInstance().getCurrentContent();
// Add EPD content arrays
JsonArray data = root["data"].to<JsonArray>();
@ -378,11 +378,9 @@ void onApiActionTimerRestart(AsyncWebServerRequest *request)
*/
void onApiFullRefresh(AsyncWebServerRequest *request)
{
forceFullRefresh();
std::array<String, NUM_SCREENS> newEpdContent = getCurrentEpdContent();
setEpdContent(newEpdContent, true);
EPDManager::getInstance().forceFullRefresh();
std::array<String, NUM_SCREENS> 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)
{