Skip to content

Device and connectivity

The sensors and radios: battery, clock, motion, LED, WiFi, Bluetooth, the USB connector and what is plugged into it, VPN and sleep.

Battery

Route Method What it does
/api/battery GET The averaged battery voltage in volts, null before the first sample.
{
  "voltage": 13.5
}

Sleep thresholds are settings, not routes: see Sleep.

Clock

All times are UTC; the device never applies a time zone.

Route Method What it does
/api/rtc GET time is the system clock, valid whether it is trusted this boot, rtc the clock chip's own reading (null when it is implausible, as on a fresh board), sntp the network time settings and the last successful sync.
/api/rtc POST {"epoch":1759900000} sets the system clock and the clock chip from a UTC epoch (2020 to 2099). The way to set the time from a browser on the access point, with no internet. Answers the GET payload.
/api/rtc/sync POST A network time sync now. Blocks up to about 30 s; 400 when offline, 500 when no server answered, else the GET payload.
{
  "time": "2026-10-09T04:06:57Z",
  "valid": true,
  "rtc": "2026-10-09T04:06:55Z",
  "sntp": {
    "enabled": true,
    "server": "pool.ntp.org",
    "last_sync": "2026-10-09T02:52:49Z"
  }
}

Network time syncs on every internet connection and every sync_interval_h after that; ntp_server and ntp_server2 are in the rtc_manager settings.

Motion sensor

Route Method What it does
/api/imu GET activity (stationary, active, unknown), acceleration in g on three axes (about 1 g on one axis at rest), die temperature. null fields when the chip cannot be read.
{
  "activity": "stationary",
  "accel": {
    "x": -0.176,
    "y": -0.217,
    "z": -0.955
  },
  "temp": 38.3
}

The motion bit in /api/status is the same information as a flag.

LED

