Skip to content

Conversation

ksedgwic
Copy link

@ksedgwic ksedgwic commented Aug 29, 2025

Render upcoming Bay Area Rapid Transit (BART) departures on LED segments.
Each platform gets its own segment; colored markers sweep toward “now” as
departure times approach.

Summary by CodeRabbit

  • New Features

    • BART Depart usermod: full end-to-end display of per-platform upcoming departures with line color-coding, smooth per-pixel interpolation, destination lists, debug-pixel overlay, boot animation and safe startup delay.
    • Live data pipeline: configurable API/key/station, adjustable update interval, exponential backoff/retries, and multiple platform views.
  • Documentation

    • Comprehensive readme with install, config, usage, troubleshooting and API/backoff guidance.
  • Chores

    • Added build manifest for the usermod.

Copy link
Contributor

coderabbitai bot commented Aug 29, 2025

Walkthrough

Adds a new BartDepart usermod: data interfaces, a LegacyBartSource HTTPS fetcher, a BartStationModel with per‑platform ETD history and merging, PlatformView renderers, utilities (time/color/hsv), TrainColor helpers, orchestration code, documentation, and a library manifest.

Changes

Cohort / File(s) Summary of changes
Model & TrainColor
usermods/usermod_v2_bartdepart/bart_station_model.h, usermods/usermod_v2_bartdepart/bart_station_model.cpp, usermods/usermod_v2_bartdepart/train_color.h, usermods/usermod_v2_bartdepart/train_color.cpp
Adds BartStationModel and nested Platform (ETD/ETDBatch), JSON parsing/update, history capping and merging, timestamp parsing, accessors and toString; introduces TrainColor enum, toString, and parseTrainColor.
Interfaces
usermods/usermod_v2_bartdepart/interfaces.h
Adds IConfigurable and templated IDataSourceT<ModelType> / IDataViewT<ModelType> interfaces for configurable data sources and views.
Legacy BART data source
usermods/usermod_v2_bartdepart/legacy_bart_source.h, usermods/usermod_v2_bartdepart/legacy_bart_source.cpp
Implements LegacyBartSource that composes BART API URLs, performs HTTPS GETs, deserializes JSON into BartStationModel, schedules fetches with exponential backoff, and provides config IO (ApiBase/ApiKey/ApiStation/UpdateSecs).
View / Rendering
usermods/usermod_v2_bartdepart/platform_view.h, usermods/usermod_v2_bartdepart/platform_view.cpp
Adds PlatformView implementing IDataViewT<BartStationModel> to render per‑platform ETDs into LED segments: color mapping, interpolation across pixels, frame heuristics, debug overlay, and config output listing destinations.
Utilities
usermods/usermod_v2_bartdepart/util.h, usermods/usermod_v2_bartdepart/util.cpp
Adds bartdepart::util helpers: FreezeGuard, time utilities (time_now_utc/time_now/current_offset/fmt_local), clamp/lerp, hsv2rgb implementation, applySaturation, and blinkDebug for rendering/debug.
Usermod orchestration
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.h, usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp
New BartDepart Usermod wiring sources, model, and views; boot sequencing, main loop, overlay drawing, config serialization/deserialization, reload logic, and registration macro.
Docs & Build
usermods/usermod_v2_bartdepart/readme.md, usermods/usermod_v2_bartdepart/library.json
Adds README with install/config/usage and a library manifest (libArchive: false).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested reviewers

  • netmindz
  • DedeHai
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (29)
usermods/usermod_v2_bartdepart/library.json (1)

1-4: Optional: add metadata for clarity.
Consider adding "version", "description", and "keywords" to help downstream tooling.

usermods/usermod_v2_bartdepart/train_color.h (1)

3-11: Use a compact underlying type for the enum.
Saves RAM/flash in switch tables and structures.

-enum class TrainColor {
+enum class TrainColor : uint8_t {
usermods/usermod_v2_bartdepart/util.cpp (2)

1-31: Include math header and favor portable rounding.
fmodf/fabsf/roundf need <math.h> on some cores; lrintf isn’t available everywhere.

-#include "util.h"
+#include "util.h"
+#include <math.h>
@@
-  uint8_t r = uint8_t(lrintf(rf * 255.f));
-  uint8_t g = uint8_t(lrintf(gf * 255.f));
-  uint8_t b = uint8_t(lrintf(bf * 255.f));
+  uint8_t r = uint8_t(roundf(rf * 255.f));
+  uint8_t g = uint8_t(roundf(gf * 255.f));
+  uint8_t b = uint8_t(roundf(bf * 255.f));

6-31: Consider reusing existing WLED/FastLED HSV utilities.
If acceptable, CRGB color; color.setHSV(h8, s8, v8); avoids custom float math and reduces code size.

usermods/usermod_v2_bartdepart/legacy_bart_source.cpp (4)

23-30: Add socket timeouts before begin().
Prevents hangs on bad networks.

   String url = composeUrl(apiBase_, apiKey_, apiStation_);
+  client_.setTimeout(5000); // ms
   if (!https_.begin(client_, url)) {

41-51: Reduce heap churn: parse JSON from stream instead of copying to String.
Avoids duplicating payload and lowers peak RAM.

-  String payload = https_.getString();
-  https_.end();
-
-  size_t jsonSz = payload.length() * 2;
-  DynamicJsonDocument doc(jsonSz);
-  auto err = deserializeJson(doc, payload);
+  // Parse directly from the HTTP stream
+  DynamicJsonDocument doc(8192); // tune; or compute via ArduinoJson Assistant
+  auto err = deserializeJson(doc, https_.getStream());
+  https_.end();

80-87: Only invalidate history when config actually changes.
Right now, any read wipes history. Compare old/new values first.

-bool LegacyBartSource::readFromConfig(JsonObject& root, bool startup_complete, bool& invalidate_history) {
+bool LegacyBartSource::readFromConfig(JsonObject& root, bool startup_complete, bool& invalidate_history) {
   bool ok = true;
-  ok &= getJsonValue(root["UpdateSecs"], updateSecs_, 60);
-  ok &= getJsonValue(root["ApiBase"], apiBase_, apiBase_);
-  ok &= getJsonValue(root["ApiKey"], apiKey_, apiKey_);
-  ok &= getJsonValue(root["ApiStation"], apiStation_, apiStation_);
-  invalidate_history = true;
+  uint16_t oldUpd = updateSecs_; String oldBase = apiBase_; String oldKey = apiKey_; String oldSt = apiStation_;
+  ok &= getJsonValue(root["UpdateSecs"], updateSecs_, updateSecs_);
+  ok &= getJsonValue(root["ApiBase"],    apiBase_,    apiBase_);
+  ok &= getJsonValue(root["ApiKey"],     apiKey_,     apiKey_);
+  ok &= getJsonValue(root["ApiStation"], apiStation_, apiStation_);
+  invalidate_history = (oldUpd != updateSecs_) || (oldBase != apiBase_) || (oldKey != apiKey_) || (oldSt != apiStation_);
   return ok;
 }

13-18: URL-encode and normalize station code.
If apiStation_ contains spaces or lowercase, encode/uppercase to avoid API surprises.

usermods/usermod_v2_bartdepart/readme.md (2)

34-40: Clarify API key usage and rate limits.
Explicitly warn that the demo key is rate‑limited and not for production; suggest per‑user keys.

Apply:

-- ApiKey: API key. The default demo key works for testing; consider providing
-  your own key for reliability.
+- ApiKey: API key. The default demo key is rate‑limited and for testing only.
+  For reliable operation, obtain and use your own key.

45-52: Tighten phrasing around virtual segments and overlap.
Minor clarity edit; avoids ambiguity about “mapping settings”.

Apply:

-- Virtual Segments: Rendering uses `virtualLength()` and `beginDraw()`, so
-  segment reverse/mirror/group settings are respected automatically. If the
-  displayed direction seems wrong, toggle the segment’s Reverse or adjust
-  mapping settings in WLED.
+- Virtual Segments: Rendering uses `virtualLength()` and `beginDraw()`, so
+  Reverse/Mirror/Grouping are respected. If direction appears wrong, toggle
+  the segment’s Reverse setting in WLED.
usermods/usermod_v2_bartdepart/legacy_bart_source.h (2)

37-39: Avoid per-call allocations for platformIds().
Return a static array or String literals to reduce heap churn on MCUs.

Apply:

-  std::vector<String> platformIds() const { return { "1", "2", "3", "4" }; }
+  const std::array<const char*,4>& platformIds() const {
+    static const std::array<const char*,4> kP = {"1","2","3","4"};
+    return kP;
+  }

Adjust call sites accordingly.


17-18: Consider widening UpdateSecs datatype or validating upper bounds.
uint16_t is fine for defaults, but add input validation (e.g., 5–3600) to prevent excessive backoff intervals.

usermods/usermod_v2_bartdepart/platform_view.cpp (4)

47-50: Frame gating should be time‑based, not call‑count‑based.
A static frame counter desyncs across PlatformViews and depends on render rate. Use millis() to toggle the preference window.

Apply:

-  static uint8_t frameCnt = 0;      // 0..99
-  bool preferFirst = frameCnt < 50; // true for first 0.5 s
-  frameCnt = (frameCnt + 1) % 100;
+  bool preferFirst = ((millis() / 500) & 1) == 0; // alternate every 0.5s

73-89: Const‑correctness and micro‑allocations in inner loop.
Iterate by const reference to avoid copies.

Apply:

-    for (auto &e : batch.etds) {
+    for (const auto& e : batch.etds) {

90-94: Prefer WLED helpers for color packing (if available).
Manual bit‑packing is fine; if WLED provides a packColor(r,g,b), use it for consistency.


95-100: Selection rule can be simplified and documented.
The 2× heuristic is clever; add a comment explaining the intent (bias early/late frames). Also guard bestB to avoid redundant multiplications.

Apply:

-      if ((preferFirst && (bestColor == 0 || newB > 2 * bestB)) ||
-          (!preferFirst && newB * 2 >= bestB)) {
+      // Prefer brighter color; bias toggles every 0.5s via preferFirst
+      if ((preferFirst && (bestColor == 0 || newB > (bestB << 1))) ||
+          (!preferFirst && (newB << 1) >= bestB)) {
usermods/usermod_v2_bartdepart/platform_view.h (3)

9-9: Decouple render offset from data source interval or make it configurable.
updateSecs_ here is a render offset but isn’t exposed in config; it can diverge from LegacyBartSource::UpdateSecs. Either rename to renderOffsetSecs_ and expose it, or plumb the data source’s interval into the view.

Apply:

-  uint16_t updateSecs_ = 60;
+  uint16_t renderOffsetSecs_ = 60; // (-) rename for intent; optionally expose in config

And update usage in .cpp.


20-25: Config I/O: validate SegmentId bounds.
Clamp to [-1, strip.getMaxSegments()-1] during read to avoid invalid indices.

Apply:

-    return getJsonValue(root["SegmentId"], segmentId_, segmentId_);
+    bool ok = getJsonValue(root["SegmentId"], segmentId_, segmentId_);
+    if (segmentId_ < -1) segmentId_ = -1;
+    if (segmentId_ >= (int16_t)strip.getMaxSegments()) segmentId_ = -1;
+    return ok;

26-28: Minor: avoid repeated c_str() lifetime pitfalls.
configKey_ is stable, but exposing a std::string and const char* can confuse callers. Consider returning std::string_view (if available) or only const char*.

usermods/usermod_v2_bartdepart/bart_station_model.h (3)

23-31: Document history retention and batch semantics.
Clarify max history length and ordering guarantees so renderers can rely on history.back() being the newest and sorted ETDs.

Add comment:

     explicit Platform(const String& platformId);
 
     void update(const JsonObject& root);
     void merge(const Platform& other);
     time_t oldest() const;
     const String& platformId() const;
     const std::deque<ETDBatch>& history() const;
     const std::vector<String>& destinations() const;
     String toString() const;
+    // Note: history_ retains up to N recent batches (newest at back), ETDs sorted by estDep.

38-40: Timezone note for parseHeaderTimestamp.
Confirm input is UTC and explicitly state that conversions don’t apply DST/local time to avoid ambiguity.


44-47: destinationsForPlatform: consider stable ordering and de‑dupe policy.
If ordering is user‑visible, define it (alpha vs. first‑seen). Also ensure de‑dupe is case‑insensitive if API case varies.

usermods/usermod_v2_bartdepart/interfaces.h (1)

29-36: Minor API polish: method naming and return type.

  • Consider renaming checkhistorycheckHistory for consistency.
  • Returning const char* (or String) from name() avoids dynamic allocations from std::string on MCUs.
usermods/usermod_v2_bartdepart/util.h (2)

11-19: Clarify FreezeGuard behavior.

Comment says “freeze during rendering,” but it’s also used to temporarily unfreeze (freezeNow=false). Reword to reflect “set to provided value, then restore”.

Apply this diff:

-// RAII guard to temporarily freeze a segment during rendering and always
-// restore its original freeze state on exit (including early returns).
+// RAII guard to temporarily set seg.freeze to the provided value and always
+// restore its original state on exit (including early returns).

80-84: Avoid function-local static with runtime init.

hsv2rgb() call in a function-local static adds guards and code size. Use a literal debug color.

Apply this diff:

-    static const uint32_t dbgCol = hsv2rgb(0.f, 0.f, 0.4f);
+    constexpr uint32_t dbgCol = RGBW32(102, 102, 102, 0); // ~0.4 white
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (1)

78-83: Skip overlay draw when disabled/off.

Avoid unnecessary work and pixel writes when the mod is disabled or WLED is off.

Apply this diff:

 void BartDepart::handleOverlayDraw() {
-  time_t now = bartdepart::util::time_now_utc();
+  if (!enabled_ || offMode) return;
+  time_t now = bartdepart::util::time_now_utc();
   for (auto& view : views_) {
     view->view(now, *model_, dbgPixelIndex_);
   }
 }
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.h (1)

46-48: Const-correct accessor.

isEnabled() can be const.

Apply this diff:

-  inline bool isEnabled() { return enabled_; }
+  inline bool isEnabled() const { return enabled_; }
usermods/usermod_v2_bartdepart/bart_station_model.cpp (2)

29-33: Handle non-numeric “minutes” robustly.

BART can return “Leaving”. Guard before atoi to avoid accidental negatives/UB if format changes.

Apply this diff:

-          int mins = atoi(est["minutes"] | "0");
+          const char* mstr = est["minutes"] | "0";
+          int mins = (mstr && mstr[0] >= '0' && mstr[0] <= '9') ? atoi(mstr) : 0;

48-55: Avoid storing empty ETD batches.

Skip appending when no ETDs matched to reduce noise and save memory.

Apply this diff:

-  history_.push_back(std::move(batch));
-  while (history_.size() > 5) {
-    history_.pop_front();
-  }
+  if (!batch.etds.empty()) {
+    history_.push_back(std::move(batch));
+    while (history_.size() > 5) {
+      history_.pop_front();
+    }
+  }
 
-  DEBUG_PRINTF("BartDepart::update platform %s: %s\n",
-               platformId_.c_str(), toString().c_str());
+  DEBUG_PRINTF("BartDepart::update platform %s: %s\n",
+               platformId_.c_str(), toString().c_str());
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between da7f107 and b4deb47.

📒 Files selected for processing (15)
  • usermods/usermod_v2_bartdepart/bart_station_model.cpp (1 hunks)
  • usermods/usermod_v2_bartdepart/bart_station_model.h (1 hunks)
  • usermods/usermod_v2_bartdepart/interfaces.h (1 hunks)
  • usermods/usermod_v2_bartdepart/legacy_bart_source.cpp (1 hunks)
  • usermods/usermod_v2_bartdepart/legacy_bart_source.h (1 hunks)
  • usermods/usermod_v2_bartdepart/library.json (1 hunks)
  • usermods/usermod_v2_bartdepart/platform_view.cpp (1 hunks)
  • usermods/usermod_v2_bartdepart/platform_view.h (1 hunks)
  • usermods/usermod_v2_bartdepart/readme.md (1 hunks)
  • usermods/usermod_v2_bartdepart/train_color.cpp (1 hunks)
  • usermods/usermod_v2_bartdepart/train_color.h (1 hunks)
  • usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (1 hunks)
  • usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.h (1 hunks)
  • usermods/usermod_v2_bartdepart/util.cpp (1 hunks)
  • usermods/usermod_v2_bartdepart/util.h (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-06-07T15:58:42.579Z
Learnt from: willmmiles
PR: wled/WLED#4712
File: wled00/FX_fcn.cpp:992-993
Timestamp: 2025-06-07T15:58:42.579Z
Learning: In WLED segments, the backing pixel buffer is always allocated to the full physical segment size, but effect rendering only uses a subset when mirror or grouping are enabled. For 2D segments, this subset is vWidth() * vHeight(), and for 1D segments it's vLength(). Color processing operations like fade_out should iterate over this rendering subset, not the full allocated buffer.

Applied to files:

  • usermods/usermod_v2_bartdepart/util.h
📚 Learning: 2025-08-26T11:51:21.817Z
Learnt from: DedeHai
PR: wled/WLED#4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR #4798, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Applied to files:

  • usermods/usermod_v2_bartdepart/util.h
🧬 Code graph analysis (11)
usermods/usermod_v2_bartdepart/train_color.h (2)
usermods/usermod_v2_bartdepart/bart_station_model.cpp (2)
  • toString (87-121)
  • toString (87-87)
usermods/usermod_v2_bartdepart/train_color.cpp (2)
  • parseTrainColor (5-13)
  • parseTrainColor (5-5)
usermods/usermod_v2_bartdepart/legacy_bart_source.h (3)
usermods/usermod_v2_bartdepart/legacy_bart_source.cpp (9)
  • LegacyBartSource (4-6)
  • fetch (20-71)
  • fetch (20-20)
  • reload (8-11)
  • reload (8-8)
  • addToConfig (73-78)
  • addToConfig (73-73)
  • readFromConfig (80-88)
  • readFromConfig (80-80)
usermods/usermod_v2_bartdepart/interfaces.h (1)
  • std (50-58)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (4)
  • addToConfig (85-97)
  • addToConfig (85-85)
  • readFromConfig (109-135)
  • readFromConfig (109-109)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.h (7)
usermods/usermod_v2_bartdepart/legacy_bart_source.cpp (5)
  • LegacyBartSource (4-6)
  • addToConfig (73-78)
  • addToConfig (73-73)
  • readFromConfig (80-88)
  • readFromConfig (80-80)
usermods/usermod_v2_bartdepart/legacy_bart_source.h (1)
  • LegacyBartSource (15-39)
usermods/usermod_v2_bartdepart/platform_view.h (4)
  • PlatformView (7-28)
  • addToConfig (20-20)
  • appendConfigData (21-21)
  • readFromConfig (23-25)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (19)
  • BartDepart (23-30)
  • setup (32-39)
  • setup (32-32)
  • loop (41-76)
  • loop (41-41)
  • handleOverlayDraw (78-83)
  • handleOverlayDraw (78-78)
  • addToConfig (85-97)
  • addToConfig (85-85)
  • appendConfigData (99-107)
  • appendConfigData (99-99)
  • readFromConfig (109-135)
  • readFromConfig (109-109)
  • showBooting (137-145)
  • showBooting (137-137)
  • doneBooting (147-151)
  • doneBooting (147-147)
  • reloadSources (153-155)
  • reloadSources (153-153)
usermods/usermod_v2_bartdepart/interfaces.h (1)
  • std (50-58)
usermods/usermod_v2_bartdepart/platform_view.cpp (2)
  • appendConfigData (107-130)
  • appendConfigData (107-107)
usermods/usermod_v2_bartdepart/util.h (1)
  • s (13-19)
usermods/usermod_v2_bartdepart/interfaces.h (5)
usermods/usermod_v2_bartdepart/util.h (1)
  • s (13-19)
usermods/usermod_v2_bartdepart/legacy_bart_source.cpp (4)
  • fetch (20-71)
  • fetch (20-20)
  • reload (8-11)
  • reload (8-8)
usermods/usermod_v2_bartdepart/platform_view.cpp (4)
  • view (25-105)
  • view (25-26)
  • appendConfigData (107-130)
  • appendConfigData (107-107)
usermods/usermod_v2_bartdepart/platform_view.h (2)
  • appendDebugPixel (19-19)
  • appendConfigData (21-21)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (2)
  • appendConfigData (99-107)
  • appendConfigData (99-99)
usermods/usermod_v2_bartdepart/legacy_bart_source.cpp (2)
usermods/usermod_v2_bartdepart/legacy_bart_source.h (1)
  • LegacyBartSource (15-39)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (4)
  • addToConfig (85-97)
  • addToConfig (85-85)
  • readFromConfig (109-135)
  • readFromConfig (109-109)
usermods/usermod_v2_bartdepart/platform_view.h (4)
usermods/usermod_v2_bartdepart/bart_station_model.cpp (2)
  • platformId (74-76)
  • platformId (74-74)
usermods/usermod_v2_bartdepart/platform_view.cpp (4)
  • view (25-105)
  • view (25-26)
  • appendConfigData (107-130)
  • appendConfigData (107-107)
usermods/usermod_v2_bartdepart/util.h (1)
  • s (13-19)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (6)
  • addToConfig (85-97)
  • addToConfig (85-85)
  • appendConfigData (99-107)
  • appendConfigData (99-99)
  • readFromConfig (109-135)
  • readFromConfig (109-109)
usermods/usermod_v2_bartdepart/util.h (1)
usermods/usermod_v2_bartdepart/util.cpp (2)
  • hsv2rgb (6-32)
  • hsv2rgb (6-6)
usermods/usermod_v2_bartdepart/bart_station_model.h (1)
usermods/usermod_v2_bartdepart/bart_station_model.cpp (2)
  • platformId (74-76)
  • platformId (74-74)
usermods/usermod_v2_bartdepart/bart_station_model.cpp (3)
usermods/usermod_v2_bartdepart/util.h (2)
  • time_now (26-26)
  • current_offset (28-33)
usermods/usermod_v2_bartdepart/train_color.cpp (2)
  • parseTrainColor (5-13)
  • parseTrainColor (5-5)
usermods/usermod_v2_bartdepart/train_color.h (1)
  • toString (13-23)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (5)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.h (2)
  • BartDepart (21-53)
  • setup (38-44)
usermods/usermod_v2_bartdepart/util.h (2)
  • time_now_utc (24-24)
  • s (13-19)
usermods/usermod_v2_bartdepart/platform_view.cpp (4)
  • view (25-105)
  • view (25-26)
  • appendConfigData (107-130)
  • appendConfigData (107-107)
usermods/usermod_v2_bartdepart/legacy_bart_source.cpp (4)
  • addToConfig (73-78)
  • addToConfig (73-73)
  • readFromConfig (80-88)
  • readFromConfig (80-80)
usermods/usermod_v2_bartdepart/platform_view.h (3)
  • addToConfig (20-20)
  • appendConfigData (21-21)
  • readFromConfig (23-25)
usermods/usermod_v2_bartdepart/platform_view.cpp (4)
usermods/usermod_v2_bartdepart/bart_station_model.cpp (2)
  • history (78-81)
  • history (79-79)
usermods/usermod_v2_bartdepart/util.h (3)
  • c (63-70)
  • blinkDebug (79-86)
  • s (13-19)
usermods/usermod_v2_bartdepart/platform_view.h (2)
  • appendConfigData (21-21)
  • configKey (26-26)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (2)
  • appendConfigData (99-107)
  • appendConfigData (99-99)
🪛 LanguageTool
usermods/usermod_v2_bartdepart/readme.md

[grammar] ~4-~4: There might be a mistake here.
Context: ...t; colored markers sweep toward “now” as departure times approach. ## Features ...

(QB_NEW_EN)


[grammar] ~9-~9: There might be a mistake here.
Context: ... (reverse, mirroring, grouping honored). - Configurable per‑platform segment mappin...

(QB_NEW_EN)


[grammar] ~10-~10: There might be a mistake here.
Context: ...mapping; disable any platform with -1. - Graceful overlay behavior (no freezing t...

(QB_NEW_EN)


[grammar] ~16-~16: There might be a mistake here.
Context: ...epartto your PlatformIO environment inplatformio_override.ini`: ``` custom_u...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...artdepart ``` Build and flash as usual: - Build: pio run -e <your_env> - Upload:...

(QB_NEW_EN)


[grammar] ~24-~24: There might be a mistake here.
Context: ...``` Build and flash as usual: - Build: pio run -e <your_env> - Upload: `pio run -e <your_env> -t upload...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ...s → “BartDepart”. The following keys are available. - BartDepart.Enabled: enable...

(QB_NEW_EN)


[grammar] ~38-~38: There might be a mistake here.
Context: ...roviding your own key for reliability. - ApiStation: station abbreviation (e.g., ...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ...). Views: PlatformView1 … PlatformView4 - SegmentId: WLED segment index to render ...

(QB_NEW_EN)


[grammar] ~44-~44: There might be a mistake here.
Context: ...er that platform (-1 disables). Notes - Virtual Segments: Rendering uses `virtua...

(QB_NEW_EN)


[grammar] ~48-~48: There might be a mistake here.
Context: ...se or adjust mapping settings in WLED. - Multiple Segments: Map different platfor...

(QB_NEW_EN)


[grammar] ~57-~57: There might be a mistake here.
Context: ...on; keep UpdateSecs at 60 to start. 3) Save and reboot. After a short boot safe...

(QB_NEW_EN)


[grammar] ~66-~66: There might be a mistake here.
Context: ...ontroller has Wi‑Fi and time (NTP) sync. - Direction looks reversed: Toggle the seg...

(QB_NEW_EN)


[grammar] ~70-~70: There might be a mistake here.
Context: ...s separate segments for more resolution. - API errors/backoff: On fetch errors the ...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
usermods/usermod_v2_bartdepart/readme.md

19-19: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (6)
usermods/usermod_v2_bartdepart/library.json (1)

1-4: LGTM: minimal manifest is fine.
Name + libArchive=false matches WLED usermod patterns.

usermods/usermod_v2_bartdepart/platform_view.cpp (1)

60-64: Double‑check Segment::beginDraw()/end pairing.
If WLED requires endDraw() or a RAII guard, add it to avoid partial state. Otherwise, add a brief comment stating it’s intentionally unpaired.

usermods/usermod_v2_bartdepart/interfaces.h (1)

8-17: Interfaces look solid and minimal.

Clear contracts and JSON hooks are consistent with WLED usermods.

usermods/usermod_v2_bartdepart/util.h (1)

35-41: Confirm gmtime_r availability across all targets.

gmtime_r is fine on ESP32/ESP8266, but double-check other build variants. If needed, add a fallback to gmtime.

usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (1)

20-22: Safety delay seems reasonable.

10s boot guard is a good balance; no issues.

usermods/usermod_v2_bartdepart/bart_station_model.cpp (1)

123-140: Timestamp parsing looks correct.

AM/PM handling and offset normalization align with API format; UTC return is appropriate.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (1)

109-113: Resolved: treat missing config as non-error

Returning true when the section is absent matches WLED conventions. Thanks for adopting the earlier suggestion.

usermods/usermod_v2_bartdepart/readme.md (1)

19-21: Resolved: fenced code block now has a language hint

The ini language tag fixes MD040. LGTM.

🧹 Nitpick comments (4)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (3)

120-127: Don’t invalidate history on every config read

LegacyBartSource::readFromConfig(...) (see legacy_bart_source.cpp 79–87) unconditionally sets invalidate_history = true, causing needless clears/reloads. Set it only when identity-affecting fields (e.g., base/station) change.

Proposed change in legacy_bart_source.cpp:

 bool LegacyBartSource::readFromConfig(JsonObject& root, bool startup_complete, bool& invalidate_history) {
   bool ok = true;
-  ok &= getJsonValue(root["UpdateSecs"], updateSecs_, 60);
-  ok &= getJsonValue(root["ApiBase"], apiBase_, apiBase_);
-  ok &= getJsonValue(root["ApiKey"], apiKey_, apiKey_);
-  ok &= getJsonValue(root["ApiStation"], apiStation_, apiStation_);
-  invalidate_history = true;
+  uint16_t prevUpdate = updateSecs_;
+  String   prevBase   = apiBase_;
+  String   prevKey    = apiKey_;
+  String   prevStation= apiStation_;
+
+  ok &= getJsonValue(root["UpdateSecs"], updateSecs_, 60);
+  ok &= getJsonValue(root["ApiBase"],   apiBase_,   apiBase_);
+  ok &= getJsonValue(root["ApiKey"],    apiKey_,    apiKey_);
+  ok &= getJsonValue(root["ApiStation"],apiStation_,apiStation_);
+
+  // Only invalidate when source identity changes (base/station)
+  invalidate_history = (apiBase_ != prevBase) || (apiStation_ != prevStation);
   return ok;
 }

147-151: Avoid forcing permanent freeze after boot

seg.freeze = true; keeps the main segment frozen indefinitely. That can surprise users and other overlays/effects.

Apply:

 void BartDepart::doneBooting() {
   Segment& seg = strip.getMainSegment();
-  seg.freeze = true;
   seg.setMode(0);
 }

If you need a static background, consider making this behavior configurable.


23-30: Verify C++ standard before refactoring
Confirm that your build requires C++14 or newer—otherwise std::make_unique won’t be available and switching from ::make_unique (the provided shim) will break C++11 builds. Check your project’s build configuration (e.g. platformio.ini or CMake settings) before applying the diff.

-  sources_.push_back(::make_unique<LegacyBartSource>());
+  sources_.push_back(std::make_unique<LegacyBartSource>());
...
usermods/usermod_v2_bartdepart/readme.md (1)

32-41: Document DebugPixelIndex config key

The code supports BartDepart.DebugPixelIndex, but it’s not listed here.

Apply:

 - BartDepart.Enabled: enable/disable the module.
+- BartDepart.DebugPixelIndex: pixel index to blink for debugging (`-1` disables).
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b4deb47 and e6bbcf3.

📒 Files selected for processing (5)
  • usermods/usermod_v2_bartdepart/legacy_bart_source.cpp (1 hunks)
  • usermods/usermod_v2_bartdepart/readme.md (1 hunks)
  • usermods/usermod_v2_bartdepart/train_color.cpp (1 hunks)
  • usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (1 hunks)
  • usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.h (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • usermods/usermod_v2_bartdepart/train_color.cpp
  • usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.h
  • usermods/usermod_v2_bartdepart/legacy_bart_source.cpp
🧰 Additional context used
🧬 Code graph analysis (1)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (5)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.h (2)
  • BartDepart (22-54)
  • setup (39-45)
usermods/usermod_v2_bartdepart/util.h (2)
  • time_now_utc (24-24)
  • s (13-19)
usermods/usermod_v2_bartdepart/platform_view.cpp (4)
  • view (25-105)
  • view (25-26)
  • appendConfigData (107-130)
  • appendConfigData (107-107)
usermods/usermod_v2_bartdepart/legacy_bart_source.cpp (4)
  • addToConfig (73-78)
  • addToConfig (73-73)
  • readFromConfig (80-88)
  • readFromConfig (80-80)
usermods/usermod_v2_bartdepart/platform_view.h (3)
  • addToConfig (20-20)
  • appendConfigData (21-21)
  • readFromConfig (23-25)
🪛 LanguageTool
usermods/usermod_v2_bartdepart/readme.md

[grammar] ~4-~4: There might be a mistake here.
Context: ...t; colored markers sweep toward “now” as departure times approach. ## Features ...

(QB_NEW_EN)


[grammar] ~9-~9: There might be a mistake here.
Context: ... (reverse, mirroring, grouping honored). - Configurable per‑platform segment mappin...

(QB_NEW_EN)


[grammar] ~10-~10: There might be a mistake here.
Context: ...mapping; disable any platform with -1. - Graceful overlay behavior (no freezing t...

(QB_NEW_EN)


[grammar] ~16-~16: There might be a mistake here.
Context: ...epartto your PlatformIO environment inplatformio_override.ini`: ```ini custo...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...artdepart ``` Build and flash as usual: - Build: pio run -e <your_env> - Upload:...

(QB_NEW_EN)


[grammar] ~24-~24: There might be a mistake here.
Context: ...``` Build and flash as usual: - Build: pio run -e <your_env> - Upload: `pio run -e <your_env> -t upload...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ...s → “BartDepart”. The following keys are available. - BartDepart.Enabled: enable...

(QB_NEW_EN)


[grammar] ~38-~38: There might be a mistake here.
Context: ...roviding your own key for reliability. - ApiStation: station abbreviation (e.g., ...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ...). Views: PlatformView1 … PlatformView4 - SegmentId: WLED segment index to render ...

(QB_NEW_EN)


[grammar] ~44-~44: There might be a mistake here.
Context: ...er that platform (-1 disables). Notes - Virtual Segments: Rendering uses `virtua...

(QB_NEW_EN)


[grammar] ~48-~48: There might be a mistake here.
Context: ...se or adjust mapping settings in WLED. - Multiple Segments: Map different platfor...

(QB_NEW_EN)


[grammar] ~57-~57: There might be a mistake here.
Context: ...on; keep UpdateSecs at 60 to start. 3) Save and reboot. After a short boot safe...

(QB_NEW_EN)


[grammar] ~66-~66: There might be a mistake here.
Context: ...ontroller has Wi‑Fi and time (NTP) sync. - Direction looks reversed: Toggle the seg...

(QB_NEW_EN)


[grammar] ~70-~70: There might be a mistake here.
Context: ...s separate segments for more resolution. - API errors/backoff: On fetch errors the ...

(QB_NEW_EN)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (1)

78-84: Overlay correctly skips when disabled/off.

The added guard prevents unintended drawing when the mod is disabled or LEDs are off. LGTM.

🧹 Nitpick comments (3)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (3)

32-39: Gate boot animation behind enabled_ to avoid surprising users when disabled.

Only show/tear down the boot UI when the mod is enabled.

 void BartDepart::setup() {
   DEBUG_PRINTLN(F("BartDepart::setup starting"));
   uint32_t now_ms = millis();
   safeToStart_ = now_ms + SAFETY_DELAY_MS;
-  showBooting();
+  if (enabled_) showBooting();
   state_ = BartDepartState::Setup;
   DEBUG_PRINTLN(F("BartDepart::setup finished"));
 }

 ...
   if (state_ == BartDepartState::Setup) {
     if (now_ms < safeToStart_) return;
     state_ = BartDepartState::Running;
-    doneBooting();
+    if (enabled_) doneBooting();
     reloadSources(now);
   }

Also applies to: 51-56


55-56: Avoid reloading sources until system time is valid.

If time isn’t synced yet (now == 0), reloads can yield bogus ETDs. Guard the reloads.

-    doneBooting();
-    reloadSources(now);
+    doneBooting();
+    if (now > 0) reloadSources(now);
 ...
-  if (becameOn || becameEnabled) {
-    reloadSources(now);
-  }
+  if (becameOn || becameEnabled) {
+    if (now > 0) reloadSources(now);
+  }

Also applies to: 60-62


23-30: Pre-reserve vector capacity to reduce heap churn on MCUs.

Small, safe optimization for fragmented heaps.

 BartDepart::BartDepart() {
-  sources_.push_back(::make_unique<LegacyBartSource>());
+  sources_.reserve(1);
+  sources_.push_back(::make_unique<LegacyBartSource>());
   model_ = ::make_unique<BartStationModel>();
-  views_.push_back(::make_unique<PlatformView>("1"));
+  views_.reserve(4);
+  views_.push_back(::make_unique<PlatformView>("1"));
   views_.push_back(::make_unique<PlatformView>("2"));
   views_.push_back(::make_unique<PlatformView>("3"));
   views_.push_back(::make_unique<PlatformView>("4"));
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e6bbcf3 and c323ab5.

📒 Files selected for processing (1)
  • usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (5)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.h (2)
  • BartDepart (22-54)
  • setup (39-45)
usermods/usermod_v2_bartdepart/util.h (2)
  • time_now_utc (24-24)
  • s (13-19)
usermods/usermod_v2_bartdepart/platform_view.cpp (4)
  • view (25-105)
  • view (25-26)
  • appendConfigData (107-130)
  • appendConfigData (107-107)
usermods/usermod_v2_bartdepart/legacy_bart_source.cpp (4)
  • addToConfig (73-78)
  • addToConfig (73-73)
  • readFromConfig (80-88)
  • readFromConfig (80-80)
usermods/usermod_v2_bartdepart/platform_view.h (3)
  • addToConfig (20-20)
  • appendConfigData (21-21)
  • readFromConfig (23-25)

@netmindz netmindz added effect usermod usermod related labels Aug 31, 2025
@netmindz
Copy link
Member

Thanks for the PR, but I wonder if it's a little too niche to be included in the main codebase (even as a usermod)

@netmindz netmindz self-assigned this Aug 31, 2025
@ksedgwic
Copy link
Author

ksedgwic commented Sep 2, 2025

I wonder if it's a little too niche to be included in the main codebase

Yeah, that makes sense. I'm going to take a look to see if I can generalize it to support GTFS-RT (https://gtfs.org/) ... that would make it applicable in many more places ...

"GTFSdepart" instead (or something like that)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
usermods/usermod_v2_bartdepart/legacy_bart_source.cpp (5)

13-18: Tiny RAM win in composeUrl: reserve and place literals in flash.

Pre-reserve and wrap constant pieces with F() to keep them out of RAM.

 static String composeUrl(const String& base, const String& key, const String& station) {
-  String url = base;
-  url += "&key="; url += key;
-  url += "&orig="; url += station;
+  String url = base;
+  url.reserve(base.length() + key.length() + station.length() + 16);
+  url += F("&key=");  url += key;
+  url += F("&orig="); url += station;
   return url;
 }

60-66: Avoid temp + move; reserve and emplace in-place.

Saves a small copy/move and pre-allocates correct capacity.

-  std::unique_ptr<BartStationModel> model(new BartStationModel());
+  auto model = std::make_unique<BartStationModel>();
+  model->platforms.reserve(platformIds().size());
   for (const String& pid : platformIds()) {
     if (pid.isEmpty()) continue;
-    BartStationModel::Platform tp(pid);
-    tp.update(root);
-    model->platforms.push_back(std::move(tp));
+    model->platforms.emplace_back(pid);
+    model->platforms.back().update(root);
   }

68-71: Optional: add slight jitter to nextFetch_ to avoid sync storms across many devices.

Keeps upstream/API happier when many nodes reboot together.

-  nextFetch_ = now + updateSecs_;
+  nextFetch_ = now + updateSecs_;
+  // Optional jitter (0-2s) to desynchronize devices:
+  // nextFetch_ += (millis() % 3);

23-33: Set timeouts and enable connection reuse (if supported) for stability.

Prevents hangs on slow networks and saves TLS handshakes.

   String url = composeUrl(apiBase_, apiKey_, apiStation_);
   if (!https_.begin(client_, url)) {
@@
   }
+  // Keep operations bounded and reuse connection if the core supports it
+  https_.setTimeout(5000);
+  #ifdef HTTPCLIENT_KEEPOPEN_SUPPORTED
+  https_.setReuse(true);
+  #endif

Please verify the exact API availability for the HTTPClient in the WLED ESP8266/ESP32 core you target (setReuse name/guard may differ).


80-95: Tighten config handling: clamp UpdateSecs, remove unused locals, and apply new interval immediately.

Prevents extreme intervals, avoids unused vars, and picks up changed cadence without waiting.

   bool ok = true;
-  uint16_t prevUpdate = updateSecs_;
+  uint16_t prevUpdate = updateSecs_;
   String   prevBase   = apiBase_;
-  String   prevKey    = apiKey_;
   String   prevStation= apiStation_;
@@
   ok &= getJsonValue(root["UpdateSecs"], updateSecs_, 60);
   ok &= getJsonValue(root["ApiBase"],    apiBase_,    apiBase_);
   ok &= getJsonValue(root["ApiKey"],     apiKey_,     apiKey_);
   ok &= getJsonValue(root["ApiStation"], apiStation_, apiStation_);
+  // Clamp UpdateSecs to sane bounds
+  if (updateSecs_ < 10)   updateSecs_ = 10;
+  if (updateSecs_ > 3600) updateSecs_ = 3600;
+  // If interval changed at runtime, fetch ASAP
+  if (startup_complete && updateSecs_ != prevUpdate) nextFetch_ = 0;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c323ab5 and 03f718b.

📒 Files selected for processing (2)
  • usermods/usermod_v2_bartdepart/legacy_bart_source.cpp (1 hunks)
  • usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-29T19:55:40.229Z
Learnt from: ksedgwic
PR: wled/WLED#4884
File: usermods/usermod_v2_bartdepart/legacy_bart_source.cpp:4-6
Timestamp: 2025-08-29T19:55:40.229Z
Learning: For the BartDepart usermod on ESP8266/ESP32, setInsecure() is used for TLS connections due to heap memory constraints, and this is acceptable because it's not a security critical application (transit departure display).

Applied to files:

  • usermods/usermod_v2_bartdepart/legacy_bart_source.cpp
📚 Learning: 2025-08-29T19:51:15.486Z
Learnt from: ksedgwic
PR: wled/WLED#4883
File: usermods/usermod_v2_skystrip/open_weather_map_source.cpp:13-19
Timestamp: 2025-08-29T19:51:15.486Z
Learning: On ESP8266/ESP32 devices with limited heap, using HTTPS for API calls that return large JSON responses may not be feasible due to the combined memory requirements of TLS handshake and JSON parsing. HTTP may be necessary despite security concerns.

Applied to files:

  • usermods/usermod_v2_bartdepart/legacy_bart_source.cpp
📚 Learning: 2025-08-29T20:00:13.351Z
Learnt from: ksedgwic
PR: wled/WLED#4884
File: usermods/usermod_v2_bartdepart/legacy_bart_source.h:19-24
Timestamp: 2025-08-29T20:00:13.351Z
Learning: For the BartDepart usermod, HTTPS with setInsecure() is necessary because BART API servers redirect HTTP requests to HTTPS, not for security reasons. The application displays public transit data and is not security critical.

Applied to files:

  • usermods/usermod_v2_bartdepart/legacy_bart_source.cpp
🧬 Code graph analysis (1)
usermods/usermod_v2_bartdepart/legacy_bart_source.cpp (2)
usermods/usermod_v2_bartdepart/legacy_bart_source.h (1)
  • LegacyBartSource (15-39)
usermods/usermod_v2_bartdepart/usermod_v2_bartdepart.cpp (4)
  • addToConfig (89-100)
  • addToConfig (89-89)
  • readFromConfig (112-137)
  • readFromConfig (112-112)
🔇 Additional comments (2)
usermods/usermod_v2_bartdepart/legacy_bart_source.cpp (2)

4-6: setInsecure() trade-off acknowledged.

Using prior learning from this PR track: setInsecure() is acceptable here given ESP heap limits and non-sensitive data. No action needed.


31-39: Good: robust non-2xx handling.

Early-exiting on non-2xx and backing off avoids parsing junk and wasting RAM/CPU.

Comment on lines +41 to +51
String payload = https_.getString();
https_.end();

size_t jsonSz = payload.length() * 2;
DynamicJsonDocument doc(jsonSz);
auto err = deserializeJson(doc, payload);
if (err) {
nextFetch_ = now + updateSecs_ * backoffMult_;
if (backoffMult_ < 16) backoffMult_ *= 2;
return nullptr;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Stream JSON instead of building a String to cut peak RAM and avoid OOM.

Current flow holds payload String and DynamicJsonDocument simultaneously. Stream-deserialize from the HTTPClient stream, size the doc from Content-Length, and log parse errors.

Apply:

-  String payload = https_.getString();
-  https_.end();
-
-  size_t jsonSz = payload.length() * 2;
-  DynamicJsonDocument doc(jsonSz);
-  auto err = deserializeJson(doc, payload);
+  size_t contentLen = https_.getSize();
+  size_t jsonSz = contentLen > 0 ? (size_t)(contentLen + 512) : 4096; // heuristic
+  DynamicJsonDocument doc(jsonSz);
+  auto err = deserializeJson(doc, https_.getStream());
+  https_.end();
   if (err) {
+    DEBUG_PRINTF("BartDepart: LegacyBartSource::fetch: JSON parse error: %s\n", err.c_str());
     nextFetch_ = now + updateSecs_ * backoffMult_;
     if (backoffMult_ < 16) backoffMult_ *= 2;
     return nullptr;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
String payload = https_.getString();
https_.end();
size_t jsonSz = payload.length() * 2;
DynamicJsonDocument doc(jsonSz);
auto err = deserializeJson(doc, payload);
if (err) {
nextFetch_ = now + updateSecs_ * backoffMult_;
if (backoffMult_ < 16) backoffMult_ *= 2;
return nullptr;
}
// Stream‐deserialize JSON instead of buffering in a String to reduce peak RAM usage
size_t contentLen = https_.getSize();
size_t jsonSz = contentLen > 0
? static_cast<size_t>(contentLen + 512) // add slack
: 4096; // fallback heuristic
DynamicJsonDocument doc(jsonSz);
auto err = deserializeJson(doc, https_.getStream());
https_.end();
if (err) {
DEBUG_PRINTF("BartDepart: LegacyBartSource::fetch: JSON parse error: %s\n", err.c_str());
nextFetch_ = now + updateSecs_ * backoffMult_;
if (backoffMult_ < 16) backoffMult_ *= 2;
return nullptr;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
effect usermod usermod related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants