Unify full firmware profiles and add dual USB logging

This commit is contained in:
mikecarper committed 2026-08-23 19:22:45 -07:00
1 parent b699e552ec
commit f2d4ffbb7e
56 files changed
+1652 -361

No files matched your search

@@ -80,6 +80,22 @@ TEST(CompanionNodePrefs, WiFiStateIsIndependentFromPowerSaving) {
EXPECT_EQ(1, prefs.powersaving_enabled);
}
TEST(CompanionNodePrefs, UsbLoggingStateIsIndependentFromTransports) {
CompanionNodePrefs prefs = {};
prefs.usb_logging_enabled = 1;
prefs.wifi_enabled = 0;
prefs.powersaving_enabled = 1;
EXPECT_EQ(1, prefs.usb_logging_enabled);
EXPECT_EQ(0, prefs.wifi_enabled);
EXPECT_EQ(1, prefs.powersaving_enabled);
prefs.usb_logging_enabled = 0;
EXPECT_EQ(0, prefs.usb_logging_enabled);
EXPECT_EQ(0, prefs.wifi_enabled);
EXPECT_EQ(1, prefs.powersaving_enabled);
}
TEST(CompanionNodePrefs, MigratesRegressedPowerSavingDefaultOnce) {
CompanionNodePrefs prefs = {};
prefs.powersaving_enabled = 0;
@@ -196,6 +196,7 @@ TEST(NodePrefs, FemGainSettingsRoundTrip) {
NodePrefs saved;
saved.radio_fem_rxgain = 0;
saved.radio_fem_txgain = 1;
saved.usb_logging_enabled = 0;
MockPrintStream output;
ASSERT_TRUE(saved.saveSerial(output));
@@ -203,15 +204,18 @@ TEST(NodePrefs, FemGainSettingsRoundTrip) {
std::string serialised(reinterpret_cast<const char*>(output.getBytes()), output.getLength());
EXPECT_NE(std::string::npos, serialised.find("fem_rxgain:0"));
EXPECT_NE(std::string::npos, serialised.find("fem_txgain:1"));
EXPECT_NE(std::string::npos, serialised.find("usb_log:0"));
MockInputStream input(serialised.c_str());
NodePrefs loaded;
loaded.radio_fem_rxgain = 1;
loaded.radio_fem_txgain = 0;
loaded.usb_logging_enabled = 1;
ASSERT_TRUE(loaded.loadSerial(input));
EXPECT_EQ(0, loaded.radio_fem_rxgain);
EXPECT_EQ(1, loaded.radio_fem_txgain);
EXPECT_EQ(0, loaded.usb_logging_enabled);
}
+102 -23
View File
@@ -38,6 +38,10 @@ const releases = [
asset("Station_G3_ESP32_logging_repeater-" + family + ".bin"),
asset("Heltec_t096_companion_radio_ble_ps_femon-" + family + ".uf2"),
asset("Heltec_t096_companion_radio_ble_ps_femon-" + family + ".zip"),
asset("RAK_4631_companion_radio_full-" + family + ".uf2"),
asset("RAK_4631_companion_radio_full-" + family + ".zip"),
asset("RAK_4631_companion_radio_usb-logging-" + family + ".uf2"),
asset("RAK_4631_companion_radio_usb-logging-" + family + ".zip"),
]),
release("repeater-room-" + family, "2026-08-23T12:00:05Z", [
asset("Station_G2_repeater-" + family + "-deadbee-merged.bin"),
@@ -76,15 +80,15 @@ const releases = [
]),
release("full-profiles-" + family, "2026-08-23T12:00:01Z", [
asset(
"Station_G2_repeater-full-logging-ota-" + family + "-merged.bin"
"Generic_ESPNOW_repeatr-full-logging-ota-" + family + "-merged.bin"
),
asset("Station_G2_repeater-full-logging-ota-" + family + ".bin"),
asset("Generic_ESPNOW_repeatr-full-logging-ota-" + family + ".bin"),
asset(
"Station_G2_repeater_observer_mqtt-full-ota-" +
"Station_G2_repeater_observer_mqtt-full-usb-wifi-ota-" +
family + "-merged.bin"
),
asset(
"Station_G2_repeater_observer_mqtt-full-ota-" + family + ".bin"
"Station_G2_repeater_observer_mqtt-full-usb-wifi-ota-" + family + ".bin"
),
]),
release(
@@ -106,8 +110,8 @@ assert(!releaseSet.releases.some(function (item) {
const catalog = picker.buildCatalog(releases);
assert.strictEqual(catalog.releaseSet.familyTag, family);
assert.strictEqual(catalog.profiles.length, 12);
assert.strictEqual(catalog.rows.length, 29);
assert.strictEqual(catalog.profiles.length, 11);
assert.strictEqual(catalog.rows.length, 33);
function profile(target) {
const found = catalog.profiles.find(function (item) {
@@ -126,14 +130,29 @@ assert.strictEqual(companionFull.ota, "lora-source");
assert.strictEqual(companionFull.feature, "full");
assert.strictEqual(companionFull.variant, "default");
const companionBle = profile("Heltec_t096_companion_radio_ble_ps_femon");
const rakFull = profile("RAK_4631_companion_radio_full");
assert.strictEqual(rakFull.logging, "usb-runtime");
assert.deepStrictEqual(rakFull.loggingModes, ["none", "usb"]);
assert.strictEqual(rakFull.dedicatedUsbLogging, true);
assert(!catalog.profiles.some(function (item) {
return item.target === "RAK_4631_companion_radio_usb-logging";
}));
assert(catalog.rows.some(function (item) {
return item.target === "RAK_4631_companion_radio_usb-logging";
}));
const companionBle = picker.parseTargetProfile(
"Heltec_t096_companion_radio_ble_ps_femon"
);
assert.strictEqual(companionBle.hardware, "Heltec_t096");
assert.strictEqual(companionBle.mode, "ble");
assert.strictEqual(companionBle.variant, "ps-femon");
assert.strictEqual(
picker.humanizeVariant(companionBle.variant),
"Power save FEM on"
);
assert.strictEqual(companionBle.variant, "default");
assert(!catalog.profiles.some(function (item) {
return item.target === "Heltec_t096_companion_radio_ble_ps_femon";
}));
assert(catalog.rows.some(function (item) {
return item.target === "Heltec_t096_companion_radio_ble_ps_femon";
}));
const fullWifiLogging = picker.parseTargetProfile(
"Heltec_v2_companion_radio_wifi-full-logging"
@@ -156,7 +175,7 @@ assert.strictEqual(heltecV4Full.target,
"heltec_v4_2_v4_3_companion_radio_full_femon");
assert.strictEqual(heltecV4Full.hardware, "heltec_v4");
assert.strictEqual(heltecV4Full.mode, "full");
assert.strictEqual(heltecV4Full.variant, "femon");
assert.strictEqual(heltecV4Full.variant, "default");
assert.strictEqual(
picker.canonicalHardware("heltec_v4_2_v4_3"),
"heltec_v4"
@@ -174,15 +193,17 @@ const heltecV4FemOff = picker.parseTargetProfile(
"heltec_v4_3_companion_radio_ble_femoff"
);
assert.strictEqual(heltecV4FemOff.hardware, "heltec_v4_3");
assert.strictEqual(heltecV4FemOff.variant, "femoff");
assert.strictEqual(heltecV4FemOff.variant, "default");
const heltecV4TftFemOff = picker.parseTargetProfile(
"heltec_v4_3_tft_companion_radio_wifi_femoff"
);
assert.strictEqual(heltecV4TftFemOff.hardware, "heltec_v4_3_tft");
assert.strictEqual(heltecV4TftFemOff.variant, "femoff");
assert.strictEqual(heltecV4TftFemOff.variant, "default");
const g2RxBoosted = profile("Station_G2_logging_repeater");
const g2RxBoosted = picker.parseTargetProfile(
"Station_G2_logging_repeater"
);
assert.strictEqual(g2RxBoosted.sourceHardware, "Station_G2_logging");
assert.strictEqual(g2RxBoosted.hardware, "Station_G2");
assert.strictEqual(g2RxBoosted.variant, "rx-boosted");
@@ -190,6 +211,12 @@ assert.strictEqual(
picker.humanizeVariant(g2RxBoosted.variant),
"RX Boosted"
);
assert(!catalog.profiles.some(function (item) {
return item.target === "Station_G2_logging_repeater";
}));
assert(catalog.rows.some(function (item) {
return item.target === "Station_G2_logging_repeater";
}));
const g3Standard = profile("Station_G3_ESP32_repeater");
assert.strictEqual(g3Standard.hardware, "Station_G3_ESP32");
@@ -200,6 +227,48 @@ assert(catalog.rows.some(function (item) {
return item.target === "Station_G3_ESP32_logging_repeater";
}));
assert.deepStrictEqual(
picker.omitNrf52TransportsReplacedByFull([
{
target: "RAK_4631_companion_radio_full",
hardware: "RAK_4631",
variant: "default",
role: "companion",
mode: "full",
logging: "none",
installKinds: ["zip", "uf2"],
},
{
target: "RAK_4631_companion_radio_usb",
hardware: "RAK_4631",
variant: "default",
role: "companion",
mode: "usb",
logging: "none",
installKinds: ["zip", "uf2"],
},
{
target: "RAK_4631_companion_radio_ble",
hardware: "RAK_4631",
variant: "default",
role: "companion",
mode: "ble",
logging: "none",
installKinds: ["zip", "uf2"],
},
{
target: "RAK_4631_companion_radio_usb-logging",
hardware: "RAK_4631",
variant: "default",
role: "companion",
mode: "usb",
logging: "usb",
installKinds: ["zip", "uf2"],
},
]).map(function (item) { return item.target; }),
["RAK_4631_companion_radio_full"]
);
const standardRepeater = profile("Station_G2_repeater");
assert.strictEqual(standardRepeater.hardwareFamily, "Station_G2");
assert.strictEqual(standardRepeater.role, "repeater");
@@ -212,16 +281,19 @@ assert.strictEqual(
"Station_G2_repeater-" + family + "-merged.bin"
);
const fullLogging = profile("Station_G2_repeater-full-logging");
assert.strictEqual(fullLogging.logging, "usb");
const fullLogging = profile("Generic_ESPNOW_repeatr-full-logging");
assert.strictEqual(fullLogging.logging, "usb-runtime");
assert.deepStrictEqual(fullLogging.loggingModes, ["none", "usb"]);
assert.strictEqual(fullLogging.ota, "lora-receiver");
assert.strictEqual(fullLogging.feature, "full");
assert.strictEqual(fullLogging.variant, "default");
const mqtt = profile("Station_G2_repeater_observer_mqtt-full");
assert.strictEqual(mqtt.logging, "wifi");
const mqtt = profile("Station_G2_repeater_observer_mqtt-full-usb-wifi");
assert.strictEqual(mqtt.logging, "runtime");
assert.deepStrictEqual(mqtt.loggingModes, ["none", "usb", "wifi", "both"]);
assert.strictEqual(mqtt.mode, "mqtt");
assert.strictEqual(mqtt.ota, "lora-receiver");
assert.strictEqual(mqtt.variant, "default");
const lora = profile(
"Station_G2_repeater_lora_ota_no_external_sensors"
@@ -275,7 +347,10 @@ const matches = catalog.profiles.filter(function (item) {
});
});
assert.strictEqual(matches.length, 1);
assert.strictEqual(matches[0].target, "Station_G2_repeater-full-logging");
assert.strictEqual(
matches[0].target,
"Station_G2_repeater_observer_mqtt-full-usb-wifi"
);
assert.deepStrictEqual(
picker.FACET_FIELDS,
@@ -371,6 +446,10 @@ assert.deepStrictEqual(
picker.facetValues(catalog.profiles, { logging: "wifi" }, "hardware"),
["Station_G2"]
);
assert.deepStrictEqual(
picker.facetValues(catalog.profiles, { logging: "both" }, "hardware"),
["Station_G2"]
);
assert.deepStrictEqual(
picker.facetValues(
catalog.profiles,
@@ -382,13 +461,13 @@ assert.deepStrictEqual(
},
"hardware"
),
["Station_G2"]
["Generic_ESPNOW", "Station_G2"]
);
assert.deepStrictEqual(
picker.uniqueValues(catalog.profiles, "hardware").sort(),
[
"Heltec_t096",
"Generic_ESPNOW",
"ProMicro",
"RAK_4631",
"Station_G2",
@@ -129,6 +129,38 @@ TEST(WebConfigBatch, SetupWiFiHandoffUsesBoundedConnectAndBriefFlushDelays) {
EXPECT_EQ(1000U, Batch::kSetupHandoffRebootConfirmMs);
}
TEST(WebConfigBatch, FullUnconfiguredSetupWindowIsAnAbsoluteThirtyMinutes) {
const uint32_t started = 1000;
EXPECT_EQ(30UL * 60UL * 1000UL, Batch::kFullSetupApWindowMs);
EXPECT_FALSE(Batch::unconfiguredSetupWindowExpired(
true, false, started + Batch::kFullSetupApWindowMs - 1, started,
Batch::kFullSetupApWindowMs));
EXPECT_TRUE(Batch::unconfiguredSetupWindowExpired(
true, false, started + Batch::kFullSetupApWindowMs, started,
Batch::kFullSetupApWindowMs));
// Saving any SSID converts the node to deployed behavior, which continues
// reconnecting and is not subject to the provisioning cutoff.
EXPECT_FALSE(Batch::unconfiguredSetupWindowExpired(
true, true, started + Batch::kFullSetupApWindowMs, started,
Batch::kFullSetupApWindowMs));
EXPECT_FALSE(Batch::unconfiguredSetupWindowExpired(
false, false, started + Batch::kFullSetupApWindowMs, started,
Batch::kFullSetupApWindowMs));
EXPECT_FALSE(Batch::unconfiguredSetupWindowExpired(
true, false, started + Batch::kFullSetupApWindowMs, started, 0));
}
TEST(WebConfigBatch, FullUnconfiguredSetupWindowSurvivesMillisRollover) {
const uint32_t started = std::numeric_limits<uint32_t>::max() - 1000;
EXPECT_FALSE(Batch::unconfiguredSetupWindowExpired(
true, false, started + Batch::kFullSetupApWindowMs - 1, started,
Batch::kFullSetupApWindowMs));
EXPECT_TRUE(Batch::unconfiguredSetupWindowExpired(
true, false, started + Batch::kFullSetupApWindowMs, started,
Batch::kFullSetupApWindowMs));
}
// --------------------------------------------------------------------------
// Result read
// --------------------------------------------------------------------------