From 6a3b8e31352b9f6d83a0d75784d26b180fc12e09 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Wed, 20 May 2026 21:53:40 +0200 Subject: [PATCH] feat: add runtime display rotation setting for e-ink builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds Settings > Display > Rotation (0°/90°/180°/270°) that persists to NodePrefs and is applied on startup. Falls back to compile-time DISPLAY_ROTATION when the field is missing from an older prefs file. Co-Authored-By: Claude Sonnet 4.6 --- examples/companion_radio/DataStore.cpp | 12 ++++++++++++ examples/companion_radio/NodePrefs.h | 3 ++- .../companion_radio/ui-new/SettingsScreen.h | 19 +++++++++++++++++++ examples/companion_radio/ui-new/UITask.cpp | 8 ++++++++ examples/companion_radio/ui-new/UITask.h | 1 + src/helpers/ui/DisplayDriver.h | 2 ++ src/helpers/ui/GxEPDDisplay.cpp | 6 ++++++ src/helpers/ui/GxEPDDisplay.h | 1 + 8 files changed, 51 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index afb9c748..e7bcab66 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -289,7 +289,18 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.clock_12h, sizeof(_prefs.clock_12h)); if (file.available()) { file.read((uint8_t *)&_prefs.use_lemon_font, sizeof(_prefs.use_lemon_font)); + if (file.available()) { + file.read((uint8_t *)&_prefs.display_rotation, sizeof(_prefs.display_rotation)); + } else { +#ifdef DISPLAY_ROTATION + _prefs.display_rotation = DISPLAY_ROTATION; +#endif + } } + } else { +#ifdef DISPLAY_ROTATION + _prefs.display_rotation = DISPLAY_ROTATION; +#endif } } } @@ -383,6 +394,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.auto_lock, sizeof(_prefs.auto_lock)); file.write((uint8_t *)&_prefs.clock_12h, sizeof(_prefs.clock_12h)); file.write((uint8_t *)&_prefs.use_lemon_font, sizeof(_prefs.use_lemon_font)); + file.write((uint8_t *)&_prefs.display_rotation, sizeof(_prefs.display_rotation)); file.close(); } diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 4f5b878a..0e1ba678 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -78,5 +78,6 @@ struct NodePrefs { // persisted to file struct DmMelodyEntry { uint8_t prefix[4]; uint8_t slot; }; // slot: 0=global,1=melody1,2=melody2 static const int DM_MELODY_TABLE_MAX = 16; DmMelodyEntry dm_melody[DM_MELODY_TABLE_MAX]; - uint8_t use_lemon_font; // 0=default Adafruit font, 1=Lemon font (Unicode, pixel-accurate wrap) + uint8_t use_lemon_font; // 0=default Adafruit font, 1=Lemon font (Unicode, pixel-accurate wrap) + uint8_t display_rotation; // 0-3; only used on e-ink displays }; \ No newline at end of file diff --git a/examples/companion_radio/ui-new/SettingsScreen.h b/examples/companion_radio/ui-new/SettingsScreen.h index 5102c8b1..93f08fdd 100644 --- a/examples/companion_radio/ui-new/SettingsScreen.h +++ b/examples/companion_radio/ui-new/SettingsScreen.h @@ -15,6 +15,9 @@ class SettingsScreen : public UIScreen { CLOCK_SECONDS, CLOCK_FORMAT, FONT, +#if defined(EINK_DISPLAY_MODEL) + ROTATION, +#endif // Sound section SECTION_SOUND, BUZZER, @@ -321,6 +324,14 @@ class SettingsScreen : public UIScreen { display.print("Font"); display.setCursor(VAL_X, y); display.print((p && p->use_lemon_font) ? "Lemon" : "Default"); +#if defined(EINK_DISPLAY_MODEL) + } else if (item == ROTATION) { + display.print("Rotation"); + display.setCursor(VAL_X, y); + { static const char* ROT_LABELS[] = { "0deg", "90deg", "180deg", "270deg" }; + uint8_t r = p ? (p->display_rotation & 3) : 0; + display.print(ROT_LABELS[r]); } +#endif } else if (item == DM_FILTER) { display.print("DM"); display.setCursor(VAL_X, y); @@ -517,6 +528,14 @@ public: _dirty = true; return true; } +#if defined(EINK_DISPLAY_MODEL) + if (_selected == ROTATION && p && (left || right || enter)) { + p->display_rotation = (p->display_rotation + (left ? 3 : 1)) & 3; + _task->applyRotation(); + _dirty = true; + return true; + } +#endif if (_selected == DM_FILTER && p && (left || right || enter)) { p->dm_show_all = p->dm_show_all ? 0 : 1; _dirty = true; diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index ecf78738..b081b577 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -817,6 +817,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no applyBrightness(); applyFont(); + applyRotation(); } void UITask::gotoSettingsScreen() { @@ -1565,6 +1566,13 @@ void UITask::applyFont() { } } +void UITask::applyRotation() { + if (_display != NULL && _node_prefs != NULL) { + _display->setDisplayRotation(_node_prefs->display_rotation); + _next_refresh = 0; + } +} + void UITask::setBrightnessLevel(uint8_t level) { if (_node_prefs == NULL) return; if (level > 4) level = 4; diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 80b57c43..806f8401 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -159,6 +159,7 @@ public: void applyTxPower(); void applyGPSInterval(); void applyFont(); + void applyRotation(); uint32_t autoOffMillis() const { if (!_node_prefs || _node_prefs->auto_off_secs == 0) return 0; return (uint32_t)_node_prefs->auto_off_secs * 1000UL; diff --git a/src/helpers/ui/DisplayDriver.h b/src/helpers/ui/DisplayDriver.h index ffda2a12..d272a9db 100644 --- a/src/helpers/ui/DisplayDriver.h +++ b/src/helpers/ui/DisplayDriver.h @@ -7,6 +7,7 @@ class DisplayDriver { int _w, _h; protected: DisplayDriver(int w, int h) { _w = w; _h = h; } + void setDimensions(int w, int h) { _w = w; _h = h; } public: enum Color { DARK=0, LIGHT, RED, GREEN, BLUE, YELLOW, ORANGE }; // on b/w screen, colors will be !=0 synonym of light @@ -196,5 +197,6 @@ public: } virtual void setBrightness(uint8_t level) { } // level 0-4 (min to max), no-op default + virtual void setDisplayRotation(uint8_t rot) { } // 0-3, no-op for fixed-orientation displays virtual void endFrame() = 0; }; diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index c1bcfaa4..7c564aec 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -150,6 +150,12 @@ uint16_t GxEPDDisplay::getTextWidth(const char* str) { return w; } +void GxEPDDisplay::setDisplayRotation(uint8_t rot) { + display.setRotation(rot & 3); + setDimensions(display.width(), display.height()); + last_display_crc_value = -1; // force redraw on next endFrame +} + void GxEPDDisplay::endFrame() { uint32_t crc = display_crc.finalize(); if (crc != last_display_crc_value) { diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index b7f53e43..a5e7931b 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -84,5 +84,6 @@ public: 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; uint16_t getTextWidth(const char* str) override; + void setDisplayRotation(uint8_t rot) override; void endFrame() override; };