From ff648fa9b268c896b66b891e4e8e54f2607a1d9a Mon Sep 17 00:00:00 2001 From: gitomarcourt Date: Fri, 3 Jul 2026 17:16:32 +0200 Subject: [PATCH] init: SNEK on Kubernetes - Dockerfile: Ubuntu 20.04 + all SNEK dependencies (libgtk2-perl, libffi6, etc.) - entrypoint.sh: flash firmware via foxctl, start main_sigfox - deploy/snek.yaml: K8s manifest (namespace, PVC, deployment, LoadBalancer) - README: full documentation covering hardware, software, decoding, callbacks --- README.md | 356 ++++++++++++++++++++++++++++++++++++++++++++ build/Dockerfile | 45 ++++++ build/entrypoint.sh | 20 +++ deploy/snek.yaml | 82 ++++++++++ 4 files changed, 503 insertions(+) create mode 100644 README.md create mode 100644 build/Dockerfile create mode 100644 build/entrypoint.sh create mode 100644 deploy/snek.yaml diff --git a/README.md b/README.md new file mode 100644 index 0000000..cf45d13 --- /dev/null +++ b/README.md @@ -0,0 +1,356 @@ +# SNEK — Sigfox Network Emulator (K8s Edition) + +Une stack complète pour émuler un réseau Sigfox depuis n'importe quel cluster Kubernetes, avec un dongle SDR physique. + +**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.). + +--- + +## Sommaire + +1. [Vue d'ensemble](#vue-densemble) +2. [Hardware — le dongle Sigfox SDR](#hardware--le-dongle-sigfox-sdr) +3. [Software — SNEK](#software--snek) +4. [Déploiement](#déploiement) +5. [Configuration des devices](#configuration-des-devices) +6. [Réception de messages (uplink)](#réception-de-messages-uplink) +7. [Décodage des payloads](#décodage-des-payloads) +8. [Callbacks HTTP (intégration avec autres services)](#callbacks-http-intégration-avec-autres-services) +9. [Downlink (messages serveur → device)](#downlink-messages-serveur--device) +10. [Persistance & backups](#persistance--backups) +11. [Dépannage](#dépannage) + +--- + +## Vue d'ensemble + +``` + ┌─────────────┐ + │ Device │ (Sens'it, capteur custom, etc.) + │ Sigfox │ + └──────┬──────┘ + │ RF 868 MHz (EU) + ▼ + ┌─────────────┐ + │ Dongle │ (NXP LPC 2cc1:8001 → 2cc1:0001 après flash) + │ SDR USB │ + └──────┬──────┘ + │ USB via /dev/bus/usb (hostPath mount) + ▼ + ┌──────────────────────────┐ + │ Pod SNEK (K8s) │ + │ - 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 │ + └─────────────┘ +``` + +--- + +## Hardware — le dongle Sigfox SDR + +**Modèle utilisé** : Sigfox SDR Dongle officiel (chipset NXP LPC). + +**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 + +### Prérequis cluster + +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 +cd snek/build +docker build --platform linux/amd64 -t 192.168.1.100:30500/snek:latest . +docker push 192.168.1.100:30500/snek:latest +``` + +Remplace `192.168.1.100:30500` par ton registry. + +### Deploy + +```bash +kubectl apply -f snek/deploy/snek.yaml +``` + +Le manifest crée : +- 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 +kubectl get pods -n snek +kubectl logs -n snek -l app=snek --tail=30 +``` + +Tu dois voir dans les logs : +``` +==> 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://: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 (10 avec la modif `MAX_DEVICES` du fichier `/root/Snek/snek.conf`). + +**Important** : SNEK décode les messages **seulement des devices enregistrés**. Un message d'un ID inconnu apparaît dans les logs mais n'est pas persisté. + +--- + +## 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 +kubectl logs -n snek -l app=snek -f | grep -i "receiving\|register" +``` + +--- + +## Décodage des payloads + +Les payloads Sigfox sont **binaires**, format propre à chaque device. Voici le décodage pour un **Sens'it Discovery en mode Temperature** (4 bytes). + +### Structure + +| Byte | Bits | Contenu | +|------|------|---------| +| 0 | 7-3 | Battery Level (5 bits, formule `V = val × 0.05 + 2.7`) | +| 0 | 2-0 | Reserved `0b110` | +| 1 | 7-3 | Mode (`00001` = Temperature) | +| 1 | 2 | Temperature MSB (1 bit) | +| 1 | 1 | Spare | +| 1 | 0 | Button Alert Flag (1 si double-clic) | +| 2 | 7-0 | Temperature LSB (formule `T = (val − 200) / 8` °C) | +| 3 | 7-0 | Humidity (formule `H = val / 2` %) | + +### Exemple + +Payload hex : `b60dc86e` + +- Byte 0 = `0xB6` = `1011 0110` → Battery = 22 × 0.05 + 2.7 = **3.8V** +- Byte 1 = `0x0D` = `0000 1101` → Mode = 1 (Temperature), Temp MSB = 1, Button = 1 +- Byte 2 = `0xC8` = 200 → Temp raw = 1×256 + 200 = 456 → **32°C** +- Byte 3 = `0x6E` = 110 → Humidity = 110 / 2 = **55%** + +### Doc officielle Sens'it + +[Sens'it Discovery Payload Structure (PDF)](https://storage.googleapis.com/public-assets-xd-sigfox-production-338901379285/build/4059ae1jy7g2jmg/sensit-discovery-payload.pdf) + +Le document décrit les 6 modes (Standby, Temperature, Light, Door, Vibration, Magnet) et le format Config payload pour les downlinks. + +--- + +## 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 + +Dans la UI SNEK → onglet **Callbacks** → **Add callback** : +- **URL** : `http://mon-service.mon-ns.svc.cluster.local:3000/api/sigfox` +- **Method** : POST +- **Content-Type** : `application/json` +- **Body** : template avec variables SNEK (voir doc SNEK) + +### Exemple de body template + +```json +{ + "device": "{device}", + "time": "{time}", + "data": "{data}", + "rssi": {rssi}, + "snr": {snr}, + "seqNumber": {seqNumber} +} +``` + +Variables disponibles : +- `{device}` : ID hex du device +- `{time}` : timestamp Unix +- `{data}` : payload en hex +- `{rssi}` : force du signal reçu +- `{snr}` : rapport signal/bruit +- `{seqNumber}` : compteur de séquence + +### Cas d'usage + +- **Ingester dans une base** : POST vers une API Node/Go/Python qui insert dans Postgres/InfluxDB +- **Alertes** : POST vers Telegram bot API pour notification instantanée +- **Bus de messages** : POST vers Kafka REST proxy, RabbitMQ HTTP plugin, etc. +- **Dashboard live** : POST vers un WebSocket relay (Socket.io, Server-Sent Events) + +--- + +## 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 + +### Le dongle n'est pas détecté + +```bash +kubectl exec -n snek -l app=snek -- lsusb | grep 2cc1 +``` + +Rien ? Vérifier : +- 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" + +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. + +### Erreur "cannot import name Fox, introspection typelib not found" + +Manque de `GI_TYPELIB_PATH=/opt/snek`. Idem que ci-dessus. + +### "No xfox device detected" en boucle + +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 + +- [Support Sigfox SDR](https://support.sigfox.com/products/SDR-dongle) +- [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 Payload](https://storage.googleapis.com/public-assets-xd-sigfox-production-338901379285/build/4059ae1jy7g2jmg/sensit-discovery-payload.pdf) + +--- + +Made with Kubernetes, some patience, and a Sigfox dongle. 📡 diff --git a/build/Dockerfile b/build/Dockerfile new file mode 100644 index 0000000..bfbe4b0 --- /dev/null +++ b/build/Dockerfile @@ -0,0 +1,45 @@ +FROM ubuntu:20.04 + +LABEL org.opencontainers.image.title="Sigfox Network Emulator (SNEK)" +LABEL org.opencontainers.image.description="SNEK 2.3.4 pre-installed with all dependencies for Sigfox SDR dongle" + +ARG DEBIAN_FRONTEND=noninteractive +ARG SNEK_URL=https://support.sigfox.com/files/5e6202fa7d734d7f7f84a08e + +# System dependencies (GTK stack + SNEK runtime deps) +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + curl \ + wget \ + ca-certificates \ + usbutils \ + libgtk2-perl \ + zenity \ + python \ + libdbus-glib-1-2 \ + && rm -rf /var/lib/apt/lists/* + +# libffi6 is not in Ubuntu 20.04 by default (SNEK is built against Ubuntu 18.04 ABI) +RUN cd /tmp && \ + wget -q http://archive.ubuntu.com/ubuntu/pool/main/libf/libffi/libffi6_3.2.1-8_amd64.deb && \ + dpkg -i libffi6_3.2.1-8_amd64.deb && \ + rm libffi6_3.2.1-8_amd64.deb + +# Download and install SNEK +RUN curl -Ls "${SNEK_URL}" -o /tmp/snek.deb && \ + dpkg -i /tmp/snek.deb || apt-get install -f -y && \ + rm /tmp/snek.deb && \ + rm -rf /var/lib/apt/lists/* + +# Entrypoint: flash firmware then start SNEK server +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENV LD_LIBRARY_PATH=/opt/snek +ENV GI_TYPELIB_PATH=/opt/snek + +WORKDIR /opt/snek + +EXPOSE 8085 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/build/entrypoint.sh b/build/entrypoint.sh new file mode 100644 index 0000000..55a1301 --- /dev/null +++ b/build/entrypoint.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -e + +echo "==> Sigfox Network Emulator container starting" +echo "==> Detecting Sigfox dongle..." + +if lsusb | grep -iE "2cc1"; then + echo "==> Dongle detected" +else + echo "!!! WARNING: no Sigfox dongle detected on /dev/bus/usb" + echo "!!! Make sure the container has: privileged: true AND /dev/bus/usb mounted" +fi + +echo "==> Flashing firmware (foxctl)..." +cd /opt/snek +./foxctl || echo "foxctl RC=$? (may be normal if firmware already loaded)" +sleep 2 + +echo "==> Starting SNEK main_sigfox on port 8085..." +exec ./main_sigfox diff --git a/deploy/snek.yaml b/deploy/snek.yaml new file mode 100644 index 0000000..6f3a290 --- /dev/null +++ b/deploy/snek.yaml @@ -0,0 +1,82 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: snek + labels: + pod-security.kubernetes.io/enforce: privileged +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: snek-data + namespace: snek +spec: + accessModes: + - ReadWriteOnce + storageClassName: local-path + resources: + requests: + storage: 2Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: snek + namespace: snek +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: snek + template: + metadata: + labels: + app: snek + spec: + containers: + - name: snek + image: 192.168.1.100:30500/snek:latest + imagePullPolicy: Always + ports: + - containerPort: 8085 + name: web + securityContext: + privileged: true + volumeMounts: + - name: usb-bus + mountPath: /dev/bus/usb + - name: snek-data + mountPath: /root/Snek + resources: + requests: + memory: 512Mi + cpu: 200m + limits: + memory: 2Gi + cpu: 1 + volumes: + - name: usb-bus + hostPath: + path: /dev/bus/usb + type: Directory + - name: snek-data + persistentVolumeClaim: + claimName: snek-data +--- +apiVersion: v1 +kind: Service +metadata: + name: snek + namespace: snek +spec: + type: LoadBalancer + loadBalancerIP: 192.168.1.212 + externalTrafficPolicy: Local + selector: + app: snek + ports: + - name: web + port: 8085 + targetPort: 8085