Repository navigation
Wi Fi and Networking
MicroCS brings the network to C# scripts: Wi-Fi (station, soft access point, both at
once), scanning, events, static IP, saved credentials, WPS / SmartConfig, power and radio
options, ESP-NOW, SNTP time, mDNS, ping — plus TcpClient, TcpListener, UdpClient,
Dns, an HTTP/1.1 client with HTTPS, an HTTP server with routes, files and
WebSockets, a WebSocket client, a captive-portal DnsServer and a JSON library.
1.13 added Wi-Fi on the ESP32; 1.14 adds the server side and the Raspberry Pi Pico W /
Pico 2 W.
if (Wifi.Connect("MyNetwork", "secret-password"))
{
var r = Http.Get("https://api.example.com/v1/status");
var doc = Json.Parse(r.Body);
Console.WriteLine($"{Wifi.Ip}: {r.StatusCode} {doc["state"]}");
}| Where | What you get |
|---|---|
| ESP32 family (ESP-IDF port, firmware you build) — ESP32, S2, S3, C2, C3, C5, C6, C61 | everything: Wifi, EspNow, Ntp (SNTP), Mdns*, Ping, sockets, Http + HTTPS |
Raspberry Pi Pico W / Pico 2 W (RP2 port with FreeRTOS, -DMICROCS_WIFI=ON) |
Wifi (station, AP, scan, static IP, saved networks), Ntp (SNTP), Mdns, Ping, sockets, Http (no HTTPS), servers — see Pico W
|
PC (./mcs, Linux / macOS) |
sockets, Dns, Http on the real network, Ntp = system clock |
PC with --sim |
additionally a simulated Wi-Fi radio: networks, AP, WPS / SmartConfig, ESP-NOW echo peer, mDNS, ping |
| other ports |
Json only, unless the port registers a backend (see Porting) |
* Mdns needs the espressif/mdns
component in the project (idf.py add-dependency espressif/mdns); MicroCS detects it.
Check at run time with Net.Has(...): "Wifi", "Wifi.AP", "Wifi.Scan", "Wifi.Save",
"Wifi.Wps", "Wifi.SmartConfig", "Wifi.Enterprise", "EspNow", "Mdns", "Ping",
"Tls" / "Https", "Sockets", "Http", "HttpServer", "WebSocket", "DnsServer",
"Ntp", "Ntp.Sntp" (the same names work with Hal.Has). Net.Backend is "esp32",
"rp2", "sim" or "none".
Contents: Wi-Fi · Events · Access point · IP & options · Saved networks · ESP-NOW · Time · mDNS & ping · TCP · UDP · DNS · HTTP · HTTP server · WebSocket · Captive portal · JSON · Errors · ESP32 setup · Pico W setup · Configuration · Simulator · Porting · Limits
bool ok = Wifi.Connect("MyNetwork", "secret-password"); // waits for an IP (15 s)
bool ok2 = Wifi.Connect("MyNetwork", "secret-password", 30000); // own timeout
Wifi.Connect("Mesh", "pw", 15000, "24:0A:C4:12:34:56", 6); // one access point (BSSID, channel)
Wifi.Connect(); // the saved network (Wifi.Save / menuconfig)
Wifi.Begin("MyNetwork", "secret-password"); // returns at once; follow Wifi.OnEvent
Wifi.WaitConnected(10000); // ... and wait later
Wifi.ConnectEnterprise("eduroam", "anonymous@uni.edu", "me@uni.edu", "pw"); // WPA2/3-Enterprise (PEAP/TTLS)
Wifi.Reconnect(); // same settings again
Wifi.Disconnect(); // leave the network, radio stays on
Wifi.Off(); // stop Wi-Fi and give its RAM backConnect returns false (it does not throw) when the network is not found, the password
is wrong or no address arrives in time; then Wifi.StatusName / Wifi.ReasonName() say
why. Bad arguments (empty SSID, a 3-character password) throw ArgumentException.
WPA3-SAE, WPA2/WPA3 transition networks and protected management frames are handled
automatically; Wifi.MinAuth = Wifi.AuthWpa2 rejects weaker networks.
Status (Wifi.Status) |
Meaning |
|---|---|
Idle, Stopped
|
not connecting / radio off |
Connecting |
association or DHCP running |
Connected |
linked and has an IP address (Wifi.IsConnected) |
NoSsid |
network not found |
WrongPassword |
authentication failed |
ConnectFailed, ConnectionLost, Disconnected
|
other failures / link dropped |
Link information: Ssid, Bssid, Rssi (dBm), Channel, Auth (+ Wifi.AuthName(a)),
Mode (ModeOff, ModeStation, ModeAccessPoint, ModeStationAp), DisconnectReason
(+ Wifi.ReasonName(r); constants ReasonAuthFail, ReasonNoApFound,
ReasonBeaconTimeout, ReasonAssocFail, ReasonHandshakeTimeout, ReasonAssocLeave).
foreach (var n in Wifi.Scan()) // strongest first; Scan(true) includes hidden networks
Console.WriteLine($"{n.Ssid,-20} {n.Rssi} dBm ch {n.Channel} {n.AuthName} {n.Quality}%");WifiNetwork: Ssid, Bssid, Rssi, Channel, Auth, AuthName, Secure, Hidden,
Protocols (Wifi.Proto* bits), Quality (0–100 %). Security constants: AuthOpen,
AuthWep, AuthWpa, AuthWpa2, AuthWpaWpa2, AuthEnterprise, AuthWpa3,
AuthWpa2Wpa3, AuthWapi, AuthOwe, AuthWpa3Enterprise192.
Wifi.OnEvent((ev, value) =>
{
if (ev == Wifi.EventGotIp) Console.WriteLine("online: " + Wifi.Ip);
if (ev == Wifi.EventDisconnected) Console.WriteLine("lost: " + Wifi.ReasonName(value));
});Handlers run on the script thread, like GPIO interrupts (during Thread.Sleep, between
statements, in Scheduler loops) — never inside the Wi-Fi driver. Events:
EventStaStart, EventStaStop, EventConnected (value = channel), EventDisconnected
(reason), EventGotIp, EventLostIp, EventGotIp6, EventScanDone, EventApStart,
EventApStop, EventApJoined, EventApLeft, EventApAssigned, EventEspNowRecv,
EventEspNowSent, EventNtpSync. Wifi.EventName(ev) gives the name.
Wifi.AutoReconnect = true (the default) reconnects after the link drops, with back-off.
Wifi.StartAp("MicroCS-Setup", "12345678"); // (ssid, password = open, channel = 1, hidden, maxClients = 4)
Console.WriteLine(Wifi.ApIp); // 192.168.4.1
foreach (var c in Wifi.ApClients()) Console.WriteLine(c.Mac + " " + c.Ip + " " + c.Rssi);
Wifi.SetApIp("10.0.0.1", "10.0.0.1", "255.255.255.0");
Wifi.StopAp();Station and AP run together (ModeStationAp): connect to the router and offer a setup
hotspot. On one radio both use the router's channel.
Wifi.SetStaticIp("192.168.1.77", "192.168.1.1", "255.255.255.0", "1.1.1.1"); // dns1, dns2 optional
Wifi.UseDhcp();
Console.WriteLine($"{Wifi.Ip} {Wifi.Netmask} {Wifi.Gateway} {Wifi.Dns} {Wifi.Dns2} dhcp={Wifi.Dhcp} {Wifi.Ipv6}");| Property | Values |
|---|---|
Hostname |
DHCP / mDNS name (set before connecting) |
Country |
"DE", "US", "JP", … — channels and power limits |
TxPower |
dBm, 2–20 |
PowerSave |
PowerNone (fastest), PowerMin (default), PowerMax (lowest current) |
ListenInterval |
beacons between wake-ups with PowerMax
|
Protocol / ApProtocol
|
Proto11b | Proto11g | Proto11n, ProtoLR (Espressif long range), Proto11ax (C5/C6) |
Bandwidth |
20 or 40 MHz |
Band |
Band2G, Band5G, BandAuto — 5 GHz on dual-band chips (ESP32-C5) |
Channel |
primary channel (while not connected) |
MinAuth |
weakest accepted security |
Mac / ApMac
|
read or set the MAC address |
AutoReconnect, Persistent
|
reconnect by itself; let the driver keep its own config in flash |
Antenna |
antenna selection on boards with two |
Unsupported values throw NotSupportedException, out-of-range ones ArgumentException.
Wifi.Save("MyNetwork", "secret-password"); // NVS flash (namespace "microcs")
Wifi.Connect(); // any later script, no password in the source
Console.WriteLine(Wifi.SavedSsid); // "" when nothing is saved
Wifi.Forget();With menuconfig → MicroCS → Wi-Fi → Connect at boot the firmware joins the saved (or
the menuconfig) network before boot.cs / main.cs run, so scripts find it online.
Where the port has no credential store of its own (the Pico W, other lwIP ports) Save
writes the file /.wifi on the script filesystem instead (MCS_WIFI_CREDS_FILE, needs
MCS_ENABLE_FS); Net.Has("Wifi.Save") tells whether saving works.
Join a network without putting its password anywhere — the board learns it and saves it
(pass save: false as the second argument to skip that):
if (Wifi.SavedSsid != "" && Wifi.Connect()) return; // known network
if (Net.Has("Wifi.Wps") && Wifi.Wps(120000)) // press the router's WPS button
Console.WriteLine("joined " + Wifi.Ssid);
else if (Net.Has("Wifi.SmartConfig") && Wifi.SmartConfig(120000)) // ESP-Touch phone app
Console.WriteLine("joined " + Wifi.Ssid);Both return false on timeout, need the station free (not connected) and are ESP32 only
(WPS push button; SmartConfig: ESP-Touch and AirKiss). On boards without them use
the captive portal.
Connectionless board-to-board packets (≈1 ms, up to EspNow.MaxData = 250 bytes per packet),
no router needed. Both boards must be on the same channel.
EspNow.Start();
Console.WriteLine("my MAC: " + Wifi.Mac);
EspNow.AddPeer("24:0A:C4:12:34:56"); // (mac, channel = 0, key = null; 16 chars = encrypted)
bool acked = EspNow.Send("24:0A:C4:12:34:56", "temp=21.5"); // string or byte[]
EspNow.Broadcast(new byte[] { 1, 2, 3 });
EspNow.OnReceive((mac, data) => Console.WriteLine(mac + ": " + Encoding.UTF8.GetString(data)));
var m = EspNow.Receive(500); // or poll: EspNowMessage or null
EspNow.Stop();Also: RemovePeer(mac), SetPmk(key16), Peers, MaxData. EspNowMessage: Mac,
Data, Text, Length, Rssi.
Ntp.Timezone = "CET-1CEST,M3.5.0,M10.5.0/3"; // POSIX TZ string
Ntp.Sync(); // pool.ntp.org, waits up to 10 s; Sync(server, ms) / Sync(ms)
Console.WriteLine(Ntp.LocalTime + " UTC " + Ntp.UtcTime + " unix " + Ntp.Time);Ntp.Start(s1, s2, s3) keeps syncing in the background; Ntp.Synced, Ntp.UtcOffset
(seconds). On the PC the system clock is used.
Mdns.Start("greenhouse"); // http://greenhouse.local/
Mdns.AddService("_http", "_tcp", 80, new[] { "path=/" });
string ip = Mdns.Resolve("printer.local"); // null when nobody answers
int ms = Ping.Send("192.168.1.1", 1000); // -1 = no answerDns.Resolve, TcpClient and Http resolve .local names through mDNS automatically.
using var tcp = new TcpClient("192.168.1.10", 5000); // (host, port, timeoutMs = 10000)
tcp.WriteLine("hello");
string reply = tcp.ReadLine(); // null when the peer closedTcpClient: Connect(host, port[, ms]), ConnectTls(host, port[, ms[, caPem]]),
Write(string | byte[]), WriteLine, Read([max]) → byte[], ReadString([max]),
ReadByte() (-1 at end), ReadLine(), ReadToEnd(), Available, Connected, Timeout
(ms, 0 = forever), NoDelay, KeepAlive, RemoteAddress/Port, LocalAddress/Port,
IsTls, Close() / Dispose().
var server = new TcpListener(8080); // or (address, port); port 0 = any free port
server.Start();
while (true)
{
var client = server.AcceptTcpClient(500); // null after 500 ms without a client
if (client == null) continue; // do other work here
client.WriteLine("hi " + client.RemoteAddress);
client.Close();
}TcpListener: Start([backlog]), Stop(), Pending(), AcceptTcpClient([ms]) /
Accept([ms]), LocalPort, Active. Studio's Tiny web server template is a complete
HTTP server in ~25 lines.
var udp = new UdpClient(5000); // bound port (0 / none = any)
udp.Send("ping", "192.168.1.20", 5000);
string text = udp.ReceiveString(1000); // null on timeout; Receive() -> byte[]
Console.WriteLine(udp.RemoteAddress + ":" + udp.RemotePort);
udp.EnableBroadcast = true; udp.Send("hello", "255.255.255.255", 5000);
udp.JoinMulticastGroup("239.1.2.3");Also Connect(host, port) + Send(data), LocalPort, Available, Timeout,
DropMulticastGroup, Close().
Dns.Resolve(host) → first IPv4 string, Dns.GetHostAddresses(host) → string[],
Dns.HostName. Unknown names throw SocketException.
var r = Http.Get("https://api.example.com/items?q=" + Http.UrlEncode("blue led"));
if (r.IsSuccess) Console.WriteLine(r.Body);
var headers = new Dictionary<string, string> { ["Authorization"] = "Bearer " + token };
r = Http.Post("https://api.example.com/items", Json.Stringify(item), "application/json", headers);
r.EnsureSuccess(); // HttpRequestException unless 2xx
string page = Http.GetString("http://192.168.1.20/status");
long bytes = Http.Download("https://example.com/firmware/app.cs", "/app.cs"); // streamed to flash| Methods |
Get, Head, Delete (url[, headers]); Post, Put, Patch (url, body[, contentType[, headers]]); Request(method, url[, body[, contentType[, headers]]]); GetString; Download(url, path[, headers])
|
| Body |
string or byte[]; Content-Type defaults to JSON when the text looks like JSON, else text/plain
|
| Headers |
Dictionary<string, string>, a "Name: value" string (lines) or string[]
|
| Settings |
Http.Timeout (ms), Http.UserAgent, Http.FollowRedirects (up to 5) |
HttpResponse |
StatusCode, Reason, IsSuccess, Body, Bytes, ContentLength, ContentType, Url, Headers, Header(name), EnsureSuccess()
|
Chunked transfer encoding and redirects are handled. A 404 is not an exception — check
StatusCode or call EnsureSuccess(). Bodies above MCS_HTTP_MAX_BODY (32 KB) throw;
use Download for big files (written to path.tmp, renamed when complete).
HTTPS / TLS (ESP32): server certificates are checked against ESP-IDF's certificate
bundle (CONFIG_MBEDTLS_CERTIFICATE_BUNDLE, on by default). For a private CA pass its PEM
to TcpClient.ConnectTls(host, port, ms, caPem). Net.Has("Tls") is false on the PC.
A successful Wifi.Connect / DHCP lease establishes Wi-Fi, not the health of a remote HTTP
service. Http: server closed before sending an HTTP response means no HTTP status line
arrived before the peer closed. Try http://example.com/, a local HTTP server, or the
endpoint's HTTPS URL (when Net.Has("Tls")); port 80 may be blocked or the service may have
stopped accepting plain HTTP. A close during headers, a chunk line or a Content-Length body
is reported separately and remains an error: incomplete data is not returned as success.
The Studio http_get.cs template is a basic web-page smoke test, not a time-API / JSON test.
var web = new HttpServer(80);
web.Get("/", req => "<h1>Hello</h1>"); // string → text/html
web.Get("/api/temp", req => new Dictionary<string, object> { ["c"] = 21.5 }); // object → JSON
web.Post("/led/{state}", req => { led.Write(req.Param("state") == "on"); return req.Redirect("/"); });
web.ServeFiles("/static", "/www"); // files from flash
web.NotFound(req => req.Send(404, "text/plain", "nothing at " + req.Path));
web.Start();
web.Run(); // or call web.Poll(ms) from your own loop| Routes |
Get, Post, Put, Delete, Patch (path, handler); On([method, ]path, handler); NotFound(handler); ServeFiles(urlPrefix, folder); WebSocket(path, onMessage[, onOpen[, onClose]]) — up to MCS_HTTP_SERVER_MAX_ROUTES (16) |
| Paths | exact "/api/status", parameters "/notes/{id}" (req.Param("id")), prefix "/files/*"
|
| Handler result |
string → text/html, byte[] → binary, any other object → JSON, null → empty reply (unless the handler already sent one); req.Status sets the status |
| Serving |
Start(), Poll(ms) (returns how much was handled), Run() (until Stop()), Stop(); Port, Running, Requests, Cors (adds Access-Control-Allow-* and answers OPTIONS) |
HttpRequest |
Method, Path, Query, Url, Host, Headers, Header(name), ContentType, ContentLength, Body, Bytes, Json, Param(name[, default]) (path → query → form fields), HasParam, RemoteAddress
|
| Replies |
Send(body) / Send(status, body) / Send(status, contentType, body), SendJson(value[, status]), SendStatus(code), SendFile(path[, contentType]), Redirect(url[, status]), SetHeader(name, value), Authenticate(user, password[, realm]) (HTTP Basic; answers 401 and returns false) |
ServeFiles maps / to index.html, picks the Content-Type from the extension and sends
file.gz with Content-Encoding: gzip when it exists. One request is handled at a time
(HTTP/1.1, Connection: close); a client gets MCS_HTTP_SERVER_TIMEOUT (3 s) to send its
request and bodies above MCS_HTTP_MAX_BODY get a 413. An exception in a handler answers
500. On the PC a port below 1024 that needs root moves to port + 8000 (:80 → :8080,
a note goes to stderr) — web.Port tells the real one.
Server side — an endpoint on an HttpServer; every browser gets a WebSocket object:
web.WebSocket("/ws",
(ws, msg) => ws.Send("echo: " + msg), // msg: string (text) or byte[] (binary)
ws => Console.WriteLine($"#{ws.Id} from {ws.RemoteAddress}"),
ws => Console.WriteLine($"#{ws.Id} closed {ws.CloseStatus}"));
web.Broadcast("{\"t\":21.5}"); // to every connected browserClient side — ws:// everywhere, wss:// where Net.Has("Tls"):
var ws = new WebSocket();
ws.Connect("ws://192.168.1.10:8765/chat", 5000); // optional third argument: extra headers
ws.OnMessage(m => Console.WriteLine(m));
ws.Send("hello"); // string → text frame, byte[] → binary
while (ws.IsOpen) ws.Poll(100); // or: var m = ws.Receive(1000);
ws.Close(1000, "bye");WebSocket: Connect, Send, SendText, SendBinary, Ping, Receive([ms]),
OnMessage, Poll([ms]), Close([code[, reason]]), IsOpen, State, CloseStatus,
CloseReason, Id, Path, RemoteAddress, Timeout, IsTls. Pings are answered and
fragmented messages joined by themselves; messages above MCS_WS_MAX_MESSAGE (8 KB) close
the connection with 1009, and one server keeps up to MCS_WS_MAX_CLIENTS (4) browsers.
Errors throw WebSocketException (an IOException).
DnsServer answers every name (or only Domain) with one address, so a phone that joins
the board's hotspot opens its setup page by itself:
Wifi.StartAp("MicroCS-Setup", "");
var dns = new DnsServer(Wifi.ApIp); // default address: Wifi.ApIp
dns.Start(); // UDP port 53 (8053 on the PC)
var web = new HttpServer(80);
web.Get("/", req => "<form method=post action=/save>...</form>");
web.Post("/save", req => { Wifi.Save(req.Param("ssid"), req.Param("pass", "")); return "saved"; });
web.NotFound(req => req.Redirect("http://" + Wifi.ApIp + "/")); // /generate_204, /hotspot-detect.html ...
web.Start();
while (true) web.Poll(100); // also answers the DnsServerDnsServer: Start([address[, port]]), Stop(), Poll(), Running, Queries, Ttl
(60 s), Domain ("*"), Address, Port. The Studio template Captive portal Wi-Fi
setup is a complete version with a network list.
Available on every port (with List + Dictionary; MCS_ENABLE_JSON).
var doc = Json.Parse("{\"temp\": 21.5, \"tags\": [\"a\", \"b\"], \"ok\": true}");
double t = doc["temp"]; string first = doc["tags"][0];
string text = Json.Stringify(new Dictionary<string, object> { ["id"] = 7, ["on"] = true });
string pretty = Json.Stringify(doc, true); // indented
class Reading { public string Name = "t1"; public double Value { get; set; } }
Console.WriteLine(JsonSerializer.Serialize(new Reading())); // {"Name":"t1","Value":0}Objects become Dictionary<string, object>, arrays List<object>, integral numbers int
(long range with MCS_INT64) and others double. Stringify writes dictionaries,
lists, arrays, HashSet, numbers (NaN/∞ → null), strings, chars, bool, null and the
fields of script objects (names starting with _ are skipped). Json.Escape(s) quotes a
string. Errors: FormatException with the offset.
| Exception | When |
|---|---|
SocketException (an IOException) |
connection refused / reset, unknown host, network down |
TimeoutException |
connect / read / accept exceeded its timeout |
HttpRequestException |
EnsureSuccess() / GetString() on a non-2xx status |
InvalidOperationException |
using a closed or unconnected socket, no saved network |
ArgumentException |
bad SSID / password / MAC / URL / header |
NotSupportedException |
the board or port lacks the feature (Net.Has) |
Blocking calls wait in short slices (MCS_NET_SLICE_MS): Ctrl-C, Stop in Studio and
--time-limit interrupt them, and event handlers keep running while a script waits.
The web templates live under Web server and WebSocket. They serve until Stop /
Ctrl-C, rather than exiting after two seconds. Open the exact http:// address printed
by the script: a listener on port 8080 needs http://<board-ip>:8080/; a bare IP uses
port 80 (the templates now default to 80). Your phone must be able to reach the board
on the same LAN; guest-network/client isolation can block it. On a restricted host,
TCP listeners and HttpServer fall back from port 80 to 8080 on permission denial
(DnsServer: 53 to 8053), with a diagnostic; boards retain their requested ports.
HttpServer.Run() and socket waits service idle/event work and cancellation, even with
continuous traffic or a browser stalled halfway through a request. A foreground script
still owns the interpreter: ordinary console/file commands wait until it stops. No
console output between requests is normal, not a signal that the board has frozen.
Use using var / Dispose() for explicit ownership. The shell also closes network
resources created or reopened during a foreground run (including boot scripts) on
completion, exceptions and Stop, so rerunning on the same port does not depend on GC.
Open WebSockets are force-closed on scope cleanup, without waiting for the peer's close
handshake. Pre-existing interactive resources are preserved unless reopened by the run;
interactive exec / REPL resources remain alive until explicitly closed or collected.
This cleanup does not turn off Wi-Fi or cancel scheduler jobs. A disposed HttpServer
cannot be restarted; Stop() alone remains reusable.
Native embedders can opt into the same ownership boundary with
mcs_net_scope_begin(vm) before execution and mcs_net_scope_end(vm, checkpoint) after
it returns (also on error/abort). Ordinary mcs_exec_* calls do not implicitly close
application-owned resources.
Nothing to do when you build the example firmware (ports/esp32/example): Wi-Fi is on by
default on chips that have it. The ready-to-flash images on the Releases page are built
without Wi-Fi (CONFIG_MICROCS_WIFI=n) — build the example yourself to get it
(ESP32 guide). In menuconfig → MicroCS → Wi-Fi and networking:
| Option | Default | |
|---|---|---|
CONFIG_MICROCS_WIFI |
y | the whole network library (off: saves ~600 KB flash, ~70 KB RAM) |
CONFIG_MICROCS_WIFI_RESERVE_KB |
72 (56 on C2) | RAM left outside the script heap for the Wi-Fi driver and lwIP |
CONFIG_MICROCS_WIFI_SSID / _PASSWORD
|
"" | network joined at boot |
CONFIG_MICROCS_WIFI_AUTOCONNECT |
y | join the Wifi.Save() network at boot |
The example's partitions.csv has a 2 MB app partition (Wi-Fi + TLS make the image
~1.6 MB) and ~1.9 MB LittleFS. In your own firmware call mcs_esp32_wifi_autoconnect(ssid, password) after the VM is created (see ports/esp32/example/main/main.c); the port
installs the backend in mcs_port_esp32_hal_init by itself. Use idf.py add-dependency espressif/mdns for Mdns. Needs ESP-IDF 5.x (Enterprise: 5.1+); CI builds it with 5.3 for ESP32, S3, C2, C3 and C6.
RAM: the driver uses ~50–70 KB (more with TLS: ~40 KB per HTTPS connection during the
handshake). On the ESP32-C2 (272 KB) keep scripts small or lower MCS_NET_MAX_READ.
The RP2 port drives the CYW43439 radio of the Pico W and Pico 2 W through the Pico
SDK's pico_cyw43_arch_lwip_sys_freertos (lwIP in its own FreeRTOS task), so Wi-Fi needs
the FreeRTOS build:
cd ports/rp2/example
cmake -B build -DPICO_BOARD=pico_w -DMICROCS_OS=freertos -DFREERTOS_KERNEL_PATH=... # pico2_w for the Pico 2 W
cmake --build buildMICROCS_WIFI turns on by itself for a *_w board with FreeRTOS (-DMICROCS_WIFI=OFF
leaves it out); without an OS the radio stays off. The Pico SDK's
lib/cyw43-driver and lib/lwip submodules must be checked out. The release page has
ready-to-flash pico_w and pico2_w images.
What works: station and access point (at once too), scan, events, static IP, hostname,
country, power save, TX power, auto-reconnect, Wifi.Save (file /.wifi), DHCP server
for AP clients (MCS_RP2_DHCPS_LEASES), SNTP, mDNS responder and .local lookups, ping,
TCP / UDP / HTTP, HttpServer, WebSocket (ws://), DnsServer. Not available: HTTPS /
wss://, ESP-NOW, WPS / SmartConfig, Enterprise, setting the MAC. The on-board LED is on
the radio chip: use the pin name "LED" (or "WL_GPIO0"; MCS_RP2_WL_PIN(n) in C). In
your own firmware call mcs_net_set(mcs_rp2_net()) — mcs_port_rp2_hal_init does it when
MCS_RP2_WIFI is 1. Options: MCS_RP2_HOSTNAME, MCS_RP2_WIFI_RETRIES,
MCS_RP2_RECONNECT_MS, MCS_RP2_DHCPS_LEASES; lwIP settings in ports/rp2/wifi/lwipopts.h
(MICROCS_LWIPOPTS_DIR to use your own).
config/mcs_user_config.h → Wi-Fi and networking:
| Option | Default | |
|---|---|---|
MCS_ENABLE_NET |
1 on ESP-IDF / Linux / macOS |
Net, Wifi, EspNow, Ntp, Mdns, Ping
|
MCS_ENABLE_SOCKETS |
= MCS_ENABLE_NET
|
TcpClient, TcpListener, UdpClient, Dns, Http
|
MCS_NET_SIM |
1 on the PC | the simulated radio (--sim) |
MCS_NET_TIMEOUT |
10000 | default socket / DNS / HTTP timeout (ms) |
MCS_WIFI_CONNECT_TIMEOUT |
15000 |
Wifi.Connect wait (ms) |
MCS_WIFI_SCAN_MAX |
24 | networks returned by Scan
|
MCS_NET_MAX_READ |
4096 | largest single read / datagram |
MCS_HTTP_MAX_BODY |
32768 | largest body kept in memory |
MCS_HTTP_MAX_HEADER |
2048 | response header bytes kept |
MCS_HTTP_MAX_REDIRECTS |
5 | redirects followed |
MCS_HTTP_SERVER_MAX_ROUTES |
16 | routes per HttpServer
|
MCS_HTTP_SERVER_TIMEOUT |
3000 | time a client has to send its request (ms) |
MCS_WS_MAX_MESSAGE |
8192 | largest WebSocket message received |
MCS_WS_MAX_CLIENTS |
4 | WebSocket clients per HttpServer
|
MCS_WIFI_CREDS_FILE |
"/.wifi" |
Wifi.Save file on ports without their own store |
MCS_ESPNOW_MAX_PEERS |
20 | peers tracked |
MCS_NET_SLICE_MS |
20 | how often blocking waits check for Stop |
MCS_ESP32_WIFI_RETRIES |
2 | extra attempts before Connect fails |
MCS_ESP32_ESPNOW_QUEUE |
8 | received ESP-NOW packets buffered |
MCS_ENABLE_JSON |
1 |
Json, JsonSerializer
|
./mcs --sim script.cs (and Studio's simulator) adds a fake radio so Wi-Fi scripts run on
the PC: networks MicroCS-Lab (WPA2, password microcs123), CoffeeShop (open),
Neighbor (WPA3, letmein99) and a hidden HiddenLab; DHCP address 192.168.1.50,
gateway 192.168.1.1; a phone that joins your soft AP; an ESP-NOW peer
24:0A:C4:00:00:99 that echoes packets; printer.local = 192.168.1.20; ping answers;
Wifi.Wps / Wifi.SmartConfig join MicroCS-Lab after ~1 s. Sockets, HTTP and the
servers use the PC's real network (ports below 1024 move to port + 8000). tests/t24_net.cs and tests/test_net_server.py exercise all of it.
A port fills a mcs_net_t table (include/mcs_net.h) — any NULL entry simply makes
that feature unavailable — and calls mcs_net_set(&table) before the HAL library opens.
Events from driver callbacks go through mcs_net_post(MCS_NET_EV_*, value) (ISR / task
safe). Sockets use BSD / lwIP sockets directly, so a port with lwIP gets TCP, UDP, DNS and
HTTP for free — and with them HttpServer, WebSocket and DnsServer; HTTPS / wss://
need the four tls_* functions, WPS / SmartConfig the provision entry (API version 2).
ports/esp32/mcs_port_esp32_net.c is the reference implementation,
ports/rp2/mcs_port_rp2_net.c an lwIP one without vendor extras and
modules/net/mcs_net_sim.c the smallest one.
- IPv4 sockets (IPv6 addresses are reported, not yet usable in sockets).
- No server-side TLS (
TcpListener,HttpServerare plain TCP); HTTP client and server are HTTP/1.1 without keep-alive (one connection per request), the server handles one request at a time. - Wi-Fi Easy Connect (DPP), WPS PIN mode, FTM and NAN are not exposed.
- Wi-Fi is not available on chips without a radio (ESP32-H2, ESP32-P4 without a companion chip), on the Arduino, STM32 and Zephyr ports yet; on the Pico W see Pico W setup for what is missing.
Generated from the MicroCS repository - edit the files there, not in the wiki.
Start here
Step-by-step guides
- Guides
- Guide PC Simulator
- Guide ESP32
- Guide ESP32 C3 OLED
- Guide Raspberry Pi Pico
- Guide STM32
- Guide Arduino
- Guide Zephyr
- Guide Peripheral Settings
- Guide DMA
Hardware
- Hardware API
- Peripheral Configuration
- DMA
- Device Drivers
- Wi Fi and Networking
- U8g2 Displays
- Filesystem
- Threads
- Scheduler
Ports
- Porting and Build Systems
- Port ESP32
- Port RP2040 RP2350
- Port STM32
- Port STM32 Firmware
- Port Zephyr
- Port Arduino
C# and the runtime
Embedding and tuning
Internals
Project