From 06ff5aa44ac592ee8e9858f0202f7e65d52088be Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Fri, 19 Jun 2026 14:25:51 -0400 Subject: [PATCH] Add SDK RTC and IMU hardware abstraction support Integrate SDK-based RTC and IMU backends as alternatives to direct hardware access. HalClock now attempts SDK RTC initialization and caches time values for reliability. HalTiltSensor adds SDK IMU backend with fallback logic. ClockOffsetActivity gains touch and swipe gesture support for field navigation. --- lib/hal/HalClock.cpp | 63 ++++++++++++++++++- lib/hal/HalClock.h | 3 + lib/hal/HalTiltSensor.cpp | 37 ++++++++++- lib/hal/HalTiltSensor.h | 5 ++ platformio.ini | 2 + .../settings/ClockOffsetActivity.cpp | 48 +++++++++++++- src/activities/settings/ClockOffsetActivity.h | 1 + .../settings/StatusBarSettingsActivity.cpp | 21 +++++++ 8 files changed, 177 insertions(+), 3 deletions(-) diff --git a/lib/hal/HalClock.cpp b/lib/hal/HalClock.cpp index 6e3c3a90..6115535d 100644 --- a/lib/hal/HalClock.cpp +++ b/lib/hal/HalClock.cpp @@ -18,8 +18,11 @@ static uint8_t bcdToDec(uint8_t bcd) { return ((bcd >> 4) * 10) + (bcd & 0x0F); static uint8_t decToBcd(uint8_t dec) { return ((dec / 10) << 4) | (dec % 10); } void HalClock::begin() { + _usesSdkRtc = false; if (!gpio.deviceIsX3()) { - _available = false; + _available = _sdkRtc.begin(); + _usesSdkRtc = _available; + LOG_INF("CLK", _available ? "SDK RTC found" : "RTC not found"); return; } @@ -57,6 +60,24 @@ bool HalClock::getTime(uint8_t& hour, uint8_t& minute) const { return true; } + if (_usesSdkRtc) { + Rtc::DateTime dt; + if (!_sdkRtc.now(dt)) { + if (!_hasCachedTime) return false; + _lastPollMs = now; + hour = _cachedHour; + minute = _cachedMinute; + return true; + } + _cachedHour = dt.hour; + _cachedMinute = dt.minute; + _lastPollMs = now; + _hasCachedTime = true; + hour = _cachedHour; + minute = _cachedMinute; + return true; + } + // Read 3 bytes starting at register 0x00: seconds, minutes, hours Wire.beginTransmission(I2C_ADDR_DS3231); Wire.write(DS3231_SEC_REG); @@ -131,6 +152,25 @@ bool HalClock::writeTimeToRTC(uint8_t hour, uint8_t minute, uint8_t second) { assert(hour < 24); assert(minute < 60); assert(second < 60); + if (_usesSdkRtc) { + Rtc::DateTime dt; + dt.hour = hour; + dt.minute = minute; + dt.second = second; + dt.year = 2000; + dt.month = 1; + dt.day = 1; + if (!_sdkRtc.set(dt)) { + LOG_ERR("CLK", "Failed to write time to SDK RTC"); + return false; + } + _lastPollMs = 0; + _cachedHour = hour; + _cachedMinute = minute; + _hasCachedTime = true; + return true; + } + Wire.beginTransmission(I2C_ADDR_DS3231); Wire.write(DS3231_SEC_REG); // Start at register 0x00 Wire.write(decToBcd(second)); // 0x00: Seconds @@ -168,6 +208,27 @@ bool HalClock::syncFromNTP() { struct tm timeinfo; gmtime_r(&now, &timeinfo); + if (_usesSdkRtc) { + Rtc::DateTime dt; + dt.year = static_cast(timeinfo.tm_year + 1900); + dt.month = static_cast(timeinfo.tm_mon + 1); + dt.day = static_cast(timeinfo.tm_mday); + dt.hour = static_cast(timeinfo.tm_hour); + dt.minute = static_cast(timeinfo.tm_min); + dt.second = static_cast(timeinfo.tm_sec); + dt.weekday = static_cast(timeinfo.tm_wday); + if (_sdkRtc.set(dt)) { + _lastPollMs = 0; + _cachedHour = dt.hour; + _cachedMinute = dt.minute; + _hasCachedTime = true; + LOG_INF("CLK", "RTC set to %04u-%02u-%02u %02u:%02u:%02u UTC", dt.year, dt.month, dt.day, dt.hour, + dt.minute, dt.second); + return true; + } + return false; + } + if (writeTimeToRTC(timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec)) { LOG_INF("CLK", "RTC set to %02d:%02d:%02d UTC", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); return true; diff --git a/lib/hal/HalClock.h b/lib/hal/HalClock.h index a0cc4825..20497332 100644 --- a/lib/hal/HalClock.h +++ b/lib/hal/HalClock.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include "HalGPIO.h" @@ -10,6 +11,8 @@ extern HalClock halClock; // Singleton class HalClock { bool _available = false; + bool _usesSdkRtc = false; + mutable Rtc _sdkRtc; mutable uint8_t _cachedHour = 0; mutable uint8_t _cachedMinute = 0; mutable bool _hasCachedTime = false; diff --git a/lib/hal/HalTiltSensor.cpp b/lib/hal/HalTiltSensor.cpp index b5808cca..a84447e2 100644 --- a/lib/hal/HalTiltSensor.cpp +++ b/lib/hal/HalTiltSensor.cpp @@ -26,6 +26,15 @@ bool HalTiltSensor::readReg(uint8_t reg, uint8_t* val) const { } bool HalTiltSensor::readGyro(float& gx, float& gy, float& gz) const { + if (_backend == Backend::SdkImu) { + Imu::Sample sample; + if (!_sdkImu.read(sample)) return false; + gx = sample.gx; + gy = sample.gy; + gz = sample.gz; + return true; + } + Wire.beginTransmission(_i2cAddr); Wire.write(REG_GX_L); // Start reading at Gyro X Low if (Wire.endTransmission(false) != 0) { @@ -52,11 +61,22 @@ bool HalTiltSensor::readGyro(float& gx, float& gy, float& gz) const { } void HalTiltSensor::begin() { + _backend = Backend::None; if (!gpio.deviceIsX3()) { - _available = false; + _available = _sdkImu.begin(); + if (!_available) { + LOG_ERR("GYR", "SDK IMU not found"); + return; + } + _backend = Backend::SdkImu; + _initMs = millis(); + _lastPollMs = millis(); + LOG_INF("GYR", "SDK IMU initialized"); return; } + _backend = Backend::Qmi8658; + // Try primary address, then alternate uint8_t whoami = 0; _i2cAddr = I2C_ADDR_QMI8658; @@ -89,6 +109,14 @@ bool HalTiltSensor::wake() { return false; } + if (_backend == Backend::SdkImu) { + _lastPollMs = millis(); + _lastTiltMs = millis(); + _wakeMs = millis(); + _isAwake = true; + return true; + } + // Wait for init to complete before waking if ((millis() - _initMs) < SLEEP_STABILIZE_MS) { return false; @@ -111,6 +139,13 @@ bool HalTiltSensor::deepSleep() { return false; } + if (_backend == Backend::SdkImu) { + clearPendingEvents(); + _inTilt = false; + _isAwake = false; + return true; + } + if ((millis() - _wakeMs) < SLEEP_STABILIZE_MS) { return false; } diff --git a/lib/hal/HalTiltSensor.h b/lib/hal/HalTiltSensor.h index ef132183..09576ca3 100644 --- a/lib/hal/HalTiltSensor.h +++ b/lib/hal/HalTiltSensor.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include "HalGPIO.h" @@ -18,8 +19,12 @@ class HalTiltSensor; extern HalTiltSensor halTiltSensor; // Singleton class HalTiltSensor { + enum class Backend : uint8_t { None, Qmi8658, SdkImu }; + bool _available = false; + Backend _backend = Backend::None; uint8_t _i2cAddr = 0; + mutable Imu _sdkImu; // Tilt gesture state machine bool _tiltForwardEvent = false; // Consumed by wasTiltedForward() diff --git a/platformio.ini b/platformio.ini index 5c94f4ac..34339a5e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -66,6 +66,8 @@ lib_deps = SDCardManager=symlink://freeink-sdk/libs/hardware/SDCardManager BoardConfig=symlink://freeink-sdk/libs/hardware/BoardConfig PowerManager=symlink://freeink-sdk/libs/hardware/PowerManager + Rtc=symlink://freeink-sdk/libs/hardware/Rtc + Imu=symlink://freeink-sdk/libs/hardware/Imu Icons=symlink://freeink-sdk/libs/assets/Icons bblanchon/ArduinoJson @ 7.4.2 ricmoo/QRCode @ 0.0.1 diff --git a/src/activities/settings/ClockOffsetActivity.cpp b/src/activities/settings/ClockOffsetActivity.cpp index 2000dacf..bc62402f 100644 --- a/src/activities/settings/ClockOffsetActivity.cpp +++ b/src/activities/settings/ClockOffsetActivity.cpp @@ -9,6 +9,7 @@ #include "CrossPointSettings.h" #include "MappedInputManager.h" +#include "components/TouchRegistry.h" #include "components/UITheme.h" #include "fontIds.h" @@ -108,14 +109,57 @@ void ClockOffsetActivity::adjustActiveField(int delta) { } } +void ClockOffsetActivity::selectRelativeField(const int delta) { + activeField = static_cast((activeField + delta + FIELD_COUNT) % FIELD_COUNT); +} + void ClockOffsetActivity::loop() { if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { finish(); return; } + switch (mappedInput.wasSwipe()) { + case MappedInputManager::SwipeDir::Up: + adjustActiveField(+1); + requestUpdate(); + return; + case MappedInputManager::SwipeDir::Down: + adjustActiveField(-1); + requestUpdate(); + return; + case MappedInputManager::SwipeDir::Left: + selectRelativeField(+1); + requestUpdate(); + return; + case MappedInputManager::SwipeDir::Right: + selectRelativeField(-1); + requestUpdate(); + return; + case MappedInputManager::SwipeDir::None: + break; + } + + int downId = -1; + if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < FIELD_COUNT) { + activeField = static_cast(downId); + requestUpdate(); + } + + int tappedId = -1; + if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0 && tappedId < FIELD_COUNT) { + const Field tappedField = static_cast(tappedId); + if (activeField == tappedField) { + adjustActiveField(+1); + } else { + activeField = tappedField; + } + requestUpdate(); + return; + } + if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { - activeField = static_cast((activeField + 1) % FIELD_COUNT); + selectRelativeField(+1); requestUpdate(); return; } @@ -183,6 +227,8 @@ void ClockOffsetActivity::render(RenderLock&&) { } const int textX = boxX + (boxWidth - widthOf(text)) / 2; renderer.drawText(UI_12_FONT_ID, textX, centreY, text, true, EpdFontFamily::BOLD); + TouchRegistry::getInstance().add(Rect{boxX - 4, centreY - 8, boxWidth + 8, fieldHeight + 16}, + static_cast(field), TouchRegistry::Item); }; drawField(signStr, x, signBoxW, FIELD_SIGN); diff --git a/src/activities/settings/ClockOffsetActivity.h b/src/activities/settings/ClockOffsetActivity.h index 8f25ee4c..f8a69a76 100644 --- a/src/activities/settings/ClockOffsetActivity.h +++ b/src/activities/settings/ClockOffsetActivity.h @@ -34,4 +34,5 @@ class ClockOffsetActivity final : public Activity { void saveToSettings() const; void adjustActiveField(int delta); void clampForSign(); + void selectRelativeField(int delta); }; diff --git a/src/activities/settings/StatusBarSettingsActivity.cpp b/src/activities/settings/StatusBarSettingsActivity.cpp index 2df09254..ce456a44 100644 --- a/src/activities/settings/StatusBarSettingsActivity.cpp +++ b/src/activities/settings/StatusBarSettingsActivity.cpp @@ -123,6 +123,27 @@ void StatusBarSettingsActivity::loop() { return; } + const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false); + const bool swiped = mappedInput.wasListScroll(selectedIndex, visibleItemCount, pageItems); + if (swiped) { + requestUpdate(); + return; + } + + int downId = -1; + if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < visibleItemCount) { + selectedIndex = downId; + requestUpdate(); + } + + int tappedId = -1; + if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0 && tappedId < visibleItemCount) { + selectedIndex = tappedId; + handleSelection(); + requestUpdate(); + return; + } + if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { handleSelection(); requestUpdate();