Backend de données
IronFlock provisionne une base de données privée pour chaque projet, propulsée par TimescaleDB. Votre application définit le schéma de données ; IronFlock crée les tables et commence à collecter les données dès qu’un appareil est ajouté à l’application.
Fonctionnement
- Définissez votre schéma de données dans
.ironflock/data-template.yml. - Utilisez le SDK IronFlock pour publier des données depuis votre code edge.
- IronFlock configure automatiquement les tables de base de données dans chaque projet où l’application est installée.
- Les données circulent des appareils via le système de messagerie vers la base de données du projet.
Chaque projet dispose de sa propre base de données physique — il n’y a aucun partage de données entre les projets.
L’utilisateur a le contrôle total sur les données collectées par votre application dans son projet. En tant que développeur, vous n’avez pas accès à ces données.
Définir le schéma de données
Créez un fichier data-template.yml dans le répertoire .ironflock/ :
data:
tables:
- tablename: sensordata
columns:
- id: tsp
name: Timestamp
description: Timestamp of measurement
path: args[0].timestamp
dataType: timestamp
- id: temperature
name: Temperature
description: Temperature reading in Celsius
path: args[0].temperature
dataType: numeric
- id: humidity
name: Humidity
description: Relative humidity percentage
path: args[0].humidity
dataType: numeric
- id: device_id
name: Device ID
description: Source device identifier
path: args[0].device_id
dataType: stringOptions des colonnes
| Champ | Description |
|---|---|
id | Identifiant interne de la colonne (utilisez tsp pour les colonnes timestamp) |
name | Nom de colonne lisible affiché dans les boards |
description | Description optionnelle |
path | Chemin vers la valeur dans l’objet de données publié (p. ex. args[0].temperature) |
dataType | L’un des types : timestamp, numeric, string, boolean |
Options des tables
Outre columns, une table accepte quelques clés optionnelles qui contrôlent la façon dont elle est décrite et dont ses données vieillissent :
data:
tables:
- tablename: sensordata
description: Relevés environnementaux de l'atelier
chunkTimeInterval: 1 hour
dropAfter: 30 days
columns:
# ...| Champ | Description |
|---|---|
tablename | Nom de la table |
description | Description optionnelle, affichée dans l’interface et utilisée par les agents IA pour comprendre la table |
chunkTimeInterval | Taille des partitions temporelles dans lesquelles la table est découpée. Par défaut 7 days |
dropAfter | Fenêtre de rétention — les partitions plus anciennes sont supprimées automatiquement |
downsample | Maintient une copie pré-agrégée pour des graphiques rapides sur de longues fenêtres — voir Sous-échantillonnage continu plus bas |
maintainLatestFlagFor | Colonnes identifiant une entité unique — voir Suivi de l’état actuel d’une entité plus bas |
private | Masque cette table aux autres applications — voir Partager des données avec d’autres applications plus bas |
chunkTimeInterval détermine la façon dont les données de séries temporelles sont partitionnées sur le disque. Choisissez-le pour qu’une partition corresponde à peu près à ce que vous interrogez en une fois : les données à haute fréquence collectées chaque seconde bénéficient de petits chunks (de quelques minutes à quelques heures), les données qui évoluent lentement de grands chunks (des semaines). Ce n’est que la valeur par défaut de l’application — le propriétaire du projet peut l’ajuster ensuite sur son propre backend de données.
dropAfter transforme la table en fenêtre glissante. Des partitions entières plus anciennes que l’intervalle indiqué sont supprimées, ce qui est bien moins coûteux que de supprimer des lignes une à une. La tâche de nettoyage s’exécute selon un rythme de dropAfter / 4 : un enregistrement peut donc survivre à son expiration pendant un quart de l’intervalle au maximum, avant que sa partition ne disparaisse. Omettez dropAfter pour conserver les données indéfiniment.
Les deux acceptent des chaînes d’intervalle PostgreSQL — 30 minutes, 1 hour, 7 days, 6 months.
Sous-échantillonnage continu
Les tableaux de bord peuvent demander à la base de données d’agréger les données — moyennes horaires, totaux journaliers, nombres d’enregistrements par machine. Calculer cela à partir des enregistrements bruts est acceptable sur une journée et coûteux sur une année. Ajoutez downsample à une table et la plateforme en maintient une copie pré-agrégée, continuellement mise à jour, puis répond aux requêtes portant sur de longues fenêtres à partir de cette copie :
data:
tables:
- tablename: sensordata
dropAfter: 30 days
downsample:
bucket: 1 minute
keepFor: 2 years
paths:
- payload.temperature
columns:
# ...| Champ | Description |
|---|---|
bucket | Granularité de la copie pré-agrégée. Par défaut 1 minute |
keepFor | Durée de conservation de l’historique sous-échantillonné. Omettez-le pour le conserver indéfiniment |
paths | Chemins de champs JSON à inclure, dans la même notation que celle des tableaux de bord |
bucket est la résolution la plus fine à partir de laquelle un graphique peut être servi — un graphique demandant des intervalles nettement plus fins lit la table brute à la place. Il accepte des intervalles de largeur fixe de 1 second à 1 day qui divisent une journée de façon régulière (1 minute, 5 minutes, 1 hour). La valeur par défaut de 1 minute convient à pratiquement tous les tableaux de bord ; un intervalle plus grossier coûte moins de stockage et moins de débit en écriture.
keepFor est ce qui rend les historiques longs possibles. Les enregistrements bruts disparaissent avec dropAfter, mais la copie sous-échantillonnée a sa propre rétention : conservez les données brutes 30 jours et les données sous-échantillonnées 2 ans, et un board pourra toujours tracer deux ans de moyennes horaires pour une fraction du stockage. Définissez-le plus long que dropAfter — la plateforme refuse l’inverse comme une erreur de configuration.
paths étend le sous-échantillonnage aux valeurs situées à l’intérieur de colonnes JSON. Les colonnes numériques sont incluses automatiquement ; les champs JSON doivent être nommés explicitement, car une colonne JSON n’a pas d’ensemble de clés fixe. Les champs non déclarés fonctionnent quand même dans les tableaux de bord — ils sont simplement calculés à partir de la table brute.
Tout le reste est automatique. Chaque colonne numérique voit ses statistiques maintenues (moyenne, somme, minimum, maximum, première et dernière valeur, ainsi qu’un nombre d’enregistrements), groupées par la clé d’entité de la table (maintainLatestFlagFor, ou l’appareil publiant). Les tableaux de bord n’ont besoin d’aucune configuration ni même d’en avoir connaissance : un widget interroge comme d’habitude, et la plateforme décide requête par requête si la copie pré-agrégée peut y répondre — en se rabattant de façon transparente sur la table brute lorsque ce n’est pas le cas, par exemple quand un filtre porte sur une colonne selon laquelle la copie ne regroupe pas.
Les changements de schéma reconstruisent la copie. Ajouter, supprimer ou retyper une colonne d’une table sous-échantillonnée — ou modifier le bloc
downsamplelui-même — reconstruit la copie pré-agrégée à partir de la table brute. Tout ce qui est plus ancien quedropAfterne peut pas être reconstitué et est perdu. Mettez ce bloc en place en même temps que la table lorsque c’est possible, et traitez les changements de schéma ultérieurs sur des tables à longue durée de vie comme une décision délibérée.
Publier des données depuis le code edge
Utilisez le SDK IronFlock pour envoyer des données depuis votre application :
Python
from ironflock import IronFlock
flock = IronFlock()
flock.publish_to_table("sensordata", {
"timestamp": "2025-01-15T10:30:00Z",
"temperature": 23.5,
"humidity": 62.1,
"device_id": "sensor-001"
})Pour les données à haute fréquence, envoyez plusieurs lignes dans un seul message au lieu d’un aller-retour par ligne en utilisant publish_rows_to_table / publishRowsToTable (fire-and-forget) ou append_rows_to_table / appendRowsToTable (qui retourne le résultat de l’insertion). Chaque lot est inséré de manière atomique — tout ou rien. Consultez la référence du SDK pour plus de détails.
Tables de transformation
Vous pouvez définir des transformations SQL qui agrègent ou traitent automatiquement vos données brutes :
data:
tables:
- tablename: sensordata
columns:
# ... colonnes de données brutes ...
transforms:
- tablename: hourly_averages
materialize: true
schedule: "0 * * * *"
sql: >
SELECT
time_bucket('1 hour', tsp) AS hour,
avg(temperature) AS avg_temp,
avg(humidity) AS avg_humidity
FROM sensordata
GROUP BY hour
columns:
- id: hour
name: Hour
dataType: timestamp
- id: avg_temp
name: Average Temperature
dataType: numeric
- id: avg_humidity
name: Average Humidity
dataType: numeric| Champ | Description |
|---|---|
tablename | Nom de la table dérivée |
materialize | Si true, les résultats sont persistés sous forme de table |
schedule | Expression cron indiquant quand la transformation s’exécute |
sql | Requête SQL qui calcule la transformation |
columns | Définitions des colonnes de sortie |
Les tables de transformation sont accessibles dans les boards et via le SDK, exactement comme les tables ordinaires.
Suivi de l’état actuel d’une entité
Pour les tables représentant l’état actuel d’entités réelles — machines, actifs, ordres de production — IronFlock supporte un modèle appelé suivi de l’état le plus récent.
Au lieu d’écraser une ligne lorsqu’une valeur change, vous ajoutez toujours une nouvelle ligne. Vous déclarez quelles colonnes identifient une entité unique, et IronFlock en déduit la ligne la plus récente par entité à chaque lecture de la table. Vous obtenez ainsi l’historique complet de chaque modification tout en pouvant interroger facilement le seul état actuel.
Activez-le sur une table avec maintainLatestFlagFor :
- tablename: machineform
maintainLatestFlagFor: ['machinename']
columns:
- id: tsp
dataType: timestamp
- id: machinename
dataType: string
- id: machinetype
dataType: string
- id: active
dataType: boolean
- id: description
dataType: stringmaintainLatestFlagFor prend une liste de colonnes identifiant une entité unique. Rien n’est écrit dans la ligne elle-même : IronFlock indexe la table selon cette clé d’entité et l’horodatage, puis sélectionne la ligne la plus récente de chaque entité au moment de la requête. Une ligne qui arrive en retard ou dans le désordre ne peut donc jamais laisser derrière elle un marqueur obsolète.
Pour n’interroger que l’état actuel des machines :
SELECT DISTINCT ON (machinename) *
FROM machineform
ORDER BY machinename, tsp DESCPour consulter l’historique complet d’une machine donnée :
SELECT * FROM machineform WHERE machinename = 'Assembly-Line-01' ORDER BY tspVous écrivez rarement cette requête à la main. Les widgets d’un board connectés à cette table disposent d’un commutateur latest dans leurs paramètres de filtre, de sorte que les utilisateurs voient toujours les valeurs actuelles sans effort supplémentaire. Depuis le SDK, activez le même mode en ajoutant {"latest": true} à filterAnd — voir getHistory.
Migration depuis
latest_flag: les versions précédentes d’IronFlock stockaient une colonne booléenne physique nomméelatest_flag. Cette colonne n’existe plus — l’état actuel est désormais dérivé en SQL, ce qui le garde correct lorsque les lignes arrivent dans le désordre. Les boards et les appels SDK existants qui filtrent surlatest_flag = truecontinuent de fonctionner : IronFlock les reconnaît et applique le mode « état le plus récent ». Le nouveau code doit utiliser l’option latest ou l’entrée de filtre{"latest": true}.
Suppression logique des enregistrements
Le modèle append-only d’IronFlock signifie que les enregistrements ne sont jamais physiquement supprimés. Utilisez plutôt une colonne booléenne deleted pour marquer un enregistrement comme supprimé. Cela préserve la piste d’audit complète tout en masquant les enregistrements supprimés dans les dashboards.
Ajoutez une colonne deleted à n’importe quelle table d’entités :
- id: deleted
name: Deleted
dataType: booleanLorsqu’un utilisateur supprime un enregistrement (par exemple via un formulaire sur le board), votre application publie une nouvelle ligne pour cette entité avec deleted: true. Combinée à maintainLatestFlagFor, cette nouvelle ligne devient l’état le plus récent.
Pour ne consulter que les enregistrements actuels et actifs (non supprimés) :
SELECT * FROM (
SELECT DISTINCT ON (machinename) *
FROM machineform
ORDER BY machinename, tsp DESC
) latest
WHERE deleted IS NULL OR deleted = falseLe contrôle sur deleted s’applique après la sélection de la ligne la plus récente de chaque machine. Cet ordre est important : filtrer d’abord les lignes supprimées ferait remonter la ligne précédente, non supprimée, comme état actuel.
Les widgets des boards et le SDK appliquent automatiquement le même ordre — combinez le commutateur latest (ou {"latest": true}) avec un filtre sur deleted et vous obtenez exactement ce comportement. Les enregistrements supprimés disparaissent du dashboard dès l’envoi du formulaire, mais restent dans la base de données à des fins d’historique et d’audit.
Partager des données avec d’autres applications
Votre data backend appartient à votre seule application : aucune autre application installée dans le projet ne voit vos tables. Deux clés optionnelles de data-template.yml changent cela.
Pour lire les données d’une autre application, listez les applications concernées dans une section consumes: de premier niveau — à côté de data:, et non à l’intérieur :
consumes:
- app: machine-monitor
reason: "Calcule le TRS à partir des flux d'état machine et de compteurs du moniteur"
data:
tables:
- tablename: oee_results
columns:
# ... les tables propres à votre application, comme d'habitudeapp est le nom technique de l’application fournisseuse, ou "*" (les guillemets sont obligatoires) pour toutes les applications du projet. reason est affichée à l’utilisateur dans la boîte de dialogue de consentement — la déclaration seule n’accorde rien tant qu’il n’a pas approuvé.
Pour garder certaines tables privées, marquez-les private: true. Tout ce que vous définissez est partageable par défaut ; une table ou une transformation privée n’apparaît jamais dans le catalogue que voient les autres applications.
data:
tables:
- tablename: measurements # partagée (par défaut)
columns: [ ... ]
- tablename: calibration_state # interne — jamais visible par les autres applications
private: true
columns: [ ... ]L’accès est en lecture seule, accordé par l’utilisateur projet par projet, et révocable à tout moment. Voir Consommer les données d’autres applications pour le modèle complet et les appels SDK qui lisent l’historique et les flux en direct d’une application fournisseuse.