Skip to content

Commit 7e6c166

Browse files
committed
fix: reject implausible RCC6 battery readings
1 parent 6c486d9 commit 7e6c166

9 files changed

Lines changed: 63 additions & 28 deletions

File tree

.github/workflows/rcc6-release-build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ jobs:
8585
8686
out = Path("release")
8787
out.mkdir()
88-
app_name = "NeonPocketMC-RCC6-Ultimate-Observer-v1.3.0-rc.2-app.bin"
89-
recovery_name = "NeonPocketMC-RCC6-Ultimate-Observer-v1.3.0-rc.2-full-recovery-preserves-meshcore-settings.bin"
90-
configurator_name = "NeonPocketMC-RCC6-Ultimate-Server-v1.3.0-rc.2-configurator.zip"
88+
app_name = "NeonPocketMC-RCC6-Ultimate-Observer-v1.3.0-rc.3-app.bin"
89+
recovery_name = "NeonPocketMC-RCC6-Ultimate-Observer-v1.3.0-rc.3-full-recovery-preserves-meshcore-settings.bin"
90+
configurator_name = "NeonPocketMC-RCC6-Ultimate-Server-v1.3.0-rc.3-configurator.zip"
9191
(out / app_name).write_bytes(app)
9292
(out / recovery_name).write_bytes(merged)
9393

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ The USB configurator accepts only the two full Room Server profiles (and the exi
5454

5555
## What each role does
5656

57-
- **RCC6 Ultimate observer/repeater:** forwards mesh traffic, observes packets, joins 2.4 GHz Wi-Fi, publishes to at most two MQTT brokers, and serves the full Ultimate dashboard. The human-first analytics and Nearby overhaul arrived in RC1; current `v1.3.0-rc.2` adds explicit saved-location advert sharing. Prior server/repeater releases remain available.
57+
- **RCC6 Ultimate observer/repeater:** forwards mesh traffic, observes packets, joins 2.4 GHz Wi-Fi, publishes to at most two MQTT brokers, and serves the full Ultimate dashboard. Current `v1.3.0-rc.3` rejects impossible RCC6 battery readings while retaining explicit saved-location advert sharing. Prior server/repeater releases remain available.
5858
- **Room Server, both sizes:** hosts the standard MeshCore room/client protocol with 32 recent posts held in RAM. A reboot clears those buffered posts. Repeating is optional but defaults off; a separate repeater is recommended.
5959
- **Room Server, minimal:** LoRa room service and USB CLI only. It has no Wi-Fi, Web dashboard, or MQTT code to configure.
6060
- **Room Server, full:** adds 2.4 GHz AP/STA onboarding, the authenticated dashboard, and one-way MQTT observation. MQTT data is never injected into RF.
@@ -65,15 +65,15 @@ All roles use fail-closed MeshCore storage and default their own adverts to **3-
6565

6666
| Image | Setup after flashing |
6767
|---|---|
68-
| Ultimate repeater/observer `v1.3.0-rc.2` | Supplied Windows/Linux network wizard |
69-
| Ultimate Room Server full headless or full TFT | `v1.3.0-rc.2` network wizard; it also changes both room passwords |
68+
| Ultimate repeater/observer `v1.3.0-rc.3` | Supplied Windows/Linux network wizard |
69+
| Ultimate Room Server full headless or full TFT | `v1.3.0-rc.3` network wizard; it also changes both room passwords |
7070
| Room Server minimal headless or minimal TFT | USB serial CLI or the generic MeshCore USB configurator; no network wizard |
7171

7272
Always attach a tuned LoRa antenna before powering or transmitting. Flash the selected application image at `0x10000`, leave USB connected, and do not deploy until the radio settings and passwords have been changed.
7373

7474
### Guided setup for observer and full profiles
7575

76-
Download `NeonPocketMC-RCC6-Ultimate-Server-v1.3.0-rc.2-configurator.zip` from the same release as the selected firmware.
76+
Download `NeonPocketMC-RCC6-Ultimate-Server-v1.3.0-rc.3-configurator.zip` from the same release as the selected firmware.
7777

7878
- **Windows:** double-click `Configure-RCC6-Windows.cmd`.
7979
- **Linux:** open the extracted folder in a terminal and run `sh configure-rcc6-linux.sh`.
@@ -264,18 +264,18 @@ Install [esptool](https://docs.espressif.com/projects/esptool/en/latest/esp32c6/
264264

265265
The current RCC6 Ultimate server release files are:
266266

267-
- `NeonPocketMC-RCC6-Ultimate-Observer-v1.3.0-rc.2-app.bin`
268-
- `NeonPocketMC-RCC6-Ultimate-Observer-v1.3.0-rc.2-full-recovery-preserves-meshcore-settings.bin`
269-
- `NeonPocketMC-RCC6-Ultimate-Server-v1.3.0-rc.2-configurator.zip`
267+
- `NeonPocketMC-RCC6-Ultimate-Observer-v1.3.0-rc.3-app.bin`
268+
- `NeonPocketMC-RCC6-Ultimate-Observer-v1.3.0-rc.3-full-recovery-preserves-meshcore-settings.bin`
269+
- `NeonPocketMC-RCC6-Ultimate-Server-v1.3.0-rc.3-configurator.zip`
270270

271-
Room Server `v1.3.0-rc.2` uses these profile-specific names:
271+
Room Server `v1.3.0-rc.3` uses these profile-specific names:
272272

273273
| Profile | Application update | Recovery image |
274274
|---|---|---|
275-
| Minimal headless | `NeonPocketMC-RCC6-Ultimate-Room-Server-minimal-headless-v1.3.0-rc.2-app.bin` | `NeonPocketMC-RCC6-Ultimate-Room-Server-minimal-headless-v1.3.0-rc.2-full-recovery-preserves-meshcore-settings.bin` |
276-
| Minimal TFT | `NeonPocketMC-RCC6-Ultimate-Room-Server-minimal-tft-v1.3.0-rc.2-app.bin` | `NeonPocketMC-RCC6-Ultimate-Room-Server-minimal-tft-v1.3.0-rc.2-full-recovery-preserves-meshcore-settings.bin` |
277-
| Full headless | `NeonPocketMC-RCC6-Ultimate-Room-Server-full-headless-v1.3.0-rc.2-app.bin` | `NeonPocketMC-RCC6-Ultimate-Room-Server-full-headless-v1.3.0-rc.2-full-recovery-preserves-meshcore-settings.bin` |
278-
| Full TFT | `NeonPocketMC-RCC6-Ultimate-Room-Server-full-tft-v1.3.0-rc.2-app.bin` | `NeonPocketMC-RCC6-Ultimate-Room-Server-full-tft-v1.3.0-rc.2-full-recovery-preserves-meshcore-settings.bin` |
275+
| Minimal headless | `NeonPocketMC-RCC6-Ultimate-Room-Server-minimal-headless-v1.3.0-rc.3-app.bin` | `NeonPocketMC-RCC6-Ultimate-Room-Server-minimal-headless-v1.3.0-rc.3-full-recovery-preserves-meshcore-settings.bin` |
276+
| Minimal TFT | `NeonPocketMC-RCC6-Ultimate-Room-Server-minimal-tft-v1.3.0-rc.3-app.bin` | `NeonPocketMC-RCC6-Ultimate-Room-Server-minimal-tft-v1.3.0-rc.3-full-recovery-preserves-meshcore-settings.bin` |
277+
| Full headless | `NeonPocketMC-RCC6-Ultimate-Room-Server-full-headless-v1.3.0-rc.3-app.bin` | `NeonPocketMC-RCC6-Ultimate-Room-Server-full-headless-v1.3.0-rc.3-full-recovery-preserves-meshcore-settings.bin` |
278+
| Full TFT | `NeonPocketMC-RCC6-Ultimate-Room-Server-full-tft-v1.3.0-rc.3-app.bin` | `NeonPocketMC-RCC6-Ultimate-Room-Server-full-tft-v1.3.0-rc.3-full-recovery-preserves-meshcore-settings.bin` |
279279

280280
Do not substitute a similarly named observer, profile, or development artifact.
281281

docs/releases/1.3-RC3.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# RCC6 Ultimate Observer & Room Server v1.3.0 RC3
2+
3+
This experimental RCC6-L62/SX1262 safety update contains the Ultimate MQTT observer/repeater and all four Room Server profiles.
4+
5+
## Battery-reading correction
6+
7+
RCC6 ADC samples above the plausible 4.50 V ceiling for a single-cell battery are now treated as unavailable. TFT and Web dashboards show an unknown state instead of an impossible voltage, stale low-battery warnings are cleared, and MQTT status omits an unavailable measurement.
8+
9+
## Files and flashing
10+
11+
The release includes observer and minimal/full Room Server app images, matching settings-preserving recovery images, the Windows/Linux configurator, exact source/licenses, and an aggregate SHA-256 manifest.
12+
13+
Normal updates use the selected app image at `0x10000`. Use a matching recovery image at `0x0` only for bootloader/partition recovery. Every recovery image stops before SPIFFS; never erase the whole chip.
14+
15+
MQTT remains observation-only and never injects broker traffic into the mesh.

examples/simple_room_server/UITask.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ void UITask::renderCurrScreen() {
105105
if (room.batt_mv != 0) {
106106
if (!_battery_low && room.batt_mv <= ROOM_LOW_BATTERY_MV) _battery_low = true;
107107
else if (_battery_low && room.batt_mv >= ROOM_LOW_BATTERY_CLEAR_MV) _battery_low = false;
108+
} else {
109+
_battery_low = false;
108110
}
109111
char value[48];
110112

@@ -144,9 +146,14 @@ void UITask::renderCurrScreen() {
144146
_display->print(value);
145147

146148
_display->setCursor(3, 86);
147-
snprintf(value, sizeof(value), "TXQ %u ERR %04X BAT %.2fV%s",
148-
(unsigned)room.tx_queue, (unsigned)room.error_flags,
149-
room.batt_mv / 1000.0f, _battery_low ? " LOW" : "");
149+
if (room.batt_mv) {
150+
snprintf(value, sizeof(value), "TXQ %u ERR %04X BAT %.2fV%s",
151+
(unsigned)room.tx_queue, (unsigned)room.error_flags,
152+
room.batt_mv / 1000.0f, _battery_low ? " LOW" : "");
153+
} else {
154+
snprintf(value, sizeof(value), "TXQ %u ERR %04X BAT --",
155+
(unsigned)room.tx_queue, (unsigned)room.error_flags);
156+
}
150157
_display->setColor(room.error_flags || _battery_low ? UIColor::warning_txt
151158
: UIColor::primary_txt);
152159
_display->print(value);

scripts/verify_rcc6_room_server.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,11 @@ def main() -> None:
6969
"WiFi.disconnect(true", "next_memory_probe = millis() + 30000")
7070

7171
ui_cpp = read("examples/simple_room_server/UITask.cpp")
72+
board_cpp = read("variants/heltec_rcc6/heltec_rcc6.cpp")
7273
boot_h = read("examples/simple_room_server/NeonPocketSplash.h")
7374
require(ui_cpp, "NeonPocketSplash::drawFrame", "NeonPocketSplash::FRAME_MILLIS",
74-
"NEONPOCKET ROOM")
75+
"NEONPOCKET ROOM", '"TXQ %u ERR %04X BAT --"')
76+
require(board_cpp, "measured <= 4500U")
7577
require(boot_h, "DURATION_MILLIS = 3200", "FRAME_MILLIS = 125",
7678
"NEONPOCKETMC", "VECTOR BOOT", "RADIO LINK", "ROOM SERVICES",
7779
"MESH READY", "MAGENTA", "drawPocket", "MESHCORE ROOM SERVER")
@@ -100,14 +102,17 @@ def main() -> None:
100102
"What this RCC6 can hear", "Signal view", "drawNeighborMap",
101103
"renderTrafficMix", "drawSignalBars", "renderFreshBars",
102104
"/api/neighbors", "No advertised repeater locations yet",
103-
'data-k="gps.adv_loc"', "Share saved coordinates")
105+
'data-k="gps.adv_loc"', "Share saved coordinates",
106+
'battValid?battVolts.toFixed(2):"—"')
104107
web_server = read("src/helpers/esp32/WebConfigServer.cpp")
105108
web_keys = read("src/helpers/WebConfigKeys.h")
106109
require(web_server, 'radio["advert_loc_policy"]', "wcIsValidAdvertLocationPolicy",
107110
'"gps advert %s"')
108111
require(web_keys, '"gps.adv_loc"', "wcIsValidAdvertLocationPolicy")
109112
require(mesh_h, "buildNeighborsJson", "has_location", "latitude_e6", "longitude_e6")
110113
require(mesh_cpp, "buildNeighborsJson", "has_location", "latitude_e6", "longitude_e6")
114+
mqtt = read("src/helpers/bridges/MQTTBridge.cpp")
115+
require(mqtt, "if (measured) battery_mv = measured;")
111116
for unsafe_route in ('/api/room/post', '/api/room/delete', '/api/room/client'):
112117
if unsafe_route in web:
113118
raise AssertionError(f"unsafe room mutation route present: {unsafe_route}")

src/helpers/bridges/MQTTBridge.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2487,7 +2487,10 @@ void MQTTBridge::publishStatusToSlot(int index) {
24872487
int packets_sent = -1;
24882488
int packets_received = -1;
24892489

2490-
if (_board) battery_mv = _board->getBattMilliVolts();
2490+
if (_board) {
2491+
const uint16_t measured = _board->getBattMilliVolts();
2492+
if (measured) battery_mv = measured;
2493+
}
24912494
if (_ms) uptime_secs = _ms->getMillis() / 1000;
24922495
if (_dispatcher) {
24932496
errors = _dispatcher->getErrFlags();
@@ -3365,7 +3368,10 @@ bool MQTTBridge::publishStatus() {
33653368
int packets_sent = -1;
33663369
int packets_received = -1;
33673370

3368-
if (_board) battery_mv = _board->getBattMilliVolts();
3371+
if (_board) {
3372+
const uint16_t measured = _board->getBattMilliVolts();
3373+
if (measured) battery_mv = measured;
3374+
}
33693375
if (_ms) uptime_secs = _ms->getMillis() / 1000;
33703376
if (_dispatcher) {
33713377
errors = _dispatcher->getErrFlags();

variants/heltec_rcc6/heltec_rcc6.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,6 @@ uint16_t HeltecRCC6Board::getBattMilliVolts() {
4545
raw /= 8;
4646
digitalWrite(PIN_ADC_CTRL, !ADC_CTRL_ENABLED);
4747

48-
return adcMultiplier * raw;
48+
const uint32_t measured = static_cast<uint32_t>(adcMultiplier * raw);
49+
return measured <= 4500U ? static_cast<uint16_t>(measured) : 0;
4950
}

variants/heltec_rcc6/platformio.ini

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ board_build.embed_files = src/certs/x509_crt_bundle.bin
5151
build_flags =
5252
${heltec_rcc6.build_flags}
5353
-D NEONPOCKET_RCC6_REPEATER=1
54-
-D FIRMWARE_VERSION='"v1.17.0-rxfix-rcc6-ultimate-v1.3.0-rc.2"'
54+
-D FIRMWARE_VERSION='"v1.17.0-rxfix-rcc6-ultimate-v1.3.0-rc.3"'
5555
-D ADVERT_NAME='"RCC6 Ultimate Observer"'
5656
-D ADVERT_LAT=0.0
5757
-D ADVERT_LON=0.0
@@ -90,7 +90,7 @@ build_flags =
9090
${heltec_rcc6.build_flags}
9191
-D NEONPOCKET_RCC6_ROOM_SERVER=1
9292
-D NEONPOCKET_SAFE_SPIFFS_BOOTSTRAP=1
93-
-D FIRMWARE_VERSION='"v1.17.0-rcc6-room-v1.3.0-rc.2"'
93+
-D FIRMWARE_VERSION='"v1.17.0-rcc6-room-v1.3.0-rc.3"'
9494
-D ADVERT_NAME='"RCC6 Room Server"'
9595
-D ADVERT_LAT=0.0
9696
-D ADVERT_LON=0.0

webui/index.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,9 +1320,10 @@ <h1 style="margin:16px 0 8px" id="reboot-title">Rebooting&hellip;</h1>
13201320
tile("Post deliveries",s.room_pushes,"pushes queued")+
13211321
tile("Build profile",s.profile||"full-headless",s.experimental?"experimental":"supported"):
13221322
tile("Neighbours",s.neighbors,"recently heard");
1323+
var battValid=Number(s.batt_mv)>0,battVolts=battValid?s.batt_mv/1000:null;
13231324
$("#tiles").innerHTML=
13241325
tile("Uptime",fmtUp(s.uptime_s))+
1325-
tile("Battery",(s.batt_mv/1000).toFixed(2),"V")+
1326+
tile("Battery",battValid?battVolts.toFixed(2):"—",battValid?"V":"unavailable")+
13261327
roleTiles+
13271328
tile("Last RF packet",s.recv?fmtAge(s.last_rx_age):"never")+
13281329
tile("Packet rate",rxrate.toFixed(1)+" RX","/min · "+txrate.toFixed(1)+" TX")+
@@ -1350,7 +1351,7 @@ <h1 style="margin:16px 0 8px" id="reboot-title">Rebooting&hellip;</h1>
13501351
push(st.hist.noise,s.noise);push(st.hist.txair,txload);push(st.hist.rxair,rxload);
13511352
push(st.hist.txq,s.tx_queue);push(st.hist.mqttq,s.mqtt_queue);push(st.hist.heap,kb);
13521353
push(st.hist.mqttok,mqttRate);push(st.hist.mqtterr,mqttErrRate);
1353-
push(st.hist.heapmin,minKb);push(st.hist.batt,s.batt_mv/1000);push(st.hist.wifi,s.wifi_rssi);
1354+
push(st.hist.heapmin,minKb);if(battValid)push(st.hist.batt,battVolts);push(st.hist.wifi,s.wifi_rssi);
13541355
renderTrafficMix(s);drawSignalBars();renderFreshBars();
13551356
var blue=color("--acc","#2b7de9"),green=color("--ok","#1d9d5f"),red=color("--err","#d64545"),orange=color("--warn","#c8871a"),mut=color("--mut","#66738a");
13561357
legend("#leg-packets",[{n:"RX",v:rxrate.toFixed(1)+"/min",c:blue},{n:"TX",v:txrate.toFixed(1)+"/min",c:green}]);
@@ -1365,7 +1366,7 @@ <h1 style="margin:16px 0 8px" id="reboot-title">Rebooting&hellip;</h1>
13651366
plot($("#spark-mqtt"),[{d:st.hist.mqttok,c:green},{d:st.hist.mqtterr,c:red}],false,0);
13661367
legend("#leg-heap",[{n:"free",v:kb+" KB",c:green},{n:"minimum",v:minKb+" KB",c:red}]);
13671368
plot($("#spark-heap"),[{d:st.hist.heap,c:green},{d:st.hist.heapmin,c:red}],false,0);
1368-
legend("#leg-link",[{n:"battery",v:(s.batt_mv/1000).toFixed(2)+" V",c:green},{n:"WiFi",v:s.wifi_rssi+" dBm",c:blue}]);
1369+
legend("#leg-link",[{n:"battery",v:battValid?battVolts.toFixed(2)+" V":"—",c:green},{n:"WiFi",v:s.wifi_rssi+" dBm",c:blue}]);
13691370
plot($("#spark-link"),[{d:st.hist.batt,c:green},{d:st.hist.wifi,c:blue}],true);
13701371
$("#stat-slots").innerHTML=sl.length?sl.map(function(x){
13711372
// "filt" is sent only when the slot is not on the all-types default. A

0 commit comments

Comments
 (0)