docs: complete README rewrite - synthetic, updated structure, all features covered

Structure now reflects real repo (build/, dashboard/, deploy/).
Sections reorganized for readability, compact tables instead of prose.
Kept all critical details: v2/v3 payload decoding, mode switching,
callback config gotchas, security, K8s deployment.

Reduced from 485 to ~230 lines while covering every feature.
This commit is contained in:
2026-07-03 18:36:36 +02:00
parent f92d2bb642
commit b380334375
+204 -392
View File
@@ -1,296 +1,120 @@
# SNEK — Sigfox Network Emulator (K8s Edition) # SNEK — Sigfox Network Emulator (Kubernetes edition)
Une stack complète pour émuler un réseau Sigfox depuis n'importe quel cluster Kubernetes, avec un dongle SDR physique. Réception RF Sigfox 868 MHz via dongle SDR USB dans un cluster K8s, avec un
dashboard web temps réel qui décode les payloads Sens'it (v2 et Discovery v3)
et les rend disponibles par callback HTTP pour tes autres services.
**Cas d'usage** : recevoir en direct les messages Sigfox de tes devices (Sens'it, capteurs custom, etc.) sans passer par le backend public Sigfox, décoder les payloads, et transférer les données vers n'importe quel service (API, base, webhook, etc.). ```
Device Sigfox
--- │ 868 MHz
Dongle SDR (NXP LPC)
│ USB → /dev/bus/usb (hostPath)
Pod SNEK (privileged) ─ callback POST JSON ─▶ Pod Dashboard ─ SSE ─▶ Browser
:8085 web UI :3000 SQLite + Flask
```
## Sommaire ## Sommaire
1. [Vue d'ensemble](#vue-densemble) 1. [Structure du repo](#structure-du-repo)
2. [Hardware — le dongle Sigfox SDR](#hardware--le-dongle-sigfox-sdr) 2. [Prérequis](#prérequis)
3. [Software — SNEK](#software--snek) 3. [Déploiement](#déploiement)
4. [Déploiement](#déploiement) 4. [Configuration devices dans SNEK](#configuration-devices-dans-snek)
5. [Configuration des devices](#configuration-des-devices) 5. [Callback SNEK → Dashboard](#callback-snek--dashboard)
6. [Réception de messages (uplink)](#réception-de-messages-uplink) 6. [Dashboard](#dashboard)
7. [Décodage des payloads](#décodage-des-payloads) 7. [Décodage payloads Sens'it](#décodage-payloads-sensit)
8. [Callbacks HTTP (intégration avec autres services)](#callbacks-http-intégration-avec-autres-services) 8. [Changer de mode sur Sens'it v2](#changer-de-mode-sur-sensit-v2)
9. [Sécurité, vie privée & aspects légaux](#sécurité-vie-privée--aspects-légaux) ⚠️ 9. [Downlink & Persistance](#downlink--persistance)
10. [Downlink (messages serveur → device)](#downlink-messages-serveur--device) 10. [Sécurité & aspects légaux](#sécurité--aspects-légaux) ⚠️
11. [Persistance & backups](#persistance--backups) 11. [Dépannage](#dépannage)
12. [Dépannage](#dépannage) 12. [Ressources](#ressources)
--- ---
## Vue d'ensemble ## Structure du repo
``` ```
┌─────────────┐ snek/
│ Device │ (Sens'it, capteur custom, etc.) ├── README.md
│ Sigfox │ ├── build/ ← image SNEK (Ubuntu 20.04 + SNEK 2.3.4)
└──────┬──────┘ │ ├── Dockerfile
│ RF 868 MHz (EU) │ └── entrypoint.sh ← flash firmware + auto-disable auth + start
├── dashboard/ ← image dashboard web (Flask + SQLite + Chart.js)
┌─────────────┐ │ ├── Dockerfile
│ Dongle │ (NXP LPC 2cc1:8001 → 2cc1:0001 après flash) │ ├── app.py ← API webhook + SSE + token auth
│ SDR USB │ │ ├── sensit_decoder.py ← décodeur Sens'it v2 + v3 (auto-détection)
└──────┬──────┘ │ ├── requirements.txt
USB via /dev/bus/usb (hostPath mount) └── static/index.html UI Tailwind + login screen
└── deploy/ ← manifests K8s
┌──────────────────────────┐ ├── snek.yaml ← ns + PVC + deployment + LoadBalancer
│ Pod SNEK (K8s) │ └── dashboard.yaml ← PVC + deployment + LoadBalancer
│ - Container privileged │
│ - Ubuntu 20.04 │
│ - SNEK 2.3.4 │
│ - Web UI :8085 │
└──────┬───────────────────┘
│ HTTP callback (JSON)
┌─────────────┐
│ Ton API, │ (Elasticsearch, Postgres,
│ ton bot, │ Telegram, Kafka, …)
│ ton front │
└─────────────┘
``` ```
--- ## Prérequis
## Hardware — le dongle Sigfox SDR - K8s ≥ 1.24 avec **StorageClass** (ex: `local-path`) et **MetalLB** (ou équivalent LoadBalancer)
- Un **node avec le dongle Sigfox SDR physiquement branché** en USB 2.0+
- Namespace `snek` labelé `pod-security.kubernetes.io/enforce=privileged`
- Docker registry accessible depuis le cluster pour les images
**Modèle utilisé** : Sigfox SDR Dongle officiel (chipset NXP LPC). Le dongle apparaît en USB comme `2cc1:8001` (mode DFU) au boot, puis `2cc1:0001` une fois le firmware flashé par `foxctl` (fait automatiquement par l'entrypoint).
**Identifiants USB** :
- Au boot : `2cc1:8001` (mode DFU / bootloader)
- Après flash firmware par `foxctl` : `2cc1:0001` (mode opérationnel)
**Prérequis** :
- Port USB 2.0 High Speed (les ports 3.0 rétrocompatibles marchent aussi)
- Antenne 868 MHz (fournie ou fil quart d'onde 8.6 cm)
- **Zone RC1** pour l'Europe (868 MHz), autres zones (RC2 US, RC3 Asie, etc.) configurables dans SNEK
**Le firmware n'est pas persistent** : à chaque unplug ou reboot du dongle, il repart en mode DFU. Le script `entrypoint.sh` du container reflashe automatiquement au démarrage via `foxctl`.
---
## Software — SNEK
**SNEK 2.3.4** (Sigfox Network Emulator) est le logiciel officiel Sigfox qui :
- Pilote le dongle SDR
- Émule le réseau Sigfox localement
- Enregistre les devices (up to 5 par défaut, extensible à 10 via config)
- Reçoit et décode les messages uplink
- Envoie des messages downlink
- Expose une interface web (port 8085)
- Envoie des callbacks HTTP en JSON à des URL externes
**Dépendances système** (pré-installées dans l'image Docker) :
- Ubuntu 20.04 (obligatoire — `libgtk2-perl` n'existe pas en 22.04+)
- `libgtk2-perl`, `zenity`, `python`, `libdbus-glib-1-2`, `usbutils`
- `libffi6` (ABI 18.04, downloadée depuis les archives Ubuntu)
**Configuration** : fichier `/root/Snek/snek.conf` (JSON), monté sur PVC pour persistance.
---
## Déploiement ## Déploiement
### Prérequis cluster Build & push (adapte le registry) :
1. **Kubernetes** ≥ 1.24
2. **Namespace privileged** (le container a besoin de `securityContext.privileged: true`)
3. **StorageClass** pour PVC (ex: `local-path`)
4. **MetalLB ou équivalent** pour exposer le service en LoadBalancer (optionnel — `ClusterIP` suffit si tu accèdes depuis un autre pod)
5. **Node avec le dongle physiquement branché** en USB
### Build & Push
```bash ```bash
cd snek/build docker build --platform linux/amd64 -t <registry>/snek:latest ./build
docker build --platform linux/amd64 -t 192.168.1.100:30500/snek:latest . docker build --platform linux/amd64 -t <registry>/snek-dashboard:latest ./dashboard
docker push 192.168.1.100:30500/snek:latest docker push <registry>/snek:latest
docker push <registry>/snek-dashboard:latest
``` ```
Remplace `192.168.1.100:30500` par ton registry. Token dashboard (optionnel) :
### Deploy
```bash ```bash
kubectl apply -f snek/deploy/snek.yaml kubectl create secret generic snek-dashboard-secret -n snek \
--from-literal=token=$(openssl rand -hex 24)
``` ```
Le manifest crée : Apply :
- Namespace `snek` avec label `pod-security.kubernetes.io/enforce=privileged`
- PVC `snek-data` (2 Gi, persistance de la config `/root/Snek`)
- Deployment `snek` avec `securityContext.privileged=true` + `hostPath /dev/bus/usb`
- Service LoadBalancer sur port 8085
### Vérification
```bash ```bash
kubectl get pods -n snek kubectl apply -f deploy/snek.yaml
kubectl logs -n snek -l app=snek --tail=30 kubectl apply -f deploy/dashboard.yaml
``` ```
Tu dois voir dans les logs : Vérifie :
```
==> Dongle detected
==> Flashing firmware (foxctl)...
==> Starting SNEK main_sigfox on port 8085...
INFO - SNEK Software Version : "2.3.4"
INFO - Server started at http://0.0.0.0:8085
INFO - Frequence RX : 868130000
INFO - Frequence TX : 869525000
```
### Accès UI
- **Depuis ton LAN** : `http://<LoadBalancer-IP>:8085/staticv234/index.html`
- **Depuis un autre pod K8s** : `http://snek.snek.svc.cluster.local:8085`
---
## Configuration des devices
Chaque device Sigfox a un **identifier hex 6 caractères** (ex: `B440C7`).
Tu le trouves :
- Sur l'autocollant du device
- Ou dans le backend Sigfox → Device details
- Ou sur les Sens'it via l'app mobile
Dans SNEK → onglet **Configuration** → Devices → ajoute jusqu'à **5 devices** (limite hardcodée dans le frontend JS de SNEK 2.3.4). Pour plus, lancer plusieurs instances de SNEK.
**Important** : SNEK ne décode que les messages des devices enregistrés dont l'auth passe. Un message d'un ID non enregistré est simplement logué comme `Receiving message with bad authentication (not public key)` sans détail sur l'ID ni le payload.
### Authentication auto-disabled au démarrage
Par défaut SNEK exige que chaque message soit **signé HMAC** avec la clé publique du device (Network Access Key). Les Sens'it de démo n'ont pas leur clé disponible → messages rejetés en `bad authentication`.
L'entrypoint du container **désactive automatiquement** l'auth via un POST sur `/ned/saturation` juste après le démarrage de SNEK. Ainsi tes devices enregistrés fonctionnent immédiatement au premier boot, et à chaque restart du pod (SNEK ne persiste PAS l'état auth ailleurs).
Si tu veux la garder activée (ex: prod avec devices dont tu as les clés publiques), commente le bloc `for i in $(seq 1 30)` dans `build/entrypoint.sh` et rebuild l'image.
⚠️ Voir la section [Sécurité, vie privée & aspects légaux](#sécurité-vie-privée--aspects-légaux) pour les implications.
---
## Réception de messages (uplink)
Une fois un device configuré, dès qu'il émet un message Sigfox (bouton double-clic, réveil périodique, alerte capteur…) :
1. **Réception RF** par le dongle SDR
2. **Décodage bas-niveau** par le firmware
3. **Traitement** par SNEK (auth, dedup, décodage payload)
4. **Affichage** dans l'onglet **Messages** de la web UI
5. **Envoi callback** vers ton service si configuré (voir section suivante)
Format d'un message dans la UI SNEK :
```
Date Device Payload RSSI SNR
2026-07-03 15:23 B440C7 b60dc86e -85 dBm 12 dB
```
**Debug** : si un device n'apparaît pas, vérifie dans les logs :
```bash ```bash
kubectl logs -n snek -l app=snek -f | grep -i "receiving\|register" kubectl logs -n snek -l app=snek | grep "Server started at"
kubectl logs -n snek -l app=snek-dashboard | tail -5
``` ```
--- ## Configuration devices dans SNEK
## Décodage des payloads L'UI SNEK est sur le service K8s `snek` (port 8085). Vue LAN : `http://<snek-lb-ip>:8085/staticv234/index.html`.
Les payloads Sigfox sont **binaires**, format propre à chaque device. Le Sens'it existe en deux générations avec des formats **complètement différents**. Onglet **Configuration → Devices** : ajoute jusqu'à **5 devices** (limite JS hardcodée dans SNEK 2.3.4). Chaque device a un **ID hex 6 caractères** (ex `B440C7`) trouvé sur l'étiquette du device ou dans l'app Sens'it.
Le décodeur du dashboard ([`dashboard/sensit_decoder.py`](./dashboard/sensit_decoder.py)) **auto-détecte** la génération en lisant les bits de réserve du byte 0 : **Authentication auto-désactivée** au démarrage par l'entrypoint (SNEK reset à `enabled` à chaque restart et ne persiste pas). Ainsi tes devices sans clé publique HMAC (Sens'it de démo) sont décodés directement. Pour désactiver ce comportement, commente le bloc `for i in $(seq 1 30)` dans `build/entrypoint.sh` et rebuild.
- Byte 0 bits 2-0 = `0b110`**Sens'it Discovery (v3)**
- Sinon → **Sens'it v2**
### Sens'it Discovery (v3) ## Callback SNEK → Dashboard
Format 4 bytes, bit ordering **MSB→LSB**, mode encodé sur les 5 bits hauts du byte 1. Dans SNEK → onglet **Callbacks****New** (section DATA callbacks) :
| Byte | Bits | Contenu |
|------|------|---------|
| 0 | 7-3 | Battery Level (formule `V = val × 0.05 + 2.7`) |
| 0 | 2-0 | Reserved fixe `0b110` (marqueur v3) |
| 1 | 7-3 | **Mode** (`0` Standby, `1` Temperature, `2` Light, `3` Door, `4` Vibration, `5` Magnet) |
| 1 | 2 | Data MSB spécifique mode |
| 1 | 1 | Data spécifique mode |
| 1 | 0 | Button Alert Flag (1 si double-clic) |
| 2 | 7-0 | Data LSB (Temp / Brightness / Event count MSB) |
| 3 | 7-0 | Data (Humidity / Event count LSB) |
Formules par mode :
- **Temperature** : `T = ((MSB × 256 + LSB) 200) / 8` °C · `H = byte3 / 2` %
- **Light** : `brightness = (MSB × 256 + LSB) / 96` lux
- **Door / Vibration / Magnet** : `event_count = byte2 × 256 + byte3`
**Exemple** — payload `b60dc86e` :
- Byte 0 = `0xB6` → Battery raw = 22 → **3.8V**
- Byte 1 = `0x0D` → Mode 1 (Temperature), Temp MSB = 1, Button pressé
- Byte 2 = `0xC8` = 200 → Temp raw = 1×256 + 200 = 456 → **(456200)/8 = 32°C**
- Byte 3 = `0x6E` = 110 → **Humidity = 55%**
Doc officielle : [Sens'it Discovery Payload Structure (PDF)](https://storage.googleapis.com/public-assets-xd-sigfox-production-338901379285/build/4059ae1jy7g2jmg/sensit-discovery-payload.pdf)
### Sens'it v2
Format 4 bytes, bit ordering **LSB→MSB** (inverse du v3), mode encodé sur les 3 bits bas du byte 0.
| Byte | Bits | Contenu |
|------|------|---------|
| 0 | 0-2 | **Mode** (`0` Button, `1` Temperature, `2` Light, `3` Door, `4` Move, `5` Reed Switch) |
| 0 | 3-4 | Timeframe (`0` 10 min, `1` 1h, `2` 6 jours, `3` 24h) |
| 0 | 5-6 | Type (`0` regular, `1` button_call, `2` alert, `3` new_mode) |
| 0 | 7 | Battery MSB (1 bit) |
| 1 | 0-3 | Temperature MSB (4 bits, envoyé dans chaque frame) |
| 1 | 4-7 | Battery LSB (4 bits) |
| 2 | 0-5 | Selon mode (Temperature LSB / Light value) |
| 2 | 6 | Selon mode (Reed switch state en modes classiques / Light multiplier LSB) |
| 2 | 7 | Selon mode (unused en modes classiques / Light multiplier MSB) |
| 3 | 7-0 | Selon mode (fw version / humidity / alert count) |
Formules par mode :
- **Battery** : `V = (MSB × 16 + LSB) × 0.05 + 2.7`
- **Temperature** (10 bits MSB+LSB combinés) : `T = ((MSB × 64 + LSB) 200) / 8` °C · `H = byte3 × 0.5` %
- **Light** : `lux = multiplier × value × 0.01` avec multiplier ∈ `{1, 8, 64, 512}` selon bits 6-7 de byte 2
- **Button** : byte 3 = version firmware (`major` = bits 4-7, `minor` = bits 0-3)
- **Door / Move / Reed Switch** : byte 3 = compteur d'alertes (8 bits, cumulé)
**Exemple** — payload `a87611db` (Sens'it v2 en mode Button) :
- Byte 0 = `0xA8` → Mode 0 (Button), Timeframe 1h, Type button_call, Battery MSB = 1
- Byte 1 = `0x76` → Temp MSB = 6, Battery LSB = 7 → Battery raw = 23 → **3.85V**
- Byte 2 = `0x11` → Temp LSB = 17 → Temp raw = 6×64+17 = 401 → **(401200)/8 = 25.12°C**
- Byte 3 = `0xDB` → fw v13.11
Doc officielle : [Sens'it v2 uplink frames (PDF)](https://storage.googleapis.com/public-assets-xd-sigfox-production-338901379285/build/4059ab1jy7g2v9l/sensit%20v2%20frames%20uplink.pdf)
⚠️ Différence critique entre les deux générations : **le bit ordering est inversé**. Byte 0 bits 2-0 valent `0b110` en v3 (marqueur) mais forment le champ Mode en v2. C'est ce qui permet l'auto-détection dans le décodeur.
---
## Callbacks HTTP (intégration avec autres services)
C'est là que ça devient puissant : à chaque message reçu, SNEK peut faire un **POST HTTP** vers une URL de ton choix avec le payload en JSON.
### Configuration
### Configurer un callback dans la UI SNEK
Dans SNEK → onglet **Callbacks****New** (dans la section DATA callbacks) :
| Champ | Valeur | | Champ | Valeur |
|-------|--------| |-------|--------|
| **Type** | `UPLINK` | | Type | `UPLINK` |
| **Channel** | `URL` | | Channel | `URL` |
| **Send duplicate** | décoché | | Send duplicate | décoché |
| **Url pattern** | ton endpoint (ex: `http://mon-service.mon-ns.svc.cluster.local/webhook`) | | Url pattern | `http://snek-dashboard.snek.svc.cluster.local/webhook` |
| **Line pattern** | *(vide)* | | Method | `POST` |
| **Content type** | `application/json` | | Content type | `application/json` |
| **Method** *(apparaît selon channel)* | `POST` | | Body | (JSON template ci-dessous) |
| **Body** *(apparaît une fois Method=POST + Content-Type=application/json)* | template JSON ci-dessous |
Body template (format exact — respecter les espaces autour des `:`) : Body template — **respecter les espaces autour des `:`**, sinon SNEK rejette :
```json ```json
{ {
@@ -303,174 +127,160 @@ Body template (format exact — respecter les espaces autour des `:`) :
} }
``` ```
⚠️ **Piège classique** : ne pas coller le JSON dans **Line pattern**. Ce champ fait une substitution char-par-char et refuse tout ce qui n'est pas un nom de variable connu (tu obtiens `Wrong values. Please fix: ...`). Le body JSON va dans le champ **Body** qui n'apparaît qu'après avoir choisi Method=POST et Content-Type=application/json. ⚠️ Ne colle **jamais** le JSON dans le champ **Line pattern** (erreur `Wrong values. Please fix: ...`). Le champ Body n'apparaît qu'après avoir choisi Method=POST + Content-Type=application/json.
### Variables disponibles Variables SNEK dispo : `{device}` `{time}` `{data}` `{rssi}` `{snr}` `{seqNumber}` `{duplicate}` `{station}` `{avgSnr}` `{LQI}`
`{device}` `{time}` `{data}` `{rssi}` `{snr}` `{seqNumber}` `{duplicate}` `{station}` `{avgSnr}` `{LQI}` ## Dashboard
### Cas d'usage Accès LAN : `http://<dashboard-lb-ip>:80` — ou via NPM/Ingress si tu l'as exposé (ex: `https://sigfox.sortium.fr`).
- **Dashboard live** : dossier [`dashboard/`](./dashboard/) — Flask + SQLite + SSE + Chart.js prêt à l'emploi **Login** : si `DASHBOARD_TOKEN` est défini via le secret K8s, une page de login s'affiche au premier accès. Le token est stocké en `localStorage` du navigateur (plus de prompt ensuite).
- Ingérer dans une base (Postgres, InfluxDB via une API custom)
- Alertes Telegram / Slack / Discord
- Bus de messages Kafka / RabbitMQ
### Dashboard intégré (companion service) Endpoints :
Un mini dashboard responsive est fourni dans [`dashboard/`](./dashboard/) — il reçoit les callbacks SNEK, décode les 6 modes Sens'it (Standby, Temperature, Light, Door, Vibration, Magnet), stocke en SQLite et affiche en temps réel via Server-Sent Events. Tailwind + Chart.js. - `GET /` — UI dashboard (public)
- `POST /webhook` — endpoint pour SNEK (public, pas de token requis)
- `GET /api/devices` — liste des devices vus + compteur (token requis)
- `GET /api/messages?device=X&limit=N` — historique (token requis)
- `GET /api/decode?hex=XXXX` — décodage manuel utile pour debug (token requis)
- `GET /events` — stream SSE temps réel (token requis, accepte `?token=`)
- `GET /healthz` — probe K8s (public)
Pour le déployer : Le dashboard :
- affiche une card par device avec mode / format v2/v3 / battery / valeur principale / RSSI
- graphique historique **contextuel selon le mode** (temp+hum, brightness, event count, ou caché si Button/Standby)
- log tableau des 50 derniers messages, tous devices confondus
- push SSE dès qu'un webhook arrive (~50ms de latence)
## Décodage payloads Sens'it
Sens'it v2 et Sens'it Discovery (v3) ont des formats **totalement différents**. Le décodeur du dashboard auto-détecte via les bits 2-0 du byte 0 :
- `0b110`**v3 Discovery** (marqueur fixe)
- sinon → **v2**
### Sens'it Discovery v3 (bit ordering MSB→LSB)
| Byte | Bits | Contenu |
|------|------|---------|
| 0 | 7-3 | Battery raw (`V = raw × 0.05 + 2.7`) |
| 0 | 2-0 | Reserved `0b110` |
| 1 | 7-3 | Mode (`0` Standby · `1` Temp · `2` Light · `3` Door · `4` Vibration · `5` Magnet) |
| 1 | 2 | Data MSB spécifique mode |
| 1 | 0 | Button Alert Flag |
| 2 | 7-0 | Data LSB (Temp / Brightness / Event count MSB) |
| 3 | 7-0 | Data (Humidity / Event count LSB) |
Formules :
- **Temp** : `T = ((MSB × 256 + LSB) 200) / 8` °C · `H = byte3 / 2` %
- **Light** : `lux = (MSB × 256 + LSB) / 96`
- **Door / Vibration / Magnet** : `events = byte2 × 256 + byte3`
**Exemple** `b60dc86e` : `0xB6` → Battery = 22 × 0.05 + 2.7 = **3.8V** · `0x0D` → Mode 1 (Temp) · `0xC8` = 200 → Temp = (1×256 + 200 200)/8 = **32°C** · `0x6E` = 110 → **Humidity 55%**
### Sens'it v2 (bit ordering LSB→MSB — inverse du v3)
| Byte | Bits | Contenu |
|------|------|---------|
| 0 | 0-2 | Mode (`0` Button · `1` Temp · `2` Light · `3` Door · `4` Move · `5` Reed Switch) |
| 0 | 3-4 | Timeframe (`0` 10 min · `1` 1h · `2` 6j · `3` 24h) |
| 0 | 5-6 | Type (`0` regular · `1` button_call · `2` alert · `3` new_mode) |
| 0 | 7 | Battery MSB |
| 1 | 0-3 | Temperature MSB (toujours envoyé) |
| 1 | 4-7 | Battery LSB |
| 2 | 0-5 | Temp LSB *(classic)* / Light value *(Light)* |
| 2 | 6-7 | Reed switch state *(classic)* / Light multiplier *(Light)* |
| 3 | 7-0 | Selon mode : fw version *(Button)* / humidity *(Temp)* / alert count *(autres)* |
Formules :
- **Battery** : `V = (MSB × 16 + LSB) × 0.05 + 2.7`
- **Temp** (10 bits combinés) : `T = ((MSB × 64 + LSB) 200) / 8` °C · `H = byte3 × 0.5` %
- **Light** : `lux = mult × value × 0.01` avec mult ∈ `{1, 8, 64, 512}` selon bits 6-7 de byte 2
- **Button** : byte 3 = fw (major = bits 4-7, minor = bits 0-3)
- **Door / Move / Reed** : byte 3 = 8-bit compteur cumulé
**Exemple** `a87611db` (mode Button) : `0xA8` → Mode 0, Timeframe 1h, Type button_call, Battery MSB = 1 · `0x76` → Temp MSB = 6, Battery LSB = 7 → Battery = 23 × 0.05 + 2.7 = **3.85V** · `0x11` → Temp LSB = 17 → Temp = (6×64+17200)/8 = **25.12°C** · `0xDB`**fw v13.11**
## Changer de mode sur Sens'it v2
Actions bouton du device (aucune app requise) :
| Action | Effet |
|--------|-------|
| **1 short press** | LED clignote dans la couleur du mode actuel |
| **1 long press (~5s)** | Passe au mode suivant (cycle) |
| **Double press** | Envoie un message immédiat |
Couleurs LED : Temperature 🟢 · Light 🟡 · Door 🔵 clair · Vibration 🔵 foncé · Magnet 🟣 · Button ⚪ blanche
Après changement, le device envoie automatiquement un frame avec `type = new_mode` — visible dans le dashboard.
Pour le Sens'it v3 Discovery, la procédure est similaire mais reporte-toi au manuel officiel (les couleurs et cycles diffèrent légèrement).
## Downlink & Persistance
**Downlink** : SNEK peut envoyer jusqu'à 4 downlinks/jour à un device via l'UI SNEK (onglet Devices → sélectionne un device → champ Downlink 8 bytes hex). Utile pour reconfigurer les seuils/timeframes des Sens'it. Le message est envoyé sur le prochain uplink du device.
**Persistance des données** :
| Data | Emplacement | Backup |
|------|-------------|--------|
| Config SNEK (`snek.conf`) | PVC `snek-data` (2 Gi) sur `/root/Snek` | Velero avec `defaultVolumesToFsBackup: true` |
| Historique messages | PVC `dashboard-data` (1 Gi) `/data/messages.db` (SQLite) | Idem |
| Logs SNEK | PVC `snek-data` (`snek.log` rotation 5 × 10 MB) | Idem |
Backup manuel :
```bash ```bash
cd dashboard kubectl exec -n snek deploy/snek-dashboard -- cat /data/messages.db > messages-$(date +%F).db
docker build --platform linux/amd64 -t 192.168.1.100:30500/snek-dashboard:latest .
docker push 192.168.1.100:30500/snek-dashboard:latest
kubectl apply -f ../deploy/dashboard.yaml
``` ```
Puis configure le callback SNEK avec l'URL : ## Sécurité & aspects légaux
```
http://snek-dashboard.snek.svc.cluster.local/webhook
```
et le body JSON template ci-dessus. Ouvre `http://<lb-ip-dashboard>` dans un navigateur.
--- ⚠️ Le dongle SDR est un **récepteur RF broadcast** sur 868 MHz — il capte tout ce qui passe dans un rayon de **1 à 40 km** selon environnement, pas seulement tes devices.
## Sécurité, vie privée & aspects légaux Par défaut, un message d'un ID non enregistré dans SNEK est logué comme `Receiving message with bad authentication` **sans détail sur l'ID ni le payload**. Bonne protection.
⚠️ **À lire attentivement avant tout déploiement en production ou test avancé.** **Attaque théoriquement possible** : enregistrer un ID device tiers + désactiver l'auth = recevoir les payloads bruts en clair tant que le device est à portée radio.
### Ce que capte physiquement le dongle
Le dongle SDR est un **récepteur RF broadcast** sur 868 MHz (RC1). Il capte **tous les messages Sigfox** dans sa portée physique :
- ~1-5 km en zone urbaine dense
- ~10-40 km en zone dégagée avec bonne antenne
Cela inclut les émissions **de tes voisins, entreprises locales, capteurs de ville, alarmes, trackers…** — pas seulement les tiens.
### Ce que SNEK expose
Par défaut (auth activée, device non enregistré) :
- Message logué comme `bad authentication`
- **Aucun** ID, **aucun** payload, **aucun** RSSI accessible
- Bonne protection par défaut
Attaque théoriquement possible :
1. Enregistrer l'ID d'un device tiers dans SNEK (ID trouvé sur l'étiquette, dans une doc leakée, en OSINT)
2. Désactiver globalement l'authentification (option UI)
3. Recevoir les payloads bruts en clair du device cible tant qu'il est à portée radio
**Limites de l'attaque** : **Limites de l'attaque** :
- Nécessite la proximité physique (pas d'attaque via internet) - proximité physique requise (pas d'attaque via internet)
- Le format des 12 bytes de payload est propriétaire à chaque vendor — inutile sans reverse engineering - format propriétaire par vendor, donc reverse engineering nécessaire pour comprendre les 12 bytes
- Certains devices utilisent le chiffrement AES-128 optionnel de Sigfox → payload chiffré - certains devices utilisent l'AES-128 optionnel de Sigfox (rare)
### Aspect légal (France) **Aspects légaux France** :
- ✅ Écoute passive du spectre RF public : légal
- ✅ Analyse de tes propres devices : légal
- ❌ Interception intentionnelle de communications tierces : **délit** (Art. 226-15 Code Pénal, 1 an prison + 45 000€)
- ❌ Collecte/redistribution/monétisation : RGPD + concurrence déloyale
-**Réception passive du spectre RF public** : légal **Bonnes pratiques** :
- **Analyse de tes propres devices** : légal - Ne configure que tes propres devices dans SNEK
- **Recherche sécurité en labo perso** : légal - Baisse le log level à `WARNING` dans `snek.conf` pour ne pas garder trace des messages non-auth
- **Interception intentionnelle de communications tierces** : **délit pénal** - Purge régulièrement `/root/Snek/snek.log`
- Article 226-15 du Code Pénal → 1 an prison + 45 000 € d'amende - Pour tes propres devices sensibles, active l'AES-128 optionnel côté device
-**Collecte / redistribution / monétisation** des données captées : illégal (RGPD si données identifiantes, concurrence déloyale si usage commercial)
### Bonnes pratiques Sigfox est conçu pour l'**authenticité** (HMAC empêche l'usurpation) mais **pas la confidentialité par défaut**.
- **Ne configure QUE tes propres devices** dans SNEK
- **Laisse l'auth activée** (paramètre par défaut)
- **Baisse le log level à WARNING** pour ne pas garder de trace des messages non-authentifiés :
```json
"root": {"level": "WARNING", ...}
```
- **Purge régulièrement** `/root/Snek/snek.log`
- Pour tes propres devices sensibles → utilise l'**AES-128 optionnel** de Sigfox
Sigfox est conçu pour l'**authenticité** (HMAC empêche l'usurpation), pas la **confidentialité par défaut**. C'est une limitation connue du protocole, à prendre en compte pour tout use case impliquant des données sensibles.
---
## Downlink (messages serveur → device)
Sigfox permet 4 downlinks max par device par jour. Envoi depuis SNEK :
1. Web UI → onglet **Devices** → sélectionner un device
2. Zone **Downlink** → 8 bytes hex à envoyer
3. Le message sera envoyé lors du prochain uplink (le device demande un downlink en cours d'émission)
**Note** : le downlink est utilisé pour reconfigurer un Sens'it (changer de mode, ajuster seuils, etc.) via le format Config payload documenté dans le PDF Sens'it.
---
## Persistance & backups
Le PVC `snek-data` (2 Gi, `local-path`) monté sur `/root/Snek` contient :
- `snek.conf` : configuration serveur (devices, callbacks, MAX_DEVICES, radio)
- `snek.log` : logs applicatifs (rotation 5 × 10 MB)
- Historique des messages reçus
**Backup** : Velero avec `defaultVolumesToFsBackup: true` dans les schedules capture le PVC.
Pour un backup manuel :
```bash
kubectl exec -n snek -l app=snek -- cat /root/Snek/snek.conf > snek-backup-$(date +%F).json
```
---
## Dépannage ## Dépannage
### Le dongle n'est pas détecté **Dongle non détecté** : `kubectl exec -n snek deploy/snek -- lsusb | grep 2cc1` — si vide, vérifie que le dongle est branché, que le pod est `privileged: true` et que `/dev/bus/usb` est bien monté.
```bash **"No xfox device detected" en boucle** : dongle branché mais firmware pas flashé. Cause probable : session USB coincée (ex: après tentative KubeVirt USB passthrough). Débranche/rebranche physiquement + `kubectl delete pod -n snek -l app=snek`.
kubectl exec -n snek -l app=snek -- lsusb | grep 2cc1
```
Rien ? Vérifier : **SNEK reçoit mais dashboard vide** : callback non configuré ou body JSON dans le mauvais champ. Vérifie que le POST arrive avec `kubectl logs -n snek -l app=snek-dashboard | grep webhook`.
- Le dongle est branché physiquement sur le node
- `hostPath /dev/bus/usb` est bien monté (dans la spec du deployment)
- Le pod est en `privileged: true`
- Sur Talos : `talosctl ls /sys/bus/usb/devices` sur le node depuis le Mac
### Erreur "libxfox.so.0: cannot open shared object file" **Chart masqué** : normal si mode Button/Standby (rien à tracer). Sinon change le device dans le dropdown.
Manque de `LD_LIBRARY_PATH=/opt/snek`. L'image Docker le définit dans le `ENV`, si tu débug en shell interactif, il faut le re-exporter. **Login page bloquée** : si tu vois `{"error": "unauthorized"}` à la racine, le `/` a été mis dans la whitelist. Vérifie que `PUBLIC_ROUTES` dans `app.py` contient bien `"/"`.
### Erreur "cannot import name Fox, introspection typelib not found" **SSE ne pousse pas** : si dashboard derrière NPM/Nginx, ajouter dans **Advanced** :
```nginx
Manque de `GI_TYPELIB_PATH=/opt/snek`. Idem que ci-dessus. proxy_http_version 1.1;
proxy_buffering off;
### "No xfox device detected" en boucle proxy_cache off;
proxy_read_timeout 24h;
Le dongle est branché mais le firmware ne s'est pas flashé. Causes possibles :
- Autre process tient le lock USB (ex: si tu as tenté KubeVirt USB passthrough avant)
- Débranche/rebranche physiquement le dongle
- Restart le pod : `kubectl delete pod -n snek -l app=snek`
### Interface UI affiche 5 devices même après passer MAX_DEVICES à 10
Cache navigateur. **Hard refresh** (`Cmd+Shift+R` / `Ctrl+Shift+R`) ou fenêtre privée.
### Le pod crash au démarrage
```bash
kubectl logs -n snek -l app=snek --previous
```
Le plus souvent : dongle absent → foxctl fait `sys.exit(1)`. Solution : brancher le dongle, redémarrer le pod.
---
## Structure du repo
```
snek/
├── README.md ← ce document
├── build/
│ ├── Dockerfile ← image Ubuntu 20.04 + SNEK + deps
│ └── entrypoint.sh ← flash firmware + start server
└── deploy/
└── snek.yaml ← namespace + PVC + deployment + service
``` ```
## Ressources ## Ressources
@@ -479,6 +289,8 @@ snek/
- [Doc SNEK PDF](https://storage.googleapis.com/public-assets-xd-support-sigfox-production-338901379285/61071942-SIGFOX%20Network%20Emulator(19-11-2019).pdf) - [Doc SNEK PDF](https://storage.googleapis.com/public-assets-xd-support-sigfox-production-338901379285/61071942-SIGFOX%20Network%20Emulator(19-11-2019).pdf)
- [Sens'it Discovery (v3) payload](https://storage.googleapis.com/public-assets-xd-sigfox-production-338901379285/build/4059ae1jy7g2jmg/sensit-discovery-payload.pdf) - [Sens'it Discovery (v3) payload](https://storage.googleapis.com/public-assets-xd-sigfox-production-338901379285/build/4059ae1jy7g2jmg/sensit-discovery-payload.pdf)
- [Sens'it v2 uplink frames](https://storage.googleapis.com/public-assets-xd-sigfox-production-338901379285/build/4059ab1jy7g2v9l/sensit%20v2%20frames%20uplink.pdf) - [Sens'it v2 uplink frames](https://storage.googleapis.com/public-assets-xd-sigfox-production-338901379285/build/4059ab1jy7g2v9l/sensit%20v2%20frames%20uplink.pdf)
- [Sens'it v2 user guide](https://support.sigfox.com/docs/sens'it-v2-user-guide) — boutons et LED
- [sigfox/sensit-sdk](https://github.com/sigfox/sensit-sdk) — SDK officiel
--- ---