diff --git a/docs/nimble_companion_trial.md b/docs/nimble_companion_trial.md index 30986855..1d9f5c06 100644 --- a/docs/nimble_companion_trial.md +++ b/docs/nimble_companion_trial.md @@ -22,8 +22,9 @@ It is not the generic XIAO profile for separately wired radio modules. Both boards retain their parent Full Companion features, USB mOTA sender, WiFi update slots, and PSRAM-backed offline queue. The V4 also retains direct MQTT. -An additional [V4 Picopixel trial](v4_pixel5_font_trial.md) uses 5-pixel capital -letters and a longer on-device message preview. +The [small-screen Picopixel layout](v4_pixel5_font_trial.md) uses 5-pixel +capital letters, a compact channel/sender line and full message previews. +A named V4 trial profile is available for hardware qualification. ## Build diff --git a/docs/v4_pixel5_font_trial.md b/docs/v4_pixel5_font_trial.md index 7c9f89e9..fc444403 100644 --- a/docs/v4_pixel5_font_trial.md +++ b/docs/v4_pixel5_font_trial.md @@ -1,23 +1,36 @@ -# V4 Picopixel message font trial +# Small-screen Picopixel message font -This optional V4.2/V4.3 OLED Full Companion build uses Adafruit's Picopixel -font for received message text. Capital letters are 5 pixels high; descenders -can occupy a sixth pixel, so lines advance by 7 pixels. Most characters -advance by 4 pixels horizontally, compared with the normal font's 6 pixels. -The normal font has 7-pixel letters and an 8-pixel line height. +Small-screen Companion builds use Picopixel for received message text and the +channel/sender line, including `Ch 0 Public`. This covers SSD1306 and SH1106 +OLEDs, ST7735 small TFTs, and the U8g2 T-Echo Card display. The shared renderer +uses the same font data on every driver. Larger displays keep their existing +font. Menus, Bluetooth PINs and WiFi setup QR codes keep their normal layout. -The title, channel/sender, menus, Bluetooth PIN and WiFi QR page retain their -normal font. The message preview buffer increases from 78 bytes (77 text -bytes plus a terminator) to 161 bytes, enough for a complete 160-byte MeshCore -message. The screen retains 32 previews; this is separate from the V4's -512-frame offline queue and its mOTA policy. +Capital letters are 5 logical pixels high; descenders can occupy a sixth +pixel, so lines advance by 7 pixels. Most characters advance by 4 pixels +horizontally. The normal OLED font uses 7-pixel letters, 8-pixel line spacing +and 6-pixel character advances. Small TFTs retain their existing logical to +physical scaling. -Five complete Picopixel message lines fit below the title and sender on the -128 x 64 OLED. A typical message can fit in full; wide letters can still -exceed the available space. The last line then ends with `...`. Text wraps -at character boundaries. Unsupported characters appear as `?`. +On a 128 x 64 OLED, compacting the channel/sender line moves message text from +y=25 to y=21. Six complete small-font rows fit instead of five, roughly 30 +additional characters depending on the letters. Text wraps at character +boundaries; the last line ends with `...` if it still cannot fit. Unsupported +characters appear as `?`. Long channel/sender names are ellipsized so they +cannot overlap the message. -## Build and install +The preview buffer holds a complete 160-byte MeshCore message plus its +terminator. Previously the main message UI allocated 78 bytes, leaving room +for only 77 bytes of text. It still retains 32 previews; larger records add +about 2.8 KB of RAM. The firmware RAM guard includes that increase. This +history is separate from the offline queue and its mOTA policy. + +The tiny 72 x 40 T-Echo Card interface now previews the latest received +message below its status bar, with three small-font message rows. A button +press dismisses it. It stores one full message, and shows `...` when the +screen fills. Incoming text does not replace an active Bluetooth pairing PIN. + +## V4 hardware trial Enable `platformio.nimble.ini` in the ignored `platformio.local.ini` as shown in the [NimBLE trial guide](nimble_companion_trial.md#build), then run: @@ -29,26 +42,32 @@ OUTPUT_DIR=.releases/v4-pixel5 bash build.sh build-firmware \ --radio-preset usa-cascadia --profile cascade --standard --require-ota ``` +The named trial keeps NimBLE, 350 contacts, 40 channels, the V4's 512-frame +PSRAM queue, USB mOTA sending and WiFi OTA support. The smaller font is also +the default in ordinary small-screen Companion builds from this source. + For a V4 already using the matching 16 MB Full Companion partition layout, flash the application `.bin` at `0x10000`, or use it for WiFi OTA. A clean -USB install uses the merged image at address 0. The trial preserves NimBLE, -350 contacts, 40 channels, USB mOTA sending and WiFi OTA support. +USB install uses the merged image at address 0. -To restore the normal message font, install -`heltec_v4_2_v4_3_companion_radio_full_femon_nimble`. Both builds use the same -partition layout; erasing settings is unnecessary. +A custom build can set `-D UI_SMALL_MESSAGE_FONT=0` to restore the old font +and spacing. Remove any explicit `UI_MSG_PREVIEW_SIZE` flag too if the old +preview capacity is desired. These are compile-time options, not CLI commands. +No settings erase is necessary when changing between matching V4 layouts. ## Verification ```sh python3 -B test/test_ssd1306_picopixel.py +python3 -B test/test_firmware_ram.py pio test -e native -f test_display_driver -f test_companion_message_history ``` -The font test uses the real SSD1306 driver, Adafruit GFX rendering and -Picopixel bitmaps with a host panel double. It checks a full 160-character -sample, all printable ASCII characters, descenders, rotations, bottom-edge -clipping, overflow markers, unsupported-character placeholders and restoring -the normal font. It runs with address/undefined-behavior sanitizers. It needs -the Adafruit GFX dependency from an existing PlatformIO build; without that -dependency it reports a skip. Run only one PlatformIO command at a time. +The native tests exercise the shared renderer, five-pixel capitals, complete +rows at display edges, long sender lines, overflow markers and tiny/rotated +screen geometry. The additional Adafruit comparison checks all 95 printable +ASCII glyphs pixel for pixel against the original Picopixel font, under +address/undefined-behavior sanitizers. That comparison needs a cached +PlatformIO Adafruit GFX library; set `MESHCORE_GFX_LIBRARY` to its directory +if needed. It reports a skip when the library is absent. The native tests do +not require that dependency. Run only one PlatformIO command at a time. diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 8d8431a2..5290406a 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -3,6 +3,9 @@ #include #include #include +#if UI_SMALL_MESSAGE_FONT == 1 + #include +#endif #include #include "../MyMesh.h" #include "../CompanionWiFi.h" @@ -1171,7 +1174,11 @@ public: }; #ifndef UI_MSG_PREVIEW_SIZE - #define UI_MSG_PREVIEW_SIZE 78 + #if UI_SMALL_MESSAGE_FONT == 1 + #define UI_MSG_PREVIEW_SIZE 161 + #else + #define UI_MSG_PREVIEW_SIZE 78 + #endif #endif #ifndef UI_COMPACT_MESSAGE_STATUS #define UI_COMPACT_MESSAGE_STATUS 0 @@ -1407,7 +1414,7 @@ public: display.setCursor(0, 0); display.setTextSize(1); display.setColor(UIColor::corp_blue); - snprintf(tmp, sizeof(tmp), "Message %d/%d", + snprintf(tmp, sizeof(tmp), display.width() < 100 ? "%d/%d" : "Message %d/%d", filtered_count == 0 ? 0 : view_offset + 1, filtered_count); display.print(tmp); @@ -1425,12 +1432,22 @@ public: mesh::ui::formatCompanionMessageAge( tmp, sizeof(tmp), companionMessageElapsedMillis(p->heard_millis)); + if (display.width() < 100) { + char* suffix = strchr(tmp, ' '); + if (suffix != nullptr) *suffix = 0; + } display.setCursor(display.width() - display.getTextWidth(tmp) - 2, 0); display.print(tmp); display.drawRect(0, layout.header_divider_y, display.width(), 1); display.setCompactText(false); +#if UI_SMALL_MESSAGE_FONT == 1 + mesh::ui::drawSmallMessageBody(display, p->origin, p->message, + layout.origin_y, layout.origin_y + 7, + UI_MESSAGE_CHANNEL_FOOTER == 1 + ? display.height() - layout.filter_height : display.height()); +#else display.setCursor(0, layout.origin_y); display.setColor(UIColor::secondary_txt); char filtered_origin[sizeof(p->origin)]; @@ -1442,6 +1459,7 @@ public: char filtered_msg[sizeof(p->message)]; display.translateUTF8ToBlocks(filtered_msg, p->message, sizeof(filtered_msg)); display.printWordWrap(filtered_msg, display.width()); +#endif renderChannelFilter(display); diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index bb771ec6..eeb70fbe 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -1,4 +1,7 @@ #include "UITask.h" +#if UI_SMALL_MESSAGE_FONT == 1 + #include +#endif #include #include #include @@ -242,6 +245,9 @@ void UITask::renderCurrScreen() { _display->setColor(UIColor::primary_txt); _display->print(_node_prefs->node_name); +#if UI_SMALL_MESSAGE_FONT == 1 + mesh::ui::drawSmallMessageBody(*_display, _origin, _msg, 12, 19); +#else _display->setCursor(0, 12); _display->setColor(UIColor::secondary_txt); _display->print(_origin); @@ -253,6 +259,7 @@ void UITask::renderCurrScreen() { _display->setColor(UIColor::primary_txt); sprintf(tmp, "%d", _msgcount); _display->print(tmp); +#endif _display->setColor(UIColor::secondary_txt); // last color will be kept on T114 } else if (!pairing_screen_active && (millis() - ui_started_at) < BOOT_SCREEN_MILLIS) { // boot screen // meshcore logo diff --git a/examples/companion_radio/ui-orig/UITask.h b/examples/companion_radio/ui-orig/UITask.h index df0ba494..f94b6e7f 100644 --- a/examples/companion_radio/ui-orig/UITask.h +++ b/examples/companion_radio/ui-orig/UITask.h @@ -31,7 +31,11 @@ class UITask : public AbstractUITask { CompanionNodePrefs* _node_prefs; char _version_info[32]; char _origin[62]; +#if UI_SMALL_MESSAGE_FONT == 1 + char _msg[161]; +#else char _msg[80]; +#endif char _alert[80]; int _msgcount; bool _need_refresh = true; diff --git a/examples/companion_radio/ui-tiny/UITask.cpp b/examples/companion_radio/ui-tiny/UITask.cpp index dc8f9c5c..d2236bb5 100644 --- a/examples/companion_radio/ui-tiny/UITask.cpp +++ b/examples/companion_radio/ui-tiny/UITask.cpp @@ -1,4 +1,7 @@ #include "UITask.h" +#if UI_SMALL_MESSAGE_FONT == 1 + #include +#endif #include #include #include "../MyMesh.h" @@ -439,6 +442,38 @@ public: }; +#if UI_SMALL_MESSAGE_FONT == 1 +class TinyMessageScreen : public UIScreen { + UITask* _task; + char _origin[62] = {}; + char _message[161] = {}; +public: + explicit TinyMessageScreen(UITask* task) : _task(task) {} + void setMessage(uint8_t path_len, const char* from, const char* message) { + if (path_len == 0xFF) { + snprintf(_origin, sizeof(_origin), "%s [direct]:", from); + } else { + snprintf(_origin, sizeof(_origin), "%s [%uh]:", from, + (unsigned int)path_len); + } + StrHelper::strncpy(_message, message, sizeof(_message)); + } + int render(DisplayDriver& display) override { + // The 72x40 interface reserves its top 8px for the scrolling status bar. + mesh::ui::drawSmallMessageBody(display, _origin, _message, 10, 17); + return 1000; + } + bool handleInput(char c) override { + if (c == KEY_NEXT || c == KEY_PREV || c == KEY_LEFT || c == KEY_RIGHT + || c == KEY_ENTER) { + _task->gotoHomeScreen(); + return true; + } + return false; + } +}; +#endif + void UITask::begin(DisplayDriver* display, SensorManager* sensors, CompanionNodePrefs* node_prefs) { _display = display; _sensors = sensors; @@ -478,6 +513,9 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, CompanionNode splash = new SplashScreen(this); home = new HomeScreen(this, &rtc_clock, sensors, node_prefs); +#if UI_SMALL_MESSAGE_FONT == 1 + msg_preview = new TinyMessageScreen(this); +#endif setCurrScreen(splash); } @@ -519,6 +557,11 @@ switch(t){ void UITask::msgRead(int msgcount) { _msgcount = msgcount; if (msgcount == 0) { +#if UI_SMALL_MESSAGE_FONT == 1 + if (curr == msg_preview + && static_cast(millis() - _msg_preview_until) < 0) return; + _deferred_msg_preview = false; +#endif gotoHomeScreen(); } } @@ -529,6 +572,12 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, (void)channel_idx; (void)channel_name; _msgcount = msgcount; +#if UI_SMALL_MESSAGE_FONT == 1 + static_cast(msg_preview)->setMessage(path_len, from_name, text); + if (isPairingScreenActive()) _deferred_msg_preview = true; + else setCurrScreen(msg_preview); + _msg_preview_until = millis() + 15000UL; +#endif if (_display != NULL) { if (!_display->isOn() && shouldWakeDisplayForMessage()) { @@ -591,6 +640,12 @@ void UITask::showPairingPin() { void UITask::finishPairingScreen(bool timed_out) { _pairing_screen_until = 0; _next_refresh = 0; +#if UI_SMALL_MESSAGE_FONT == 1 + if (_deferred_msg_preview) { + _deferred_msg_preview = false; + setCurrScreen(msg_preview); + } +#endif if (_display == NULL) return; if (timed_out) { diff --git a/examples/companion_radio/ui-tiny/UITask.h b/examples/companion_radio/ui-tiny/UITask.h index 7b6bd444..1ab30413 100644 --- a/examples/companion_radio/ui-tiny/UITask.h +++ b/examples/companion_radio/ui-tiny/UITask.h @@ -56,7 +56,11 @@ class UITask : public AbstractUITask { UIScreen* splash; UIScreen* home; - // UIScreen* msg_preview; +#if UI_SMALL_MESSAGE_FONT == 1 + UIScreen* msg_preview = nullptr; + bool _deferred_msg_preview = false; + unsigned long _msg_preview_until = 0; +#endif UIScreen* curr; diff --git a/platformio.nimble.ini b/platformio.nimble.ini index 70bb17b7..3e141c05 100644 --- a/platformio.nimble.ini +++ b/platformio.nimble.ini @@ -23,7 +23,7 @@ lib_ignore = ${companion_nimble_trial.lib_ignore} extends = env:heltec_v4_2_v4_3_companion_radio_full_femon_nimble build_flags = ${env:heltec_v4_2_v4_3_companion_radio_full_femon_nimble.build_flags} - -D UI_SSD1306_PICOPIXEL_MESSAGES=1 + -D UI_SMALL_MESSAGE_FONT=1 -D UI_MSG_PREVIEW_SIZE=161 [env:Xiao_S3_WIO_companion_radio_full_nimble] diff --git a/scripts/check_firmware_ram.py b/scripts/check_firmware_ram.py index 7d2dd2b3..cf53ac53 100644 --- a/scripts/check_firmware_ram.py +++ b/scripts/check_firmware_ram.py @@ -88,7 +88,12 @@ def requirements(platform, defines, target): # ui-new retains 32 previews in one heap allocation. The baseline # covers 78 bytes per message; budget larger buffers explicitly, # including worst-case 8-byte Entry alignment. - extra = max(0, integer(defines, "UI_MSG_PREVIEW_SIZE", 78) - 78) + small_display = display in { + "SSD1306Display", "SH1106Display", "ST7735Display", "U8g2Display", + } + small_font = integer(defines, "UI_SMALL_MESSAGE_FONT", int(small_display)) + default_preview = 161 if small_font else 78 + extra = max(0, integer(defines, "UI_MSG_PREVIEW_SIZE", default_preview) - 78) if extra: parts["expanded_message_previews"] = 32 * ((extra + 7) // 8) * 8 parts["allocation_and_transient_margin"] = 16384 if platform == "ESP32_PLATFORM" else 4096 diff --git a/src/helpers/ui/DisplayBuildFlags.h b/src/helpers/ui/DisplayBuildFlags.h index b58e2fa7..a65ea04d 100644 --- a/src/helpers/ui/DisplayBuildFlags.h +++ b/src/helpers/ui/DisplayBuildFlags.h @@ -26,6 +26,14 @@ #define MESHCORE_HAS_SMALL_DISPLAY 1 #endif +#ifndef UI_SMALL_MESSAGE_FONT + #if defined(MESHCORE_HAS_SMALL_DISPLAY) + #define UI_SMALL_MESSAGE_FONT 1 + #else + #define UI_SMALL_MESSAGE_FONT 0 + #endif +#endif + #undef MESHCORE_DISPLAY_TOKEN_JOIN #undef MESHCORE_DISPLAY_TOKEN_JOIN_INNER #undef MESHCORE_SMALL_DISPLAY_CLASS_U8g2Display diff --git a/src/helpers/ui/Pixel5FontData.h b/src/helpers/ui/Pixel5FontData.h new file mode 100644 index 00000000..3b3c0abd --- /dev/null +++ b/src/helpers/ui/Pixel5FontData.h @@ -0,0 +1,159 @@ +#pragma once + +// Picopixel by Sebastian Weber, from Adafruit GFX Library 1.12.6. +// Data is unchanged; the type names are independent of a display library. +// Source: https://github.com/adafruit/Adafruit-GFX-Library/blob/1.12.6/Fonts/Picopixel.h +/* +Software License Agreement (BSD License) + +Copyright (c) 2012 Adafruit Industries. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +namespace mesh { +namespace ui { + +struct Pixel5Glyph { + uint16_t bitmap_offset; + uint8_t width, height, advance; + int8_t x_offset, y_offset; +}; + +static const uint8_t pixel5Bitmaps[] = { + 0xE8, 0xB4, 0x57, 0xD5, 0xF5, 0x00, 0x4E, 0x3E, 0x80, 0xA5, 0x4A, 0x4A, + 0x5A, 0x50, 0xC0, 0x6A, 0x40, 0x95, 0x80, 0xAA, 0x80, 0x5D, 0x00, 0x60, + 0xE0, 0x80, 0x25, 0x48, 0x56, 0xD4, 0x75, 0x40, 0xC5, 0x4E, 0xC5, 0x1C, + 0x97, 0x92, 0xF3, 0x1C, 0x53, 0x54, 0xE5, 0x48, 0x55, 0x54, 0x55, 0x94, + 0xA0, 0x46, 0x64, 0xE3, 0x80, 0x98, 0xC5, 0x04, 0x56, 0xC6, 0x57, 0xDA, + 0xD7, 0x5C, 0x72, 0x46, 0xD6, 0xDC, 0xF3, 0xCE, 0xF3, 0x48, 0x72, 0xD4, + 0xB7, 0xDA, 0xF8, 0x24, 0xD4, 0xBB, 0x5A, 0x92, 0x4E, 0x8E, 0xEB, 0x58, + 0x80, 0x9D, 0xB9, 0x90, 0x56, 0xD4, 0xD7, 0x48, 0x56, 0xD4, 0x40, 0xD7, + 0x5A, 0x71, 0x1C, 0xE9, 0x24, 0xB6, 0xD4, 0xB6, 0xA4, 0x8C, 0x6B, 0x55, + 0x00, 0xB5, 0x5A, 0xB5, 0x24, 0xE5, 0x4E, 0xEA, 0xC0, 0x91, 0x12, 0xD5, + 0xC0, 0x54, 0xF0, 0x90, 0xC7, 0xF0, 0x93, 0x5E, 0x71, 0x80, 0x25, 0xDE, + 0x5E, 0x30, 0x6E, 0x80, 0x77, 0x9C, 0x93, 0x5A, 0xB8, 0x45, 0x60, 0x92, + 0xEA, 0xAA, 0x40, 0xD5, 0x6A, 0xD6, 0x80, 0x55, 0x00, 0xD7, 0x40, 0x75, + 0x90, 0xE8, 0x71, 0xE0, 0xBA, 0x40, 0xB5, 0x80, 0xB5, 0x00, 0x8D, 0x54, + 0xAA, 0x80, 0xAC, 0xE0, 0xE5, 0x70, 0x6A, 0x26, 0xFC, 0xC8, 0xAC, 0x5A}; + +static const Pixel5Glyph pixel5Glyphs[] = {{0, 0, 0, 2, 0, 1}, // 0x20 ' ' + {0, 1, 5, 2, 0, -4}, // 0x21 '!' + {1, 3, 2, 4, 0, -4}, // 0x22 '"' + {2, 5, 5, 6, 0, -4}, // 0x23 '#' + {6, 3, 6, 4, 0, -4}, // 0x24 '$' + {9, 3, 5, 4, 0, -4}, // 0x25 '%' + {11, 4, 5, 5, 0, -4}, // 0x26 '&' + {14, 1, 2, 2, 0, -4}, // 0x27 ''' + {15, 2, 5, 3, 0, -4}, // 0x28 '(' + {17, 2, 5, 3, 0, -4}, // 0x29 ')' + {19, 3, 3, 4, 0, -3}, // 0x2A '*' + {21, 3, 3, 4, 0, -3}, // 0x2B '+' + {23, 2, 2, 3, 0, 0}, // 0x2C ',' + {24, 3, 1, 4, 0, -2}, // 0x2D '-' + {25, 1, 1, 2, 0, 0}, // 0x2E '.' + {26, 3, 5, 4, 0, -4}, // 0x2F '/' + {28, 3, 5, 4, 0, -4}, // 0x30 '0' + {30, 2, 5, 3, 0, -4}, // 0x31 '1' + {32, 3, 5, 4, 0, -4}, // 0x32 '2' + {34, 3, 5, 4, 0, -4}, // 0x33 '3' + {36, 3, 5, 4, 0, -4}, // 0x34 '4' + {38, 3, 5, 4, 0, -4}, // 0x35 '5' + {40, 3, 5, 4, 0, -4}, // 0x36 '6' + {42, 3, 5, 4, 0, -4}, // 0x37 '7' + {44, 3, 5, 4, 0, -4}, // 0x38 '8' + {46, 3, 5, 4, 0, -4}, // 0x39 '9' + {48, 1, 3, 2, 0, -3}, // 0x3A ':' + {49, 2, 4, 3, 0, -3}, // 0x3B ';' + {50, 2, 3, 3, 0, -3}, // 0x3C '<' + {51, 3, 3, 4, 0, -3}, // 0x3D '=' + {53, 2, 3, 3, 0, -3}, // 0x3E '>' + {54, 3, 5, 4, 0, -4}, // 0x3F '?' + {56, 3, 5, 4, 0, -4}, // 0x40 '@' + {58, 3, 5, 4, 0, -4}, // 0x41 'A' + {60, 3, 5, 4, 0, -4}, // 0x42 'B' + {62, 3, 5, 4, 0, -4}, // 0x43 'C' + {64, 3, 5, 4, 0, -4}, // 0x44 'D' + {66, 3, 5, 4, 0, -4}, // 0x45 'E' + {68, 3, 5, 4, 0, -4}, // 0x46 'F' + {70, 3, 5, 4, 0, -4}, // 0x47 'G' + {72, 3, 5, 4, 0, -4}, // 0x48 'H' + {74, 1, 5, 2, 0, -4}, // 0x49 'I' + {75, 3, 5, 4, 0, -4}, // 0x4A 'J' + {77, 3, 5, 4, 0, -4}, // 0x4B 'K' + {79, 3, 5, 4, 0, -4}, // 0x4C 'L' + {81, 5, 5, 6, 0, -4}, // 0x4D 'M' + {85, 4, 5, 5, 0, -4}, // 0x4E 'N' + {88, 3, 5, 4, 0, -4}, // 0x4F 'O' + {90, 3, 5, 4, 0, -4}, // 0x50 'P' + {92, 3, 6, 4, 0, -4}, // 0x51 'Q' + {95, 3, 5, 4, 0, -4}, // 0x52 'R' + {97, 3, 5, 4, 0, -4}, // 0x53 'S' + {99, 3, 5, 4, 0, -4}, // 0x54 'T' + {101, 3, 5, 4, 0, -4}, // 0x55 'U' + {103, 3, 5, 4, 0, -4}, // 0x56 'V' + {105, 5, 5, 6, 0, -4}, // 0x57 'W' + {109, 3, 5, 4, 0, -4}, // 0x58 'X' + {111, 3, 5, 4, 0, -4}, // 0x59 'Y' + {113, 3, 5, 4, 0, -4}, // 0x5A 'Z' + {115, 2, 5, 3, 0, -4}, // 0x5B '[' + {117, 3, 5, 4, 0, -4}, // 0x5C '\' + {119, 2, 5, 3, 0, -4}, // 0x5D ']' + {121, 3, 2, 4, 0, -4}, // 0x5E '^' + {122, 4, 1, 4, 0, 1}, // 0x5F '_' + {123, 2, 2, 3, 0, -4}, // 0x60 '`' + {124, 3, 4, 4, 0, -3}, // 0x61 'a' + {126, 3, 5, 4, 0, -4}, // 0x62 'b' + {128, 3, 3, 4, 0, -2}, // 0x63 'c' + {130, 3, 5, 4, 0, -4}, // 0x64 'd' + {132, 3, 4, 4, 0, -3}, // 0x65 'e' + {134, 2, 5, 3, 0, -4}, // 0x66 'f' + {136, 3, 5, 4, 0, -3}, // 0x67 'g' + {138, 3, 5, 4, 0, -4}, // 0x68 'h' + {140, 1, 5, 2, 0, -4}, // 0x69 'i' + {141, 2, 6, 3, 0, -4}, // 0x6A 'j' + {143, 3, 5, 4, 0, -4}, // 0x6B 'k' + {145, 2, 5, 3, 0, -4}, // 0x6C 'l' + {147, 5, 3, 6, 0, -2}, // 0x6D 'm' + {149, 3, 3, 4, 0, -2}, // 0x6E 'n' + {151, 3, 3, 4, 0, -2}, // 0x6F 'o' + {153, 3, 4, 4, 0, -2}, // 0x70 'p' + {155, 3, 4, 4, 0, -2}, // 0x71 'q' + {157, 2, 3, 3, 0, -2}, // 0x72 'r' + {158, 3, 4, 4, 0, -3}, // 0x73 's' + {160, 2, 5, 3, 0, -4}, // 0x74 't' + {162, 3, 3, 4, 0, -2}, // 0x75 'u' + {164, 3, 3, 4, 0, -2}, // 0x76 'v' + {166, 5, 3, 6, 0, -2}, // 0x77 'w' + {168, 3, 3, 4, 0, -2}, // 0x78 'x' + {170, 3, 4, 4, 0, -2}, // 0x79 'y' + {172, 3, 4, 4, 0, -3}, // 0x7A 'z' + {174, 3, 5, 4, 0, -4}, // 0x7B '{' + {176, 1, 6, 2, 0, -4}, // 0x7C '|' + {177, 3, 5, 4, 0, -4}, // 0x7D '}' + {179, 4, 2, 5, 0, -3}}; // 0x7E '~' + + +} // namespace ui +} // namespace mesh diff --git a/src/helpers/ui/Pixel5Text.h b/src/helpers/ui/Pixel5Text.h new file mode 100644 index 00000000..1c2f1f39 --- /dev/null +++ b/src/helpers/ui/Pixel5Text.h @@ -0,0 +1,97 @@ +#pragma once + +#include "DisplayDriver.h" +#include "DisplayTextLayout.h" +#include "Pixel5FontData.h" + +namespace mesh { +namespace ui { + +// Draw through the common pixel interface so OLED, TFT and U8g2 panels use +// identical glyphs. This stack wrapper leaves the driver's normal font alone. +class Pixel5Text : public DisplayDriver { + DisplayDriver& _display; + int _x = 0, _y = 0; + + static const Pixel5Glyph& glyph(uint8_t c) { + return pixel5Glyphs[(c >= 32 && c <= 126 ? c : '?') - 32]; + } + +public: + explicit Pixel5Text(DisplayDriver& display) + : DisplayDriver(display.width(), display.height()), _display(display) {} + static constexpr int line_height = 7; + static constexpr int glyph_height = 6; // 5px capitals plus descenders + + bool isOn() override { return _display.isOn(); } + void turnOn() override { _display.turnOn(); } + void turnOff() override { _display.turnOff(); } + void clear() override { _display.clear(); } + void startFrame(ColorVal bkg) override { _display.startFrame(bkg); } + void endFrame() override { _display.endFrame(); } + void setTextSize(int) override {} // This font deliberately has one size. + void setColor(ColorVal color) override { _display.setColor(color); } + void setCursor(int x, int y) override { _x = x; _y = y; } + void fillRect(int x, int y, int w, int h) override { + _display.fillRect(x, y, w, h); + } + void drawRect(int x, int y, int w, int h) override { + _display.drawRect(x, y, w, h); + } + void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override { + _display.drawXbm(x, y, bits, w, h); + } + uint16_t getTextWidth(const char* str) override { + uint16_t result = 0; + while (*str) result += glyph((uint8_t)*str++).advance; + return result; + } + void print(const char* str) override { + while (*str) { + const Pixel5Glyph& g = glyph((uint8_t)*str++); + for (int row = 0; row < g.height; ++row) { + const int y = _y + 4 + g.y_offset + row; + if (y < 0 || y >= height()) continue; + for (int col = 0; col < g.width; ++col) { + const int bit = row * g.width + col; + if (!(pixel5Bitmaps[g.bitmap_offset + bit / 8] + & (0x80 >> (bit % 8)))) continue; + const int x = _x + g.x_offset + col; + if (x >= 0 && x < width()) _display.fillRect(x, y, 1, 1); + } + } + _x += g.advance; + } + } + void printWordWrap(const char* str, int max_width) override { + if (max_width > width() - _x) max_width = width() - _x; + const int lines = (height() - _y + line_height - glyph_height) / line_height; + drawTextWrapped(*this, _x, _y, max_width, line_height, lines, str); + } +}; + +inline int smallMessageLineCount(int height, int message_y) { + const int remaining = height - message_y; + return remaining < Pixel5Text::glyph_height ? 0 + : (remaining + Pixel5Text::line_height - Pixel5Text::glyph_height) + / Pixel5Text::line_height; +} + +inline void drawSmallMessageBody(DisplayDriver& display, const char* origin, + const char* message, int origin_y = 14, + int message_y = 21, int bottom = -1) { + Pixel5Text text(display); + char translated[161]; + text.translateUTF8ToBlocks(translated, origin, sizeof(translated)); + text.setColor(UIColor::secondary_txt); + text.drawTextEllipsized(0, origin_y, display.width(), translated); + text.translateUTF8ToBlocks(translated, message, sizeof(translated)); + text.setColor(UIColor::primary_txt); + const int height = bottom < 0 || bottom > display.height() + ? display.height() : bottom; + drawTextWrapped(text, 0, message_y, display.width(), Pixel5Text::line_height, + smallMessageLineCount(height, message_y), translated); +} + +} // namespace ui +} // namespace mesh diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index 51ea567a..069faf00 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -1,16 +1,5 @@ #include "SSD1306Display.h" -#if defined(UI_SSD1306_PICOPIXEL_MESSAGES) && UI_SSD1306_PICOPIXEL_MESSAGES == 1 -#include -#include "DisplayTextLayout.h" - -// The message UI has already replaced unsupported UTF-8 with CP437 blocks. -// Picopixel is ASCII-only; retain a visible placeholder for those characters. -static uint8_t picopixelCharacter(uint8_t c) { - return c >= 0x20 && c <= 0x7E ? c : '?'; -} -#endif - bool SSD1306Display::i2c_probe(TwoWire& wire, uint8_t addr) { wire.beginTransmission(addr); uint8_t error = wire.endTransmission(); @@ -123,43 +112,13 @@ void SSD1306Display::setColor(ColorVal c) { } void SSD1306Display::setCursor(int x, int y) { -#if defined(UI_SSD1306_PICOPIXEL_MESSAGES) && UI_SSD1306_PICOPIXEL_MESSAGES == 1 - // GFX custom fonts take a baseline; the shared UI takes a top coordinate. - if (_picopixel_message) y += 4; -#endif display.setCursor(x, y); } void SSD1306Display::print(const char* str) { -#if defined(UI_SSD1306_PICOPIXEL_MESSAGES) && UI_SSD1306_PICOPIXEL_MESSAGES == 1 - if (_picopixel_message) { - while (*str) display.write(picopixelCharacter((uint8_t)*str++)); - return; - } -#endif display.print(str); } -#if defined(UI_SSD1306_PICOPIXEL_MESSAGES) && UI_SSD1306_PICOPIXEL_MESSAGES == 1 -void SSD1306Display::printWordWrap(const char* str, int max_width) { - const int x = display.getCursorX(); - const int y = display.getCursorY(); - if (max_width > width() - x) max_width = width() - x; - // Capitals are 5px high; descenders can use a sixth pixel. Leave one blank - // row between lines and never start a partially visible final line. - const int max_lines = (height() - y + 1) / 7; - display.setFont(&Picopixel); - display.setTextSize(1); - display.setTextWrap(false); - _picopixel_message = true; - mesh::ui::drawTextWrapped(*this, x, y, max_width, 7, max_lines, str); - _picopixel_message = false; - display.setFont(nullptr); - display.setTextWrap(true); - display.setCursor(x, y); -} -#endif - void SSD1306Display::fillRect(int x, int y, int w, int h) { display.fillRect(x, y, w, h, _color); } @@ -173,17 +132,6 @@ void SSD1306Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { } uint16_t SSD1306Display::getTextWidth(const char* str) { -#if defined(UI_SSD1306_PICOPIXEL_MESSAGES) && UI_SSD1306_PICOPIXEL_MESSAGES == 1 - if (_picopixel_message) { - // Include advances for spaces; GFX's ink bounds omit trailing whitespace. - uint16_t width = 0; - while (*str) { - const uint8_t c = picopixelCharacter((uint8_t)*str++); - width += pgm_read_byte(&PicopixelGlyphs[c - 0x20].xAdvance); - } - return width; - } -#endif int16_t x1, y1; uint16_t w, h; display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); diff --git a/src/helpers/ui/SSD1306Display.h b/src/helpers/ui/SSD1306Display.h index a732e477..d753d9d7 100644 --- a/src/helpers/ui/SSD1306Display.h +++ b/src/helpers/ui/SSD1306Display.h @@ -27,9 +27,6 @@ class SSD1306Display : public DisplayDriver { #endif uint8_t _color; RefCountedDigitalPin* _peripher_power; -#if defined(UI_SSD1306_PICOPIXEL_MESSAGES) && UI_SSD1306_PICOPIXEL_MESSAGES == 1 - bool _picopixel_message = false; -#endif bool i2c_probe(TwoWire& wire, uint8_t addr); void applyRotation(); @@ -54,9 +51,6 @@ public: void setColor(ColorVal c) override; void setCursor(int x, int y) override; void print(const char* str) override; -#if defined(UI_SSD1306_PICOPIXEL_MESSAGES) && UI_SSD1306_PICOPIXEL_MESSAGES == 1 - void printWordWrap(const char* str, int max_width) override; -#endif void fillRect(int x, int y, int w, int h) override; void drawRect(int x, int y, int w, int h) override; void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; diff --git a/src/helpers/ui/ST7735Display.cpp b/src/helpers/ui/ST7735Display.cpp index c83ee71b..34f4962c 100644 --- a/src/helpers/ui/ST7735Display.cpp +++ b/src/helpers/ui/ST7735Display.cpp @@ -632,7 +632,10 @@ void ST7735Display::print(const char* str) { void ST7735Display::fillRect(int x, int y, int w, int h) { if (!spriteReady()) return; - sprite->fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, curr_color); + // Scale the edges, so adjacent 1px font cells meet at fractional scaling. + const int left = x*SCALE_X, top = y*SCALE_Y; + sprite->fillRect(left, top, int((x+w)*SCALE_X)-left, + int((y+h)*SCALE_Y)-top, curr_color); } void ST7735Display::drawRect(int x, int y, int w, int h) { diff --git a/test/fixtures/ssd1306_picopixel/render.cpp b/test/fixtures/ssd1306_picopixel/render.cpp index 2ac6e7f6..f0cbc381 100644 --- a/test/fixtures/ssd1306_picopixel/render.cpp +++ b/test/fixtures/ssd1306_picopixel/render.cpp @@ -1,9 +1,11 @@ #include +#include +#include #include #include #include #include -#include + void save(const char* file) { auto& c=*Adafruit_SSD1306::last; std::ofstream f(file); @@ -15,56 +17,33 @@ int main(int argc, char** argv) { assert(d.begin()); auto& c=*Adafruit_SSD1306::last; const char* message="GIANT KILLER: Out on the Mercerwood mesh today, just the V4 and a small battery Checking the smaller font so the rest of this message is visible. 0123456789 END"; + assert(std::string(message).size()==160); d.startFrame(); d.setCursor(0,0); d.print("Message 3/29"); d.setCursor(86,0); d.print("1m ago"); d.drawRect(0,11,128,1); - d.setCursor(0,14); d.print("Ch 0 Public [4h]:"); - d.setCursor(0,25); -#if UI_SSD1306_PICOPIXEL_MESSAGES == 1 - d.printWordWrap(message,128); +#if UI_SMALL_MESSAGE_FONT == 1 + mesh::ui::drawSmallMessageBody(d,"Ch 0 Public [4h]:",message); #else - d.print(std::string(message,77).c_str()); + d.setCursor(0,14); d.print("Ch 0 Public [4h]:"); + d.setCursor(0,25); d.print(std::string(message,77).c_str()); #endif if (argc > 1) save(argv[1]); -#if UI_SSD1306_PICOPIXEL_MESSAGES == 1 - std::string shown; - for (const auto& g:c.glyphs) { - shown += g.c; - if (g.w && g.h) assert(g.x>=0 && g.x+g.w<=128 && g.y>=25 && g.y+g.h<=64); - } - assert(shown==message); +#if UI_SMALL_MESSAGE_FONT == 1 assert(c.outside==0); - assert(d.getTextWidth("ABC")==18); // regular font restored - d.setCursor(0,0); d.print("Regular font"); - assert(d.getTextWidth("ABC")==18); - int checks=1; - for (int rotation:{0,90,180,270}) for (int top:{0,25,55,58,59,63,64}) { - d.setRotationDegrees(rotation); - d.startFrame(); d.setCursor(0,top); - d.printWordWrap(std::string(160,'W').c_str(),d.width()); - for (const auto& g:c.glyphs) { - if (g.w && g.h) assert(g.x>=0 && g.x+g.w<=d.width() && g.y>=top && g.y+g.h<=d.height()); - } - if (!c.glyphs.empty()) { - std::string row; - for (const auto& g:c.glyphs) row += g.c; - while (!row.empty() && row.back()==' ') row.pop_back(); - assert(row==std::string(160, 'W') || (row.size()>=3 && row.substr(row.size()-3)=="...")); - } - assert(c.outside==0); - ++checks; - } - d.setRotationDegrees(0); + assert(d.getTextWidth("ABC")==18); // regular driver font untouched + mesh::ui::Pixel5Text text(d); + GFXcanvas1 reference(128,64); + reference.setFont(&Picopixel); + reference.setTextColor(1); for (int ch=32;ch<=126;ch++) { - d.startFrame(); d.setCursor(0,25); - d.printWordWrap(std::string(160,char(ch)).c_str(),128); - assert(c.outside==0); - ++checks; + d.startFrame(); text.setCursor(3,25); + text.print(std::string(1,char(ch)).c_str()); + reference.fillScreen(0); reference.setCursor(3,29); reference.write(ch); + for (int y=0;y<64;y++) for (int x=0;x<128;x++) + assert(c.getPixel(x,y)==reference.getPixel(x,y)); + assert(text.getTextWidth(std::string(1,char(ch)).c_str())==PicopixelGlyphs[ch-32].xAdvance); } - d.startFrame(); d.setCursor(0,25); - d.printWordWrap("A\xDB" "B",128); - assert(c.glyphs.size()==3 && c.glyphs[1].c=='?'); - std::cout << "Checks: " << checks+1 << "; sample characters: " << shown.size() << "; sample: " << shown << '\n'; + std::cout << "95 glyphs match Adafruit GFX Picopixel pixel for pixel; normal font preserved\n"; #endif } diff --git a/test/test_display_driver/test_display_driver.cpp b/test/test_display_driver/test_display_driver.cpp index f407d2a4..f52f67d5 100644 --- a/test/test_display_driver/test_display_driver.cpp +++ b/test/test_display_driver/test_display_driver.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -133,6 +134,66 @@ TEST(DisplayDriver, EllipsizesOnlyAtUTF8CodepointBoundaries) { EXPECT_TRUE(isValidUTF8(display.printed.c_str())); } +TEST(Pixel5Text, CapitalsAreFivePixelsAndNormalFontIsUntouched) { + TestDisplay display(128, 64, 6); + mesh::ui::Pixel5Text text(display); + text.setCursor(0, 0); + text.print("A"); + const char* rows[] = {"010", "101", "111", "101", "101"}; + bool pixels[5][3] = {}; + for (const auto& pixel : display.fills) { + ASSERT_GE(pixel.x, 0); ASSERT_LT(pixel.x, 3); + ASSERT_GE(pixel.y, 0); ASSERT_LT(pixel.y, 5); + pixels[pixel.y][pixel.x] = true; + } + for (int y = 0; y < 5; ++y) for (int x = 0; x < 3; ++x) + EXPECT_EQ(rows[y][x] == '1', pixels[y][x]); + EXPECT_EQ(12, text.getTextWidth("ABC")); + EXPECT_EQ(18, display.getTextWidth("ABC")); +} + +TEST(Pixel5Text, CompactOriginAllowsSixCompleteMessageRows) { + TestDisplay display(128, 64); + mesh::ui::drawSmallMessageBody(display, std::string(61, 'W').c_str(), + std::string(160, 'W').c_str()); + ASSERT_FALSE(display.fills.empty()); + bool final_dots[3] = {}; + for (const auto& pixel : display.fills) { + EXPECT_GE(pixel.x, 0); EXPECT_LT(pixel.x, 128); + EXPECT_GE(pixel.y, 14); EXPECT_LT(pixel.y, 64); + // The origin stays in its own line; its long name cannot cover the body. + EXPECT_TRUE(pixel.y < 20 || pixel.y >= 21); + for (int i = 0; i < 3; ++i) + if (pixel.y == 60 && pixel.x == 120 + 2 * i) final_dots[i] = true; + } + for (bool dot : final_dots) EXPECT_TRUE(dot); + EXPECT_EQ(5, mesh::ui::smallMessageLineCount(64, 25)); + EXPECT_EQ(6, mesh::ui::smallMessageLineCount(64, 21)); + EXPECT_EQ(3, mesh::ui::smallMessageLineCount(40, 17)); + EXPECT_EQ(0, mesh::ui::smallMessageLineCount(64, 59)); +} + +TEST(Pixel5Text, GlyphsStayInsideTinyAndRotatedScreens) { + for (auto dimensions : {std::pair{72, 40}, {64, 128}, {128, 64}}) { + TestDisplay display(dimensions.first, dimensions.second); + mesh::ui::Pixel5Text text(display); + for (int c = 32; c <= 126; ++c) { + display.fills.clear(); + text.setCursor(0, display.height() - 6); + text.printWordWrap(std::string(160, char(c)).c_str(), display.width()); + for (const auto& pixel : display.fills) { + EXPECT_GE(pixel.x, 0); EXPECT_LT(pixel.x, display.width()); + EXPECT_GE(pixel.y, display.height() - 6); + EXPECT_LT(pixel.y, display.height()); + } + } + display.fills.clear(); + text.setCursor(0, display.height() - 5); + text.printWordWrap("must not start a clipped final line", display.width()); + EXPECT_TRUE(display.fills.empty()); + } +} + TEST(DisplayDriver, QrCodeIsOptionalByDefault) { TestDisplay display; EXPECT_FALSE(display.drawQrCode("WIFI:T:nopass;S:MC-Set-90DF;;", diff --git a/test/test_firmware_ram.py b/test/test_firmware_ram.py index 7852e446..00e5f8ae 100644 --- a/test/test_firmware_ram.py +++ b/test/test_firmware_ram.py @@ -67,7 +67,7 @@ def esp_fixture(path, modern=False, fragmented=False): class FirmwareRamTest(unittest.TestCase): def test_longer_display_previews_reserve_heap_and_contiguous_history(self): - defines = {"DISPLAY_CLASS": "SSD1306Display"} + defines = {"DISPLAY_CLASS": "SSD1306Display", "UI_SMALL_MESSAGE_FONT": 0} base = ram.requirements("ESP32_PLATFORM", defines, "v4_companion") expanded = ram.requirements("ESP32_PLATFORM", { **defines, "UI_MSG_PREVIEW_SIZE": 161, @@ -80,6 +80,10 @@ class FirmwareRamTest(unittest.TestCase): **defines, "UI_MSG_PREVIEW_SIZE": 32, }, "v4_companion") self.assertEqual(smaller, base) + default = ram.requirements("ESP32_PLATFORM", { + "DISPLAY_CLASS": "SSD1306Display", + }, "v4_companion") + self.assertEqual(default, expanded) def test_published_image_tables_match_elf_and_use_its_own_reservations(self): with tempfile.TemporaryDirectory() as temp: @@ -166,7 +170,8 @@ int main() { subprocess.run([str(binary)], check=True) def test_t096_release_fails_and_exact_boundary_passes(self): - flags = {"COMPANION_RADIO_FULL": 1, "DISPLAY_CLASS": "ST7735Display", "BLE_PIN_CODE": 123456} + flags = {"COMPANION_RADIO_FULL": 1, "DISPLAY_CLASS": "ST7735Display", + "BLE_PIN_CODE": 123456, "UI_SMALL_MESSAGE_FONT": 0} with tempfile.TemporaryDirectory() as temp, contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()): path = Path(temp) / "firmware.elf" for available, accepted in ((54724, False), (73727, False), (73728, True), (74060, True)): diff --git a/test/test_ssd1306_picopixel.py b/test/test_ssd1306_picopixel.py index b20f583f..ff649907 100644 --- a/test/test_ssd1306_picopixel.py +++ b/test/test_ssd1306_picopixel.py @@ -3,8 +3,7 @@ Build a V4 environment first to install Adafruit GFX, or point MESHCORE_GFX_LIBRARY to that library's directory. Only the hardware panel, -Arduino strings and Print glue are replaced; font metrics and drawing come -from the library used by the firmware. +Arduino strings and Print glue are replaced; the shared pixel renderer is compared with the original Adafruit font. """ import os @@ -35,7 +34,7 @@ class SSD1306PicopixelTest(unittest.TestCase): result = subprocess.run([ "c++", "-std=c++17", "-g", "-fsanitize=address,undefined", "-DARDUINO=10819", - f"-DUI_SSD1306_PICOPIXEL_MESSAGES={enabled}", + f"-DUI_SMALL_MESSAGE_FONT={enabled}", "-I", str(FIXTURE / "mocks"), "-I", str(ROOT / "src"), "-I", str(library), str(ROOT / "src/helpers/ui/SSD1306Display.cpp"),