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é.available_connectivity: liste des connectivités supportées par le device (wifi,lte).network_infos: informations réseau du device ;has_roamingindique si le WiFi multi-borne est actif (condition pourv1.board.wifi.connect, voir §4.2).- 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,
"available_connectivity": ["wifi", "lte"],
"network_infos": { "has_roaming": false },
"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, ou (calibration auto) tant que le device n'est pas à l'état idle. |
-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. |
L'objet error peut porter un champ data optionnel, quand le device a un détail exploitable à ajouter à son code d'erreur — typiquement une chaîne reason en snake_case, stable et destinée au code appelant, pas à l'affichage. Un error sans data reste la forme normale : ne pas en faire une condition.
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.
Changer de point d'accès WiFi — v1.board.wifi.connect
📡 Phyling-LTE en mode WiFi multi-borne uniquement. Vérifiez
network_infos.has_roaming(voir §1) avant de l'appeler ; les autres firmwares ne déclarent pas cette méthode et répondent-32601.
Demande au capteur de quitter immédiatement sa borne actuelle, sans attendre que les conditions habituelles (marge de gain, seuils de RSSI, délai entre deux bascules) soient réunies. Utile pour dégager un capteur collé à une borne saturée, et pour rejouer un essai de roaming sans dépendre des conditions radio.
Trois formes, selon les paramètres fournis :
| Paramètre | Effet |
|---|---|
| (aucun) | Le capteur lance un scan complet, puis se connecte à la borne au meilleur RSSI. |
ssid | SSID d'un réseau activé de la configuration du device. Le capteur se connecte à la meilleure autre borne de ce SSID présente dans sa table de voisinage. |
bssid | Adresse MAC d'une borne, au format xx:xx:xx:xx:xx:xx. Le capteur s'y connecte si elle est présente dans sa table de voisinage. |
Fournir ssid et bssid à la fois est une erreur de paramètres.
Dans les trois formes, être déjà sur la borne demandée est un succès, pas un refus : le capteur ne bouge pas, aucune coupure n'a lieu, et la réponse le dit avec changed: false. La question « suis-je sur la bonne borne ? » se lit donc sur le code de retour, et changed dit seulement s'il a fallu bouger pour y être.
{ "method": "v1.board.wifi.connect" }
{ "method": "v1.board.wifi.connect", "params": { "ssid": "Phyling-Stade" } }
{ "method": "v1.board.wifi.connect", "params": { "bssid": "cf:b8:ed:3c:01:02" } }Avec ssid ou bssid, le capteur ne lance pas de scan pour trouver la cible : il n'utilise que ce que ses scans lui ont déjà appris. Une borne jamais vue est donc refusée, pas tentée à l'aveugle. Sans paramètre, au contraire, le scan est le but même de l'appel — la réponse met alors une à deux secondes de plus à arriver. La bascule elle-même emprunte dans tous les cas le chemin normal du firmware : c'est le même comportement que celui déclenché spontanément par la dégradation du signal.
Résultat :
{
"from_bssid": "cf:b8:ed:3e:01:02",
"from_rssi": -74,
"bssid": "cf:b8:ed:3c:01:02",
"rssi": -61,
"channel": 6,
"changed": true
}| Champ | Description |
|---|---|
from_bssid / from_rssi | Borne quittée et son dernier RSSI mesuré (dBm). |
bssid / rssi | Borne visée et son dernier RSSI mesuré (dBm). |
channel | Canal de la borne visée ; 0 si le capteur laisse le supplicant la chercher. |
changed | false = la borne demandée était déjà la borne courante, aucune bascule n'a eu lieu et les champs from_* répètent la borne conservée. |
La réponse arrive dès que la demande d'association est partie, pas à la fin de la bascule : le lien est coupé pendant quelques secondes ensuite. Suivez la reprise sur les champs de télémétrie temps réel exposés par board/status (ap_bssid, net_state, cut_ms, roam_count).
Erreurs possibles :
Chaque refus du device porte un error.data.reason (cf. §4.1) qui précise laquelle des causes du code s'applique :
{
"code": -32000,
"message": "Service unavailable",
"data": { "reason": "target_not_seen", "current_bssid": "cf:b8:ed:3e:01:02" }
}| Code | reason | Cause |
|---|---|---|
-32601 | (aucun) | Firmware sans roaming (méthode non déclarée). |
-32602 | ambiguous_target | ssid et bssid fournis ensemble. |
-32602 | malformed_bssid | bssid qui n'est pas au format xx:xx:xx:xx:xx:xx. |
-32602 | malformed_target | ssid ou bssid fourni n'est pas une chaîne, ou ssid dépasse la longueur maximale acceptée. |
-32602 | ssid_not_configured | ssid qui ne correspond à aucun réseau activé de la configuration du device. |
-32000 | target_not_seen | La borne visée n'est pas dans la table de voisinage (aucun scan ne l'a vue). |
-32000 | not_associated | Le capteur n'est associé à aucune borne. |
-32000 | radio_busy | Une bascule ou un scan est déjà en cours. |
-32000 | scan_timeout | Le scan demandé (appel sans paramètre) n'a pas abouti. |
-32603 | switch_failed | Le supplicant a refusé la demande d'association ; data.errno porte son code. |
-32003 | (aucun) | Timeout côté serveur (pas de réponse du device). |
Sur target_not_seen et not_associated, data porte en plus current_bssid, la borne sur laquelle le capteur se trouve — absent si le lien est coupé et qu'il n'y en a aucune.
🔎 Distinguer les deux refus les plus courants.
ssid_not_configuredveut dire que ce réseau n'est pas (ou plus) activé dans lewifi.jsondu device — c'est un problème de configuration.target_not_seenveut dire que la cible est bien configurée mais que le capteur ne la voit pas — c'est un problème de couverture radio.
Lister les calibrations automatiques — v1.board.calib.auto.get
📡 Phyling-LTE uniquement. Les autres devices ne déclarent pas cette commande : vérifiez la présence de la clé
calib_autodansfeatures(voir §1) avant de l'appeler, sinon la requête répond-32601.
Retourne la liste des calibrations que le capteur sait exécuter lui-même, sans intervention du serveur.
{ "method": "v1.board.calib.auto.get" }Résultat :
{
"calibrations": [
{
"module": "imu",
"name": "gyro",
"duration": 3,
"title": { "fr": "Calibration gyro", "en": "Gyro calibration" },
"description": { "fr": "Posez le capteur…", "en": "Place the sensor…" }
}
]
}| Champ | Description |
|---|---|
module | Nom de l'instance du module dans la configuration du device (pas son type). À réutiliser tel quel dans v1.board.calib.auto.start. |
name | Identifiant de la calibration, à réutiliser tel quel. |
duration | Durée de la mesure en secondes, propre à chaque calibration. |
title | Libellé court localisé (fr / en), à afficher dans votre interface. |
description | Consigne localisée à suivre avant de lancer la calibration (position du capteur, immobilité…). |
Erreurs possibles : -32000, -32003, -32603 (échec interne côté device en listant les calibrations).
Lancer une calibration automatique — v1.board.calib.auto.start
📡 Phyling-LTE uniquement. Même remarque que ci-dessus sur la clé
calib_autodefeatures.
{
"method": "v1.board.calib.auto.start",
"params": {
"module": "imu",
"name": "gyro"
}
}module et name doivent provenir de v1.board.calib.auto.get.
Résultat :
{
"set_status": {
"state": "calibrating"
}
}La réponse arrive immédiatement, sans attendre la fin de la mesure. Le device passe alors à l'état calibrating (LED cyan) pendant duration secondes, puis enregistre le résultat et revient à idle. Suivez l'avancement sur le champ state du status du device.
Déroulé complet :
- Le device doit être à l'état
idle: une calibration ne peut pas démarrer pendant un enregistrement. - Affichez la
descriptionde la calibration et attendez que l'utilisateur ait placé le capteur. - Envoyez la commande. Le capteur ne doit pas bouger tant qu'il n'est pas revenu à
idle. - À la fin, la nouvelle calibration est appliquée et publiée automatiquement ;
v1.board.calib.getrenvoie la version à jour.
⚠️ Si le capteur a bougé pendant la mesure, ou si le résultat sort des plages attendues, la calibration est rejetée : rien n'est enregistré, l'ancienne calibration reste en place et le device revient à
idle. Recommencez sur un support stable.
Erreurs possibles : -32602 (module ou calibration inconnue), -32000 (module déconnecté), -32001 (device pas à l'état idle, ou calibration déjà en cours), -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).