Skip to content

ESP32-S3 NimBLE Full Companion trial

These optional builds use NimBLE-Arduino 2.5.1 with the existing ESP32 Arduino 2.0.17 toolchain. They are hardware qualification builds, outside the normal release matrix and firmware picker.

See the 2026-09-08 hardware results for measured RAM, MAC-policy checks, WiFi updates, and remaining qualification.

The contact-cache guide adds six optional 350-contact trials for ESP32 boards without PSRAM, including Heltec V3. Their qualification results include linked RAM margins, V4/RAK hardware tests and the mOTA queue policy.

Hardware Trial environment Contacts Channels Offline queue
Heltec V4.2/V4.3 OLED, FEM on heltec_v4_2_v4_3_companion_radio_full_femon_nimble 350 40 512
XIAO ESP32-S3 with WIO SX1262 Xiao_S3_WIO_companion_radio_full_nimble 350 40 256

The XIAO profile uses WIO radio pins CS 41, DIO1 39, BUSY 40 and RESET 42. It is not the generic XIAO profile for separately wired radio modules. Both boards retain their parent Full Companion features, USB mOTA sender, WiFi update slots, and PSRAM-backed offline queue. The V4 also retains direct MQTT.

The small-screen message layout uses 6-pixel capitals on 128x64 panels and 5-pixel capitals on very tiny panels. It includes a compact channel/sender line and full message previews. Font selection is automatic.

Build

Enable the optional configuration in the ignored platformio.local.ini:

[platformio]
extra_configs =
  variants/*/platformio.ini
  platformio.nimble.ini

If that file already contains local settings, merge this list with them. Run one PlatformIO/build.sh command at a time. Both commands below use USA Cascade:

OUTPUT_DIR=.releases/nimble-v4 bash build.sh build-firmware \
  heltec_v4_2_v4_3_companion_radio_full_femon_nimble \
  --firmware-version v1.17.1.5-halo-keymind-cascade-nimble-test \
  --radio-preset usa-cascadia --profile cascade --standard --require-ota

OUTPUT_DIR=.releases/nimble-xiao bash build.sh build-firmware \
  Xiao_S3_WIO_companion_radio_full_nimble \
  --firmware-version v1.17.1.5-halo-keymind-cascade-nimble-test \
  --radio-preset usa-cascadia --profile cascade --standard --require-ota

Use the merged image at address 0 for a USB installation. The V4 uses 16 MB flash; the XIAO uses 8 MB. Both use DIO flash mode. Use the application-only .bin for an existing, matching WiFi OTA layout. Remove the optional config entry when finished to return to the ordinary release matrix.

Bluetooth identity and pairing

All existing MAC settings remain available from the ASCII USB terminal and the app's authenticated local CLI. A setting takes effect after reboot.

Command Behavior
get bluetooth.mac Show the saved MAC policy.
set bluetooth.mac C2:17:15:04:00:01 Use this custom random-static address.
set bluetooth.mac random Generate one address and retain it across boots.
set bluetooth.mac random-every-boot Generate a new address on each boot.
set bluetooth.mac random-after-connect Rotate on the next boot after an authenticated connection; otherwise retain the address.
set bluetooth.mac default Restore the factory Bluetooth address.
set bluetooth.stealth on Pair once, then accept the saved bonded peer.
set bluetooth.stealth off Restore ordinary discovery without changing the MAC policy.
get bluetooth.stealth Show pairing or bonded-peer-only state.

The ble.mac and ble.stealth aliases also work. A literal custom address must be a valid BLE random-static address (first byte C0-FF). Forget the old entry in the phone's Bluetooth settings and pair again after an address change or switching Bluetooth libraries. Existing Bluedroid bonds are not migrated.

Stealth does not disable the selected rotation policy. If rotation changes the address on reboot, first pairing opens again for the new identity.

Pairing requires encryption, PIN authentication and a stored bond. NimBLE's controller byte order is converted explicitly, so a custom address appears over the air in the order entered. Failed identity setup stops advertising and retries initialization instead of advertising the wrong address.

V4 WiFi setup QR

The active WiFi setup page displays a compact QR code with a one-pixel white border. The normal open setup network fits a 44 x 44 pixel code, with 2 x 2 pixel modules. The SSID, portal address and HOLD STOP hint remain beside it. Short-click to leave the page; hold the button on this page to start or stop the setup AP. From USB, start webconfig ap starts the same setup session.

When the setup AP is inactive and the node has joined WiFi, this page shows WIFI CONNECTED, IP <address>, SETUP AP INACTIVE, then HOLD START AP from top to bottom. The address appears once assigned and clears when the station disconnects. While the setup AP is active, the QR page shows the setup portal's address instead.

Regression checks

python3 -B test/test_nimble_companion.py
python3 -B test/test_bluetooth_mac_contract.py
python3 -B test/test_bluetooth_pairing_ui.py
python3 -B test/test_heltec_v4_wifi_setup_page.py
python3 -B test/test_firmware_ram.py
python3 -B test/test_esp32_dram.py
python3 -B test/test_shared_mota_queue.py
python3 -B test/test_companion_mqtt_stack.py
pio test -e native -f test_companion_node_prefs \
  -f test_ble_tx_stall_watchdog -f test_companion_frame_queue \
  -f test_display_driver

The adapter test compiles the actual transport under address/undefined-behavior sanitizers with API doubles. It injects allocation, identity and bond failures; checks PIN authentication and stealth; and exercises 176-byte frames, MTU and notification subscription gating, retry and duplicate prevention. Embedded builds compile against the pinned real library. RAM reports retain the existing wireless budget; linked headroom is not a measurement of live free heap. The MQTT save callback also has a compiler-enforced stack-frame regression check: nested preference copies previously overflowed the V4 loop task when saving MQTT settings through WebConfig, despite sufficient free heap.

Hardware qualification must additionally verify pairing and reconnection, advertised addresses for every MAC policy, contact synchronization, WiFi/BLE coexistence, and uptime under load. BlueZ results do not establish iOS or Android camera/pairing compatibility.