API - Temps réel
API - Temps réel
Cette page détaille la consommation des flux temps réel Phyling : ouverture de la connexion Socket.IO, souscriptions aux rooms disponibles, sélection des signaux de télémétrie et exécution de commandes RPC.
Snapshot REST indispensable
Avant d'ouvrir la socket, constituez un snapshot complet de chaque device via la route settings. Ce référentiel vous permet de connaître les modules disponibles, les commandes RPC, les clés temps réel et d'appliquer correctement les patchs reçus en flux.
Récupérer les réglages device
GET /devices/rt/{client_id}/{device_number}/settings
Authorization: Bearer <access_token> || ApiKey <key>La réponse sert de snapshot de référence pour le device. On y retrouve :
- Les métadonnées globales (
battery,state,version,timezone,sport, etc.). epoch: horodatage Unix courant du device, en secondes (float, précision milliseconde) — même base de temps que la colonneepochdes flux de mesure et d'indicateurs.stream_outside_record: booléen indiquant que le device diffuse un flux temps réel hors enregistrement (les clés temps réel sont alors exposées même sans record en cours).mqtt_refresh_delay: intervalle (en secondes) entre deux réveils MQTT du device.0pour les devices toujours connectés (Maxi) ; positif pour les Phyling-LTE en économie d'énergie. Une commande RPC peut mettre jusqu'à ce délai avant d'atteindre le device (voir §4).disconnect_timeout: délai (en secondes) sans nouvelle du device au-delà duquel il est considéré déconnecté.- Les rattachements (
client,group,user) avec leurs identifiants. - Un résumé des modules de la config avec des infos dessus.
- Le dictionnaire
featureslistant toutes les commandes RPC disponibles (voir §4). - Les clés temps réel (quand disponibles) via
realtime_data_keysetrealtime_indicator_keys.
Extrait simplifié :
{
"number": 10300378,
"name": "Maxi 378",
"state": "record",
"is_connected": true,
"battery": 39,
"timezone": "Europe/Paris",
"epoch": 1762441207.142,
"stream_outside_record": false,
"mqtt_refresh_delay": 0,
"disconnect_timeout": 30,
"recTime": 23.142,
"selectionStartTime": 0,
"startRecTimeSinceEpoch": 0,
"deviceRecId": 42,
"features": {
"record": { "cmd_start": "v1.record.rec.start", "cmd_stop": "v1.record.rec.stop" },
"restart": { "cmd": "v1.board.restart" },
"...": "..."
},
"modules": {
"gps": { "id": 1, "is_connected": false, "realtime": false, "type": "gps" },
"imu": { "id": 2, "is_connected": true, "realtime": true, "type": "imu" }
},
"realtime_data_keys": [
{ "name": "gps.gpstimeUs", "label": "gps.gpstimeUs", "description": "", "unit": "us", "precision": 3, "yRange": [] },
{ "name": "gps.gpsTimeAccuracyNs", "label": "gps.gpsTimeAccuracyNs", "description": "", "unit": "ns", "precision": 3, "yRange": [] },
{ "...": "..." }
],
"realtime_indicator_keys": [
{ "unit": { "fr": "cpm", "en": "spm" }, "description": { "fr": "Cadence", "en": "Cadence" }, "precision": 1, "enabled": true, "name": "cadence", "type": "number" },
{ "...": "..." }
],
"client": { "id": 2, "name": "phyling" },
"group": { "id": 1, "name": "phyling" },
"user": { "active": true, "firstname": "<firstname>", "id": 42, "lastname": "<lastname>", "mail": "<mail>" },
"sport": { "display_name": "Aviron", "name": "aviron" },
"version": "v7.0.1",
"...": "..."
}Fusionner settings et status
Les événements Socket.IO app/device/{device}/board/status émettent des patchs partiels qui réutilisent la même structure que settings. Après chaque patch, effectuez un merge profond avec votre snapshot local pour conserver l'état à jour (batterie, modules connectés, nouvelles clés temps réel, etc.). Les exemples realtime.html et single-device.html fournis dans ce dépôt contiennent une fonction deepMerge réutilisable si besoin.
1. Connexion Socket.IO
- Déduisez l'URL Socket.IO à partir de l'URL API REST : même host/port,
ws://lorsque l'API est en HTTP,wss://sinon.
const apiBase = 'https://api.app.phyling.fr';
const socketBase = 'wss://api.app.phyling.fr';
const socket = io(socketBase, { transports: ['websocket'] });- Attendez l'événement
connectpour garantir que la socket est prête. - Émettez
subscribepour chaque room à écouter en incluant le token d'accès.
socket.emit('subscribe', {
authorization: `Bearer ${accessToken}`,
room: `app/client/${clientId}/device/list_connected`
});- Sur
disconnect, relancez la connexion puis réémettez les souscriptions actives. - Pour quitter une room, envoyez
socket.emit('unsubscribe', { room }).
🔐 Chaque
subscribedoit inclure{ token: <access_token>, room: <room> }. Sans token valide, la socket rejette l'abonnement.
2. Rooms disponibles
| Room | Description | Payload reçu |
|---|---|---|
app/client/{client_id}/device/list_connectedRenvoie sur app/client/device/list_connected | Liste complète des devices d'un client avec leur état de connexion. | Tableau d'objets { number, name, is_connected, state, ... } pour détecter les (dé)connexions et initialiser vos stores. |
app/device/{device_number}/ind/json/allRenvoie sur app/device/ind/json/all | Instantané des indicateurs calculés pour un device. | Objet { number, recTime, epoch, indicators: { ... } } exploitable pour du monitoring (voir §2.1). |
app/device/{device_number}/board/statusRenvoie sur app/device/board/status | Patchs d'état à fusionner avec settings. | Mises à jour de batterie, state, modules, clés temps réel, epoch, etc. |
app/device/{device_number}/data/json/allRenvoie sur app/device/data/json/all | Flux temps réel des mesures sélectionnées. | Objet { number, recTime, data: { module: { T:[], epoch:[], <signal>:[] } } } (voir §2.1). |
2.1. Format des payloads de mesure et d'indicateurs
Indicateurs — app/device/ind/json/all
Le payload porte un epoch global (au même niveau que number et recTime) et le dictionnaire indicators (dernière valeur connue de chaque indicateur).
recTime: temps écoulé depuis le début du record, en secondes.epoch: horodatage Unix absolu correspondant, en secondes (float, précision milliseconde).indicators:{ <nom>: { "x": <temps>, "y": <valeur> } }. Chaque indicateur fournit son pointx(temps) /y(valeur).
{
"number": 10300378,
"recTime": 12.345,
"epoch": 1762441219.345,
"indicators": {
"speed": { "x": 12.345, "y": 28.9 },
"power": { "x": 12.345, "y": 410.0 }
}
}Mesures — app/device/data/json/all
Le payload regroupe les mesures par module. Chaque module fournit, en plus de ses signaux, deux colonnes de temps alignées échantillon par échantillon avec les signaux :
T: temps depuis le début du record, en secondes (axe local au record).epoch: horodatage Unix absolu de chaque échantillon, en secondes (permet de recaler le flux sur une horloge absolue, utile pour synchroniser plusieurs devices ou un flux hors record).
Toutes les colonnes d'un même module (T, epoch et les signaux) ont la même longueur : l'indice i de chaque tableau correspond au même échantillon.
{
"number": 10300378,
"recTime": 12.345,
"data": {
"imu": {
"T": [12.301, 12.311, 12.321],
"epoch": [1762441219.301, 1762441219.311, 1762441219.321],
"acc_x": [0.10, 0.12, 0.09],
"acc_y": [-0.01, 0.00, 0.02],
"acc_z": [9.79, 9.80, 9.81]
}
},
"selections": [
{ "num": 1, "start": 5.0, "stop": 9.5 },
{ "num": 2, "start": 11.0, "stop": -1 }
]
}selections liste les sélections (SelectionRT) du record en cours ou arrêtées il y a moins de 30 s. Chaque entrée porte son numéro (num) et ses bornes en secondes depuis le début du record (start, stop) ; un stop négatif signale une sélection encore en cours.
Le
recTimeau niveau racine reflète l'avancement du record au moment de l'envoi ; pour positionner précisément chaque point, utilisez la colonneT(ouepoch) du module concerné.
3. Sélectionner les signaux diffusés
Avant de recevoir app/device/{device}/data/json/all, adressez la sélection désirée via la route REST dédiée.
POST /devices/rt/{client_id}/{device_number}/realtime
Authorization: Bearer <access_token>
Content-Type: application/json
{
"realtime_data_keys": [
"imu.acc_x",
"imu.acc_y",
"imu.acc_z"
]
}Vous pouvez également activer des modules entiers :
POST /devices/rt/{client_id}/{device_number}/realtime
Authorization: Bearer <access_token>
Content-Type: application/json
{
"modules": ["imu"]
}- Référez-vous aux noms listés dans
realtime_data_keys(route settings ou dernier status). - Envoyer une liste vide coupe immédiatement le flux temps réel pour ce device.
- Répétez périodiquement la sélection (~30 s) pour maintenir la diffusion active.
- L'activation est réalisée au niveau module : demander
imu.acc_xactive tout le moduleimu. Si un autre client activegps, tout le monde reçoit à la fois imu et gps.
4. Exécuter des commandes (RPC)
Les commandes device sont asynchrones et transitent par deux routes : on envoie la commande via /rpc/request (qui répond immédiatement), puis on récupère le résultat en interrogeant /rpc/response.
1. Envoyer la commande :
POST /devices/rt/{client_id}/{device_number}/rpc/request
Authorization: Bearer <access_token>
{
"method": "v1.record.rec.start",
"params": {}
}methoddoit correspondre à une valeur listée danssettings.features. Exemple :settings.features.record.cmd_startcontient la valeur"v1.record.rec.start"à utiliser dansmethod.feature/cmd_typesont une syntaxe alternative :{ "feature": "record", "cmd_type": "cmd_start" }est équivalent à{ "method": "v1.record.rec.start" }.
La route répond toujours immédiatement :
201: la commande est en cours de traitement. La réponse contient l'idde la requête, à utiliser pour récupérer le résultat.{ "message": "Command is being processed", "id": "3f2a1c9e-1b2c-4d5e-8f90-abcdef123456" }200: la commande a un résultat immédiat (notification ou valeur en cache) — aucun polling nécessaire.
2. Récupérer le résultat :
POST /devices/rt/{client_id}/{device_number}/rpc/response
Authorization: Bearer <access_token>
{
"id": "3f2a1c9e-1b2c-4d5e-8f90-abcdef123456"
}202: la commande est toujours en cours de traitement — réessayez plus tard.200(ou un code d'erreur mappé) : le résultat est disponible. Le polling est idempotent : le résultat reste consultable jusqu'à son expiration (quelques minutes après le timeout de la commande).404:idinconnu ou expiré.
En pratique, on interroge /rpc/response une fois par seconde après le 201, jusqu'à obtenir un code différent de 202. Surveillez également app/device/{device}/board/status pour voir l'état fusionné avec vos settings locaux.
⏱️ Délai d'exécution (Phyling-LTE). Sur un device en économie d'énergie, le résultat peut mettre jusqu'à
mqtt_refresh_delay + 4secondes à devenir disponible (le device ne se réveille qu'à intervallemqtt_refresh_delay, cf. §1). Une commande commev1.record.rec.start/stoppeut donc renvoyer202pendant plusieurs secondes ; au-delà,/rpc/responserenvoie l'erreur-32003(timeout). Pour les devices toujours connectés (mqtt_refresh_delay = 0), le délai reste de 4 s.
4.1 Codes d'erreur RPC
Chaque réponse RPC peut porter un objet error au lieu d'un result. Les codes suivent la spécification JSON-RPC 2.0 et sont étendus par des codes Phyling négatifs dans la plage -32000 à -32004.
| Code | Message | Signification |
|---|---|---|
| Erreurs JSON-RPC standard | ||
-32700 | Parse error | JSON invalide reçu par le serveur. |
-32600 | Invalid Request | L'objet envoyé n'est pas une requête JSON-RPC valide. |
-32601 | Method not found | La méthode n'existe pas ou n'est pas disponible. |
-32602 | Invalid params | Paramètre(s) invalide(s). |
-32603 | Internal error | Erreur interne JSON-RPC. |
| Erreurs Phyling | ||
-32000 | Service unavailable | MQTT déconnecté, device hors ligne, en cours d'extinction, etc. |
-32001 | Record in progress | La méthode RPC ne peut pas être exécutée pendant un enregistrement. |
-32002 | Not recording | La méthode RPC ne peut pas être exécutée en dehors d'un enregistrement. |
-32003 | Request timeout | La réponse du device n'est jamais arrivée dans le délai imparti. |
-32004 | Max size reached | Le contenu dépasse la taille maximale acceptée. |
4.2 RPC courants
Arrêt / Redémarrage — v1.board.shutdown · v1.board.restart
{ "method": "v1.board.shutdown" }
{ "method": "v1.board.restart" }Résultat :
{ "actions": ["shutdown"] }
{ "actions": ["restart"] }Erreurs possibles : -32000 (service unavailable), -32001 (record en cours), -32003 (timeout).
Un enregistrement en cours bloque l'arrêt/redémarrage. Stoppez d'abord l'enregistrement via
v1.record.rec.stop.
Lire un fichier — v1.board.<elem>.get
<elem> peut être : config, calib, wifi, device_info.
{ "method": "v1.board.config.get" }Résultat :
{ "content": { /* contenu JSON du fichier */ } }Erreurs possibles : -32000, -32003.
Écrire un fichier — v1.board.<elem>.set
<elem> peut être : config, calib, wifi, device_info.
{
"method": "v1.board.config.set",
"params": { "content": { /* contenu JSON complet */ } }
}Variantes par élément :
| Élément | Variante params | Effet |
|---|---|---|
calib | { "merge_calib": { … } } | Fusionne partiellement avec la calibration existante (ajout module, update factor…) |
Résultat : { "actions": ["restart"] } (sauf pour calib qui ne redémarre pas).
Erreurs possibles : -32000, -32001 (record en cours), -32602 (params invalides), -32003.
Démarrer un enregistrement — v1.record.rec.start
{ "method": "v1.record.rec.start" }Résultat :
{
"set_status": {
"state": "record",
"maxiRecId": 42
}
}Erreurs possibles : -32000, -32001 (enregistrement déjà en cours), -32003.
Arrêter un enregistrement — v1.record.rec.stop
{
"method": "v1.record.rec.stop",
"params": { "force_stop": false }
}force_stop: false(défaut) : le device passe en mode recovery (recovery_record) avant de s'arrêter complètement. À utiliser dans le cas nominal.force_stop: true: le device saute le mode recovery et s'arrête immédiatement.
Résultat :
{
"set_status": {
"state": "idle"
}
}ou "state": "recovery_record" si le device est en recovery.
Erreurs possibles : -32000, -32002 (aucun enregistrement en cours), -32003.
5. Spécificités Phyling-LTE
Certains devices Phyling-LTE (capteurs connectés en LTE-M) présentent des caractéristiques particulières lors de la reprise de connexion réseau. Ces comportements sont intégrés par design et transparents pour l'utilisateur.
Données anciennes hors d'ordre à la reprise réseau
Lors d'une interruption prolongée du réseau (underground, tunnel, etc.), un device Phyling-LTE continue d'enregistrer les mesures localement. À la reconnexion, le device renvoie ces données « historiques » mélangées à de nouvelles données, et potentiellement hors ordre chronologique à cause des délais de buffering MQTT.
Exemple : un device offline 5 minutes renvoie des données de t=100s à t=305s. À la reconnexion, les payloads reçus peuvent arriver dans un ordre qui ne garantit pas t=100 avant t=300.
Implication pour l'API : les clients qui consomment app/device/{device}/data/json/all peuvent recevoir des messages avec epoch ou T décroissants. Appliquez un filtre de fraîcheur dans votre code consommateur (ex. ignorer un échantillon si son timestamp est plus ancien que le dernier reçu).
realtimeRate et realtimeSlowRate comme plafonds nominaux
La métadonnée realtimeRate (cadence de diffusion temps réel, Hz) indiquée dans settings est un plafond nominal, pas une garantie. En cas de congestion réseau ou de backlog de données, le device Phyling-LTE peut réduire dynamiquement la cadence d'envoi pour éviter la saturation du lien. Cette réduction est transparente et temporaire : la cadence revient au plafond dès que le backlog est résorbé.
Les devices Phyling-LTE exposent également realtimeSlowRate : cadence réduite (Hz) utilisée automatiquement quand le lien est dégradé ou que le backlog est important. Le device bascule entre realtimeRate et realtimeSlowRate sans intervention extérieure.
Implication : ne supposez pas une cadence fixe 1/realtimeRate entre deux messages. Utilisez toujours les timestamps des colonnes T ou epoch pour positionner les données sur votre axe des temps ; ne vous fiez pas à une cadence constante.
6. Ressources complémentaires
Des exemples prêts à l'emploi sont disponibles dans le dépôt open-source Phyling pour tester rapidement l'API sans partir de zéro : exemples RAW API.
- Bibliothèque et exemples Phyling : github.com/phyling-sport/phyling — client Python haut niveau + exemples complets (authentification, temps réel, RPC).
- Exemples RAW API : RAW API examples — appels directs à l'API REST et Socket.IO sans dépendance tierce (
minimal_oauth.py,minimal_apikey.html,realtime.html,single-device.html). - La documentation Swagger fournie avec l'API inventorie l'ensemble des routes REST (login, refresh, devices, temps réel, RPC).