Self-Hosted HTTP Mode
HTTP mode is the backend mode for operators who want UPS data on their own server — no cloud account, no third-party services. The UPS POSTs signed telemetry JSON to an endpoint you host, and your server queues commands in return.
How It Works
The UPS is an outbound-only HTTP client, so it works behind cellular carrier NAT — no public IP, port forwarding, or VPN needed. Every ~30 s it POSTs telemetry to {base}/api/v1/devices/{device_id}/telemetry ({device_id} defaults to the SIM's ICCID); commands ride back in the POST response, so command latency is up to ~30 s. Both directions are HMAC-SHA256 signed, so nothing can be forged even over plain http:// — TLS is optional and adds confidentiality only.
UPS ── POST /api/v1/devices/<id>/telemetry ──────────────▶ your server
{"ts":1780047458,"fw_ver":"…","uptime_s":137,
"power":{…},"host":{…},"net":{…},"acks":[]}
UPS ◀───────────────────────────── 200 OK (signed) ── your server
{"commands":[{"id":"c-0043","cmd":"ui.beep","args":{}}]}
~30 s later, next POST:
{"ts":…, …, "acks":["c-0043"]}
An id in acks confirms that the UPS took the command onto its internal bus, not that it was executed — see Available Commands for how to tell.
This page is a quick-start — see the full HTTP mode specification for signature headers, body schema, and replay protection.
What You Need
| Requirement | Notes |
|---|---|
| UPS with LTE-M module | HTTP mode runs over the cellular link |
| A small Linux server with a public IP | Any VPS or always-on host works — Step 1 walks you through a 5-minute setup |
| The device's HTTP key | Read it off the OLED (below) — this is the shared HMAC secret |
| Brief access to the Raspberry Pi | To send the endpoint URL to the UPS over the USB serial link |
The HTTP Key
The device generates its own HTTP-mode secret and shows it on the OLED: open the menu (hold LEFT for 2 s on the Home screen), then Network → HTTP Key. It is a 16-character code (Crockford base32, ~80 bits); case and separators don't matter when typing it in. New key re-rolls it (after a confirmation screen) — the old key stops working at once, no reboot needed. The key survives reboots and mode switches; only a factory reset clears it. See Display & Menu.
Setup
Four steps in three places: your server (steps 1–2), the Raspberry Pi (step 3), and the UPS OLED menu (step 4).
Step 1 — Get a Server
Any always-on Linux machine the UPS can reach over the internet works: a VPS you already rent, or a home server with a public IP. If you have one, skip to Step 2.
If you don't, a minimal VPS is set up in about five minutes and the smallest tier is more than enough — the reference server is a single zero-dependency Python script. For example, on DigitalOcean:
- Sign up, then choose Create → Droplets.
- Pick a region near you, the Ubuntu 24.04 (LTS) image, and the cheapest plan (from a few dollars a month, billed per second up to the monthly cap — destroy the droplet anytime and pay only for the time it existed).
- Add your SSH key (or choose a root password) and click Create Droplet. After about a minute, note the droplet's public IPv4 address — that address goes into the device URL in step 3.
Any other provider (Hetzner, AWS Lightsail, OVH, …) works the same way — just make sure the plan includes a public IPv4 address (the very cheapest tiers at some providers are IPv6-only, which the UPS's cellular link cannot reach).
Firewall — the port must be open
The UPS connects to your server on the port you choose below (8080 in the examples). A fresh DigitalOcean droplet accepts it out of the box, but:
- if the host firewall is active (
sudo ufw statussaysactive), allow the port:sudo ufw allow 8080/tcp - if your provider has a cloud firewall / security group attached to the server (DigitalOcean Cloud Firewalls, AWS security groups), add an inbound rule for TCP 8080 there too.
Step 2 — Start the Server
First read your two values off the hardware:
- HTTP key — on the UPS OLED: menu → Network → HTTP Key (above), e.g.
ABCD EFGH JKMN PQRS. - ICCID — the SIM's serial number, printed on the SIM card/tray. This is the device ID.
Then SSH into your server and download the reference server:
curl -fsSLO https://raw.githubusercontent.com/Web3-Pi/Web3-Pi-UPS/main/examples/http-control-server/server.py
It comes from
examples/http-control-server
in the public UPS repository — a single script with no dependencies beyond Python 3, which Ubuntu ships with.
Now start it, substituting your HTTP key (quotes matter, spaces inside don't) and ICCID:
You should see:
listening on http://0.0.0.0:8080
endpoint POST /api/v1/devices/<ICCID>/telemetry
device_id <ICCID>
type 'help' for the command menu; Ctrl-C / Ctrl-D to quit
Keep it running
The server runs in the foreground — it prints telemetry and takes commands from your keyboard — so it stops when your SSH session drops. Run it inside tmux (sudo apt install -y tmux, then tmux; detach with Ctrl-B then D, tmux attach brings it back) or screen (detach with Ctrl-A then D, screen -r brings it back).
If your server stops answering while the internet is reachable (server down, wrong URL, wrong key), the UPS keeps its cellular link up and simply retries every 30 s; after about 30 minutes without a successful POST it shows ! MODEM / NO UPLINK / no uplink on the OLED and beeps. This is harmless — power to the Pi is unaffected — and it clears itself as soon as the server answers again. Only when the internet itself is unreachable (no answer from the server and from public DNS for about 6–7 minutes after the last successful POST) does the UPS re-establish the cellular link.
Step 3 — Point the UPS at Your Server
The endpoint URL is sent to the UPS over the USB serial link from the Raspberry Pi it powers — it is stored on the device, no re-flash needed. On the Raspberry Pi:
curl -fsSLO https://raw.githubusercontent.com/Web3-Pi/Web3-Pi-UPS/main/examples/http-control-server/send_config.py
sudo apt install -y python3-serial
sudo systemctl stop w3p-ups # frees the UPS serial port
python3 send_config.py --url http://<your-server-ip>:8080
sudo systemctl start w3p-ups
The serial port is auto-detected, and success looks like:
auto-detected port: /dev/ttyACM0 (Web3_Pi_UPS …)
sent net.config HTTP_URL = "http://<your-server-ip>:8080" (… bytes) to /dev/ttyACM0
device confirmed net.config: OK
no confirmation seen … instead of that last line usually still means the URL was applied (the reply can get lost amid telemetry) — carry on and confirm in Verify below. A permission error opening the port means your user isn't in the dialout group — re-run with sudo. And if the host service isn't installed, the systemctl lines report Unit w3p-ups.service not loaded — that's fine, the port is already free; skip them.
The URL is stored until factory reset. Optional, as a separate run (the two flags can't be combined): send_config.py --device-id <id> overrides the ICCID default; an empty string (--url "" / --device-id "") clears the respective setting.
Step 4 — Switch the UPS to HTTP Mode
On the UPS OLED: menu → Network → Mode → HTTP. The setting is saved and the LTE-M module reboots (~30 s telemetry gap; power to the Pi is unaffected).
Verify
Within ~30 s of the mode switch, the first telemetry POST shows up in the server terminal. Then test the command path end-to-end: type beep at the server prompt — the UPS picks it up on its next POST and sounds the buzzer (up to ~30 s later).
If nothing arrives after a couple of minutes, check in order:
- Cellular link — was the device reporting before the switch (e.g. online in the web panel in MQTT mode)? If it has never been online in any mode, the problem is cellular coverage, not your server.
- Server — still running, and the port open (firewall — Step 1).
- URL sent in step 3 — right IP, right port,
http://prefix, no trailing slash, and the final URL only — the UPS does not follow redirects (firmware esp32:0.8.10 or newer: a3xxis logged and nothing is applied; older firmware followed redirects instead). - Key and device ID — a wrong
--secretis logged by the server as! rejected: bad signature; a wrong--device-idis silent (the server answers 404 without printing anything), so re-check the ICCID digit-for-digit.
No hardware yet?
test_client.py from the same
examples directory
simulates a correctly-signed device POST, so you can smoke-test the server before touching the UPS.
Adding TLS
Put any reverse proxy (Caddy, nginx) with a certificate in front of the server and re-run send_config.py with the https:// URL. Use a publicly-trusted certificate (Caddy obtains a Let's Encrypt one automatically — this needs a domain name); self-signed certificates are rejected by the device.
Data usage on the bundled SIM
The SIM shipped with the UPS is a prepaid 1NCE pool of 500 MB or 10 years, whichever comes first — a lifetime allowance, not a monthly plan. Measured on the bench: plain http:// at the default 30 s poll uses ≈ 1.6 KB per poll ≈ 4.7 MB/day, so the pool lasts ≈ 107 days. https:// costs several times more — every poll opens a fresh TLS connection with a full handshake — estimated at ≈ 16–26 MB/day, i.e. 3–4 weeks per pool. Both directions are HMAC-signed, so plain http:// is already safe against forgery: prefer it on the bundled SIM, and use https:// with your own SIM or data plan (top-ups of the bundled SIM are not self-service yet). The poll period is fixed in the current firmware. Measurement method and how to read your own usage from net.bytes_tx / net.bytes_rx: spec §9 Data budget.
Available Commands
These five commands are the complete set the HTTP backend executes (same semantics as the web panel commands; anything else is refused and reported back in the next POST's rejected list — firmware esp32:0.8.10 or newer; older firmware ignores unknown commands silently):
| Command | Effect |
|---|---|
ui.beep |
Sound the buzzer — a quick end-to-end test |
ui.display_msg |
Show a short text message on the OLED (40 characters visible; shown as a plain notice on rp2040 1.2.2 or newer — older RP2040 firmware displayed it as a MODEM alarm) |
host.shutdown |
Graceful Raspberry Pi shutdown (the delay argument is currently ignored — immediate) |
host.reset |
Reboot the Raspberry Pi (the delay argument is currently ignored — immediate) |
power.cycle |
Power-cycle the OUT port (fixed 1.5 s off time in the current CH32X firmware) |
What an ack means
An id in acks means the UPS accepted the command and handed it to its internal bus (to the display controller, the Raspberry Pi host service or the power controller) — it does not confirm that the command was executed. Execution is visible out-of-band: ui.beep is audible; ui.display_msg appears on the OLED; after host.shutdown the host object drops out of telemetry within about two minutes; after host.reset host.uptime_s restarts from a small value in a later report (the host object may or may not drop out in between — a Pi that is back within 90 s never disappears); power.cycle shows as the VBUS_OUT_EN bit of power.flags clearing / vbus_out_mv dipping, but a 1.5 s dip is usually missed by the 30 s poll — the Pi rebooting (host.uptime_s restarting) is the reliable sign. Details: Verifying that a command took effect in the specification.
Keep responses small
The UPS applies at most 8 commands per poll and drops a response body larger than 2047 bytes as a whole — nothing in it is applied, and the next POST tells the server so in a resp_dropped field. Commands past the eighth are neither applied nor acked, so a longer queue simply drains 8 per poll. This is firmware esp32:0.8.10 or newer (check fw_ver in the POST); older firmware has no such guard — an over-size response is silently dropped and never acked, so keep responses small on every version. The reference server enforces both limits by default (--max-commands 8, response cap 2047 B); if you write your own server, see Receiver limits (device side) in the specification.
No firmware updates in HTTP mode
Remote firmware updates are delivered over MQTT (or Arkiv) only. To update a device parked in HTTP mode, switch it back to MQTT from the OLED menu for the update, then switch back — or flash it locally over USB with the Workbench.
Security Notes
- Guard the HTTP key. If it leaks, re-roll it on the OLED (HTTP Key → New key) and update your server.
- Keep the server clock NTP-synced. The device timestamps requests from network time; a drifting server clock rejects valid requests as stale.
- The reference server is an example, not hardened infrastructure: no operator authentication, persistence, or rate limiting.
Web panel in HTTP mode
HTTP-mode devices still appear on the web panel's HTTP page, but your server is the backend — the panel cannot configure the endpoint.