WiFi as a Companion Link¶
ALONGSIDE OTHER TRANSPORTS · TCP 5000 · CREDENTIALS IN THE BINARY · ESP32 ONLY
In MeshCore, WiFi is not a network layer but one of the ways a companion app talks to a node. Since v1.17.1 it no longer replaces the BLE link or the serial cable but sits alongside them: which transports are included is a build choice, and a build can include several. This chapter describes how that choice is made, what travels over TCP, and why your network credentials end up inside the firmware.
Note
Source. This page has been verified against the firmware itself:
MeshCore v1.17.1, commit d929643, 14 August 2026 — files
examples/companion_radio/main.cpp,
src/helpers/esp32/SerialWifiInterface.cpp,
src/helpers/BaseSerialInterface.h, src/helpers/MultiSerialInterface.h
and the WIFI_SSID flags in variants/. The counts can be reproduced
with
tools/hardware-overview.py
using --pin d929643.
Every flag adds a transport¶
The companion firmware selects its transports with preprocessor directives, not with a setting. For WiFi:
examples/companion_radio/main.cpp r.34-46
// include wifi interface
#ifdef WIFI_SSID
#ifndef TCP_PORT
#define TCP_PORT 5000
#endif
#ifdef ESP32
// include esp32 wifi interface
#include <helpers/esp32/SerialWifiInterface.h>
SerialWifiInterface wifi_interface;
#else
#error "SerialWifiInterface is not defined for this platform"
#endif
#endif
In v1.16.0 this was an #elif chain: if WIFI_SSID was defined, BLE and the
serial variant never made it into the binary. In v1.17.1 each transport has
its own #if. At start-up the firmware registers WiFi with
interface_manager, a MultiSerialInterface:
examples/companion_radio/main.cpp r.208-210
WiFi.begin(WIFI_SSID, WIFI_PWD);
wifi_interface.begin(TCP_PORT);
interface_manager.addInterface(InterfaceType::WiFi, &wifi_interface);
A node can therefore talk to an app over BLE and over WiFi at the same time,
if the build sets both flags. Switching without reflashing is still not
possible: what is not compiled in is not there. How MultiSerialInterface
serves the transports together, and what limit there is on their number, is
in USB Serial.
Note
That explains why nodes with the same chip behave differently: which transport they speak is a property of the firmware on them, not of the board. Which boards offer which connection options is in Node Matrix.
ESP32 only¶
On any other platform a build with WIFI_SSID stops with an error
(examples/companion_radio/main.cpp r.43-45). That shows up in the variant
files:
WIFI_SSID |
Count |
|---|---|
| active | 25 lines in 21 variant directories |
| commented out | 4 lines, all four RP2040 |
Counted across variants/*/platformio.ini; lines starting with ; are
commented out and do not count as a build flag. More lines than directories,
because a variant file can hold several [env:…] sections.
The four commented-out lines are in rak11310, rpi_picow,
waveshare_rp2040_lora and xiao_rp2040. In v1.16.0 main.cpp still held
a commented-out RP2040 branch that referred to a SerialWifiInterface for
RP2040. That branch is gone in v1.17.1, and no such class exists in the
firmware. Enabling one of the four lines produces the error.
What travels over the link¶
SerialWifiInterface opens a TCP server. The port is TCP_PORT, and if the
variant does not set it, it is 5000.
src/helpers/esp32/SerialWifiInterface.cpp r.4-7
void SerialWifiInterface::begin(int port) {
// wifi setup is handled outside of this class, only starts the server
server.begin(port);
}
The class does nothing to the network itself — connecting happens outside
the class, in main.cpp. Since v1.17.1 that is also where the node is kept
from sleeping while WiFi is active (r.195), and where it reconnects every ten
seconds after the connection drops (r.198-206 and r.265-273). What travels
over the link is the same as with BLE and serial: frames of at most 176
bytes, behind the same interface.
src/helpers/BaseSerialInterface.h r.5
#define MAX_FRAME_SIZE 176 // +4 for transport codes (region scoping)
Over TCP every frame gets the same header as over the cable, > or < and
two length bytes (src/helpers/esp32/SerialWifiInterface.cpp r.91-94); a
frame that does not start with < is skipped by the node (r.143-144). All
transports implement BaseSerialInterface with writeFrame() and
checkRecvFrame(), and the rest of the firmware only sees
MultiSerialInterface, not which transport sits underneath. What those
frames look like byte by byte is in USB Serial.
The credentials live in the binary¶
WIFI_SSID and WIFI_PWD are build flags. They are substituted into the
code at compile time:
examples/companion_radio/main.cpp r.208-209
WiFi.begin(WIFI_SSID, WIFI_PWD);
wifi_interface.begin(TCP_PORT);
Warning
The name and password of your WiFi network end up as readable text inside the firmware image. Whoever has the binary — or reads out the node — has your network password. Do not share self-built WiFi firmware, and do not put a node with WiFi firmware on a network you cannot afford to lose. A guest network or a separate VLAN is exactly what this situation calls for.
This is a different trade-off from BLE, where a PIN code sits in the firmware but no network secret. See BLE Architecture.
Sources¶
Firmware, commit d929643 (v1.17.1, 14 August 2026):
examples/companion_radio/main.cpp— the#ifper transport, the registration withinterface_manager,WiFi.begin()and the reconnectingsrc/helpers/esp32/SerialWifiInterface.cpp— the TCP server, the header and the send queuesrc/helpers/BaseSerialInterface.h— the shared interface andMAX_FRAME_SIZEsrc/helpers/MultiSerialInterface.h— the class that serves all transports together
Related in this documentation:
- BLE Architecture — the transport that since v1.17.1 can sit alongside WiFi in one build
- USB Serial — the same frame over a cable, and
MultiSerialInterface - The Hardware of a Node — where this part sits
- Node Matrix — which board has WiFi on board
- The Four Platform Families — which families know WiFi
Translated from Dutch by Anthropic Claude