feat(display): add R8 portrait observer layouts

This commit is contained in:
agessaman
2026-08-28 09:21:38 -07:00
parent 85fd9b09be
commit fc361ca94b
11 changed files with 482 additions and 12 deletions
+82 -5
View File
@@ -3,6 +3,10 @@
#include <Arduino.h>
#include <helpers/CommonCLI.h>
#ifdef DISPLAY_REDRAW_ON_CHANGE
#include <helpers/ui/DisplayFrameSignature.h>
#endif
#ifndef USER_BTN_PRESSED
#define USER_BTN_PRESSED LOW
#endif
@@ -40,6 +44,9 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi
_started_at = millis();
_node_prefs = node_prefs;
_display->turnOn();
#ifdef DISPLAY_REDRAW_ON_CHANGE
_frame_valid = false;
#endif
#if defined(PIN_USER_BTN) && defined(DISPLAY_CLASS)
user_btn.begin();
@@ -163,6 +170,54 @@ void UITask::renderCurrScreen() {
}
}
#ifdef DISPLAY_REDRAW_ON_CHANGE
uint32_t UITask::getFrameSignature() {
uint32_t signature = DisplayFrameSignature::INITIAL;
char tmp[80];
if (millis() < _started_at + BOOT_SCREEN_MILLIS) {
signature = DisplayFrameSignature::append(signature, "boot");
return DisplayFrameSignature::append(signature, _version_info);
}
if (_powering_off_at > 0) {
return DisplayFrameSignature::append(signature, "powering-off");
}
#ifdef WITH_WEBCONFIG
if (WebConfigServer::isRebootPending()) {
return DisplayFrameSignature::append(signature, "rebooting");
}
char wc_ssid[33], wc_ip[16];
if (WebConfigServer::getSetupInfo(wc_ssid, sizeof(wc_ssid), wc_ip, sizeof(wc_ip))) {
signature = DisplayFrameSignature::append(signature, "setup");
signature = DisplayFrameSignature::append(signature, wc_ssid);
return DisplayFrameSignature::append(signature, wc_ip);
}
#endif
signature = DisplayFrameSignature::append(signature, "home");
signature = DisplayFrameSignature::append(signature, _node_prefs->node_name);
snprintf(tmp, sizeof(tmp), "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf);
signature = DisplayFrameSignature::append(signature, tmp);
snprintf(tmp, sizeof(tmp), "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr);
signature = DisplayFrameSignature::append(signature, tmp);
#ifdef WITH_MQTT_BRIDGE
if (WiFi.status() == WL_CONNECTED) {
IPAddress ip = WiFi.localIP();
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
signature = DisplayFrameSignature::append(signature, tmp);
} else {
signature = DisplayFrameSignature::append(signature, "wifi-disconnected");
}
#endif
return signature;
}
#endif
void UITask::loop() {
#if defined(PIN_USER_BTN) && defined(DISPLAY_CLASS)
int ev = user_btn.check();
@@ -171,6 +226,9 @@ void UITask::loop() {
// TODO: any action ?
} else {
_display->turnOn();
#ifdef DISPLAY_REDRAW_ON_CHANGE
_frame_valid = false;
#endif
}
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
@@ -184,21 +242,40 @@ void UITask::loop() {
// While the setup portal is up there's no user button to wake the screen
// reliably - keep it on so the join instructions stay visible.
if (WebConfigServer::getSetupInfo(NULL, 0, NULL, 0)) {
if (!_display->isOn()) _display->turnOn();
if (!_display->isOn()) {
_display->turnOn();
#ifdef DISPLAY_REDRAW_ON_CHANGE
_frame_valid = false;
#endif
}
_auto_off = millis() + AUTO_OFF_MILLIS;
}
#endif
if (_display->isOn()) {
if (millis() >= _next_refresh) {
_display->startFrame();
renderCurrScreen();
_display->endFrame();
bool redraw = true;
#ifdef DISPLAY_REDRAW_ON_CHANGE
uint32_t frame_signature = getFrameSignature();
redraw = !_frame_valid || frame_signature != _last_frame_signature;
#endif
if (redraw) {
_display->startFrame();
renderCurrScreen();
_display->endFrame();
#ifdef DISPLAY_REDRAW_ON_CHANGE
_last_frame_signature = frame_signature;
_frame_valid = true;
#endif
}
_next_refresh = millis() + 1000; // refresh every second
_next_refresh = millis() + 1000; // check for visible changes every second
}
if (millis() > _auto_off) {
_display->turnOff();
#ifdef DISPLAY_REDRAW_ON_CHANGE
_frame_valid = false;
#endif
}
}
+8 -1
View File
@@ -13,10 +13,17 @@ class UITask {
unsigned long _powering_off_at = 0;
unsigned long _started_at = 0;
#ifdef DISPLAY_REDRAW_ON_CHANGE
uint32_t _last_frame_signature = 0;
bool _frame_valid = false;
uint32_t getFrameSignature();
#endif
void renderCurrScreen();
public:
UITask(mesh::MainBoard& board, DisplayDriver& display) : _board(&board), _display(&display) { _next_read = _next_refresh = 0; }
void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version);
void loop();
};
};
+78 -5
View File
@@ -2,6 +2,10 @@
#include <Arduino.h>
#include <helpers/CommonCLI.h>
#ifdef DISPLAY_REDRAW_ON_CHANGE
#include <helpers/ui/DisplayFrameSignature.h>
#endif
#ifndef USER_BTN_PRESSED
#define USER_BTN_PRESSED LOW
#endif
@@ -36,6 +40,9 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi
_auto_off = millis() + AUTO_OFF_MILLIS;
_node_prefs = node_prefs;
_display->turnOn();
#ifdef DISPLAY_REDRAW_ON_CHANGE
_frame_valid = false;
#endif
// strip off dash and commit hash by changing dash to null terminator
// e.g: v1.2.3-abcdef -> v1.2.3
@@ -144,6 +151,50 @@ void UITask::renderCurrScreen() {
}
}
#ifdef DISPLAY_REDRAW_ON_CHANGE
uint32_t UITask::getFrameSignature() {
uint32_t signature = DisplayFrameSignature::INITIAL;
char tmp[80];
if (millis() < BOOT_SCREEN_MILLIS) {
signature = DisplayFrameSignature::append(signature, "boot");
return DisplayFrameSignature::append(signature, _version_info);
}
#ifdef WITH_WEBCONFIG
if (WebConfigServer::isRebootPending()) {
return DisplayFrameSignature::append(signature, "rebooting");
}
char wc_ssid[33], wc_ip[16];
if (WebConfigServer::getSetupInfo(wc_ssid, sizeof(wc_ssid), wc_ip, sizeof(wc_ip))) {
signature = DisplayFrameSignature::append(signature, "setup");
signature = DisplayFrameSignature::append(signature, wc_ssid);
return DisplayFrameSignature::append(signature, wc_ip);
}
#endif
signature = DisplayFrameSignature::append(signature, "home");
signature = DisplayFrameSignature::append(signature, _node_prefs->node_name);
snprintf(tmp, sizeof(tmp), "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf);
signature = DisplayFrameSignature::append(signature, tmp);
snprintf(tmp, sizeof(tmp), "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr);
signature = DisplayFrameSignature::append(signature, tmp);
#ifdef WITH_MQTT_BRIDGE
if (WiFi.status() == WL_CONNECTED) {
IPAddress ip = WiFi.localIP();
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
signature = DisplayFrameSignature::append(signature, tmp);
} else {
signature = DisplayFrameSignature::append(signature, "wifi-disconnected");
}
#endif
return signature;
}
#endif
void UITask::loop() {
#ifdef PIN_USER_BTN
if (millis() >= _next_read) {
@@ -154,6 +205,9 @@ void UITask::loop() {
// TODO: any action ?
} else {
_display->turnOn();
#ifdef DISPLAY_REDRAW_ON_CHANGE
_frame_valid = false;
#endif
}
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
}
@@ -167,21 +221,40 @@ void UITask::loop() {
// While the setup portal is up there's no user button to wake the screen
// reliably - keep it on so the join instructions stay visible.
if (WebConfigServer::getSetupInfo(NULL, 0, NULL, 0)) {
if (!_display->isOn()) _display->turnOn();
if (!_display->isOn()) {
_display->turnOn();
#ifdef DISPLAY_REDRAW_ON_CHANGE
_frame_valid = false;
#endif
}
_auto_off = millis() + AUTO_OFF_MILLIS;
}
#endif
if (_display->isOn()) {
if (millis() >= _next_refresh) {
_display->startFrame();
renderCurrScreen();
_display->endFrame();
bool redraw = true;
#ifdef DISPLAY_REDRAW_ON_CHANGE
uint32_t frame_signature = getFrameSignature();
redraw = !_frame_valid || frame_signature != _last_frame_signature;
#endif
if (redraw) {
_display->startFrame();
renderCurrScreen();
_display->endFrame();
#ifdef DISPLAY_REDRAW_ON_CHANGE
_last_frame_signature = frame_signature;
_frame_valid = true;
#endif
}
_next_refresh = millis() + 1000; // refresh every second
_next_refresh = millis() + 1000; // check for visible changes every second
}
if (millis() > _auto_off) {
_display->turnOff();
#ifdef DISPLAY_REDRAW_ON_CHANGE
_frame_valid = false;
#endif
}
}
}
+8 -1
View File
@@ -10,10 +10,17 @@ class UITask {
NodePrefs* _node_prefs;
char _version_info[32];
#ifdef DISPLAY_REDRAW_ON_CHANGE
uint32_t _last_frame_signature = 0;
bool _frame_valid = false;
uint32_t getFrameSignature();
#endif
void renderCurrScreen();
public:
UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; }
void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version);
void loop();
};
};
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <stdint.h>
namespace DisplayFrameSignature {
static constexpr uint32_t INITIAL = 2166136261u;
inline uint32_t append(uint32_t signature, const char *text) {
if (text) {
while (*text) {
signature ^= static_cast<uint8_t>(*text++);
signature *= 16777619u;
}
}
// Separate adjacent fields so { "ab", "c" } differs from { "a", "bc" }.
signature ^= 0xFFu;
signature *= 16777619u;
return signature;
}
} // namespace DisplayFrameSignature
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <stdint.h>
namespace DisplayViewport {
struct Geometry {
int16_t logical_width;
int16_t logical_height;
int16_t physical_width;
int16_t physical_height;
int16_t mapX(int16_t x) const {
return static_cast<int16_t>((static_cast<int32_t>(x) * physical_width) / logical_width);
}
int16_t mapY(int16_t y) const {
return static_cast<int16_t>((static_cast<int32_t>(y) * physical_height) / logical_height);
}
uint16_t spanX(int16_t x, int16_t width) const { return static_cast<uint16_t>(mapX(x + width) - mapX(x)); }
uint16_t spanY(int16_t y, int16_t height) const {
return static_cast<uint16_t>(mapY(y + height) - mapY(y));
}
uint16_t logicalWidthForPhysical(uint16_t width) const {
return static_cast<uint16_t>((static_cast<uint32_t>(width) * logical_width + physical_width - 1) /
physical_width);
}
};
inline uint8_t selectTextScale(uint16_t width_at_scale_one, uint8_t preferred_scale, uint8_t minimum_scale,
uint16_t available_width) {
if (static_cast<uint32_t>(width_at_scale_one) * preferred_scale <= available_width) {
return preferred_scale;
}
return minimum_scale;
}
} // namespace DisplayViewport
+134
View File
@@ -1,5 +1,9 @@
#include "ST7789LCDDisplay.h"
#ifdef ST7789_PORTRAIT_PROFILE
#include "DisplayViewport.h"
#endif
#ifndef PIN_TFT_MISO
#define PIN_TFT_MISO -1
#endif
@@ -19,6 +23,16 @@
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#ifdef ST7789_PORTRAIT_PROFILE
#ifndef ST7789_PORTRAIT_TEXT_SCALE
#define ST7789_PORTRAIT_TEXT_SCALE 2
#endif
static DisplayViewport::Geometry portraitViewport(int16_t physical_width, int16_t physical_height) {
return {128, 64, physical_width, physical_height};
}
#endif
bool ST7789LCDDisplay::i2c_probe(TwoWire& wire, uint8_t addr) {
return true;
}
@@ -64,7 +78,13 @@ bool ST7789LCDDisplay::begin() {
display.fillScreen(ST77XX_BLACK);
display.setTextColor(ST77XX_WHITE);
#ifdef ST7789_PORTRAIT_PROFILE
_logical_text_size = 1;
display.setTextSize(ST7789_PORTRAIT_TEXT_SCALE);
display.setTextWrap(false);
#else
display.setTextSize(2 * DISPLAY_SCALE_X);
#endif
display.cp437(true); // Use full 256 char 'Code Page 437' font
#ifdef HELTEC_V4_R8_TFT
@@ -113,12 +133,22 @@ void ST7789LCDDisplay::clear() {
void ST7789LCDDisplay::startFrame(ColorVal bkg) {
display.fillScreen(bkg);
display.setTextColor(_color = UIColor::primary_txt);
#ifdef ST7789_PORTRAIT_PROFILE
_logical_text_size = 1;
display.setTextSize(ST7789_PORTRAIT_TEXT_SCALE);
#else
display.setTextSize(1 * DISPLAY_SCALE_X); // This one affects size of Please wait... message
#endif
display.cp437(true); // Use full 256 char 'Code Page 437' font
}
void ST7789LCDDisplay::setTextSize(int sz) {
#ifdef ST7789_PORTRAIT_PROFILE
_logical_text_size = sz > 0 ? static_cast<uint8_t>(sz) : 1;
display.setTextSize(_logical_text_size * ST7789_PORTRAIT_TEXT_SCALE);
#else
display.setTextSize(sz * DISPLAY_SCALE_X);
#endif
}
void ST7789LCDDisplay::setColor(ColorVal c) {
@@ -126,24 +156,70 @@ void ST7789LCDDisplay::setColor(ColorVal c) {
}
void ST7789LCDDisplay::setCursor(int x, int y) {
#ifdef ST7789_PORTRAIT_PROFILE
DisplayViewport::Geometry viewport = portraitViewport(display.width(), display.height());
display.setCursor(viewport.mapX(x), viewport.mapY(y));
#else
display.setCursor(x * DISPLAY_SCALE_X, y * DISPLAY_SCALE_Y);
#endif
}
void ST7789LCDDisplay::print(const char* str) {
#ifdef ST7789_PORTRAIT_PROFILE
int16_t cursor_x = display.getCursorX();
if (cursor_x < 0) {
cursor_x = 0;
display.setCursor(cursor_x, display.getCursorY());
}
if (cursor_x >= display.width()) return;
printFitted(str, static_cast<uint16_t>(display.width() - cursor_x));
#else
display.print(str);
#endif
}
void ST7789LCDDisplay::fillRect(int x, int y, int w, int h) {
#ifdef ST7789_PORTRAIT_PROFILE
DisplayViewport::Geometry viewport = portraitViewport(display.width(), display.height());
display.fillRect(viewport.mapX(x), viewport.mapY(y), viewport.spanX(x, w), viewport.spanY(y, h), _color);
#else
display.fillRect(x * DISPLAY_SCALE_X, y * DISPLAY_SCALE_Y, w * DISPLAY_SCALE_X, h * DISPLAY_SCALE_Y, _color);
#endif
}
void ST7789LCDDisplay::drawRect(int x, int y, int w, int h) {
#ifdef ST7789_PORTRAIT_PROFILE
DisplayViewport::Geometry viewport = portraitViewport(display.width(), display.height());
display.drawRect(viewport.mapX(x), viewport.mapY(y), viewport.spanX(x, w), viewport.spanY(y, h), _color);
#else
display.drawRect(x * DISPLAY_SCALE_X, y * DISPLAY_SCALE_Y, w * DISPLAY_SCALE_X, h * DISPLAY_SCALE_Y, _color);
#endif
}
void ST7789LCDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
uint8_t byteWidth = (w + 7) / 8;
#ifdef ST7789_PORTRAIT_PROFILE
DisplayViewport::Geometry viewport = portraitViewport(display.width(), display.height());
int16_t physical_y = viewport.mapY(y);
for (int j = 0; j < h; j++) {
// Scale both bitmap axes from the logical X ratio so logo pixels stay square.
int16_t y0 = physical_y + viewport.mapX(j);
int16_t y1 = physical_y + viewport.mapX(j + 1);
for (int i = 0; i < w; i++) {
uint8_t byte = bits[j * byteWidth + i / 8];
bool pixelOn = byte & (0x80 >> (i & 7));
if (pixelOn) {
int16_t x0 = viewport.mapX(x + i);
int16_t x1 = viewport.mapX(x + i + 1);
display.fillRect(x0, y0, x1 - x0, y1 - y0, _color);
}
}
}
#else
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
uint8_t byte = bits[j * byteWidth + i / 8];
@@ -158,16 +234,74 @@ void ST7789LCDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h)
}
}
}
#endif
}
uint16_t ST7789LCDDisplay::getTextWidth(const char* str) {
#ifdef ST7789_PORTRAIT_PROFILE
uint8_t physical_scale = selectTextScale(str, display.width());
uint16_t physical_width = measureTextWidth(str, physical_scale);
if (physical_width > display.width()) physical_width = display.width();
DisplayViewport::Geometry viewport = portraitViewport(display.width(), display.height());
return viewport.logicalWidthForPhysical(physical_width);
#else
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
return w / DISPLAY_SCALE_X;
#endif
}
#ifdef ST7789_PORTRAIT_PROFILE
uint16_t ST7789LCDDisplay::measureTextWidth(const char* str, uint8_t physical_scale) {
int16_t x1, y1;
uint16_t w, h;
display.setTextSize(physical_scale);
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
return w;
}
uint8_t ST7789LCDDisplay::selectTextScale(const char* str, uint16_t available_width) {
uint16_t width_at_scale_one = measureTextWidth(str, 1);
uint8_t preferred_scale = _logical_text_size * ST7789_PORTRAIT_TEXT_SCALE;
return DisplayViewport::selectTextScale(width_at_scale_one, preferred_scale, _logical_text_size,
available_width);
}
void ST7789LCDDisplay::printFitted(const char* str, uint16_t available_width) {
if (!str || available_width == 0) return;
uint8_t physical_scale = selectTextScale(str, available_width);
if (measureTextWidth(str, physical_scale) <= available_width) {
display.print(str);
return;
}
static const char* ellipsis = "...";
uint16_t ellipsis_width = measureTextWidth(ellipsis, physical_scale);
if (ellipsis_width > available_width) return;
char fitted[256];
size_t len = strlen(str);
if (len > sizeof(fitted) - 4) len = sizeof(fitted) - 4;
memcpy(fitted, str, len);
fitted[len] = 0;
while (len > 0 && measureTextWidth(fitted, physical_scale) + ellipsis_width > available_width) {
--len;
while (len > 0 && (static_cast<uint8_t>(fitted[len]) & 0xC0) == 0x80)
--len;
fitted[len] = 0;
}
memcpy(fitted + len, ellipsis, 4);
display.setTextSize(physical_scale);
display.print(fitted);
}
#endif
void ST7789LCDDisplay::endFrame() {
// display.display();
}
+8
View File
@@ -16,6 +16,14 @@ class ST7789LCDDisplay : public DisplayDriver {
uint16_t _color;
RefCountedDigitalPin* _peripher_power;
#ifdef ST7789_PORTRAIT_PROFILE
uint8_t _logical_text_size = 1;
uint16_t measureTextWidth(const char* str, uint8_t physical_scale);
uint8_t selectTextScale(const char* str, uint16_t available_width);
void printFitted(const char* str, uint16_t available_width);
#endif
bool i2c_probe(TwoWire& wire, uint8_t addr);
public:
#ifdef USE_PIN_TFT
+1
View File
@@ -30,6 +30,7 @@ does not reflect the GoogleTest count — run the built binary directly
| `test_mqtt_topic_router` | `src/helpers/MQTTTopicRouter.h` | complete preset/custom topic-routing contract; MeshRank all types except raw; required identifiers; invalid inputs/slots; exact buffer boundaries |
| `test_mqtt_connection_policy` | `src/helpers/MQTTConnectionPolicy.h` | reconnect guard/backoff/stagger and breaker transitions; stable reset; JWT lifetime/renewal policy; exact timing boundaries and 32-bit `millis()` rollover; WiFi current-outage start sticky across STA reconnect attempts |
| `test_alert_fault_policy` | `src/helpers/AlertFaultPolicy.h` | WiFi/MQTT fault edge detector; `OutageSnapshot` (down / started_ms / initiating reason) fed to tick and `formatWifiAlert`; reason-8 reconnects change neither duration nor initiating reason; flap between status polls; down at `millis()==0`; packed 64-bit cross-task word; rate-limit floor and first-fire; 5 s poll cadence and `millis()` rollover |
| `test_display_viewport` | `src/helpers/ui/DisplayViewport.h`, `src/helpers/ui/DisplayFrameSignature.h` | logical-to-physical portrait mapping; fractional span coverage; fitted-width conversion; preferred/fallback text scaling; stable visible-frame change detection |
| `test_mqtt_packet_queue_policy` | `src/helpers/MQTTPacketQueuePolicy.h` | queue-full eviction; stale-disconnect flush; adaptive drain limits; bounded QoS0 retries; exact timing boundaries and 32-bit `millis()` rollover |
| `test_mqtt_packet_filter` | `src/helpers/MQTTPacketFilter.h` | per-slot 0-15 allowlist parsing/formatting, numeric and named spellings; exact bounds; membership; candidate/eligible split and retry-completion policy; pre-queue union gate; default-mask detection |
| `test_mqtt_runtime_buffer_lifecycle` | `src/helpers/MQTTRuntimeBufferLifecycle.h` | idempotent allocation/release; partial-allocation degradation; retry of only missing buffers |
@@ -0,0 +1,83 @@
#include "helpers/ui/DisplayFrameSignature.h"
#include "helpers/ui/DisplayViewport.h"
#include <gtest/gtest.h>
namespace {
const DisplayViewport::Geometry kPortrait{ 128, 64, 240, 320 };
} // namespace
TEST(DisplayViewport, MapsLogicalBoundsToPortraitPanel) {
EXPECT_EQ(0, kPortrait.mapX(0));
EXPECT_EQ(240, kPortrait.mapX(128));
EXPECT_EQ(0, kPortrait.mapY(0));
EXPECT_EQ(320, kPortrait.mapY(64));
}
TEST(DisplayViewport, MapsObserverRowsAcrossPortraitHeight) {
EXPECT_EQ(0, kPortrait.mapY(0));
EXPECT_EQ(70, kPortrait.mapY(14));
EXPECT_EQ(100, kPortrait.mapY(20));
EXPECT_EQ(120, kPortrait.mapY(24));
EXPECT_EQ(150, kPortrait.mapY(30));
EXPECT_EQ(200, kPortrait.mapY(40));
EXPECT_EQ(240, kPortrait.mapY(48));
EXPECT_EQ(250, kPortrait.mapY(50));
}
TEST(DisplayViewport, FractionalHorizontalSpansHaveNoGapsOrOverlaps) {
int16_t previous_end = 0;
int total_width = 0;
for (int x = 0; x < 128; ++x) {
int16_t start = kPortrait.mapX(x);
int16_t end = kPortrait.mapX(x + 1);
EXPECT_EQ(previous_end, start);
EXPECT_TRUE(end - start == 1 || end - start == 2);
total_width += end - start;
previous_end = end;
}
EXPECT_EQ(240, previous_end);
EXPECT_EQ(240, total_width);
}
TEST(DisplayViewport, ConvertsFittedPhysicalWidthBackToLogicalWidth) {
EXPECT_EQ(0, kPortrait.logicalWidthForPhysical(0));
EXPECT_EQ(64, kPortrait.logicalWidthForPhysical(120));
EXPECT_EQ(122, kPortrait.logicalWidthForPhysical(228));
EXPECT_EQ(128, kPortrait.logicalWidthForPhysical(240));
}
TEST(DisplayViewport, SelectsPreferredTextScaleOnlyWhenItFits) {
EXPECT_EQ(2, DisplayViewport::selectTextScale(114, 2, 1, 240));
EXPECT_EQ(1, DisplayViewport::selectTextScale(150, 2, 1, 240));
EXPECT_EQ(1, DisplayViewport::selectTextScale(192, 2, 1, 229));
EXPECT_EQ(1, DisplayViewport::selectTextScale(300, 2, 1, 240));
}
TEST(DisplayFrameSignature, ChangesWithVisibleContent) {
uint32_t initial = DisplayFrameSignature::INITIAL;
uint32_t home = DisplayFrameSignature::append(initial, "home");
EXPECT_EQ(home, DisplayFrameSignature::append(initial, "home"));
EXPECT_NE(home, DisplayFrameSignature::append(initial, "setup"));
EXPECT_NE(DisplayFrameSignature::append(home, "10.0.0.1"), DisplayFrameSignature::append(home, "10.0.0.2"));
}
TEST(DisplayFrameSignature, KeepsAdjacentFieldsDistinct) {
uint32_t first = DisplayFrameSignature::append(DisplayFrameSignature::INITIAL, "ab");
first = DisplayFrameSignature::append(first, "c");
uint32_t second = DisplayFrameSignature::append(DisplayFrameSignature::INITIAL, "a");
second = DisplayFrameSignature::append(second, "bc");
EXPECT_NE(first, second);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+16
View File
@@ -334,6 +334,7 @@ build_flags =
-D ESP32_CPU_FREQ=160
-D MQTT_WIFI_TX_POWER=WIFI_POWER_11dBm
-D WITH_SNMP=1
-D DISPLAY_REDRAW_ON_CHANGE=1
build_src_filter = ${heltec_v4_r8_tft.build_src_filter}
+<helpers/bridges/MQTTBridge.cpp>
+<helpers/MQTTMessageBuilder.cpp>
@@ -351,6 +352,13 @@ lib_deps =
paulstoffregen/Time@1.6.1
0neblock/SNMP_Agent
[env:heltec_v4_r8_tft_portrait_repeater_observer_mqtt]
extends = env:heltec_v4_r8_tft_repeater_observer_mqtt
build_flags =
${env:heltec_v4_r8_tft_repeater_observer_mqtt.build_flags}
-D ST7789_PORTRAIT_PROFILE=1
-D DISPLAY_ROTATION=2
[env:heltec_v4_r8_tft_room_server]
extends = heltec_v4_r8_tft
build_flags =
@@ -393,6 +401,7 @@ build_flags =
-D ESP32_CPU_FREQ=160
-D MQTT_WIFI_TX_POWER=WIFI_POWER_11dBm
-D WITH_SNMP=1
-D DISPLAY_REDRAW_ON_CHANGE=1
build_src_filter = ${heltec_v4_r8_tft.build_src_filter}
+<helpers/bridges/MQTTBridge.cpp>
+<helpers/MQTTMessageBuilder.cpp>
@@ -410,6 +419,13 @@ lib_deps =
paulstoffregen/Time@1.6.1
0neblock/SNMP_Agent
[env:heltec_v4_r8_tft_portrait_room_server_observer_mqtt]
extends = env:heltec_v4_r8_tft_room_server_observer_mqtt
build_flags =
${env:heltec_v4_r8_tft_room_server_observer_mqtt.build_flags}
-D ST7789_PORTRAIT_PROFILE=1
-D DISPLAY_ROTATION=2
[env:heltec_v4_r8_tft_terminal_chat]
extends = heltec_v4_r8_tft
build_flags =