The radio is simulated. There is no 802.11 anywhere in Mokxi and nothing a sketch does can reach the network your browser is on. What is faithful is the Arduino code you write, which is the whole point, because it is the code you would write for a real board.
Every board with a radio on the real hardware has it: the ESP32 family (the
ESP32-C3, ESP32-C6, classic ESP32 and ESP32-S3, and the boards
built on them, such as the XIAO and the Nano ESP32), the ESP8266 boards,
the Raspberry Pi Pico W and the Uno R4 WiFi. On every other board here,
including a plain Raspberry Pi Pico, which has no wireless part on it,
#include <WiFi.h> stops with a sentence saying so rather than writing to an
address its chip does not have. That is on purpose: a stub that answered "not
connected" would be a lie with a plausible shape, and a compile error is not.
What is real and what is not
Real, in the sense that it behaves the way your board will:
- the calls (
WiFi.begin(),WiFi.status(),WiFi.localIP(),HTTPClient,WebServer) with the signatures a tutorial gives you; - the status codes, which are Arduino's own enum, value for value;
- the timing: joining takes between one and three seconds, and a wrong password fails after five, which is where a real board on a quiet network lands;
- the cost: a fetch really takes 60 ms of the sketch's time, and a sketch that
forgets
server.handleClient()really never answers.
Not real, and never pretended to be:
- no radio. No channel, no scan, no
scanNetworks(), no beacon, no WPA2, no encryption of any kind. The password is compared as a string. - no real internet unless you switch it on. No TCP, no DNS, no sockets,
nothing that leaves the browser tab. Four made-up names under
.mokxiexist and no others, until the Cloud panel's Real internet switch is on: then a request for any other name goes out through Mokxi's gateway. - no TLS on the board.
WiFiClientSecurecompiles and then fails, saying so. Anhttps://URL fails the same way, unless Real internet is on: then the gateway does the TLS for anHTTPClientrequest. A TLS on the board that trusted everything would teach you that https is free. - no second machine. One board, one network, one address. Nothing outside this browser tab can reach the server your sketch runs.
RSSI()is a setting, not a measurement. Moving the board on the canvas changes nothing.- NTP gives the simulation's clock, not the real time. There is no UDP and
no NTP server, and the simulation does not know today's date, so
configTime()and NTPClient'supdate()set the clock to 2026-01-01 00:00:00 UTC at power-up plus the seconds since, and the serial monitor says so once. - Bluetooth is its own simulation. The ESP32 boards' Bluetooth serial and BLE talk to a phone in the Bluetooth pane (Bluetooth, simulated). The Pico W's Bluetooth is not simulated.
Setting up the network
Click the board and look at the properties panel: wifiSsid, wifiPassword and wifiRssi. Those three are the one access point this board can see. With no radio there is nothing to scan, so the network is a property of the board rather than a part you place beside it.
Match them in your sketch and it joins. Get either one wrong on purpose and watch what happens. That is the more useful lesson of the two.
The Cloud panel edits the same three while the sketch is running, so you can pull the network out from under a running board and see the connect loop start again.
Joining
#include <WiFi.h>
void setup() {
Serial.begin(115200);
WiFi.begin("Mokxi-Lab", "breadboard");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(250);
}
Serial.println(WiFi.localIP()); // 192.168.4.2
}
WiFi.status() walks WL_IDLE_STATUS, then WL_DISCONNECTED while it
associates, then WL_CONNECTED. There is no WL_CONNECTING in Arduino, which
is why the loop above is written the way every tutorial writes it.
A wrong password becomes WL_CONNECT_FAILED after five seconds. A name that is
not the one on the board becomes WL_NO_SSID_AVAIL. The address is always
192.168.4.2, and it is the same address after a disconnect() and a second
begin().
Fetching
#include <HTTPClient.h>
WiFiClient client;
HTTPClient http;
http.begin(client, "http://weather.mokxi/");
int code = http.GET();
if (code > 0) Serial.println(http.getString());
else Serial.println(HTTPClient::errorToString(code));
http.end();
Four addresses exist on this simulated internet:
http://time.mokxi/: the simulation's clock, as an ISO string. It is the kernel's clock counted from a fixed midnight, not the time of day, because the simulation does not know what day it is.http://weather.mokxi/: JSON with a temperature and a humidity you set in the Cloud panel. Change one and the next fetch brings the new number back.http://echo.mokxi/: whatever youPOST, straight back. It is also the one WebSocket server here: the WebSockets library'sWebSocketsClientpointed atecho.mokxisends each message as one request to it and gets it back asWStype_TEXT. That is a simulated echo carried as HTTP, not a WebSocket on a wire; any other WebSocket host needs a real board.http://api.mokxi/: the status, content type and JSON body you set in the Cloud panel, so you can practice a success, an error or missing data.
Anything else does not resolve. http://example.com/ comes back as
HTTPC_ERROR_CONNECTION_REFUSED, and HTTPClient::errorToString() tells you
why in one line: no real internet: names under .mokxi only. That refusal is
readable from the sketch on purpose. It is a thing worth teaching, not a thing
to hide.
Serving
#include <WebServer.h>
WebServer server(80);
void setup() {
// ... join the network first ...
server.on("/", [](){ server.send(200, "text/html", "<h1>hello</h1>"); });
server.begin();
}
void loop() { server.handleClient(); }
on(path, handler), onNotFound(), send(code, type, body), arg(name),
hasArg(), uri() and method(). Your handlers are your code, running on the
board's core at the board's clock.
The Web panel (a tab beside Serial along the bottom) is the browser for
them. It has an address bar starting at http://192.168.4.2/, a Refresh, and
the page itself in a frame with scripts switched off. Links and buttons in
that page come back round into your sketch, which is what makes "switch the LED
from a web page" work end to end.
If the page never arrives, the panel says so, and after ten seconds the network
answers 504 itself: the usual reason is a loop() that has stopped calling
handleClient().
The Cloud panel
Every request, both ways: what the board asked the simulated internet for, and what the Web panel asked the board for, with the method, the address, the status, the bodies and, when something failed, a line saying why. It is the network tab for a board.
It also holds the two weather.mokxi dials, the three access-point settings
and the API lab, where you choose what api.mokxi answers. A lesson can turn
one and watch the sketch notice.
A Blynk sketch gets a Blynk section there too, with its virtual pins and a box to send each one a value: see Blynk, simulated.
On the Pico W
The Pico W is a Pico with a wireless chip, and its WiFi code is the same
WiFi.h as above. Two things differ from a plain Pico, as on the real board:
LED_BUILTINis the on-board LED, and GP25 is not. The LED sits on the wireless chip, sodigitalWrite(LED_BUILTIN, HIGH)lights it. GP25 drives the wireless chip's select line, so a sketch that writes GP25 lights nothing, here and on a desk.- The LED is on or off only.
analogWrite(LED_BUILTIN, ...)does nothing, so fade an LED on an ordinary pin instead.
MicroPython and CircuitPython run on the Pico W as well, with its CYW43439
modeled from its pins: network.WLAN in MicroPython and wifi.radio in
CircuitPython join the same simulated access point, with the board's WiFi
properties as its name and password. See the Pico W;
the full detail is in the Pico W model.
Wired Ethernet (ETH.h)
On the ESP32 boards, the core's wired Ethernet library builds and runs too, for sketches written for a WT32-ETH01, an Olimex ESP32-POE or a W5500 module. Pick an ESP32 DevKit for a WT32-ETH01 or ESP32-POE sketch: it is the same chip.
#include <ETH.h>
void onEvent(arduino_event_id_t event) {
if (event == ARDUINO_EVENT_ETH_GOT_IP) Serial.println(ETH.localIP());
}
void setup() {
Serial.begin(115200);
Network.onEvent(onEvent); // WiFi.onEvent(onEvent) on core 2.x
ETH.begin();
}
There is no cable and no Ethernet chip. ETH.begin() joins the same simulated
network WiFi does, with no network name or password, and about 1.5 seconds
later it has the address 192.168.4.2. From then on WiFiClient,
HTTPClient, WebServer and PubSubClient work over it exactly as they do
over WiFi, the Web panel reaches the sketch's server, and the Real internet
switch and its limits are the same.
- Both cores'
ETH.begin()calls build: core 2.x'sETH.begin(addr, power, mdc, mdio, type, clock), core 3.x'sETH.begin(ETH_PHY_LAN8720, addr, mdc, mdio, power, clock)and its W5500 form,ETH.begin(ETH_PHY_W5500, addr, cs, irq, rst, SPI). - The events arrive in the core's order:
ARDUINO_EVENT_ETH_START,ARDUINO_EVENT_ETH_CONNECTED,ARDUINO_EVENT_ETH_GOT_IP, and afterETH.end(),ARDUINO_EVENT_ETH_DISCONNECTEDandARDUINO_EVENT_ETH_STOP. ETH.linkUp(),ETH.linkSpeed()(100) andETH.fullDuplex()(true) read as a board plugged into a switch. The cable is always plugged in.ETH.config(ip, gateway, mask, dns)sets a static address, andETH.localIP()reports it. The Web panel still reaches the board at192.168.4.2.- The board has one link to the network.
WiFi.begin()afterETH.begin()moves it to WiFi, andWiFi.status()reads that one link either way. - Only the classic ESP32 has the Ethernet controller a LAN8720 needs. On an ESP32-S3, C3 or C6 a real board uses a W5500 over SPI; a LAN8720 sketch still runs here, and the serial monitor says so once.
Real internet
The Real internet switch at the top of the Cloud panel works for anyone
signed in: Free sends 100 requests a day, and Pro has more. Off, which is how
every session starts, nothing above changes. On, a request for a name outside
.mokxi (https:// included) goes out to the real internet through Mokxi's
gateway, and the answer comes back to your sketch; the .mokxi names stay
simulated. A board on the Wokwi-GUEST access point, as a Wokwi import is,
turns it on by itself when it runs. Signed out, the switch says to sign in
free, and the board keeps the simulated network.
A Telegram bot (UniversalTelegramBot.h) works this way too, and only this
way. Real internet has the details and the limits.
Try these
- WiFi: join the network: the connect loop, the address and the signal.
- WiFi: fetch the weather:
HTTPClientagainstweather.mokxi, and a deliberate request toexample.comso you read the refusal yourself. - WiFi: a lamp on a web page: two buttons and a form that switch the LED on GPIO 8.
- API lab: fetch JSON: a normal
HTTPClientGET toapi.mokxi. Change the answer in the Cloud panel and run the request again.
The first three ship for the ESP32-C3, ESP32-C6, ESP32 DevKit, ESP32-S3, ESP8266 and Pico W, and the Uno R4 WiFi has its own join example. They all compile in the browser, exactly like every other example.
The whole contract, register by register and limit by limit, is in Simulated WiFi.