Model zaufania
Dwie bariery, każda zamyka inne drzwi:
| bariera | dowodzi | sprawdza |
|---|---|---|
| PIN noda | jesteś fizycznie w sieci właściciela | node |
| grant backendu | node istnieje, ma właściciela, ma wolny slot | backend |
Trzy pytania są rozdzielone przy każdym żądaniu:
| pytanie | niesione przez |
|---|---|
| kim jesteś | token (sma_…) |
| co możesz robić | zakresy (wiersze na serwerze, nie bajty w tokenie) |
| jaki format wysyłasz | URL |
Token to 24 losowe bajty z prefiksem sma_ — niczego nie koduje. Uprawnienia żyją po stronie serwera i można je edytować albo odwołać bez ponownego wydawania czegokolwiek.
Parowanie — jak dostajesz token
Wszystko to dzieje się w LAN-ie, przez lokalny serwer HTTP noda (port 80). Potrzebujesz IP noda i jego PIN-u. (Żeby odkryć nody, odpytaj /info po całej podsieci — co odpowie, jest nodem.)
1 — sprawdź noda
GET http://<node-ip>/info
→ 200 { "device_id": "…", "firmware": "1.00", "ws_connected": true }
ws_connected musi być true — node przekazuje Twoje żądanie do backendu przez własne łącze.
2 — poproś o autoryzację
POST http://<node-ip>/ext/authorize
Authorization: Bearer <PIN>
{ "kind": "lora-gw", "name": "rooftop RAK", "scopes": "radio.frames,radio.spectrum" }
scopes to string rozdzielany przecinkami, nie tablica JSON. Odpowiedzi: 202 {"req":"<id>"} przekazane, odpytuj o odpowiedź · 403 zły PIN · 400 brak kind/scopes.
3 — odpytuj o grant
GET http://<node-ip>/ext/authorize?req=<id>
Authorization: Bearer <PIN>
→ { "status": "pending" }
→ { "status": "granted", "token": "sma_…", "id": "<attachment-id>", "exp": "<iso>" }
→ { "status": "denied", "reason": "…" }
Odpytuj raz na sekundę; node trzyma odpowiedź przez około 5 minut. Przechowuj token z trybem 0600 — od tej chwili rozmawiasz z backendem bezpośrednio, a node jest poza pętlą.
Wydany, ale nieużyty token wygasa po 30 minutach. Jego pełny czas życia (30 dni, odnawialny) zaczyna się przy pierwszym użyciu. Jeden node ręczy za maksymalnie cztery przystawki; właściciel może odwołać każdą z nich z apki, co natychmiast unieważnia token.
Rozmowa z backendem
Bazowy URL https://sensmos.com/v1/ext, każde żądanie z Authorization: Bearer sma_….
| wywołanie | cel |
|---|---|
GET /v1/ext/me | czy nadal żyjesz, co możesz wysyłać, jakie integracje/formaty przyjmuje serwer (catalog) — wywołaj przy starcie |
POST /v1/ext/renew | przedłuż ważność tokena; raz dziennie w zupełności wystarczy |
POST /v1/ext/<integration>/<format> | wyślij dane → 200 {"ok":true,"accepted":n} |
Działające endpointy:
| URL | zakres | body |
|---|---|---|
/v1/ext/radio/rxpk | radio.frames | JSON PUSH_DATA packet forwardera Semtech, bez zmian |
/v1/ext/radio/frames | radio.frames | natywny format ramki (poniżej) |
/v1/ext/radio/channel | radio.spectrum | jeden odczyt widma (poniżej) |
Formaty danych
rxpk — packet forwarder Semtech, jeden do jednego. Wyślij obiekt dokładnie tak, jak forwarder wyprodukował go w PUSH_DATA:
{ "rxpk": [ {
"time": "2026-08-21T20:48:11Z",
"freq": 867.3, "datr": "SF9BW125", "modu": "LORA",
"rssi": -44, "lsnr": 12.0, "size": 38, "stat": 1,
"data": "4FNNT1MgY2Nk…"
} ] }
datr jest parsowane tylko dla LoRa; modulacja pochodzi z modu. stat zgodnie z Semtech (1 CRC OK, -1 błąd, 0 brak). tmst jest ignorowane; bez time serwer stempluje czas odbioru.
frames — format natywny, dla przystawek pisanych pod nas (własny odbiornik, SDR, mostek Meshtastic):
{ "frames": [ {
"ts": 1755808091, "freq": 867.3, "sf": 9, "mode": 0,
"rssi": -44, "snr": 12.0, "len": 38, "crc": true,
"hex": "e0534d4f53…", "dir": "rx"
} ] }
mode 0 LoRa / 1 FSK; sf 0 dla FSK; hex = payload PHY, zachowywane pierwsze 128 bajtów. dir: "tx" jest dla nadań Twojej bramy, które inni mogą usłyszeć (zwykłe downlinki z odwróconym IQ niczego nie dowodzą — pomiń je); dla tx rssi niesie moc TX.
channel — jeden odczyt widma:
{ "freq": 869.618, "bw": 62.5, "sf": 8, "sync": 18, "noise": -118, "peak": -97 }
freq wymagane, reszta domyślnie 0; noise/peak w dBm.
Limity i błędy
| kod | body | znaczenie |
|---|---|---|
| 401 | invalid_token | token nieznany, wygasły albo odwołany |
| 403 | missing_scope | token ważny, ale nie na ten endpoint |
| 400 | komunikat parsera | body nie pasuje do formatu |
| 404 | unknown_endpoint + catalog | nie ma takiej integracji/formatu |
| 413 | too_many | przekroczony limit paczki — 200 ramek na żądanie |
Grupuj po swojej stronie — raz na sekundę albo gdy uzbiera się 50 ramek, co nastąpi pierwsze. Payloady aplikacyjne pozostają zaszyfrowane end-to-end: serwer pracuje na metadanych radiowych i rozpoznaje ramki Sensmos w ruchu; nie ma kluczy do niczyich payloadów LoRaWAN i ich nie chce.
Co robią dane
Ramki lądują w tych samych tabelach i na tej samej mapie co ramki usłyszane przez własne radio noda — przystawka nie jest osobnym bytem, to lepsza antena dla noda, który za nią poręczył. Nadania awaryjne i ramki czujników Sensmos są rozpoznawane i dostarczane właścicielom; zweryfikowane beacony Sensmos budują krawędzie pokrycia radiowego między nodami; profil danych ręczącego noda zyskuje kategorię RF.
Gotowe przystawki: Brama LoRaWAN (pasywny nasłuch). Źródła obu agentów są na GitHubie.
Dodawanie nowego typu integracji
Strona serwerowa każdej integracji to jeden deklaratywny wpis w rejestrze (zakresy, formaty, parser, handler). Kandydaci w tym samym wzorcu, żaden jeszcze nie działa: widok nieba GNSS i zakłócenia (gnss.sky, celowo bez pozycji), podsumowania odbioru ADS-B i AIS per okno, rtl_433, liczniki energii. Jeśli masz sprzęt i chcesz, żeby jakiś format był przyjmowany, odezwij się na Discordzie — wpis formatu to mała, łatwa do przejrzenia zmiana.