Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ scripts/capture_patterns.py --host cube.local <pattern id>` (frames come from
repository secrets), but anything compiled in can be read out of a release.
The broker's ACL is what keeps the shared login to a cube's own topics; a
new setting reachable over MQTT goes in `lib/remote` and `Cube::applyRemote`.
- **Parse JSON from the network through `WaitingStream`.**
`NetworkClientSecure` returns -1 from `read(buf, n)` whenever no data has
arrived yet, and `NetworkClient::readBytes()` takes that as the end, so
`deserializeJson(doc, http.getStream())` fails with `IncompleteInput` at
the first pause. That broke every development-channel update check in
v0.6.0-b2. Wrap the client: `WaitingStream body(http.getStream())`.
- **Updates only go forwards.** The GitHub updater installs a release only
if `firmware_image::isNewer()` says its tag is newer than `FW_VERSION`.
GitHub's "latest" is the newest *stable* release, older than any beta.
Expand Down
4 changes: 3 additions & 1 deletion lib/cube/updates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <esp_ota_ops.h>
#include <esp_partition.h>
#include "fonts.h"
#include "waiting_stream.h"


// The cube's custom app descriptor, placed right after the standard one in
Expand Down Expand Up @@ -780,7 +781,8 @@ bool Updates::findFirmwareRelease(String &tag, String &firmwareUrl)
releaseFilter["assets"][0]["browser_download_url"] = true;

JsonDocument doc(spiRamAllocator());
DeserializationError err = deserializeJson(doc, http.getStream(), DeserializationOption::Filter(filter));
WaitingStream body(http.getStream());
DeserializationError err = deserializeJson(doc, body, DeserializationOption::Filter(filter));
http.end();
if (err)
{
Expand Down
41 changes: 41 additions & 0 deletions lib/cube_utils/waiting_stream.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* @file waiting_stream.h
* @brief A Stream view of a network client that waits for data instead of
* reporting the end of it.
*/
#ifndef WAITING_STREAM_H
#define WAITING_STREAM_H

#include <Arduino.h>

/**
* @brief Wraps a network client so a parser reading from it waits through
* gaps in the data.
*
* NetworkClientSecure::read(buf, size) returns -1 whenever no decrypted data
* is ready yet, and NetworkClient::readBytes() treats -1 as an error and
* stops. ArduinoJson reads through readBytes(), so the first pause in a
* download -- every time, on a busy network -- ended the input and the parse
* failed with IncompleteInput. This class overrides only the single-byte
* read(), so Stream's own readBytes() is used: it retries until the client's
* timeout passes with nothing arriving.
*/
class WaitingStream : public Stream
{
public:
/** @param source the client to read from; its timeout is used. */
explicit WaitingStream(Stream &source) : source(source)
{
setTimeout(source.getTimeout());
}

int available() override { return source.available(); }
int read() override { return source.read(); }
int peek() override { return source.peek(); }
size_t write(uint8_t) override { return 0; }

private:
Stream &source;
};

#endif
4 changes: 3 additions & 1 deletion lib/patterns/weather/weather_pattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <NetworkClientSecure.h>
#include "waiting_stream.h"
#include <WiFi.h>
#include <math.h>

Expand Down Expand Up @@ -84,7 +85,8 @@ namespace
http.end();
return false;
}
const DeserializationError err = deserializeJson(doc, http.getStream(), DeserializationOption::Filter(filter));
WaitingStream body(http.getStream());
const DeserializationError err = deserializeJson(doc, body, DeserializationOption::Filter(filter));
http.end();
if (err)
{
Expand Down
Loading