Repository navigation
MQTT telemetry and remote settings - #83
Merged
Merged
Conversation
Cubes report to an MQTT broker over wss:// and take settings from it. Each picks a random ID once and publishes under cube/<id>/: status (retained, with an offline last will), boot (version, boot log, MAC, SSID/BSSID, local and public IP), health, usage and perf every interval, a WiFi scan at boot and hourly, crash summaries and core dumps (once per dump, remembered by CRC once the broker acknowledges every part), and retained settings. Commands arrive on cube/<id>/set/<name>, checked by lib/remote (hardware free, host tested) and applied the way the dashboard applies them; results go to ack. The broker and its shared login come from the gitignored secrets.h; builds without one never connect. The Startup card's neighbour on the System tab and /api/v1/telemetry show the connection. Also: Updates::checkNow() (the check_updates command), and CONFIG_MBEDTLS_DEBUG off -- ESP-IDF's TLS layer, which the MQTT client uses, turned it into a dozen log lines per TLS record, and on the USB-Serial-JTAG console those block when no host is reading. CLAUDE.md now says the generated sdkconfig is only regenerated when missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests124/124 passed.
|
Static analysisNo defects found.
|
Firmware size-24.9 KB across 1 environment.
|
| Section | Base | Head | Δ |
|---|---|---|---|
.flash.rodata |
534.8 KB | 517.6 KB | -17.2 KB |
.flash.text |
1.18 MB | 1.17 MB | -7.7 KB |
.dram0.bss |
37.3 KB | 37.8 KB | +504 B |
.dram0.data |
23.8 KB | 23.8 KB | +16 B |
esp32-s3-devkitc-1 — 401 symbol(s) changed
| Symbol | Base | Head | Δ |
|---|---|---|---|
ssl_parse_client_hello |
2.7 KB | 0 B | -2.7 KB |
mbedtls_ssl_handshake_client_step |
5.7 KB | 3.5 KB | -2.2 KB |
LostMode::tryOpenNetworks() |
0 B | 1.8 KB | +1.8 KB |
Cube::initAPI()::{lambda(AsyncWebServerRequest*)#13}::operator()(AsyncWebServerRequest*) const |
0 B | 1.8 KB | +1.8 KB |
Cube::initAPI()::{lambda(AsyncWebServerRequest*)#12}::operator()(AsyncWebServerRequest*) const |
1.7 KB | 0 B | -1.7 KB |
mbedtls_x509_crt_info |
1.2 KB | 0 B | -1.2 KB |
void std::__introsort_loop<__gnu_cxx::__normal_iterator<LostMode::tryOpenNetworks()::Open*, std::vector<LostMode::tryOpenNetworks()::Open, std::allocator<LostMode::tryOpenNetworks()::Open> > >, int, __gnu_cxx::__ops::_Iter_comp_iter<LostMode::tryOpenNetworks()::{lambda(LostMode::tryOpenNetworks()::Open const&, LostMode::tryOpenNetworks()::Open const&)#1}> >(__gnu_cxx::__normal_iterator<LostMode::tryOpenNetworks()::Open*, std::vector<LostMode::tryOpenNetworks()::Open, std::allocator<LostMode::tryOpenNetworks()::Open> > >, __gnu_cxx::__normal_iterator<LostMode::tryOpenNetworks()::Open*, std::vector<LostMode::tryOpenNetworks()::Open, std::allocator<LostMode::tryOpenNetworks()::Open> > >, int, __gnu_cxx::__ops::_Iter_comp_iter<LostMode::tryOpenNetworks()::{lambda(LostMode::tryOpenNetworks()::Open const&, LostMode::tryOpenNetworks()::Open const&)#1}>) |
0 B | 1.1 KB | +1.1 KB |
Cube::initAPI()::{lambda(AsyncWebServerRequest*)#6}::operator()(AsyncWebServerRequest*) const |
0 B | 1.0 KB | +1.0 KB |
_ZZN4Cube7initAPIEvENKUlP21AsyncWebServerRequestE5_clES1_$isra$0 |
0 B | 998 B | +998 B |
_ZZN4Cube7initAPIEvENKUlP21AsyncWebServerRequestE4_clES1_$isra$0 |
974 B | 0 B | -974 B |
LostMode::setPin(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) |
0 B | 934 B | +934 B |
Cube::applyRemote[abi:cxx11](remote::Command const&) |
0 B | 930 B | +930 B |
mbedtls_x509_info_subject_alt_name |
871 B | 0 B | -871 B |
mbedtls_x509_dn_gets |
798 B | 0 B | -798 B |
ssl_parse_certificate_request |
748 B | 0 B | -748 B |
| +386 more |
Baseline: 9ddd045. Image size is firmware.bin against the smaller app partition slot. RAM is static allocation only — the linker cannot see heap or stack.
Lost mode, from MQTT (lost_message, lost_pin, lost_silent, lost_mode): the cube shows the message on a hidden Lost pattern (unless silent), locks its dashboard and API (423) and firmware uploads (GitHub updates still install), and reports a retained location every two minutes -- a fresh public IP, its network and access point, and a scan. It persists through restarts, power loss and WiFi resets. WiFi setup stays open on purpose: someone setting it up on their network is its likeliest way back online; offline for a few minutes, it also tries open networks, keeping one only if the broker answers through it, without touching the saved network. lost_mode false, or the PIN (a salted hash; five guesses, then one per 15 minutes) on the dashboard, turns it off. boot and wifi are retained now, so a client subscribing later still sees the SSID and scan; health carries the SSID; and every (re)connection publishes a retained network message with a fresh public IP. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cubes in the field report to an MQTT broker (EMQX, behind Tailscale Funnel) and take settings from it. Always on with no opt-out, as agreed while the project is in testing. The README's new Telemetry section lists everything that's sent.
What's published (
cube/<id>/…, JSON)${clientid}.status: retainedonline, withofflineas the last will.boot: version, build, chip, MAC, SSID, BSSID, RSSI, channel, local IP, public IP (from ipify), reset reason, rollbacks and the boot log.health/usage/perf: everytelemetry_interval(default 300 s), each switchable. Uptime and memory low-water marks, the current BSSID, seconds per pattern, brightness and features in use, and renderer timings.wifi: a scan at boot and hourly, up to 30 networks.crash: the core dump summary (task, PC, cause, backtrace, ELF SHA), then the dump in base64 parts. It's sent once per dump, and only marked sent (by CRC) once the broker has acknowledged every part.resend_crashsends it again.settings: retained, republished each interval and after every command.ack: the result of each command.Remote settings (
cube/<id>/set/<name>)restart,check_updatesandresend_crash.lib/remote(hardware free, 6 host tests) validates each one: known names, integer ranges, flag spellings, text limits.Cube::applyRemoteapplies them through the same code paths as the dashboard, so the dashboard stays in sync.Lost mode
lost_messageandlost_pin(4–12 digits, stored as a salted SHA-256), optionallylost_silent, thenlost_mode true.lost_silent, patterns run as usual.locationevery 2 minutes: a fresh public IP, network, BSSID and a scan;loststatus;networkreport whenever it comes online anywhere.lost_mode false, or the PIN on the dashboard's System tab: five guesses, then one per 15 minutes, counted across restarts.Retained reports
bootandwifiare now retained, so a client that subscribes later still sees the SSID and scan. Health reports include the SSID, and each (re)connection publishes a retainednetworkmessage with a re-fetched public IP.Plumbing
wss://, with certificates checked against the existing bundle, its own task and auto-reconnect. Publishing happens from a low-priorityTelemetrytask.MQTT_URL/MQTT_USER/MQTT_PASSWORDfrom the gitignoredsecrets.h. Builds without them don't connect (this PR's CI build included)./api/v1/telemetry.Updates::checkNow(): wakes the GitHub updater.CONFIG_MBEDTLS_DEBUGoff: ESP-IDF's TLS layer, unlike Arduino's, turned it into verbose logging of every TLS record. On the USB-Serial-JTAG console those writes block when no host is reading.Tested on the cube against EMQX
wss://emqx.tail1ca91.ts.net:8443/mqtt) with thecubelogin. Anonymous connections are refused.adminlogin.Follow-up
Release builds need
build-release.ymlto writesecrets.hfrom repository secrets, which depends on elliotmatson/pio-actions#27 (abuild-envsecret) being released. That change follows as a separate small PR.🤖 Generated with Claude Code