The LED has a priority ladder: idle (the configured idle colour), status (activity), alert (yours), critical (the firmware's do-not-power-off indication during an update). A client can set and release the alert slot only; the firmware's own indications stay honest.

Route Method What it does
/api/led GET What the LED shows now: the winning priority, the mode (off, solid, blink_slow, blink_fast, breathe) and the colour.
/api/led PUT {"r":0,"g":120,"b":255,"mode":"breathe"} sets the alert indication. Colours 0 to 255, mode optional (solid by default). 400 on bad values.
/api/led DELETE Releases the alert indication; the LED falls back to status or idle.
{
  "priority": "idle",
  "mode": "solid",
  "r": 0,
  "g": 204,
  "b": 255
}

The idle colour and mode are the led_manager settings. breathe is timed by the LED chip itself: about one second up, one down, two dark.

WiFi

Route Method What it does
/api/wifi/status GET Station and access point state: joined, the address, the access point's address and client count, whether the access point still has the factory password, DNS servers, and sta_attempt (the last join attempt: network, reason code of the last disconnect, consecutive failures, whether that network is now tried after the others).
/api/wifi/scan GET Scans for networks. Blocks about 2 s; a phone on the access point may lose it for the scan.
/api/wifi/try POST {"ssid":"...","password":"...","channel":6} tries a network now without saving anything: joins, waits for an address (20 s in all), reports, lets go, and rejoins the configured network. 202 and the trial's status. channel (optional, from the scan) limits the search to that channel. 409 while a trial runs or when the station is off.
/api/wifi/try GET The trial's state and result: connected, password (wrong password), not_found, refused, no_ip or timeout, with the address, signal and time it took when it worked. The last result stays until the next trial.
{
  "enabled": true,
  "sta_connected": true,
  "ip": "10.42.1.194",
  "ap_started": false,
  "ap_default_password": false,
  "clients": 0,
  "ap_ip": "192.168.4.1",
  "dns": [
    "10.42.1.1",
    "1.1.1.1"
  ],
  "sta_attempt": {
    "ssid": "WICAN_TEST_AP",
    "reason": 8,
    "fail_count": 0,
    "deprioritised": false
  }
}
{
  "networks": [
    {
      "ssid": "ESPNetLink_457FED",
      "rssi": -20,
      "channel": 6,
      "auth_mode": "WPA2_PSK",
      "bssid": "02:00:00:00:00:01"
    },
    {
      "ssid": "Network-1",
      "rssi": -27,
      "channel": 1,
      "auth_mode": "WPA2_WPA3_PSK",
      "bssid": "02:00:00:00:00:02"
    },
    {
      "ssid": "WiCAN_32E3C5",
      "rssi": -34,
      "channel": 6,
      "auth_mode": "WPA2_PSK",
      "bssid": "02:00:00:00:00:03"
    },
    {
      "ssid": "WICAN_TEST_AP",
      "rssi": -45,
      "channel": 6,
      "auth_mode": "WPA2_PSK",
      "bssid": "02:00:00:00:00:04"
    },
    {
      "ssid": "Network-2",
      "rssi": -55,
      "channel": 10,
      "auth_mode": "WPA2_WPA3_PSK",
      "bssid": "02:00:00:00:00:05"
    },
    {
      "ssid": "Network-3",
      "rssi": -56,
      "channel": 2,
      "auth_mode": "WPA2_PSK",
      "bssid": "02:00:00:00:00:06"
    },
    {
      "ssid": "Network-4",
      "rssi": -57,
      "channel": 10,
      "auth_mode": "WPA2_WPA3_PSK",
      "bssid": "02:00:00:00:00:07"
    },
    {
      "ssid": "Network-5",
      "rssi": -57,
      "channel": 10,
      "auth_mode": "WPA2_WPA3_PSK",
      "bssid": "02:00:00:00:00:08"
    },
    {
      "ssid": "",
      "rssi": -57,
      "channel": 10,
      "auth_mode": "WPA2_PSK",
      "bssid": "02:00:00:00:00:09"
    },
    {
      "ssid": "Network-6",
      "rssi": -65,
      "channel": 11,
      "auth_mode": "WPA2_WPA3_PSK",
      "bssid": "02:00:00:00:00:0a"
    },
    {
      "ssid": "Network-7",
      "rssi": -80,
      "channel": 6,
      "auth_mode": "WPA2_PSK",
      "bssid": "02:00:00:00:00:0b"
    },
    {
      "ssid": "Network-8",
      "rssi": -81,
      "channel": 5,
      "auth_mode": "WPA2_PSK",
      "bssid": "02:00:00:00:00:0c"
    },
    {
      "ssid": "Network-9",
      "rssi": -81,
      "channel": 5,
      "auth_mode": "WPA_WPA2_PSK",
      "bssid": "02:00:00:00:00:0d"
    },
    {
      "ssid": "Network-10",
      "rssi": -82,
      "channel": 6,
      "auth_mode": "WPA2_PSK",
      "bssid": "02:00:00:00:00:0e"
    },
    {
      "ssid": "",
      "rssi": -82,
      "channel": 2,
      "auth_mode": "WPA_WPA2_PSK",
      "bssid": "02:00:00:00:00:0f"
    },
    {
      "ssid": "Network-11",
      "rssi": -86,
      "channel": 2,
      "auth_mode": "WPA2_PSK",
      "bssid": "02:00:00:00:00:10"
    },
    {
      "ssid": "Network-7",
      "rssi": -88,
      "channel": 11,
      "auth_mode": "WPA2_PSK",
      "bssid": "02:00:00:00:00:11"
    },
    {
      "ssid": "",
      "rssi": -89,
      "channel": 2,
      "auth_mode": "WPA_WPA2_PSK",
      "bssid": "02:00:00:00:00:12"
    },
    {
      "ssid": "Network-12",
      "rssi": -89,
      "channel": 11,
      "auth_mode": "WPA2_PSK",
      "bssid": "02:00:00:00:00:13"
    },
    {
      "ssid": "Network-13",
      "rssi": -89,
      "channel": 11,
      "auth_mode": "WPA2_WPA3_PSK",
      "bssid": "02:00:00:00:00:14"
    },
    {
      "ssid": "",
      "rssi": -89,
      "channel": 12,
      "auth_mode": "WPA_WPA2_PSK",
      "bssid": "02:00:00:00:00:15"
    },
    {
      "ssid": "Network-14",
      "rssi": -90,
      "channel": 11,
      "auth_mode": "WPA2_PSK",
      "bssid": "02:00:00:00:00:16"
    },
    {
      "ssid": "Network-15",
      "rssi": -91,
      "channel": 1,
      "auth_mode": "WPA2_PSK",
      "bssid": "02:00:00:00:00:17"
    },
    {
      "ssid": "",
      "rssi": -91,
      "channel": 7,
      "auth_mode": "WPA2_PSK",
      "bssid": "02:00:00:00:00:18"
    }
  ]
}

WiFi configuration (mode, access point, your networks, trust) is the wifi_manager settings document; there are no other WiFi routes.

Bluetooth LE

Route Method What it does
/api/ble GET The link (connected, secured = paired and authenticated, the negotiated payload size, the PHYs in use) and the stream channels with their byte counters and loss indicators.
/api/ble/blast POST {"bytes":N,"chunk":490} pumps a counting pattern over the data pipe, for throughput tests. 409 without a link.
{
  "enabled": false,
  "connected": false,
  "secured": false,
  "pairing_enabled": true,
  "max_payload": 20,
  "phy": "1m",
  "advertising": "legacy",
  "phy_tx": 0,
  "phy_rx": 0,
  "blast": {
    "running": false,
    "bytes": 0,
    "target": 0,
    "ms": 0
  },
  "channels_cap": 4,
  "channels": [
    {
      "name": "http",
      "uuid_out": "FFF3",
      "uuid_in": "FFF4",
      "rx_size": 32768,
      "rx_bytes": 0,
      "tx_bytes": 0,
      "rx_overflow": 0,
      "tx_timeouts": 0,
      "tx_link_down": 0,
      "rx_pending": 0,
      "out": "none",
      "out_modes": "both",
      "tx_notifications": 0,
      "tx_indications": 0
    }
  ]
}

BLE is off by default; ble_manager.enabled turns it on (reboot to apply). The GATT contract is on the BLE API pages.

USB connector

Route Method What it does
/api/usb GET The connector's role and what is on it: device_present, host_active, the enumerated device (vendor and product ids, class, a product name for the devices WiCAN knows), the USB Ethernet uplink (eth_connected, driver, address), the vbus state.
/api/usb/acm GET Whether a serial (CDC-ACM) device is bound, whether anyone is reading it, and what it turned out to be: console (an ESPNetLink's command prompt) or nmea (a GPS receiver streaming NMEA sentences).
/api/usb/acm/cmd POST {"cmd":"lte -s","timeout_ms":8000} sends one line to an ESPNetLink's own console over the cable and returns its reply. 409 when no device is bound or it is busy.
/api/gps GET The last live fix: position, accuracy, altitude, speed in m/s, heading, satellites, age. {"valid":false} without a fix. Comes from an ESPNetLink or a plain USB GPS receiver.
{
  "enabled": true,
  "device_present": true,
  "host_active": true,
  "eth_connected": false,
  "driver": "",
  "ip": "",
  "attaches": 1,
  "vid": "0000",
  "pid": "0000",
  "vbus": true,
  "device": null
}
{
  "valid": false
}

The same fix is published as AutoPID parameters (gps_latitude, gps_longitude, gps_altitude, gps_speed in km/h, gps_heading, gps_satellites), so it reaches Home Assistant, the dashboard, the logger and the rules like any vehicle value. Only a live fix is reported; a cached position is never presented as current.

Route Method What it does
/api/espnetlink GET The dongle as internet uplink: mode (wifi_modem, usb_ncm, usb_rndis), pairing state, the dongle's firmware and API level, its health (LTE connected, signal, operator, SIM state, connection stage, carrier address, GPS fix) and the carrier settings relayed to it.
/api/espnetlink/pair POST {"ssid":"...","password":"..."} pairs by hand: stores the dongle's access point as a trusted fallback network. Reboot to apply. 400 when no fallback slot is free.
/api/espnetlink/repair POST Cycles the dongle's USB power so it enumerates again and pairing restarts. 409 when the USB host is not up.
{
  "enabled": true,
  "mode": "wifi_modem",
  "auto_pair": true,
  "paired": true,
  "ssid": "ESPNetLink_457FED",
  "device_id": "14c19f457fed",
  "uplink": "wifi",
  "on_link": false,
  "host": "",
  "pair_blocked_factory_pw": false,
  "last_error": "",
  "dongle_fw": "",
  "dongle_api": 0,
  "dongle_api_min": 7,
  "health_unsupported": false,
  "apn": "",
  "carrier_synced": true,
  "carrier_note": "",
  "usb": {
    "attached": false,
    "pair_state": "idle",
    "cuts": 0,
    "vbus_cycles": 0,
    "errors": 0
  },
  "gps": {
    "valid": false
  },
  "dongle": {
    "valid": true,
    "lte_connected": true,
    "rssi_dbm": -55,
    "operator": "ALDI Mobile",
    "network_type": "eMTC",
    "gps_fix": true,
    "usb_data": false,
    "sim": "ready",
    "stage": "connected",
    "ip": "10.195.16.154",
    "age_s": 4150
  },
  "polls": 133,
  "failures": 1,
  "link_ups": 1
}

Zero-touch pairing is held (pair_blocked_factory_pw) while the access point still has the factory password: set your own, and the next restart completes the pairing.

VPN

Route Method What it does
/api/vpn GET State (disabled, waiting, connecting, connected), type (wireguard or tailscale), endpoint, counters.
/api/vpn/keygen POST Generates a WireGuard key pair on the device; the private key goes straight into the pending settings and never leaves. Answers the public key.
{
  "state": "disabled",
  "type": "wireguard",
  "endpoint": "203.0.113.10:51820",
  "ts_ip": "",
  "ts_peers": 0,
  "connects": 0,
  "failures": 0,
  "uptime_s": 0
}

Sleep

Route Method What it does
/api/sleep GET Sleep state and the countdown: state (normal, low_voltage, sleeping, wake_pending), the voltage, the sleep and wake thresholds, pending (which rule will put the device to sleep first: delay or critical) and sleep_in_s, the holds used this boot.
/api/sleep/hold POST {"minutes":10} keeps the device awake that much longer (1 to 30, three holds per boot). 409 when nothing is counting down or the holds are used up.
{
  "enabled": true,
  "state": "normal",
  "voltage": 13.5,
  "sleep_v": 13.1,
  "wake_v": 13.2,
  "naps": 0,
  "pending": "none",
  "sleep_in_s": 0,
  "critical_v": 11.9,
  "critical_s": 300,
  "hold_s": 0,
  "holds_left": 3,
  "holds_max": 3
}

Two rules put an awake device to sleep. The sleep delay: when the battery stays under sleep_v for sleep_delay_min. The critical floor: when it stays under 11.90 V for 5 minutes, whether sleep is enabled or not. pending names the one that will fire first. The web interface shows the countdown in its header and offers the hold a minute before the end. Thresholds and delays are the sleep_manager settings; there is no route to force a sleep.