ruvnet / ruvnet/RuView

firmware: aggregator IP change (DHCP) silently kills every node with no way to recover except USB re-provisioning — proposal: POST /config on the OTA server

Open
#1,895 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug firmware
Dominant language
Rust
Stars
94.4k
Forks
12.5k
Avg merge
21h 27m
Merged PRs (30d)
43

Description

Summary

When the aggregator host's IP changes — an ordinary DHCP lease change on a home network — every provisioned node is bricked as a sensor until someone physically reconnects it to a computer over USB. target_ip lives in NVS and there is no way to change it except provision.py over serial. On a deployed mesh with nodes on chargers around the house, that means collecting every board.

Filing this as an enhancement request, with a small patch that worked for me.

What it looks like when it happens

The node stays perfectly healthy and reachable: it associates, keeps its IP, serves GET /ota/status on :8032, and captures CSI at full rate. Only the egress path is dead. The console fills with:

W (2446) stream_sender: sendto ENOMEM — backing off for 100 ms (streak 1)
W (2456) stream_sender: sendto suppressed (ENOMEM backoff, 1 dropped)
I (2566) stream_sender: ENOMEM backoff expired, resuming sends (3 were suppressed)
W (2566) stream_sender: sendto ENOMEM — backing off for 200 ms (streak 2)
...

forever, starting from the very first frame. Server side shows esp32:offline and nodes going stale.

The cause is simply that nothing answers ARP at the stored target_ip, so lwIP cannot queue the datagram and returns ERR_MEM. Worth noting because this is the same console signature as #1135, which had an entirely different root cause — that made it a confusing thing to diagnose. A one-line hint in the log when the ARP entry for target_ip is unresolved would have saved a lot of time.

Environment

3 × ESP32-S3-DevKitC-1 N16R8, firmware 0.8.8, aggregator on macOS in Docker. The host's lease moved from .100 to .167 and all three nodes went silent simultaneously.

Suggested fix

Expose the aggregator target on the OTA HTTP server, which already exists, already listens on 8032, and already has PSK auth. I added this locally as POST /config in main/ota_update.c, body "<ip>" or "<ip>:<port>":

$ curl -X POST -H "Authorization: Bearer $PSK" --data-binary "192.168.1.167:5005" \
       http://192.168.1.101:8032/config
{"status":"ok","target_ip":"192.168.1.167","target_port":5005,"message":"Rebooting..."}

The handler validates the dotted quad and port range, writes target_ip / target_port into the csi_cfg NVS namespace, and reboots. It reuses ota_check_auth() so it is exactly as protected as firmware upload, and it fails closed on a node with no PSK provisioned.

This turned a "collect all the boards" job into three commands with the nodes left where they were. Happy to open a PR if the approach looks reasonable — I'd want guidance first on whether you'd prefer a general NVS key/value endpoint over a narrow target-only one, and whether a reboot or a live re-open of the socket is preferred.

Alternative / complementary ideas

  • Re-resolve target_ip periodically, or accept a hostname (mDNS ruview-installation.local is already advertised by the server) instead of a literal address.
  • Have the node fall back to broadcast or mDNS discovery after N consecutive ENOMEM/unreachable failures, rather than backing off forever against an address that no longer exists.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read main/ota_update.c and the existing OTA HTTP routes, especially ota_check_auth(), then inspect how provision.py writes target_ip and target_port in the csi_cfg NVS namespace. Confirm the preferred scope for the configuration endpoint and restart behavior with maintainers. Done means an authenticated update path handles the proposed address and port validation, persists the values, and restores node streaming after reboot.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
authentication, embedded-iot, networking
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
63/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.