diff --git a/boards/gat562_mesh_evb_pro/src/gat562_board.cpp b/boards/gat562_mesh_evb_pro/src/gat562_board.cpp index 419d34b7..f5519c55 100644 --- a/boards/gat562_mesh_evb_pro/src/gat562_board.cpp +++ b/boards/gat562_mesh_evb_pro/src/gat562_board.cpp @@ -13,7 +13,10 @@ #include #include +#include +#include #include +#include #include #include @@ -67,6 +70,11 @@ struct GpsRuntimeState TinyGPSPlus parser{}; ::gps::GpsState data{}; ::gps::GnssStatus status{}; + struct GsvCollector + { + std::array<::gps::GnssSatInfo, ::gps::kMaxGnssSats> sats{}; + std::size_t count = 0; + }; uint32_t last_motion_ms = 0; uint32_t collection_interval_ms = 60000; uint32_t motion_idle_timeout_ms = 0; @@ -82,6 +90,13 @@ struct GpsRuntimeState uint32_t last_time_sync_log_ms = 0; uint32_t last_time_sync_epoch_logged = 0; uint32_t last_status_log_ms = 0; + std::array gsv{}; + std::array<::gps::GnssSatInfo, ::gps::kMaxGnssSats> sats{}; + std::array used_sat_ids{}; + char nmea_line[96] = {}; + std::size_t sat_count = 0; + std::size_t used_sat_count = 0; + std::size_t nmea_line_len = 0; bool enabled = true; bool powered = false; bool initialized = false; @@ -90,6 +105,16 @@ struct GpsRuntimeState } s_gps; constexpr uint32_t kMinValidEpochSeconds = 1700000000UL; +constexpr std::size_t kNmeaFieldMax = 24; + +enum class CollectorSlot : uint8_t +{ + GPS = 0, + GLN = 1, + GAL = 2, + BD = 3, + UNKNOWN = 4, +}; uint32_t readSystemEpochSeconds() { @@ -106,6 +131,327 @@ void syncSystemClockFromEpoch(uint32_t epoch_s) (void)epoch_s; } +CollectorSlot collectorSlotForTalker(const char* talker) +{ + if (!talker || talker[0] == '\0' || talker[1] == '\0') + { + return CollectorSlot::UNKNOWN; + } + if (talker[0] == 'G' && talker[1] == 'P') + { + return CollectorSlot::GPS; + } + if (talker[0] == 'G' && talker[1] == 'L') + { + return CollectorSlot::GLN; + } + if (talker[0] == 'G' && talker[1] == 'A') + { + return CollectorSlot::GAL; + } + if ((talker[0] == 'G' && talker[1] == 'B') || (talker[0] == 'B' && talker[1] == 'D')) + { + return CollectorSlot::BD; + } + return CollectorSlot::UNKNOWN; +} + +::gps::GnssSystem systemForSlot(CollectorSlot slot) +{ + switch (slot) + { + case CollectorSlot::GPS: + return ::gps::GnssSystem::GPS; + case CollectorSlot::GLN: + return ::gps::GnssSystem::GLN; + case CollectorSlot::GAL: + return ::gps::GnssSystem::GAL; + case CollectorSlot::BD: + return ::gps::GnssSystem::BD; + default: + return ::gps::GnssSystem::UNKNOWN; + } +} + +bool parseUint(const char* text, uint32_t* out) +{ + if (!text || !*text || !out) + { + return false; + } + char* end = nullptr; + unsigned long value = std::strtoul(text, &end, 10); + if (end == text) + { + return false; + } + *out = static_cast(value); + return true; +} + +bool parseInt(const char* text, int* out) +{ + if (!text || !*text || !out) + { + return false; + } + char* end = nullptr; + long value = std::strtol(text, &end, 10); + if (end == text) + { + return false; + } + *out = static_cast(value); + return true; +} + +bool verifyChecksum(const char* line) +{ + if (!line || line[0] != '$') + { + return false; + } + + const char* star = std::strchr(line, '*'); + if (!star || !star[1] || !star[2]) + { + return true; + } + + uint8_t checksum = 0; + for (const char* cursor = line + 1; cursor < star; ++cursor) + { + checksum ^= static_cast(*cursor); + } + + char checksum_text[3] = {star[1], star[2], '\0'}; + char* end = nullptr; + long expected = std::strtol(checksum_text, &end, 16); + return end != checksum_text && checksum == static_cast(expected & 0xFF); +} + +std::size_t splitFields(char* sentence, std::array& fields) +{ + fields.fill(nullptr); + std::size_t count = 0; + char* cursor = sentence; + while (cursor && *cursor && count < fields.size()) + { + fields[count++] = cursor; + char* comma = std::strchr(cursor, ','); + if (!comma) + { + break; + } + *comma = '\0'; + cursor = comma + 1; + } + return count; +} + +bool usedSat(uint16_t sat_id) +{ + for (std::size_t i = 0; i < s_gps.used_sat_count; ++i) + { + if (s_gps.used_sat_ids[i] == sat_id) + { + return true; + } + } + return false; +} + +void mergeGnssSatellites() +{ + s_gps.sat_count = 0; + for (std::size_t collector_index = 0; collector_index < s_gps.gsv.size(); ++collector_index) + { + auto& collector = s_gps.gsv[collector_index]; + for (std::size_t sat_index = 0; + sat_index < collector.count && s_gps.sat_count < s_gps.sats.size(); + ++sat_index) + { + auto sat = collector.sats[sat_index]; + sat.used = usedSat(sat.id); + s_gps.sats[s_gps.sat_count++] = sat; + } + } + + s_gps.status.sats_in_view = static_cast(std::min(s_gps.sat_count, 255U)); + if (s_gps.used_sat_count > 0) + { + s_gps.status.sats_in_use = static_cast(std::min(s_gps.used_sat_count, 255U)); + } +} + +void clearGpsObservations() +{ + s_gps.parser = TinyGPSPlus{}; + s_gps.data = ::gps::GpsState{}; + s_gps.status = ::gps::GnssStatus{}; + s_gps.sats.fill(::gps::GnssSatInfo{}); + s_gps.used_sat_ids.fill(0); + s_gps.gsv.fill(GpsRuntimeState::GsvCollector{}); + s_gps.sat_count = 0; + s_gps.used_sat_count = 0; + s_gps.last_nmea_ms = 0; + s_gps.nmea_line_len = 0; + s_gps.nmea_line[0] = '\0'; + s_gps.nmea_seen = false; +} + +void parseGsaSentence(const std::array& fields, std::size_t count) +{ + if (count < 4) + { + return; + } + + s_gps.used_sat_count = 0; + for (std::size_t i = 3; i <= 14 && i < count; ++i) + { + uint32_t sat_id = 0; + if (!parseUint(fields[i], &sat_id) || sat_id == 0 || s_gps.used_sat_count >= s_gps.used_sat_ids.size()) + { + continue; + } + s_gps.used_sat_ids[s_gps.used_sat_count++] = static_cast(sat_id); + } + mergeGnssSatellites(); +} + +void parseGsvSentence(const char* talker, const std::array& fields, std::size_t count) +{ + if (count < 4) + { + return; + } + + const CollectorSlot slot = collectorSlotForTalker(talker); + auto& collector = s_gps.gsv[static_cast(slot)]; + + uint32_t msg_num = 0; + if (!parseUint(fields[2], &msg_num)) + { + return; + } + + uint32_t sats_in_view = 0; + if (parseUint(fields[3], &sats_in_view)) + { + s_gps.status.sats_in_view = static_cast(std::min(sats_in_view, 255U)); + } + + if (msg_num == 1) + { + collector.count = 0; + } + + for (std::size_t base = 4; base + 3 < count && collector.count < collector.sats.size(); base += 4) + { + uint32_t sat_id = 0; + if (!parseUint(fields[base], &sat_id) || sat_id == 0) + { + continue; + } + + ::gps::GnssSatInfo sat{}; + sat.id = static_cast(sat_id); + sat.sys = systemForSlot(slot); + + uint32_t elevation = 0; + if (parseUint(fields[base + 1], &elevation)) + { + sat.elevation = static_cast(std::min(elevation, 90U)); + } + + uint32_t azimuth = 0; + if (parseUint(fields[base + 2], &azimuth)) + { + sat.azimuth = static_cast(std::min(azimuth, 359U)); + } + + int snr = -1; + if (parseInt(fields[base + 3], &snr)) + { + sat.snr = static_cast(std::clamp(snr, -1, 99)); + } + + collector.sats[collector.count++] = sat; + } + + mergeGnssSatellites(); +} + +void parseNmeaSentence(char* sentence) +{ + if (!sentence || sentence[0] != '$' || !verifyChecksum(sentence)) + { + return; + } + + char* payload = sentence + 1; + char* star = std::strchr(payload, '*'); + if (star) + { + *star = '\0'; + } + + std::array fields{}; + const std::size_t count = splitFields(payload, fields); + if (count == 0 || !fields[0] || std::strlen(fields[0]) < 5) + { + return; + } + + char talker[3] = {fields[0][0], fields[0][1], '\0'}; + const char* type = fields[0] + std::strlen(fields[0]) - 3; + + if (std::strcmp(type, "GSA") == 0) + { + parseGsaSentence(fields, count); + } + else if (std::strcmp(type, "GSV") == 0) + { + parseGsvSentence(talker, fields, count); + } +} + +void processGpsNmeaChar(char ch) +{ + if (ch == '$') + { + s_gps.nmea_line_len = 0; + s_gps.nmea_line[s_gps.nmea_line_len++] = ch; + return; + } + + if (s_gps.nmea_line_len == 0) + { + return; + } + + if (ch == '\r' || ch == '\n') + { + if (s_gps.nmea_line_len > 0) + { + s_gps.nmea_line[s_gps.nmea_line_len] = '\0'; + parseNmeaSentence(s_gps.nmea_line); + s_gps.nmea_line_len = 0; + } + return; + } + + if (s_gps.nmea_line_len + 1 < sizeof(s_gps.nmea_line)) + { + s_gps.nmea_line[s_gps.nmea_line_len++] = ch; + } + else + { + s_gps.nmea_line_len = 0; + } +} + uint8_t daysInMonth(int year, uint8_t month) { static constexpr uint8_t kDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; @@ -363,8 +709,12 @@ void refreshGpsFix() ? static_cast(s_gps.parser.location.age()) : 0xFFFFFFFFUL; - s_gps.status.sats_in_use = s_gps.data.satellites; - s_gps.status.sats_in_view = s_gps.data.satellites; + s_gps.status.sats_in_use = s_gps.used_sat_count > 0 + ? static_cast(std::min(s_gps.used_sat_count, 255U)) + : s_gps.data.satellites; + s_gps.status.sats_in_view = s_gps.sat_count > 0 + ? static_cast(std::min(s_gps.sat_count, 255U)) + : s_gps.data.satellites; s_gps.status.hdop = s_gps.parser.hdop.isValid() ? static_cast(s_gps.parser.hdop.hdop()) : 0.0f; @@ -957,7 +1307,11 @@ void Gat562Board::applyGpsConfig(const app::AppConfig& config) s_gps.nmea_sentence_mask = config.privacy_nmea_sentence; s_gps.motion_idle_timeout_ms = config.motion_config.idle_timeout_ms; s_gps.motion_sensor_id = config.motion_config.sensor_id; - s_gps.enabled = true; + s_gps.enabled = (config.gps_mode != 0); + if (!s_gps.enabled) + { + clearGpsObservations(); + } Serial.printf( "[gat562][gps] config enabled=%u interval_ms=%lu strategy=%u mode=%u sat_mask=0x%02X nmea_hz=%u nmea_mask=0x%02X motion_idle_ms=%lu motion_sensor=%u\n", static_cast(s_gps.enabled ? 1 : 0), @@ -982,7 +1336,9 @@ void Gat562Board::tickGps() { s_gps.nmea_seen = true; s_gps.last_nmea_ms = millis(); - s_gps.parser.encode(static_cast(Serial1.read())); + const char ch = static_cast(Serial1.read()); + s_gps.parser.encode(ch); + processGpsNmeaChar(ch); } applyGpsTimeIfValid(); refreshGpsFix(); @@ -1027,19 +1383,20 @@ bool Gat562Board::gpsGnssSnapshot(::gps::GnssSatInfo* out, { *status = s_gps.status; } - if (out && max > 0 && s_gps.data.satellites > 0) + if (out && max > 0 && s_gps.sat_count > 0) { - out[0].id = 0; - out[0].sys = ::gps::GnssSystem::GPS; - out[0].snr = -1; - out[0].used = s_gps.data.valid; + const std::size_t copy_count = std::min(max, s_gps.sat_count); + for (std::size_t index = 0; index < copy_count; ++index) + { + out[index] = s_gps.sats[index]; + } if (out_count) { - *out_count = 1; + *out_count = copy_count; } return true; } - return s_gps.data.valid || s_gps.data.satellites > 0; + return s_gps.data.valid || s_gps.data.satellites > 0 || s_gps.sat_count > 0; } void Gat562Board::setGpsCollectionInterval(uint32_t interval_ms) { s_gps.collection_interval_ms = interval_ms; } @@ -1048,6 +1405,11 @@ void Gat562Board::setGpsConfig(uint8_t mode, uint8_t sat_mask) { s_gps.gnss_mode = mode; s_gps.sat_mask = sat_mask; + s_gps.enabled = (mode != 0); + if (!s_gps.enabled) + { + clearGpsObservations(); + } } void Gat562Board::setGpsNmeaConfig(uint8_t output_hz, uint8_t sentence_mask) { @@ -1056,8 +1418,20 @@ void Gat562Board::setGpsNmeaConfig(uint8_t output_hz, uint8_t sentence_mask) } void Gat562Board::setGpsMotionIdleTimeout(uint32_t timeout_ms) { s_gps.motion_idle_timeout_ms = timeout_ms; } void Gat562Board::setGpsMotionSensorId(uint8_t sensor_id) { s_gps.motion_sensor_id = sensor_id; } -void Gat562Board::suspendGps() { s_gps.enabled = false; } -void Gat562Board::resumeGps() { s_gps.enabled = true; } +void Gat562Board::suspendGps() +{ + s_gps.enabled = false; + clearGpsObservations(); +} + +void Gat562Board::resumeGps() +{ + s_gps.enabled = (s_gps.gnss_mode != 0); + if (!s_gps.enabled) + { + clearGpsObservations(); + } +} void Gat562Board::setCurrentEpochSeconds(uint32_t epoch_s) { if (epoch_s < kMinValidEpochSeconds) diff --git a/modules/ui_mono_128x64/include/ui/mono_128x64/runtime.h b/modules/ui_mono_128x64/include/ui/mono_128x64/runtime.h index 196c5f0f..f79203f3 100644 --- a/modules/ui_mono_128x64/include/ui/mono_128x64/runtime.h +++ b/modules/ui_mono_128x64/include/ui/mono_128x64/runtime.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace ui::mono_128x64 { @@ -200,6 +201,7 @@ class Runtime : public chat::ChatService::IncomingTextObserver void drawMenuList(const char* title, const char* const* items, size_t count, size_t selected); void drawFooterHint(const char* hint); void drawTextClipped(int x, int y, int w, const char* text, bool inverse = false); + void refreshGnssSnapshot(bool force = false); bool editUsesHexCharset() const; bool usesSmartCompose() const; @@ -303,6 +305,12 @@ class Runtime : public chat::ChatService::IncomingTextObserver char compose_candidates_[kComposeCandidateMax][kComposeCandidateWidth] = {}; size_t compose_candidate_count_ = 0; size_t compose_candidate_index_ = 0; + platform::ui::gps::GpsState gnss_snapshot_state_{}; + platform::ui::gps::GnssStatus gnss_snapshot_status_{}; + std::array gnss_snapshot_sats_{}; + size_t gnss_snapshot_count_ = 0; + uint32_t gnss_snapshot_updated_ms_ = 0; + bool gnss_snapshot_valid_ = false; }; } // namespace ui::mono_128x64 diff --git a/modules/ui_mono_128x64/src/runtime.cpp b/modules/ui_mono_128x64/src/runtime.cpp index 4372f137..c240b6e2 100644 --- a/modules/ui_mono_128x64/src/runtime.cpp +++ b/modules/ui_mono_128x64/src/runtime.cpp @@ -8,7 +8,6 @@ #include "chat/runtime/self_identity_policy.h" #include "chat/usecase/chat_service.h" #include "chat/usecase/contact_service.h" - #include #include #include @@ -135,6 +134,7 @@ constexpr size_t kNodeInfoPageSize = 6; constexpr size_t kInfoPageSize = 6; constexpr size_t kGnssSummaryPageSize = 6; constexpr size_t kGnssSatPageSize = 5; +constexpr uint32_t kGnssSnapshotRefreshMs = 5000; constexpr int kTimezoneMin = -12 * 60; constexpr int kTimezoneMax = 14 * 60; constexpr int kTimezoneStep = 60; @@ -1701,6 +1701,29 @@ void Runtime::renderBootLog() } } +void Runtime::refreshGnssSnapshot(bool force) +{ + const uint32_t now = nowMs(); + const bool stale = !gnss_snapshot_valid_ || + now < gnss_snapshot_updated_ms_ || + (now - gnss_snapshot_updated_ms_) >= kGnssSnapshotRefreshMs; + if (!force && !stale) + { + return; + } + + gnss_snapshot_state_ = host_.gps_data_fn ? host_.gps_data_fn() : platform::ui::gps::GpsState{}; + gnss_snapshot_status_ = platform::ui::gps::GnssStatus{}; + gnss_snapshot_count_ = 0; + gnss_snapshot_sats_.fill(platform::ui::gps::GnssSatInfo{}); + (void)platform::ui::gps::get_gnss_snapshot(gnss_snapshot_sats_.data(), + gnss_snapshot_sats_.size(), + &gnss_snapshot_count_, + &gnss_snapshot_status_); + gnss_snapshot_updated_ms_ = now; + gnss_snapshot_valid_ = true; +} + void Runtime::renderScreensaver() { char protocol[8] = {}; @@ -1731,8 +1754,10 @@ void Runtime::renderScreensaver() sizeof(freq)); } + refreshGnssSnapshot(); const auto battery = host_.battery_info_fn ? host_.battery_info_fn() : platform::ui::device::BatteryInfo{}; - const auto gps = host_.gps_data_fn ? host_.gps_data_fn() : platform::ui::gps::GpsState{}; + const auto& gps = gnss_snapshot_state_; + const auto& gnss_status = gnss_snapshot_status_; const int unread = app() ? app()->getChatService().getTotalUnread() : 0; const bool gps_enabled = host_.gps_enabled_fn && host_.gps_enabled_fn(); const bool ble_enabled = app() && app()->isBleEnabled(); @@ -1745,9 +1770,14 @@ void Runtime::renderScreensaver() { std::snprintf(bat_pct_buf, sizeof(bat_pct_buf), "BAT:--"); } - if (gps.satellites > 0) + const unsigned satellite_count = gnss_status.sats_in_view > 0 + ? static_cast(gnss_status.sats_in_view) + : (gnss_snapshot_count_ > 0 + ? static_cast(gnss_snapshot_count_) + : static_cast(gps.satellites)); + if (satellite_count > 0) { - std::snprintf(sat_buf, sizeof(sat_buf), "SAT %u", static_cast(gps.satellites)); + std::snprintf(sat_buf, sizeof(sat_buf), "SAT %u", satellite_count); } else if (gps_enabled) { @@ -2386,16 +2416,16 @@ void Runtime::renderSettingPopup() formatSettingPopupValue(value, sizeof(value)); constexpr int kBoxX = 8; - constexpr int kBoxY = 18; + constexpr int kBoxY = 14; constexpr int kBoxW = 112; - constexpr int kBoxH = 28; + constexpr int kBoxH = 36; display_.fillRect(kBoxX, kBoxY, kBoxW, kBoxH, false); drawFrame(display_, kBoxX, kBoxY, kBoxW, kBoxH); const int title_w = text_renderer_.measureTextWidth(title); text_renderer_.drawText(display_, kBoxX + std::max(2, (kBoxW - title_w) / 2), kBoxY + 3, title); const int value_w = text_renderer_.measureTextWidth(value); - text_renderer_.drawText(display_, kBoxX + std::max(2, (kBoxW - value_w) / 2), kBoxY + 13, value); - drawTextClipped(kBoxX + 2, kBoxY + 21, kBoxW - 4, "ADJ ARROWS SEL OK BACK ESC"); + text_renderer_.drawText(display_, kBoxX + std::max(2, (kBoxW - value_w) / 2), kBoxY + 14, value); + drawTextClipped(kBoxX + 2, kBoxY + 25, kBoxW - 4, "ADJ ARROWS SEL OK BACK ESC"); } void Runtime::renderInfoPage() @@ -2587,11 +2617,11 @@ void Runtime::renderInfoPage() void Runtime::renderGnssPage() { - const auto state = host_.gps_data_fn ? host_.gps_data_fn() : platform::ui::gps::GpsState{}; - platform::ui::gps::GnssStatus status{}; - std::size_t sat_count = 0; - platform::ui::gps::GnssSatInfo sats[::gps::kMaxGnssSats] = {}; - (void)platform::ui::gps::get_gnss_snapshot(sats, ::gps::kMaxGnssSats, &sat_count, &status); + refreshGnssSnapshot(); + const auto& state = gnss_snapshot_state_; + const auto& status = gnss_snapshot_status_; + const std::size_t sat_count = gnss_snapshot_count_; + const auto* sats = gnss_snapshot_sats_.data(); char summary_lines[14][40] = {}; size_t summary_count = 0; @@ -2706,7 +2736,7 @@ void Runtime::renderGnssPage() const size_t sat_visible = std::min(kGnssSatPageSize, sat_count - std::min(sat_start, sat_count)); if (sat_visible == 0) { - text_renderer_.drawText(display_, 0, 22, "NO SAT DATA"); + text_renderer_.drawText(display_, 0, 22, "NO SATELLITE DATA"); return; } @@ -2736,6 +2766,10 @@ void Runtime::enterPage(Page page) { page_ = page; page_entered_ms_ = nowMs(); + if (page == Page::Screensaver || page == Page::GnssPage) + { + refreshGnssSnapshot(true); + } if (page == Page::ChatList) { rebuildConversationList(); diff --git a/platform/nrf52/arduino_common/src/platform_ui_settings_store.cpp b/platform/nrf52/arduino_common/src/platform_ui_settings_store.cpp index 44ae7e9a..dcd3a6fe 100644 --- a/platform/nrf52/arduino_common/src/platform_ui_settings_store.cpp +++ b/platform/nrf52/arduino_common/src/platform_ui_settings_store.cpp @@ -200,10 +200,15 @@ void ensureLoaded() { return; } - loadedFlag() = true; clearAllStores(); - if (!ensureFs() || !InternalFS.exists(kSettingsPath)) + if (!ensureFs()) + { + return; + } + loadedFlag() = true; + + if (!InternalFS.exists(kSettingsPath)) { return; } @@ -221,18 +226,25 @@ void ensureLoaded() return; } + std::map loaded_ints; + std::map loaded_bools; + std::map loaded_uints; + std::map> loaded_blobs; + for (uint32_t i = 0; i < header.record_count; ++i) { RecordHeader rec{}; if (!readPod(file, &rec)) { - break; + file.close(); + return; } std::string key(rec.key_len, '\0'); if (rec.key_len > 0 && file.read(&key[0], rec.key_len) != rec.key_len) { - break; + file.close(); + return; } switch (static_cast(rec.type)) @@ -242,9 +254,10 @@ void ensureLoaded() int32_t value = 0; if (rec.value_len != sizeof(value) || !readPod(file, &value)) { + file.close(); return; } - intStore()[key] = value; + loaded_ints[key] = value; break; } case ValueType::Bool: @@ -252,9 +265,10 @@ void ensureLoaded() uint8_t value = 0; if (rec.value_len != sizeof(value) || !readPod(file, &value)) { + file.close(); return; } - boolStore()[key] = (value != 0); + loaded_bools[key] = (value != 0); break; } case ValueType::Uint: @@ -262,9 +276,10 @@ void ensureLoaded() uint32_t value = 0; if (rec.value_len != sizeof(value) || !readPod(file, &value)) { + file.close(); return; } - uintStore()[key] = value; + loaded_uints[key] = value; break; } case ValueType::Blob: @@ -272,9 +287,10 @@ void ensureLoaded() std::vector value(rec.value_len, 0); if (rec.value_len > 0 && file.read(value.data(), rec.value_len) != rec.value_len) { + file.close(); return; } - blobStore()[key] = value; + loaded_blobs[key] = std::move(value); break; } default: @@ -282,6 +298,7 @@ void ensureLoaded() std::vector skip(rec.value_len, 0); if (rec.value_len > 0 && file.read(skip.data(), rec.value_len) != rec.value_len) { + file.close(); return; } break; @@ -290,6 +307,10 @@ void ensureLoaded() } file.close(); + intStore() = std::move(loaded_ints); + boolStore() = std::move(loaded_bools); + uintStore() = std::move(loaded_uints); + blobStore() = std::move(loaded_blobs); } } // namespace diff --git a/platform/nrf52/arduino_common/src/platform_ui_time_runtime.cpp b/platform/nrf52/arduino_common/src/platform_ui_time_runtime.cpp index 09a970d1..dcaec110 100644 --- a/platform/nrf52/arduino_common/src/platform_ui_time_runtime.cpp +++ b/platform/nrf52/arduino_common/src/platform_ui_time_runtime.cpp @@ -12,22 +12,20 @@ namespace constexpr const char* kSettingsNs = "settings"; constexpr const char* kTimezoneKey = "timezone_offset"; -int& timezoneOffsetStorage() +int stored_timezone_offset_min() { - static int s_offset_min = ::platform::ui::settings_store::get_int(kSettingsNs, kTimezoneKey, 0); - return s_offset_min; + return ::platform::ui::settings_store::get_int(kSettingsNs, kTimezoneKey, 0); } } // namespace int timezone_offset_min() { - return timezoneOffsetStorage(); + return stored_timezone_offset_min(); } void set_timezone_offset_min(int offset_min) { - timezoneOffsetStorage() = offset_min; ::platform::ui::settings_store::put_int(kSettingsNs, kTimezoneKey, offset_min); } diff --git a/scripts/platformio-pre.py b/scripts/platformio-pre.py index e8ceca24..cc2b9f74 100644 --- a/scripts/platformio-pre.py +++ b/scripts/platformio-pre.py @@ -19,6 +19,31 @@ env.Append(CPPDEFINES=["LV_CONF_INCLUDE_SIMPLE"]) print(f"[pio] pre: Added LVGL config include path: {ui_dir}") +# On nordicnrf52/Windows builds, `${platformio.packages_dir}` inside `build_flags` +# can be rewritten into an invalid builder-local include path. Inject the +# TinyUSB framework paths here using the resolved absolute package location so +# framework libraries like `Wire` can include ``. +platform = env.PioPlatform() +framework_dir = platform.get_package_dir("framework-arduinoadafruitnrf52") +if framework_dir: + framework_include_candidates = [ + os.path.join(framework_dir, "libraries", "Adafruit_TinyUSB_Arduino", "src"), + os.path.join(framework_dir, "libraries", "Adafruit_TinyUSB_Arduino", "src", "arduino"), + os.path.join(framework_dir, "libraries", "SPI"), + os.path.join(framework_dir, "libraries", "Wire"), + os.path.join(framework_dir, "libraries", "Bluefruit52Lib", "src"), + os.path.join(framework_dir, "libraries", "Bluefruit52Lib", "src", "services"), + os.path.join(framework_dir, "libraries", "Adafruit_nRFCrypto", "src"), + os.path.join(framework_dir, "libraries", "Adafruit_LittleFS", "src"), + os.path.join(framework_dir, "libraries", "InternalFileSytem", "src"), + ] + existing_cpppath = set(env.get("CPPPATH", [])) + include_paths = [path for path in framework_include_candidates if os.path.isdir(path)] + missing_paths = [path for path in include_paths if path not in existing_cpppath] + if missing_paths: + env.Append(CPPPATH=missing_paths) + print(f"[pio] pre: Added nRF52 framework include paths: {', '.join(missing_paths)}") + # Generate protobuf files if .proto files exist proto_dir = os.path.join(project_dir, "lib", "meshtastic_protobufs") generate_script = os.path.join(project_dir, "scripts", "generate_protobuf.py")