Compress fonts and add Oswald font

This commit is contained in:
Djuri 2024-12-29 23:28:22 +01:00
parent 73a20cf9a7
commit 90d91ba216
13 changed files with 4197 additions and 7571 deletions

View file

@ -65,6 +65,7 @@
#define DEFAULT_ZAP_NOTIFY_PUBKEY "b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422"
#define DEFAULT_LED_FLASH_ON_ZAP true
#define DEFAULT_FL_FLASH_ON_ZAP true
#define DEFAULT_FONT_NAME "antonio"
#define DEFAULT_HTTP_AUTH_ENABLED false
#define DEFAULT_HTTP_AUTH_USERNAME "btclock"
@ -93,3 +94,7 @@ enum DataSourceType {
};
#define DEFAULT_DATA_SOURCE BTCLOCK_SOURCE
#ifndef DEFAULT_BOOT_TEXT
#define DEFAULT_BOOT_TEXT "BTCLOCK"
#endif

View file

@ -59,7 +59,7 @@ MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = {
MCP23X17_Pin(mcp2, 5),
MCP23X17_Pin(mcp2, 7),
};
#else
#else
Native_Pin EPD_CS[NUM_SCREENS] = {
Native_Pin(2),
Native_Pin(4),
@ -123,10 +123,20 @@ QueueHandle_t updateQueue;
int fgColor = GxEPD_WHITE;
int bgColor = GxEPD_BLACK;
#define FONT_SMALL Antonio_SemiBold20pt7b
#define FONT_BIG Antonio_SemiBold90pt7b
#define FONT_MEDIUM Antonio_SemiBold40pt7b
#define FONT_SATSYMBOL Satoshi_Symbol90pt7b
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];
@ -149,8 +159,83 @@ void forceFullRefresh()
}
}
GFXfont font90;
void setupDisplays()
{
String fontName = preferences.getString("fontName", DEFAULT_FONT_NAME); // Default to antonio
if (fontName == "antonio")
{
// Load Antonio fonts
antonioFonts.big = FontLoader::loadCompressedFont(
Antonio_SemiBold90pt7b_Properties.compressedData,
Antonio_SemiBold90pt7b_Properties.glyphs,
Antonio_SemiBold90pt7b_Properties.compressedSize,
Antonio_SemiBold90pt7b_Properties.originalSize,
Antonio_SemiBold90pt7b_Properties.first,
Antonio_SemiBold90pt7b_Properties.last,
Antonio_SemiBold90pt7b_Properties.yAdvance);
antonioFonts.medium = FontLoader::loadCompressedFont(
Antonio_SemiBold40pt7b_Properties.compressedData,
Antonio_SemiBold40pt7b_Properties.glyphs,
Antonio_SemiBold40pt7b_Properties.compressedSize,
Antonio_SemiBold40pt7b_Properties.originalSize,
Antonio_SemiBold40pt7b_Properties.first,
Antonio_SemiBold40pt7b_Properties.last,
Antonio_SemiBold40pt7b_Properties.yAdvance);
antonioFonts.small = FontLoader::loadCompressedFont(
Antonio_SemiBold20pt7b_Properties.compressedData,
Antonio_SemiBold20pt7b_Properties.glyphs,
Antonio_SemiBold20pt7b_Properties.compressedSize,
Antonio_SemiBold20pt7b_Properties.originalSize,
Antonio_SemiBold20pt7b_Properties.first,
Antonio_SemiBold20pt7b_Properties.last,
Antonio_SemiBold20pt7b_Properties.yAdvance);
FONT_BIG = antonioFonts.big;
FONT_MEDIUM = antonioFonts.medium;
FONT_SMALL = antonioFonts.small;
}
else if (fontName == "oswald")
{
// Load Oswald fonts
oswaldFonts.big = FontLoader::loadCompressedFont(
Oswald_Medium80pt7b_Properties.compressedData,
Oswald_Medium80pt7b_Properties.glyphs,
Oswald_Medium80pt7b_Properties.compressedSize,
Oswald_Medium80pt7b_Properties.originalSize,
Oswald_Medium80pt7b_Properties.first,
Oswald_Medium80pt7b_Properties.last,
Oswald_Medium80pt7b_Properties.yAdvance);
oswaldFonts.medium = FontLoader::loadCompressedFont(
Oswald_Medium30pt7b_Properties.compressedData,
Oswald_Medium30pt7b_Properties.glyphs,
Oswald_Medium30pt7b_Properties.compressedSize,
Oswald_Medium30pt7b_Properties.originalSize,
Oswald_Medium30pt7b_Properties.first,
Oswald_Medium30pt7b_Properties.last,
Oswald_Medium30pt7b_Properties.yAdvance);
oswaldFonts.small = FontLoader::loadCompressedFont(
Oswald_Medium20pt7b_Properties.compressedData,
Oswald_Medium20pt7b_Properties.glyphs,
Oswald_Medium20pt7b_Properties.compressedSize,
Oswald_Medium20pt7b_Properties.originalSize,
Oswald_Medium20pt7b_Properties.first,
Oswald_Medium20pt7b_Properties.last,
Oswald_Medium20pt7b_Properties.yAdvance);
FONT_BIG = oswaldFonts.big;
FONT_MEDIUM = oswaldFonts.medium;
FONT_SMALL = oswaldFonts.small;
}
FONT_SATSYMBOL = &Satoshi_Symbol90pt7b;
std::lock_guard<std::mutex> lockMcp(mcpMutex);
for (uint i = 0; i < NUM_SCREENS; i++)
@ -160,13 +245,10 @@ void setupDisplays()
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, NULL, 11, NULL);
for (uint i = 0; i < NUM_SCREENS; i++)
{
// epdUpdateSemaphore[i] = xSemaphoreCreateBinary();
// xSemaphoreGive(epdUpdateSemaphore[i]);
int *taskParam = new int;
*taskParam = i;
@ -184,239 +266,255 @@ void setupDisplays()
}
else
{
#ifdef IS_BTCLOCK_V8
epdContent = {"B", "T", "C", "L", "O", "C", "K", "v8"};
#else
epdContent = {"B", "T", "C", "L", "O", "C", "K"};
#endif
// Get custom text from preferences or use default "BTCLOCK"
String customText = preferences.getString("displayText", DEFAULT_BOOT_TEXT);
// Initialize array with spaces
std::array<String, NUM_SCREENS> newContent;
newContent.fill(" ");
// Fill in the custom text, truncating if longer than NUM_SCREENS
for (size_t i = 0; i < std::min(customText.length(), (size_t)NUM_SCREENS); i++) {
newContent[i] = String(customText[i]);
}
epdContent = newContent;
}
setEpdContent(epdContent);
setEpdContent(epdContent);
}
void setEpdContent(std::array<String, NUM_SCREENS> newEpdContent)
{
setEpdContent(newEpdContent, false);
setEpdContent(newEpdContent, false);
}
void setEpdContent(std::array<std::string, NUM_SCREENS> newEpdContent)
{
std::array<String, NUM_SCREENS> conv;
std::array<String, NUM_SCREENS> conv;
for (size_t i = 0; i < newEpdContent.size(); ++i)
{
conv[i] = String(newEpdContent[i].c_str());
}
for (size_t i = 0; i < newEpdContent.size(); ++i)
{
conv[i] = String(newEpdContent[i].c_str());
}
return setEpdContent(conv);
return setEpdContent(conv);
}
void setEpdContent(std::array<String, NUM_SCREENS> newEpdContent,
bool forceUpdate)
{
std::lock_guard<std::mutex> lock(epdUpdateMutex);
std::lock_guard<std::mutex> lock(epdUpdateMutex);
waitUntilNoneBusy();
waitUntilNoneBusy();
for (uint i = 0; i < NUM_SCREENS; i++)
{
if (newEpdContent[i].compareTo(currentEpdContent[i]) != 0 || forceUpdate)
for (uint i = 0; i < NUM_SCREENS; i++)
{
epdContent[i] = newEpdContent[i];
UpdateDisplayTaskItem dispUpdate = {i};
xQueueSend(updateQueue, &dispUpdate, portMAX_DELAY);
if (newEpdContent[i].compareTo(currentEpdContent[i]) != 0 || forceUpdate)
{
epdContent[i] = newEpdContent[i];
UpdateDisplayTaskItem dispUpdate = {i};
xQueueSend(updateQueue, &dispUpdate, portMAX_DELAY);
}
}
}
}
void prepareDisplayUpdateTask(void *pvParameters)
{
UpdateDisplayTaskItem receivedItem;
UpdateDisplayTaskItem receivedItem;
while (1)
{
// Wait for a work item to be available in the queue
if (xQueueReceive(updateQueue, &receivedItem, portMAX_DELAY))
while (1)
{
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
// 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
showDigit(epdIndex, epdContent[epdIndex].c_str()[0], updatePartial,
&FONT_BIG);
}
}
bool updatePartial = true;
xTaskNotifyGive(tasks[epdIndex]);
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]);
}
}
}
}
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]);
const int epdIndex = *(int *)pvParameters;
delete (int *)pvParameters;
for (;;)
{
std::lock_guard<std::mutex> lockMcp(mcpMutex);
// Wait for the task notification
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
displays[epdIndex].init(0, false, 40);
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));
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);
break;
}
vTaskDelay(pdMS_TO_TICKS(100));
tries++;
}
}
uint count = 0;
while (EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10)
{
vTaskDelay(pdMS_TO_TICKS(100));
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);
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) {
displays[dispNum].setRotation(1);
} else {
displays[dispNum].setRotation(2);
}
displays[dispNum].setFont(&FONT_SMALL);
displays[dispNum].setTextColor(getFgColor());
if (preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0)
{
displays[dispNum].setRotation(1);
}
else
{
displays[dispNum].setRotation(2);
}
displays[dispNum].setFont(FONT_SMALL);
displays[dispNum].setTextColor(getFgColor());
// 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;
// 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;
// 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;
// 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;
// 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, lineX;
if (tbbw < ttbh)
lineWidth = tbbw;
else
lineWidth = ttbw;
lineX = round((displays[dispNum].width() - lineWidth) / 2);
displays[dispNum].fillScreen(getBgColor());
displays[dispNum].setCursor(tx, ty);
displays[dispNum].print(top);
displays[dispNum].fillRoundRect(lineX, displays[dispNum].height() / 2 - 3,
lineWidth, 6, 3, getFgColor());
displays[dispNum].setCursor(bx, by);
displays[dispNum].print(bottom);
displays[dispNum].fillScreen(getBgColor());
displays[dispNum].setCursor(tx, ty);
displays[dispNum].print(top);
displays[dispNum].fillRoundRect(lineX, displays[dispNum].height() / 2 - 3,
lineWidth, 6, 3, getFgColor());
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) {
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 showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font)
{
String str(chr);
if (chr == '.') {
if (chr == '.')
{
str = "!";
}
setupDisplay(dispNum, font);
int16_t tbx, tby;
uint16_t tbw, tbh;
displays[dispNum].getTextBounds(str, 0, 0, &tbx, &tby, &tbw, &tbh);
@ -427,57 +525,64 @@ void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font)
displays[dispNum].setCursor(x, y);
displays[dispNum].print(str);
if (chr == '.') {
displays[dispNum].fillRect(x, y, displays[dispNum].width(),
round(displays[dispNum].height() * 0.9), getBgColor());
if (chr == '.')
{
displays[dispNum].fillRect(x, y, displays[dispNum].width(),
round(displays[dispNum].height() * 0.9), getBgColor());
}
}
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;
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;
return maxDescent;
}
void showChars(const uint dispNum, const String &chars, bool partial,
const GFXfont *font)
{
setupDisplay(dispNum, font);
setupDisplay(dispNum, font);
int16_t tbx, tby;
uint16_t tbw, tbh;
displays[dispNum].getTextBounds(chars, 0, 0, &tbx, &tby, &tbw, &tbh);
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:
uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx;
uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby;
// 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++)
{
char c = chars[i];
if (c == '.' || c == ',')
{
// For the dot, calculate its specific descent
GFXglyph *dotGlyph = &font->glyph[c - font->first];
int16_t dotDescent = dotGlyph->yOffset;
for (int i = 0; i < chars.length(); i++) {
char c = chars[i];
if (c == '.' || c == ',') {
// For the dot, calculate its specific descent
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 {
// For other characters, use the original y-position
displays[dispNum].setCursor(x, y);
displays[dispNum].print(c);
// Draw the dot with adjusted y-position
displays[dispNum].setCursor(x, y + dotDescent + dotGlyph->height + 8);
displays[dispNum].print(c);
}
else
{
// For other characters, use the original y-position
displays[dispNum].setCursor(x, y);
displays[dispNum].print(c);
}
// Move x-position for the next character
x += font->glyph[c - font->first].xAdvance;
}
// Move x-position for the next character
x += font->glyph[c - font->first].xAdvance;
}
}
int getBgColor() { return bgColor; }
@ -490,142 +595,146 @@ void setFgColor(int color) { fgColor = color; }
std::array<String, NUM_SCREENS> getCurrentEpdContent()
{
return currentEpdContent;
return currentEpdContent;
}
void renderText(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(GxEPD_WHITE);
displays[dispNum].setTextColor(GxEPD_BLACK);
displays[dispNum].setCursor(0, 50);
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::stringstream ss;
ss.str(text.c_str());
std::string line;
std::string line;
while (std::getline(ss, line, '\n'))
{
if (line.rfind("*", 0) == 0)
while (std::getline(ss, line, '\n'))
{
line.erase(std::remove(line.begin(), line.end(), '*'), line.end());
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());
displays[dispNum].setFont(&FreeSansBold9pt7b);
displays[dispNum].println(line.c_str());
}
else
{
displays[dispNum].setFont(&FreeSans9pt7b);
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].setRotation(2);
displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(),
displays[dispNum].height());
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 = getMiningPoolLogo();
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 = getMiningPoolLogo();
if (logo.size == 0) {
Serial.println(F("No logo found"));
return false;
}
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
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());
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());
// displays[dispNum].drawInvertedBitmap(0,0, getOceanIcon(), 122, 250, getFgColor());
}
void renderQr(const uint dispNum, const String &text, bool partial)
{
#ifdef USE_QR
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);
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);
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);
}
}
}
#endif
}
void waitUntilNoneBusy()
{
for (int i = 0; i < NUM_SCREENS; i++)
{
uint count = 0;
while (EPD_BUSY[i].digitalRead())
for (int i = 0; i < NUM_SCREENS; i++)
{
count++;
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;
}
uint count = 0;
while (EPD_BUSY[i].digitalRead())
{
count++;
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;
}
}
}
}
}

