Skip to content

Commit f2e61db

Browse files
Niko11111claude
andcommitted
fix: stop pulling GitHub's whole release list onto the heap (v0.6.3-beta.15)
With pre-releases enabled the update check asked for the full /releases list and ran it through http.getString() before parsing. That answer is 109 KB today against roughly 145 KB of free heap, so the body alone took three quarters of what was available and the document tree was built on top of it. It failed or succeeded depending on how fragmented the heap happened to be, which is why "JSON error" appeared once and then went away on a retry. Three changes, all on the same path: - per_page=5 on the request. The list is unbounded otherwise and grows with every release ever cut. - The response is parsed straight off the socket instead of being materialised as a String first. - A filter keeps only tag_name and draft, so what lands in RAM is a handful of bytes rather than a mirror of GitHub's answer. The capacity DynamicJsonDocument carried was a fiction, incidentally: ArduinoJson 7 ignores it and grows the document as needed, so the 8192 suggested a bound that was never enforced. Also fixes a dangling pointer on the same path. tag was a const char* into the document, the document was destroyed at the end of the block, and tag was read afterwards. It survived because the freed block usually still held the bytes - but since v0.6.3-beta.14 that pointer is what the download URL is built from, so a garbage read would now produce a garbage URL. The tag is copied into a local buffer while the document is alive. New code uses add<JsonObject>() rather than the deprecated createNestedObject() the older filters in this repo use. Same result, no new deprecation warnings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent b164c65 commit f2e61db

3 files changed

Lines changed: 52 additions & 25 deletions

File tree

src/app_config.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#pragma once
22

3-
#define FW_VERSION "v0.6.3-beta.14"
3+
#define FW_VERSION "v0.6.3-beta.15"
44
#define DONATION_URL "ko-fi.com/formfollowsfunction"
55

66
// Backlight PWM duty on GPIO45, 8 bit, straight through to LovyanGFX. Not a

src/main.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// ============================================================
22
// SpoolmanScale – Bambu NFC Tag Reader & Decoder
33
// Board: WT32-SC01 Plus (ESP32-S3)
4-
// Version: v0.6.3-beta.14
4+
// Version: v0.6.3-beta.15
55
//
66
// Reads Bambu Lab MIFARE Classic tags, derives keys via KDF
77
// (HKDF/SHA256, master key from Bambu-Research-Group/RFID-Tag-Guide),

src/ui/ota_github.cpp

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ void doGithubOtaCheck() {
5656
WiFiClientSecure client;
5757
client.setInsecure();
5858
HTTPClient http;
59+
// per_page caps the list. Unbounded it answers with every release ever cut -
60+
// 109 KB at the time of writing, against roughly 145 KB of free heap.
5961
String url = gh_prerelease
60-
? "https://api.github.com/repos/Niko11111/SpoolmanScale/releases"
62+
? "https://api.github.com/repos/Niko11111/SpoolmanScale/releases?per_page=5"
6163
: "https://api.github.com/repos/Niko11111/SpoolmanScale/releases/latest";
6264
http.begin(client, url);
6365
http.addHeader("User-Agent", "SpoolmanScale-ESP32");
@@ -73,33 +75,58 @@ void doGithubOtaCheck() {
7375
return;
7476
}
7577

76-
String payload = http.getString();
77-
http.end();
78+
// Parsed straight off the socket and through a filter. getString() used to
79+
// pull the whole body onto the heap first, which on the pre-release path was
80+
// 109 KB of a 145 KB heap before parsing had even started - hence the
81+
// intermittent "JSON error", which came and went with heap fragmentation.
82+
// The capacity argument DynamicJsonDocument used to carry was a fiction:
83+
// ArduinoJson 7 ignores it and grows the document as needed.
84+
//
85+
// The filter keeps only the two keys this function looks at, so what is built
86+
// in RAM is a handful of bytes rather than a mirror of GitHub's answer.
87+
char tag[40] = "";
88+
bool parse_ok = false;
7889

79-
const char* tag = "";
8090
if (gh_prerelease) {
81-
DynamicJsonDocument doc(8192);
82-
doc.clear();
83-
if (deserializeJson(doc, payload)) {
84-
lv_label_set_text(lbl_gh_status, "JSON error");
85-
lv_obj_set_style_text_color(lbl_gh_status, lv_color_hex(0xff8080), 0);
86-
return;
87-
}
88-
JsonArray arr = doc.as<JsonArray>();
89-
for (JsonObject rel : arr) {
90-
if (rel["draft"] | false) continue;
91-
tag = rel["tag_name"] | "";
92-
if (tag[0] != '\0') break;
91+
// add<JsonObject>() rather than the createNestedObject() the older filters
92+
// in this repo use - same result, and it is the form ArduinoJson 7 keeps.
93+
JsonDocument filter;
94+
JsonObject f = filter.to<JsonArray>().add<JsonObject>();
95+
f["tag_name"] = true;
96+
f["draft"] = true;
97+
98+
JsonDocument doc;
99+
if (!deserializeJson(doc, http.getStream(), DeserializationOption::Filter(filter))) {
100+
for (JsonVariant v : doc.as<JsonArray>()) {
101+
JsonObject rel = v.as<JsonObject>();
102+
if (rel["draft"] | false) continue;
103+
const char* t = rel["tag_name"] | "";
104+
if (t[0] != '\0') {
105+
// Copied while the document is still alive. The pointer dies with it,
106+
// and it is read further down to build the download URL.
107+
strncpy(tag, t, sizeof(tag) - 1);
108+
break;
109+
}
110+
}
111+
parse_ok = true;
93112
}
94113
} else {
95-
DynamicJsonDocument doc(2048);
96-
doc.clear();
97-
if (deserializeJson(doc, payload)) {
98-
lv_label_set_text(lbl_gh_status, "JSON error");
99-
lv_obj_set_style_text_color(lbl_gh_status, lv_color_hex(0xff8080), 0);
100-
return;
114+
JsonDocument filter;
115+
filter["tag_name"] = true;
116+
117+
JsonDocument doc;
118+
if (!deserializeJson(doc, http.getStream(), DeserializationOption::Filter(filter))) {
119+
const char* t = doc["tag_name"] | "";
120+
strncpy(tag, t, sizeof(tag) - 1);
121+
parse_ok = true;
101122
}
102-
tag = doc["tag_name"] | "";
123+
}
124+
http.end();
125+
126+
if (!parse_ok) {
127+
lv_label_set_text(lbl_gh_status, "JSON error");
128+
lv_obj_set_style_text_color(lbl_gh_status, lv_color_hex(0xff8080), 0);
129+
return;
103130
}
104131

105132
if (tag[0] == '\0') {

0 commit comments

Comments
 (0)