Skip to content
Open
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
16 changes: 3 additions & 13 deletions platformio_tubes.ini
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ build_flags =
-D PIXEL_COUNTS=150
-D TUBES_HARDWARE_FAMILY=TubeHardwareDig2Go
-D TUBES_FIRMWARE_VARIANT=TubeVariantStandard
-D TUBES_ENABLE_DIG2GO_PEER_PROPAGATION=1
-D TUBES_DIG2GO_LEGACY_PULL_HOST=1
-D TUBES_DIG2GO_DYNAMIC_ENROLLMENT=1
# OTA accepts only firmware from the same hardware family, so this identity
# must override the generic ESP32 release inherited by the Dig2Go base build.
-D WLED_RELEASE_NAME=\"DIG2GO_TUBES\"
Expand All @@ -116,19 +119,6 @@ lib_ignore =
lib_deps =
${env:esp32_quinled_dig2go.lib_deps}

# Explicitly triggered Dig2Go peer propagation. This carries the standard
# DIG2GO_TUBES identity and contains no bench auto-start, PRIME MAC, or
# test-only boot trigger. Its bounded production marker lets a just-migrated
# legacy receiver pass one baton; S3/Easy Flash start seed turns only after
# direct human input.
[env:esp32_quinled_dig2go_tubes_p2p]
extends = env:esp32_quinled_dig2go_tubes
build_flags =
${env:esp32_quinled_dig2go_tubes.build_flags}
-D TUBES_ENABLE_DIG2GO_PEER_PROPAGATION=1
-D TUBES_DIG2GO_LEGACY_PULL_HOST=1
-D TUBES_DIG2GO_DYNAMIC_ENROLLMENT=1