View file

@ -3,7 +3,7 @@
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeSansBold9pt7b.h>
#include <GxEPD2_BW.h>
#include "gzip_decompressor.hpp"
#include <mcp23x17_pin.hpp>
#include <mutex>
@ -16,6 +16,18 @@
#include "icons/icons.h"
#include "mining_pool_stats_fetch.hpp"
// Font includes
#include "../fonts/antonio-semibold20.h"
#include "../fonts/antonio-semibold40.h"
#include "../fonts/antonio-semibold90.h"
// Oswald fonts
#include "../fonts/oswald-medium20.h"
#include "../fonts/oswald-medium30.h"
#include "../fonts/oswald-medium80.h"
#include "../fonts/sats-symbol.h"
#ifdef USE_QR
#include "qrcodegen.h"
#endif

View file

@ -0,0 +1,49 @@
#pragma once
#include "rom/miniz.h"
#include <Arduino.h>
class GzipDecompressor {
public:
static bool decompressData(const uint8_t* input, size_t inputSize,
uint8_t* output, size_t* outputSize) {
if (!input || !output || !outputSize || inputSize < 18) { // Minimum gzip size
return false;
}
tinfl_decompressor* decomp = new tinfl_decompressor;
if (!decomp) {
return false;
}
tinfl_init(decomp);
size_t inPos = 10; // Skip gzip header
size_t outPos = 0;
while (inPos < inputSize - 8) { // -8 for footer
size_t inBytes = inputSize - inPos - 8;
size_t outBytes = *outputSize - outPos;
tinfl_status status = tinfl_decompress(decomp,
&input[inPos], &inBytes,
output, &output[outPos], &outBytes,
TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
inPos += inBytes;
outPos += outBytes;
if (status == TINFL_STATUS_DONE) {
*outputSize = outPos;
delete decomp;
return true;
} else if (status < 0) {
delete decomp;
return false;
}
}
delete decomp;
return false;
}
};

View file

@ -3,7 +3,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"};
"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName"};
static const char *const PROGMEM uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout"};
@ -675,7 +675,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root["nostrZapNotify"] = preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED);
root["nostrZapPubkey"] = preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY);
root["ledFlashOnZap"] = preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP);
root["fontName"] = preferences.getString("fontName", DEFAULT_FONT_NAME);
// Custom endpoint settings (only used for CUSTOM_SOURCE)
root["customEndpoint"] = preferences.getString("customEndpoint", DEFAULT_CUSTOM_ENDPOINT);
root["customEndpointDisableSSL"] = preferences.getBool("customEndpointDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL);