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
2 changes: 1 addition & 1 deletion .github/workflows/rcc6-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ jobs:
shell: bash
run: |
out=".pio/build/${{ matrix.environment }}"
grep -aFq 'NeonPocketMC RCC6 Headless 1.0.0-rc.3 mode=' "$out/firmware.bin"
grep -aFq 'NeonPocketMC RCC6 Headless 1.0.0-rc.4 mode=' "$out/firmware.bin"

- name: Upload exact firmware candidate
if: startsWith(matrix.environment, 'heltec_rcc6_headless_')
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

Screenless MeshCore companion firmware for the Heltec RadioCore RCC6-L62/SX1262. Choose one transport: Bluetooth, native USB/serial, or Wi-Fi Web/TCP.

Current experimental release: [`v1.0.0-rc.3`](https://github.com/n30nex/NeonPocketMC-RCC6-Headless/releases/tag/v1.0.0-rc.3). RC3 rejects impossible RCC6 battery readings instead of reporting them to companion clients.
Current experimental release: [`v1.0.0-rc.4`](https://github.com/n30nex/NeonPocketMC-RCC6-Headless/releases/tag/v1.0.0-rc.4). RC4 fixes saved-location adverts on GPS-less hardware and retains the battery-reading safeguards.

> **RCC6-L62 only. Do not flash RC52, RC32, T114, Heltec V3/V4, or an RCC6 with different radio hardware.**

Expand Down
9 changes: 9 additions & 0 deletions docs/releases/1.0-RC4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# NeonPocketMC RCC6 Headless v1.0.0-rc.4

Experimental prerelease built from the exact tagged `main` commit and MeshCore 1.17.1.

- Saved latitude/longitude adverts work without a physical GPS.
- Unsupported live-GPS policy requests are normalized to saved-coordinate mode.
- BLE, native USB, and Web/TCP remain separate images.

Use app-only images at `0x10000` for normal updates. Recovery images reset NVS while preserving MeshCore storage.
7 changes: 7 additions & 0 deletions examples/companion_radio/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,10 @@ void MyMesh::begin(bool has_display) {
_store->loadPrefs(_prefs);
sensors.node_lat = _prefs.node_lat;
sensors.node_lon = _prefs.node_lon;
if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE && sensors.getLocationProvider() == nullptr) {
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
_store->savePrefs(_prefs);
}

// sanitise bad pref values
_prefs.rx_delay_base = constrain(_prefs.rx_delay_base, 0, 20.0f);
Expand Down Expand Up @@ -1587,6 +1591,9 @@ void MyMesh::handleCmdFrame(size_t len) {

if (len >= 4) {
_prefs.advert_loc_policy = cmd_frame[3];
if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE && sensors.getLocationProvider() == nullptr) {
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
}
if (len >= 5) {
_prefs.multi_acks = cmd_frame[4];
}
Expand Down
3 changes: 2 additions & 1 deletion examples/companion_radio/NodePrefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#define ADVERT_LOC_NONE 0
#define ADVERT_LOC_SHARE 1
#define ADVERT_LOC_PREFS 2

class NodePrefs : public ConfigSerializer { // persisted to file
public:
Expand Down Expand Up @@ -135,4 +136,4 @@ class NodePrefs : public ConfigSerializer { // persisted to file
// new accessor methods
bool isRepeatEn() const { return repeat.disable_fwd == 0; }
void setRepeatEn(bool en) { repeat.disable_fwd = en ? 0 : 1; }
};
};
2 changes: 1 addition & 1 deletion examples/companion_radio/UltimateWebApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ void UltimateWebApi::handleLocation() {
sensors.node_lat = latitude;
sensors.node_lon = longitude;
if (has_advertise) {
the_mesh.getNodePrefs()->advert_loc_policy = advertise ? ADVERT_LOC_SHARE : ADVERT_LOC_NONE;
the_mesh.getNodePrefs()->advert_loc_policy = advertise ? ADVERT_LOC_PREFS : ADVERT_LOC_NONE;
}
the_mesh.savePrefs();
String json = F("{\"saved\":true,\"latitude\":"); json += String(latitude, 6);
Expand Down
1,090 changes: 545 additions & 545 deletions examples/companion_radio/webui/Rcc6WebUiAssets.h

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions examples/companion_radio/webui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion examples/companion_radio/webui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rcc6-meshcore-webui",
"version": "2.3.0-rc.3",
"version": "2.3.0-rc.4",
"private": true,
"type": "module",
"scripts": {
Expand Down
11 changes: 9 additions & 2 deletions scripts/verify_rcc6_headless.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
ui_task = (ROOT / "examples/companion_radio/ui-new/UITask.cpp").read_text(encoding="utf-8")
web = (ROOT / "examples/companion_radio/webui/src/app.js").read_text(encoding="utf-8")
common_cli = (ROOT / "src/helpers/CommonCLI.cpp").read_text(encoding="utf-8")
mesh = (ROOT / "examples/companion_radio/MyMesh.cpp").read_text(encoding="utf-8")
share_command = common_cli.index('strcmp(command, "gps advert share")')
gps_hardware_guard = common_cli.index("#if ENV_INCLUDE_GPS == 1", share_command)

required = {
"heltec_rcc6_headless_companion_ble": ["BLE_PIN_CODE=123456", 'NEONPOCKET_HEADLESS_MODE=\'"ble"\''],
Expand Down Expand Up @@ -42,8 +45,12 @@
assert 'display.print("--")' in ui_task
assert "ultimate.batteryMv > 0" in web
assert '(key === "battery" && raw <= 0)' in web
assert common_cli.index('strcmp(command, "gps advert prefs")') < \
common_cli.index("#if ENV_INCLUDE_GPS == 1"), \
assert common_cli.index('strcmp(command, "gps advert prefs")') < share_command, \
"saved-coordinate advert policy must not require physical GPS hardware"
assert "_sensors->getLocationProvider() != NULL" in \
common_cli[share_command:gps_hardware_guard], \
"live-location advert policy must require an actual GPS provider"
assert "_sensors->getLocationProvider() == NULL" in common_cli
assert mesh.count("sensors.getLocationProvider() == nullptr") >= 2

print("RCC6 headless companion contract verified")
16 changes: 12 additions & 4 deletions src/helpers/CommonCLI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) {
// fs->remove("/com_prefs"); // remove old
}
}
if (_prefs->advert_loc_policy == ADVERT_LOC_SHARE && _sensors->getLocationProvider() == NULL) {
_prefs->advert_loc_policy = ADVERT_LOC_PREFS;
savePrefs(fs);
}
}

void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { // Legacy prefs loader
Expand Down Expand Up @@ -336,14 +340,18 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
_prefs->advert_loc_policy = ADVERT_LOC_NONE;
savePrefs();
strcpy(reply, "ok");
} else if (strcmp(command, "gps advert share") == 0) {
_prefs->advert_loc_policy = ADVERT_LOC_SHARE;
savePrefs();
strcpy(reply, "ok");
} else if (strcmp(command, "gps advert prefs") == 0) {
_prefs->advert_loc_policy = ADVERT_LOC_PREFS;
savePrefs();
strcpy(reply, "ok");
} else if (strcmp(command, "gps advert share") == 0) {
if (_sensors->getLocationProvider() != NULL) {
_prefs->advert_loc_policy = ADVERT_LOC_SHARE;
savePrefs();
strcpy(reply, "ok");
} else {
strcpy(reply, "gps provider not found");
}
#if ENV_INCLUDE_GPS == 1
} else if (memcmp(command, "gps on", 6) == 0) {
if (_sensors->setSettingValue("gps", "1")) {
Expand Down
6 changes: 5 additions & 1 deletion tools/verify_ultimate_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ def text(path: str) -> str:
"32 event queue": "pending_capacity = 32" in service,
"64 network nodes": "network_capacity = 64" in service,
"signed OTA": "Ed25519::verify" in web and "Update.begin" in web,
"GPS-less advert policy normalization":
text("src/helpers/CommonCLI.cpp").count("_prefs->advert_loc_policy = ADVERT_LOC_PREFS;") >= 2 and
"_sensors->getLocationProvider() == NULL" in text("src/helpers/CommonCLI.cpp") and
mesh.count("sensors.getLocationProvider() == nullptr") >= 2,
"location endpoint": all(value in web for value in
['"/api/ultimate/location"', 'extractBool(body, "advertise"',
"advert_loc_policy = advertise ? ADVERT_LOC_SHARE : ADVERT_LOC_NONE"]),
"advert_loc_policy = advertise ? ADVERT_LOC_PREFS : ADVERT_LOC_NONE"]),
"six-area UI": all(name in ui for name in ["HOME", "INBOX", "NETWORK", "RADIO", "TOOLS", "POWER"]),
"adaptive animation cadence": all(value in service_cpp for value in
["getRecommendedFrameMillis", "flush >= 45000", "flush >= 90000"]) and
Expand Down
4 changes: 2 additions & 2 deletions variants/heltec_rcc6/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ build_flags =
${heltec_rcc6.build_flags}
-D FIRMWARE_VERSION='"v1.17.1"'
-D NEONPOCKET_HEADLESS=1
-D NEONPOCKET_HEADLESS_VERSION='"1.0.0-rc.3"'
-D NEONPOCKET_HEADLESS_VERSION='"1.0.0-rc.4"'
-D NEONPOCKET_SAFE_SPIFFS_BOOTSTRAP=1
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
Expand Down Expand Up @@ -297,7 +297,7 @@ build_flags =
-D NEONPOCKET_UI=1
-D NEONPOCKET_RCC6_UI_EXTENSIONS=1
-D NEONPOCKET_ULTIMATE=1
-D NEONPOCKET_ULTIMATE_VERSION='"2.3.0-rc.3"'
-D NEONPOCKET_ULTIMATE_VERSION='"2.3.0-rc.4"'
-D NEONPOCKET_MEMORY_GATE_BYTES=32768
-D NEONPOCKET_SAFE_SPIFFS_BOOTSTRAP=1
-D NV3001B_USE_FRAMEBUFFER=1
Expand Down
Loading