Official page: frank.rh1.tech — hub for all FRANK boards and firmware.
AT modem firmware for ESP-01 (ESP8266). The host MCU (e.g. RP2350 on FRANK) sends AT commands over UART to manage WiFi and TCP/TLS/UDP sockets.
- AT commands over UART (115200 baud default, up to 3M)
- WiFi scanning, connect/disconnect, auto-reconnect
- 4 concurrent sockets -- TCP, TLS/SSL (BearSSL), UDP
- DNS resolution
- Binary data framing with async receive events
- Fits on ESP-01 (1MB flash, ~50KB free RAM)
- ESP-01 module (ESP8266, 1MB flash)
- USB-serial adapter for flashing and testing (3.3V FTDI or similar)
- Host MCU with UART (e.g. RP2350)
| ESP-01 Pin | Host MCU |
|---|---|
| TX | UART RX |
| RX | UART TX |
| VCC | 3.3V |
| GND | GND |
| CH_PD (EN) | 3.3V |
No hardware flow control -- the ESP-01 does not expose CTS/RTS.
- PlatformIO (CLI or IDE)
./build.shOr directly:
cd esp01-firmware
pio run./flash.shEnvironment variables:
FLASH_PORT-- serial port (auto-detected if not set)FLASH_BAUD-- flash baud rate (default 115200)
# Basic tests (no WiFi needed)
./test.sh
# With WiFi
./test.sh --wifi MyNetwork,mypassword
# Full test (includes TLS + UDP)
./test.sh --wifi MyNetwork,mypassword --full
# Custom TLS host
./test.sh --wifi MyNetwork,mypassword --full --tls-host rh1.tech:443UART at 115200 baud, 8N1 (default).
| Term | Meaning |
|---|---|
\r\n |
Carriage return + line feed. Every command and response line ends with this. |
> |
Prompt. The modem is waiting for raw binary bytes (see Data Send Flow). |
OK |
Command succeeded. |
ERROR:<reason> |
Command failed. Reason is a short machine-readable token. |
+TAG:... |
Prefixed response data or async event. |
- Commands are ASCII, terminated by
\r\n(bare\ror\nalso accepted). - Commands are case-insensitive (
at+wstat=AT+WSTAT). - The modem does not echo input.
- Unsolicited (async) events may arrive at any time while in command mode.
On power-up or reset, the modem emits:
+READY\r\n
The host must wait for +READY before sending commands.
Test the connection.
-> AT
<- OK
Reset the module. Responds OK, then reboots. Wait for +READY.
-> AT+RST
<- OK
... module reboots ...
<- +READY
Query firmware version.
-> AT+VER
<- +VER:frank-netcard,1.0.0
<- OK
Query free heap memory (bytes). Useful before opening TLS connections.
-> AT+HEAP
<- +HEAP:38240
<- OK
Change UART baud rate. Takes effect immediately after OK.
Valid range: 9600 -- 3000000.
-> AT+BAUD=230400
<- OK
... host must now switch to 230400 ...
Scan for nearby access points. Blocks for a few seconds.
-> AT+WSCAN
<- +WSCAN:MyNetwork,-42,4,6
<- +WSCAN:OtherAP,-71,7,1
<- OK
Response fields: ssid,rssi_dBm,encryption_type,channel
Encryption types (ESP8266 constants):
| Value | Meaning |
|---|---|
| 2 | WPA (TKIP) |
| 4 | WPA2 (CCMP) |
| 5 | WEP |
| 7 | Open |
| 8 | WPA/WPA2 Auto |
Connect to a WiFi network. Blocks up to 15 seconds.
-> AT+WJOIN=MyNetwork,mypassword123
<- +WJOIN:192.168.1.42,255.255.255.0,192.168.1.1
<- OK
Response fields: ip,subnet_mask,gateway
On failure:
<- ERROR:CONNECT_FAILED
Disconnect from WiFi. Also closes all open sockets.
-> AT+WQUIT
<- OK
Query current WiFi status.
When connected:
-> AT+WSTAT
<- +WSTAT:CONNECTED,MyNetwork,192.168.1.42,255.255.255.0,192.168.1.1,192.168.1.1,-42
<- OK
Response fields: status,ssid,ip,subnet_mask,gateway,dns_server,rssi_dBm
When disconnected:
-> AT+WSTAT
<- +WSTAT:DISCONNECTED
<- OK
Resolve a hostname via DNS. Requires active WiFi connection.
-> AT+RESOLVE=example.com
<- +RESOLVE:93.184.216.34
<- OK
On failure:
<- ERROR:DNS_FAILED
Up to 4 concurrent sockets (IDs 0-3). TCP, TLS, and UDP.
Open a socket connection.
id: 0-3type:TCP,TLS(orSSL),UDPhost: hostname or IP address (DNS resolution is automatic)port: 1-65535
-> AT+SOPEN=0,TCP,example.com,80
<- OK
-> AT+SOPEN=1,TLS,example.com,443
<- OK
-> AT+SOPEN=2,UDP,8.8.8.8,53
<- OK
Possible errors:
| Error | Meaning |
|---|---|
INVALID_ID |
ID not in 0-3 |
ALREADY_OPEN |
Socket ID already in use |
NO_WIFI |
Not connected to WiFi |
INVALID_PORT |
Port out of range |
LOW_MEMORY |
Not enough heap for TLS (need ~25KB free) |
DNS_FAILED |
Could not resolve hostname (TCP and TLS only) |
CONNECT_FAILED |
TCP connection or TLS handshake failed |
CONNECT_FAILED:ssl=N |
TLS handshake failed; N is the BearSSL error code |
BIND_FAILED |
Could not bind UDP local port |
INVALID_TYPE |
Unknown socket type |
TLS notes:
- Certificate verification is disabled (ESP-01 has too little RAM for a CA store).
- TLS uses BearSSL. One connection needs ~20-40KB of heap.
- In practice you get 1-2 concurrent TLS connections on an ESP-01.
Send binary data over a socket. Max length: 1024 bytes.
Flow:
-> AT+SSEND=0,13
<- >
-> Hello, World!
<- SEND OK
Step by step:
- Host sends
AT+SSEND=0,13\r\n - Modem responds
>\r\n-- it is now in binary receive mode - Host sends exactly 13 raw bytes (no
\r\nterminator needed) - Modem sends data over the socket, responds
SEND OK\r\norSEND FAIL\r\n
Watch out:
- After the
>prompt, send exactlylengthraw bytes. No framing. - Async events (+SRECV) are paused while in binary mode.
- 10-second timeout -- if no data arrives, the modem responds
ERROR:SEND_TIMEOUTand returns to command mode. - For payloads larger than 1024 bytes, split across multiple AT+SSEND commands.
Close a socket.
-> AT+SCLOSE=0
<- OK
Query status of all open sockets.
-> AT+SSTAT
<- +SSTAT:0,TCP,CONNECTED,example.com,80
<- +SSTAT:1,TLS,CONNECTED,example.com,443
<- OK
Response fields: id,type,state,remote_host,remote_port
If no sockets are open, only OK is returned.
These can arrive at any time while in command mode. The host parser must handle them interleaved with command responses.
Incoming socket data. After the \r\n that ends the header line,
exactly length raw bytes follow. No trailing \r\n after the data.
<- +SRECV:0,45\r\n<exactly 45 bytes of raw data>
Parsing on the host side:
- Read a line (up to
\r\n) - If line starts with
+SRECV:, extractidandlength - Read exactly
lengthbytes -- these are raw payload (may contain\r\n) - Return to line-reading mode
Data arrives in chunks of up to 1024 bytes. A large response (e.g. HTTP body) will produce multiple +SRECV events.
The remote end closed a TCP/TLS connection (or WiFi dropped). All buffered data has already been delivered via +SRECV before this event fires.
<- +SCLOSED:0
WiFi connection lost. All sockets are automatically closed. Each open socket also gets a +SCLOSED event.
<- +WDISCONN
<- +SCLOSED:0
<- +SCLOSED:1
WiFi reconnected (auto-reconnect). Sockets are NOT restored -- they must be reopened by the host.
<- +WCONN:192.168.1.42
-> AT
<- OK
-> AT+WJOIN=MyWiFi,password123
<- +WJOIN:192.168.1.42,255.255.255.0,192.168.1.1
<- OK
-> AT+SOPEN=0,TCP,example.com,80
<- OK
-> AT+SSEND=0,37
<- >
-> GET / HTTP/1.0\r\nHost: example.com\r\n\r\n
<- SEND OK
<- +SRECV:0,512
<- <512 bytes: HTTP/1.0 200 OK...>
<- +SRECV:0,1024
<- <1024 bytes of HTML...>
<- +SRECV:0,89
<- <89 bytes: end of HTML>
<- +SCLOSED:0
-> AT+HEAP
<- +HEAP:42000
<- OK
-> AT+SOPEN=0,TLS,example.com,443
<- OK
-> AT+SSEND=0,39
<- >
-> GET / HTTP/1.0\r\nHost: example.com\r\n\r\n
<- SEND OK
<- +SRECV:0,1024
<- <response data...>
<- +SCLOSED:0
The host UART parser is a line-oriented state machine:
STATE: READLINE
|
Read chars until \r\n -> got a line
|
+-- starts with "+SRECV:" -> extract id, length -> STATE: READDATA(length)
+-- starts with "+SCLOSED:" / "+WDISCONN" / "+WCONN:" -> handle async event
+-- starts with ">" -> currently in SSEND flow, send the data bytes
+-- "OK" -> command complete (success)
+-- "ERROR:..." -> command complete (failure)
+-- "SEND OK" / "SEND FAIL" -> send complete
+-- starts with "+" -> response data line (e.g. +WSTAT:..., +WSCAN:...)
STATE: READDATA(remaining)
|
Read raw bytes, decrement remaining
|
When remaining == 0 -> deliver data to application -> STATE: READLINE
Only one command at a time. Send a command, wait for OK or ERROR before sending the next. Async events (+SRECV, +SCLOSED, +WDISCONN) can arrive while waiting -- buffer them or dispatch immediately in your read loop.
The ESP-01 does not expose CTS/RTS pins, so there is no hardware flow control. At 115200 baud this is not a problem. For higher throughput:
- Increase baud with AT+BAUD (up to 3M)
- Keep host UART RX reads fast (interrupt-driven, not polled)
- The modem pauses async events during AT+SSEND binary mode
When +WDISCONN fires:
- All sockets are already closed (you'll get +SCLOSED for each)
- Wait for +WCONN (auto-reconnect is enabled)
- Reopen any sockets you need
The ESP-01 has ~50KB free RAM at runtime. Rough costs:
- Each TCP socket: ~2-4KB
- Each TLS socket: ~20-40KB (BearSSL)
- Practical maximum: 4 TCP, or 1-2 TLS + 1-2 TCP
- Check AT+HEAP before opening TLS connections
GNU General Public License v3. See LICENSE for details.
Mikhail Matveev xtreme@rh1.tech