Network (Net::HTTP / WebSocket / TLS)¶
New in 2.0. Apps can talk to the internet with the API you already know from CRuby:
Net::HTTP, WebSocket, and TLS underneath both.
Available on both machines — Modern reaches the network through the ESP32-C6, Retro through the ESP32-S3's own radio — and in the Linux simulator, which uses the host's network and OpenSSL.
Before you start¶
The device needs Wi-Fi credentials in /etc/wifi.toml. See
Connecting to Wi-Fi for how to write that file on the device.
Retro runs one radio at a time
The ESP32-S3 has a single radio, so Wi-Fi and BLE cannot both be up. If the BLE web
console is running, Wi-Fi will not start; set ble_auto_start to false in Config
and reboot. On Modern the ESP32-C6 handles both at once and there is no conflict.
Checking the connection¶
if FmrbApp.wifi_connected?
info = FmrbApp.wifi_info
# => { connected: true, ip: "192.168.10.16", ssid: "my-ap", hostname: "fmruby" }
Log.info("address: #{info[:ip]}")
end
| Method | Returns |
|---|---|
FmrbApp.wifi_connected? |
true / false. Allocates nothing, so it is safe to call in a loop |
FmrbApp.wifi_info |
A Hash with :connected, :ip, :ssid, :hostname, or nil on a machine with no Wi-Fi |
An app that needs the network should wait for it rather than assume it, because Wi-Fi comes up after the desktop does:
def on_create
@ready = false
end
def on_update
unless @ready
return unless FmrbApp.wifi_connected?
@ready = true
fetch_data
end
end
HTTP¶
require 'net/http'
require 'json'
# One-shot GET
body = Net::HTTP.get(URI.parse("http://example.com/api/status"))
# With the response object
res = Net::HTTP.get_response(URI.parse("https://example.com/data.json"))
if res.code == "200"
data = ::JSON.parse(res.body)
end
# Form POST
res = Net::HTTP.post_form(URI.parse("https://example.com/post"), { "key" => "value" })
# Reusing a session
http = Net::HTTP.new("example.com", 443)
http.use_ssl = true
http.start do |h|
res = h.get("/index.html")
res = h.post("/api", '{"a":1}', { "Content-Type" => "application/json" })
end
HTTPS works out of the box: certificates are verified against the bundled CA set, so a
plain https:// URL needs no extra setup.
Write ::JSON, not JSON
Inside a class body, a bare constant like JSON is looked up as
YourClass::JSON and fails. Prefix it with ::.
Fetching without stopping (FmrbNet.request)¶
New in 2.1. Net::HTTP waits for the server, which is fine for a tool that only ever runs
on a board. An app that should also work in the browser asks this way instead: the request
is made once, and its state is read from on_update.
def on_create
@req = FmrbNet.request("https://example.org/thing.json")
end
def on_update
if @req && @req.done?
@req.ok? ? use(@req.body) : Log.warn(@req.error)
@req = nil
end
50
end
FmrbNet.request(url) |
Starts one fetch and returns a request object |
done? |
Whether the answer has arrived. Ask it from on_update, do not loop on it |
ok? |
True when it arrived and the status says so |
status / body / error |
The HTTP status, the body, and why it failed |
It is deliberately not a callback: a block stored now and called later is the shape that breaks on the Spinel engine when it captures a local variable.
What differs between the machines is where the waiting happens, not what the app writes.
On a board the fetch happens inside request, so that app's own task waits there and
done? is true immediately — the rest of the machine keeps running, because those tasks
are preemptive. In the browser nothing waits: the page fetches while the machine carries on
drawing, which is why an app that blocks cannot be written there at all.
In the browser the page's rules apply
The fetch is the browser's, so a server that does not allow cross-origin requests
cannot be read from Studio, and Net::HTTP and WebSocket — sockets — are not there at
all. The App Store is written this way, which is why it works on both boards and in a
browser tab.
WebSocket¶
require 'net/websocket'
Net::WebSocket::Client.connect("wss://echo.example.com/ws") do |ws|
ws.send_text("hello")
msg = ws.receive(timeout: 5)
ws.close
end
receive polls rather than blocking outright, so it will not freeze the VM for the whole
timeout.
Differences from CRuby¶
The API is shaped like CRuby's, but this is a microcontroller. The limits that actually bite:
| TLS version | TLS 1.2 only. There is no TLS 1.3 |
| Blocking | The calls are synchronous. While a request is in flight, that app processes no events — its window will not repaint and its input is queued. Keep requests short, and set timeouts |
| Response size | The whole body lands in your app's memory pool (512 KB or 1 MB depending on the slot). A large download will exhaust it |
| HTTP features | Chunked transfer, redirects and keep-alive are handled to the extent picoruby's net/http implements them, not to the extent CRuby does |
| Certificates | Custom CA or client certificates are passed as PEM strings, not file paths |
Because a request stalls the app that makes it, the usual shape is: fetch once at startup or on a timer, keep the result, and draw from the copy.
A worked example¶
The bundled Weather app (/app/demo/weather.app.rb) fetches a forecast over HTTPS from
Open-Meteo, parses the JSON and draws it. It waits for the network, retries until the first
success, and is a good starting point to copy.
The Net Test app (/app/test/net_test.app.rb) exercises the API piece by piece and is
useful when something does not work. It is hidden from the launcher with the rest of
/app/test, so start it from the editor or the shell.
Related¶
- Connecting to Wi-Fi
- Remote Desktop — the other thing Wi-Fi gets you
- Constants & System Info —
FmrbConst::HAS_WIFI