# Waveshare ESP32-S3-Touch-AMOLED-2.16 Tubes field target. DATA_PINS=255 keeps
# WLED's generic one-pin config loader unchanged; BusTubesNull consumes the
# target-scoped sentinel without allocating or touching a physical output.
Expand Down
21 changes: 20 additions & 1 deletion test/tubes_mesh/dig2go_peer_propagation_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,27 @@ static void explicitPropagationCommandIsSeparateFromOtaSelection() {

static void barePowerSaveCommandRemainsIntact() {
const std::string controller = readSource("usermods/Tubes/controller.h");
EXPECT(controller.find("key == 'P' && command[1] == '!' && !command[2]")
!= std::string::npos);
EXPECT(controller.find("requestLocalPropagation();") != std::string::npos);
EXPECT(controller.find("key == 'P' && strchr(command + 1, ',')")
!= std::string::npos);
EXPECT(controller.find("else if (key == 'P')") != std::string::npos);
}

static void localPropagationUsesTheExactExistingFleetOffer() {
const std::string controller = readSource("usermods/Tubes/controller.h");
const auto begin = controller.find("void requestLocalPropagation()");
const auto end = controller.find("void requestFleetUpdate", begin);
EXPECT(begin != std::string::npos && end != std::string::npos);
const std::string trigger = controller.substr(begin, end - begin);
EXPECT(trigger.find("makeModernPropagationServeCommand") != std::string::npos);
EXPECT(trigger.find("node.header.id") != std::string::npos);
EXPECT(trigger.find("applyCommand(COMMAND_FLEET_UPGRADE, &offer)")
!= std::string::npos);
EXPECT(trigger.find("sendV3ControlCommand") == std::string::npos);
}

static void laptopFleetToolCannotStartPropagation() {
const std::string tool = readSource("usermods/Tubes/fleet_pull_update.py");
EXPECT(tool.find("--propagate") == std::string::npos);
Expand All @@ -53,7 +69,7 @@ static void laptopFleetToolCannotStartPropagation() {

static void productionBuildHasNoBenchBootTriggers() {
const std::string config = readSource("platformio_tubes.ini");
const auto begin = config.find("[env:esp32_quinled_dig2go_tubes_p2p]");
const auto begin = config.find("[env:esp32_quinled_dig2go_tubes]");
const auto end = config.find("\n[env:", begin + 1);
EXPECT(begin != std::string::npos && end != std::string::npos);
const std::string environment = config.substr(begin, end - begin);
Expand All @@ -63,6 +79,8 @@ static void productionBuildHasNoBenchBootTriggers() {
EXPECT(environment.find("AUTO_TRIGGER") == std::string::npos);
EXPECT(environment.find("PRIME_MAC") == std::string::npos);
EXPECT(environment.find("BOOT_FALLBACK_TEST") == std::string::npos);

EXPECT(config.find("[env:esp32_quinled_dig2go_tubes_p2p]") == std::string::npos);
}

static void oneTurnAdvertisesToLegacyAndCurrentPeers() {
Expand Down Expand Up @@ -129,6 +147,7 @@ static void failedPullKeepsRestoringUntilMeshIsStarted() {
int main() {
explicitPropagationCommandIsSeparateFromOtaSelection();
barePowerSaveCommandRemainsIntact();
localPropagationUsesTheExactExistingFleetOffer();
laptopFleetToolCannotStartPropagation();
productionBuildHasNoBenchBootTriggers();
oneTurnAdvertisesToLegacyAndCurrentPeers();
Expand Down
19 changes: 10 additions & 9 deletions usermods/Tubes/DIG2GO_P2P_STEVE_HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,18 @@ decision is local to the devices. The seed and its children discover eligible
receivers, serve the image, persist continuation, recover, and stop without a
laptop roster or server.

The production review environment is:
The canonical v52 Dig2Go environment is:

```sh
pio run -e esp32_quinled_dig2go_tubes_p2p
pio run -e esp32_quinled_dig2go_tubes
```

It retains the standard `DIG2GO_TUBES` firmware identity. It enables the host
and dynamic Dig2Go enrollment, but contains no PRIME MAC, automatic source
trigger, or test-only boot fallback. It does retain the bounded production
first-boot marker described below for a just-migrated legacy receiver.
It retains the standard `DIG2GO_TUBES` firmware identity and now enables the
host and dynamic Dig2Go enrollment by default. A local `P!` command explicitly
starts one turn; merely carrying the capability does not advertise or serve.
The build contains no PRIME MAC, automatic source trigger, or test-only boot
fallback. The old `_p2p` environment is removed so v52 has one canonical
Dig2Go artifact and capability set.

## Evidence boundary

Expand Down Expand Up @@ -98,8 +100,7 @@ or final S3/Easy Flash activation UX.
bash test/tubes_mesh/run.sh
node --test tools/fleet-update-protocol-test.js
pio run -e esp32_quinled_dig2go_tubes
pio run -e esp32_quinled_dig2go_tubes_p2p
```

The ordinary Dig2Go build remains a regression control with P2P disabled.
C3 family propagation and its device flow are intentionally deferred.
All canonical v52 Dig2Go images carry the same dormant propagation capability.
C3 family serving and its device flow are intentionally deferred.
26 changes: 25 additions & 1 deletion usermods/Tubes/controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -3572,7 +3572,7 @@ class PatternController : public MessageReceiver {
// AI: below section was generated by an AI
Serial.println(F("b###.# - set bpm\ns - start phrase\n\np### - preferred table pattern\npW,<fx>,<speed>,<intensity>,<c1>,<c2>,<c3>,<mask>,<values>,<sync>,<fallback>,<hold>\npT,<tubes>,<sync>,<fallback>,<hold>\nm### - sync mode\nc### - colors\nc<id>,<hold> - timed/held built-in palette\ne### - effects\nn - force next\n\ni### - set Control ID\nB/K/C### - set Beat/Pattern/Palette Channel ID\ng - become Palette Master\ng0:RRGGBB,...,255:RRGGBB - schedule a custom gradient\ngH,<hold>,0:RRGGBB,...,255:RRGGBB - timed/held gradient\nd0/1 - set local debugging\nD0/1 - set debugging on selected devices\nl### - brightness"));
Serial.println(F("@ - set power saving mode\nU - begin auto-update\nP - toggle all power saves\nO - locally schedule sound toggle on Beat owner\nO0 - locally schedule sound off\nO1 - locally schedule rotating sound programs\nO### - locally schedule raw WLED effect on Beat owner\nO<fx>,<pal>,<speed>,<intensity>,<c1>,<c2>,<c3>,<checks>,<blend>,<opacity>[,<chance>] - locally schedule all overlay settings\nJ1/J0 - enter/exit audio workshop; J+/J- vote, J>/J< browse\nj0/1 - local automatic microphone tempo off/on\n==== wifi ====\na - turn on access point\nq - turn off access point\nt0/1 - Tubes mode off/on"));
Serial.println(F("==== selected actions ====\nD0/1 - set debugging\nU - begin auto-update\nX - restart\nf### - flash connected device\nF### - flash selected devices\nr/R### - set local/selected role\n==== mesh actions ====\n* - enter OTA select mode (double-click to Ready)\nP<offer> - explicitly start peer propagation\nA - turn on access point (Ready to update)\nW - forget WiFi client\nV### - auto-upgrade to version\nz - report all visible devices\nz############ - probe a device by MAC\nyhhhh - select one hexadecimal Device ID for update\n(hhhh/)hhhh - select/unselect a Device ID\nM - cancel manual pattern override"));
Serial.println(F("==== selected actions ====\nD0/1 - set debugging\nU - begin auto-update\nX - restart\nf### - flash connected device\nF### - flash selected devices\nr/R### - set local/selected role\n==== mesh actions ====\n* - enter OTA select mode (double-click to Ready)\nP! - make this connected Dig2Go serve its running image\nP<offer> - explicitly start peer propagation\nA - turn on access point (Ready to update)\nW - forget WiFi client\nV### - auto-upgrade to version\nz - report all visible devices\nz############ - probe a device by MAC\nyhhhh - select one hexadecimal Device ID for update\n(hhhh/)hhhh - select/unselect a Device ID\nM - cancel manual pattern override"));
// AI: end
}

Expand Down Expand Up @@ -4585,6 +4585,10 @@ class PatternController : public MessageReceiver {
// AI: below section was generated by an AI
// Bare P is the deployed mesh power-save toggle. Only the structured,
// comma-delimited P form belongs to the additive propagation command.
if (key == 'P' && command[1] == '!' && !command[2]) {
requestLocalPropagation();
return;
}
if (key == 'Y' || (key == 'P' && strchr(command + 1, ','))) {
requestFleetUpdate(command + 1, key == 'P');
return;
Expand Down Expand Up @@ -5003,6 +5007,26 @@ class PatternController : public MessageReceiver {
}

// AI: below section was generated by an AI
// Explicit USB/Electron action: ask only this connected Dig2Go to serve its
// running image through the existing exact-target FleetUpdateOffer path.
// Bare P remains the deployed power-save toggle; structured P<offer> remains
// the remote/operator form.
void requestLocalPropagation() {
#if TUBES_ENABLE_DIG2GO_PEER_PROPAGATION
FleetUpdateOffer offer;
uint32_t nonce = esp_random();
if (!nonce) nonce = 1;
if (!makeModernPropagationServeCommand(
offer, RELEASE_VERSION, nonce, node.header.id)) {
Serial.println(F("TUBE_PROPAGATE_LOCAL rejected=invalid"));
return;
}
applyCommand(COMMAND_FLEET_UPGRADE, &offer);
#else
Serial.println(F("TUBE_PROPAGATE_LOCAL rejected=unsupported"));
#endif
}

// Parses one explicit LAN update offer and sends it through the Control tree.
// Format: release,IPv4,port,start-window-ms,target-device-id,nonce,SSID,password.
void requestFleetUpdate(char* text, bool propagate = false) {
Expand Down
1 change: 1 addition & 0 deletions usermods/Tubes/docs/PROTOCOL.md
Original file line number Diff line number Diff line change
Expand Up @@ -1509,6 +1509,7 @@ activate field diagnostics as soon as it connects.
| `y####` | Route an update-selection request to one four-digit hexadecimal Device ID. The matching device reports its stable MAC, then starts `WLED-UPDATE` without physical selection. |
| `Y<release>,<IPv4>,<port>,<window>,<target>,<nonce>,<SSID>,<password>` | Emit one gen1 parallel-pull offer. `target=0000` addresses every compatible pole; normal operation uses `fleet_pull_update.py` so secrets are not printed. |
| `P<offer>` | Send an exact-target `Fleet Update Propagate` command. The chosen current Dig2Go immediately serves its running image; this never enters `WLED-UPDATE` selection or requires a button. |
| `P!` | Locally ask the USB-connected Dig2Go to serve its running image through the same exact-target `FleetUpdateOffer` path. This is the offline Easy Flash action; it does not broadcast a source-selection command. |
| `O`, `O0`, `O1` | Locally ask the connected Beat owner to schedule toggle, disable, or rotating sound-overlay behavior. The resulting Beat state is the only wire message. |
| `O<fx>,<pal>,<speed>,<intensity>,<c1>,<c2>,<c3>,<checks>,<blend>,<opacity>[,<chance>]` | Schedule an exact overlay on the connected Beat owner. The optional chance controls each pole's independent acceptance of an accent and defaults to 255 for the legacy ten-field form. |
| `J1`, `J0` | Enter or leave the audio workshop. Entry holds a black base and makes the connected pole Beat Master; exit restores normal scheduled pattern, palette, and overlay behavior. |
Expand Down
2 changes: 1 addition & 1 deletion usermods/Tubes/updater.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#include "legacy_auto_update_wire.h"

#ifndef RELEASE_VERSION
#define RELEASE_VERSION 51
#define RELEASE_VERSION 52
#endif

// AI: below section was generated by an AI
Expand Down
Loading