Give Full builds exclusive USB mOTA ownership

This commit is contained in:
mikecarper committed 2026-09-03 02:33:23 -07:00
1 parent 529a4fa3a3
commit 942df304ca
6 files changed
+49 -14

No files matched your search

+2 -2
View File
@@ -2903,6 +2903,7 @@ declare_build_capability_contract() {
record_build_expectation "companion.bluetooth" \
"Companion: starting Bluetooth"
record_build_expectation "companion.usb_logging" "get usb.logging"
record_build_expectation "companion.usb_mota_source" "ota folder on"
if is_nrf52_companion_radio_full_target "$env_name"; then
record_build_expectation "companion.dedicated_usb_logging" \
"get usb.logging"
@@ -2924,7 +2925,6 @@ declare_build_capability_contract() {
record_build_expectation "companion.direct_mqtt" "mqtt.status"
fi
else
record_build_expectation "companion.usb_mota_source" "ota folder on"
record_build_expectation "companion.ble_mota_source" \
"Bluetooth mOTA source"
fi
@@ -3427,7 +3427,7 @@ apply_companion_radio_full_profile() {
# release matrix (ESP32_FULL_BUILD remains 0 in that path). Mark that
# layout explicitly so Full-only runtime policies, including the bounded
# first-boot WebConfig window, cannot silently compile with legacy defaults.
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DWIFI_OTA_SEEDER=1 -DCOMPANION_FEATURE_NETWORK_TERMINAL=1 -DCOMPANION_FEATURE_MEMORY_DIAGNOSTICS=1 -DMESHCORE_EXPANDED_PARTITION_PROFILE=1"
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DWIFI_OTA_SEEDER=1 -DCOMPANION_FEATURE_USB_MOTA_SOURCE=1 -DCOMPANION_FEATURE_NETWORK_TERMINAL=1 -DCOMPANION_FEATURE_MEMORY_DIAGNOSTICS=1 -DMESHCORE_EXPANDED_PARTITION_PROFILE=1"
# Both Indicator radio layouts share the same original NVS/SPIFFS data
# placement. The LoRa WiFi base names that map directly; the ESP-NOW USB
+8 -6
View File
@@ -595,17 +595,19 @@ able to report this event. A different baud rate, including 57600, does not
select ASCII mode.
On an ESP32 Full Companion built with `OTA_FOLDER_SERIAL`, `motatool` can keep
the shared serial console open as an mOTA folder source when WiFi is
unavailable:
the USB serial port open as an mOTA folder source when WiFi is unavailable:
```sh
motatool serve --serial /dev/ttyACM0 --dir ./motas -v
```
The tool sends `ota folder on` through the text CLI, then uses the shared
serial mOTA framing. This is separate from the nRF52 exclusive USB ownership
mode described below. TCP port 5001 remains the preferred unattended source
transport.
The tool sends the exact completed `ota folder on` line when it opens the port.
Firmware recognizes it in startup ASCII or idle Binary Companion mode, then
transfers the port to exclusive USB mOTA ownership before binary request/reply
frames begin. The text terminal and Binary Companion are unavailable on that
TTY until `motatool` detaches the folder. This matches the nRF52 ownership
behavior described below. TCP port 5001 remains the preferred unattended
source transport.
## nRF52 USB mOTA mode
+4 -2
View File
@@ -63,8 +63,10 @@ and is not a controller or source port. With default logging off, interface
`02` is not enumerated. BLE remains available.
ESP32 Full Companion normally uses its dedicated TCP seeder on port 5001.
ESP32 builds that also retain serial folder support use the shared-console
mOTA framing rather than the nRF52 exclusive USB ownership state.
ESP32 builds that also retain serial folder support use the same exclusive USB
mOTA ownership state as nRF52. The exact `ota folder on` line transfers the USB
port from startup ASCII or idle Binary Companion mode before binary mOTA frames
begin; neither console mode shares that port until the folder detaches.
## Destination requirements
+10 -4
View File
@@ -24,7 +24,12 @@
#define COMPANION_FEATURE_DEDICATED_USB_LOGGING 1
#endif
#endif
#if defined(NRF52_PLATFORM) && defined(OTA_FOLDER_SERIAL)
// A Full Companion cannot leave its line-oriented terminal active while the
// serial folder source exchanges binary request/reply frames on that same
// stream. Both nRF52 CDC and ESP32's inherited OTA_FOLDER_SERIAL transport
// therefore use the exclusive USB mOTA owner state in main.cpp.
#if defined(OTA_FOLDER_SERIAL) \
&& (defined(NRF52_PLATFORM) || defined(ESP32_PLATFORM))
#ifndef COMPANION_FEATURE_USB_MOTA_SOURCE
#define COMPANION_FEATURE_USB_MOTA_SOURCE 1
#endif
@@ -68,9 +73,10 @@
#endif
#if COMPANION_FEATURE_USB_MOTA_SOURCE \
&& !(defined(NRF52_PLATFORM) && defined(OTA_FOLDER_SERIAL) \
&& defined(ENABLE_USB_INTERFACE) && defined(ENABLE_OTA))
#error "COMPANION_FEATURE_USB_MOTA_SOURCE requires nRF52 USB folder seeding"
&& !((defined(NRF52_PLATFORM) || defined(ESP32_PLATFORM)) \
&& defined(OTA_FOLDER_SERIAL) && defined(ENABLE_USB_INTERFACE) \
&& defined(ENABLE_OTA))
#error "COMPANION_FEATURE_USB_MOTA_SOURCE requires a USB serial folder source"
#endif
#if COMPANION_FEATURE_BLE_MOTA_SOURCE \
+4
View File
@@ -433,6 +433,8 @@ for full_env in "${SUPPORTED_PIO_ENVS[@]}"; do
|| fail "$full_env Full Companion omitted its capability identity"
[[ "$PLATFORMIO_BUILD_FLAGS" == *"BLE_PIN_CODE=123456"* ]] \
|| fail "$full_env Full Companion omitted Bluetooth pairing"
[[ "$PLATFORMIO_BUILD_FLAGS" == *"COMPANION_FEATURE_USB_MOTA_SOURCE=1"* ]] \
|| fail "$full_env Full Companion left the serial folder source sharing the USB text parser"
case "${full_env,,}" in
sensecapindicator-espnow_companion_radio_full|\
sensecapindicator-lora_companion_radio_full)
@@ -1015,6 +1017,8 @@ declare_build_capability_contract \
expectations=" ${BUILD_EXPECTATIONS[*]} "
[[ "$expectations" == *"companion.direct_mqtt=mqtt.status"* ]] \
|| fail "MQTT-capable ESP32 Full contract omitted direct MQTT"
[[ "$expectations" == *"companion.usb_mota_source=ota folder on"* ]] \
|| fail "ESP32 Full contract omitted exclusive USB folder seeding"
PIO_ENV_PLATFORM_BY_NAME[RAK_3401_companion_radio_full]=NRF52_PLATFORM
BUILD_CAPABILITIES=()
+21
View File
@@ -8,6 +8,27 @@ root = Path(__file__).resolve().parents[1]
source = (root / "examples/companion_radio/MyMesh.cpp").read_text()
features = (root / "examples/companion_radio/CompanionFeatures.h").read_text()
# Every Full Companion which inherits OTA_FOLDER_SERIAL must put that shared
# byte stream under the exclusive mOTA owner. Otherwise the ASCII terminal can
# consume and echo a delayed lowercase `ms` host response between source
# transactions while valid uppercase `MS` requests continue.
assert re.search(
r"#if defined\(OTA_FOLDER_SERIAL\)\s*\\?\s*"
r"&& \(defined\(NRF52_PLATFORM\) \|\| defined\(ESP32_PLATFORM\)\)\s*"
r"#ifndef COMPANION_FEATURE_USB_MOTA_SOURCE\s+"
r"#define COMPANION_FEATURE_USB_MOTA_SOURCE 1",
features,
)
assert re.search(
r"#if COMPANION_FEATURE_USB_MOTA_SOURCE\s*\\?\s*"
r"&& !\(\(defined\(NRF52_PLATFORM\) \|\| defined\(ESP32_PLATFORM\)\)\s*\\?\s*"
r'&& defined\(OTA_FOLDER_SERIAL\).*?defined\(ENABLE_USB_INTERFACE\).*?'
r'defined\(ENABLE_OTA\)\)\s+'
r'#error "COMPANION_FEATURE_USB_MOTA_SOURCE requires a USB serial folder source"',
features,
re.DOTALL,
)
local_start = source.index("bool MyMesh::handleLocalControlCommand(")
local_end = source.index("\n#if COMPANION_FEATURE_TEMP_RADIO\nvoid MyMesh::serviceTempRadio()", local_start)
local = source[local_start:local_end]