Node HTTP API (LAN, port 80)
Authentication: Authorization: Bearer <PIN> (the PIN you set when adding the node). GET /info is open.
| Endpoint | Purpose |
|---|---|
GET /info | id, firmware, alias, uptime, lora{board,role,rx_key,open} when a radio is present |
GET /data/status | current entities (own / public / subscribed / diagnostics) |
POST /data {entity_id,value,unit} | write an entity (own. / native pub.) |
GET /data/native | catalogue of native entities the firmware knows |
GET/POST /node/mqtt | local broker configuration |
GET/POST /node/lora_emerg | emergency entity set + command webhook |
GET/POST /node/lora_rx {key,open} | LoRa receive key phrase (never returned) and plain-frame opt-in |
POST /node/lorasend {dst,sub,payload,aes,via?} | transmit a LoRa DATA frame (own sensor: radio; other node: via the network) |
GET /lora/inbox | LoRa inbox: cmds (emergency commands) and frames (sensor frames, via rf/ws/tx) |
GET /lora/last | radio state incl. TX diagnostics (link) |
POST /node/alias | node label (shown in /info) |
POST /node/pair / DELETE | remote-access pairing key (LAN only, by design) |
MQTT
Topic root sensmos/<id8>/. Status, diagnostics and entities are published with Home Assistant discovery. Messages arrive on sensmos/<id8>/msg as {from, eid, p} — e.g. {"from":"lora","eid":"lora_frame.3","p":"21.5"} for a LoRa frame from sensor 3, {"from":"owner","eid":"lora_cmd","p":"water_off"} for an emergency command.
Software nodes — data without hardware
POST https://api.sensmos.com/v1/ingest with {key, entities:[{entity_id,value,unit}], lat?, lon?, label?} puts your data on the live map as a software node (Home Assistant, ESPHome, a script — anything). Key ≥ 32 characters is your identity. Software nodes are data-only: they do not earn and are not probe targets.
LoRa DATA frame (for sensor builders)
plain: [0xE0][0x02][flags][dst 4B][sub 1B][payload 1..128 B][CRC32 LE]
encrypted: [0xE0][0x02][flags][dst 4B][sub 1B][nonce 4B][AES-256-CTR(payload+CRC32)]
dst= first 4 bytes of the target node's id (the 8-hex id shown in the app);sub= your sensor's number behind that node (1–255).flagsbit0 = AES; bit1 = "last hop" — set only by the node when it transmits to a sensor. A sensor accepts a frame only when bit1 is set,dstis its node andsubis its own; it sends its own uplinks with bit1 = 0.- CRC32 (zlib) of the payload, little-endian, inside the encrypted block — a wrong key fails the CRC.
- AES-256-CTR, key = SHA-256(key phrase), counter block
[nonce 4B][dst 4B][sub][0…], random nonce per frame. - Radio: EU 868.1 MHz / US 903.9 MHz, BW 125 kHz, SF11, CR 4/5, sync word 0x34, LoRa CRC on, normal IQ. Listen before talk; respect the 1% duty cycle (≈25 frames/hour at 30 bytes).
- One radio hop only. Uplinks are heard by any node or gateway and delivered to the target node over the network; downlinks to a sensor are transmitted only by that sensor's own node.
An actuator that never transmits is invisible to the network — send a short "hello" once an hour so the network learns who hears it. The full engineering contract lives in the firmware repository (DOCS/dev/LORA-MESSAGING.md).
Gateways as listeners
A LoRaWAN gateway you run (Semtech packet forwarder) can hear for the whole network: a passive agent forwards every received frame to the backend under your node's identity — setup on LoRaWAN gateway, the contract (pairing, tokens, formats) on Attachments & the EXT API. Frames for Sensmos nodes are routed; foreign LoRaWAN traffic is only counted as spectrum statistics.