SDK IronFlock
Le SDK IronFlock permet à vos applications edge d’interagir avec la plateforme IronFlock. Il gère l’authentification automatiquement lorsqu’il s’exécute sur un appareil enregistré et fournit des fonctions pour publier des données, interroger l’historique, appeler des procédures distantes entre appareils et mettre à jour les métadonnées des appareils.
| SDK | Package | Prérequis |
|---|---|---|
| Python | ironflock sur PyPI | Python 3.8+ |
| JavaScript | ironflock sur npm | Node.js 18+ ou navigateur moderne |
Installation
Python
pip install ironflockOu ajoutez ironflock au requirements.txt de votre application.
Démarrage rapide
Python
import asyncio
from ironflock import IronFlock
async def main():
while True:
await ironflock.publish_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60
})
await asyncio.sleep(5)
ironflock = IronFlock(mainFunc=main)
ironflock.run()Lorsqu’il est utilisé à l’intérieur d’un conteneur d’application IronFlock, le SDK lit automatiquement les identifiants de connexion depuis l’environnement — aucune configuration manuelle n’est nécessaire.
Options du constructeur
Python
ironflock = IronFlock(
mainFunc=main, # async function to run after connecting
serial_number="abc123" # override device serial (optional)
)| Paramètre | Description |
|---|---|
mainFunc | Une fonction asynchrone qui s’exécute une fois la connexion établie |
serial_number | Remplace le numéro de série de l’appareil. Par défaut, la variable d’environnement DEVICE_SERIAL_NUMBER |
Publier des données
publishToTable / publish_to_table
Publie un enregistrement de données dans une table de flotte. Le nom de la table doit correspondre à une table définie dans le data-template.yml de votre application. Le SDK achemine automatiquement les données vers la bonne base de données du projet.
Python
await ironflock.publish_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60,
"device_id": "sensor-001"
})appendToTable / append_to_table
Ajoute des données à une table de flotte via un appel de procédure distante plutôt que via pub/sub. Utilisez ceci lorsque vous avez besoin de confirmation que les données ont été persistées.
Python
result = await ironflock.append_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60
})publishRowsToTable / publish_rows_to_table
Publie plusieurs lignes dans un seul message (insertion en masse) dans une table de flotte. La plateforme insère l’intégralité du lot de manière atomique (tout ou rien) en une seule opération. Utilisez ceci pour les données à haute fréquence, où un aller-retour par ligne serait trop coûteux. Comme publishToTable, ceci fonctionne en mode « fire-and-forget » — l’accusé de réception confirme la livraison au routeur, et non l’insertion en base de données.
Python
await ironflock.publish_rows_to_table("sensordata", [
{"tsp": "2024-01-15T10:30:00.000Z", "temperature": 22.5},
{"tsp": "2024-01-15T10:30:01.000Z", "temperature": 22.7},
])Le second argument est une liste non vide d’objets de lignes à insérer.
appendRowsToTable / append_rows_to_table
Ajoute plusieurs lignes via un seul appel de procédure distante (insertion en masse) dans une table de flotte. La plateforme insère l’intégralité du lot de manière atomique (tout ou rien) : si une ligne est invalide, le lot entier est rejeté et rien n’est persisté. Préférez ceci à publishRowsToTable / publish_rows_to_table lorsque vous avez besoin de connaître le résultat de l’insertion.
Python
result = await ironflock.append_rows_to_table("sensordata", [
{"tsp": "2024-01-15T10:30:00.000Z", "temperature": 22.5},
{"tsp": "2024-01-15T10:30:01.000Z", "temperature": 22.7},
])
# result -> {"success": True, "count": 2}reportError / report_error
Signale une erreur applicative dans la table error-logs de votre flotte. Il s’agit d’un wrapper pratique au-dessus de publishToTable / appendToTable : il estampille la ligne avec source: "app", un niveau de gravité level et un horodatage, puis l’écrit comme n’importe quelle ligne de table normale. L’erreur arrive dans la même table error-logs que celle utilisée par les erreurs système de fleetdb (étiquetées source: "system"), elle est donc interrogeable avec getHistory, diffusable en flux avec subscribeToTable / subscribe_to_table, utilisable dans les modèles de tableaux et livrée en temps réel sur transformed.error-logs — sans déclencher la notification d’erreur système de la plateforme.
Python
# Fire-and-forget (default): publishes to the error-logs table
await ironflock.report_error("Sensor read timed out", level="warn")
# Pass an exception to capture its traceback (falls back to the message)
try:
risky_operation()
except Exception as err:
await ironflock.report_error(err)
# Use the append RPC when you want to await the insert outcome
await ironflock.report_error("Calibration failed", level="error", append=True)Paramètres :
| Paramètre | Type | Description |
|---|---|---|
error | str / string ou exception / Error | Le message d’erreur, ou une exception dont la trace/pile (ou le message) est enregistrée |
level | str / string, optionnel | Gravité : "error", "warn", "info" ou "debug". Par défaut "error" |
append | bool / boolean, optionnel | Lorsque true, utilise la procédure distante d’ajout (renvoie le résultat de l’insertion). Par défaut false (publication en mode « fire-and-forget ») |
tsp | str / string, optionnel | Horodatage ISO-8601 de remplacement. Par défaut, l’heure actuelle |
En Python, les options sont des arguments nommés (
report_error(error, level=..., append=..., tsp=...)) ; en JavaScript, elles sont passées via un objet d’options (reportError(error, { level, append, tsp })).
publish
Publie un message sur n’importe quel topic WAMP. Utilisez ceci pour la messagerie personnalisée ou les événements qui ne correspondent pas à une table de base de données.
Python
await ironflock.publish("com.myapp.alerts", {
"level": "warning",
"message": "Temperature threshold exceeded"
})Interroger les données historiques
getHistory
Récupère des données historiques depuis une table de flotte. Supporte le filtrage, les plages temporelles et la pagination.
Python
# Simple query
data = await ironflock.getHistory("sensordata", {"limit": 100})
# Query with time range and filters
data = await ironflock.getHistory("sensordata", {
"limit": 500,
"offset": 0,
"timeRange": {
"start": "2026-01-01T00:00:00Z",
"end": "2026-03-01T00:00:00Z"
},
"filterAnd": [
{"column": "temperature", "operator": ">", "value": 20},
{"column": "humidity", "operator": "<=", "value": 80}
]
})
# Current value(s) only: the "latest" marker returns the newest row per entity
current = await ironflock.getHistory("sensordata", {
"limit": 100,
"filterAnd": [{"latest": True}]
})Paramètres de requête :
| Champ | Type | Description |
|---|---|---|
limit | int / number | Nombre maximum de lignes à renvoyer (1–10 000, obligatoire) |
offset | int / number | Décalage pour la pagination |
timeRange | dict / object | {"start": "<ISO datetime>", "end": "<ISO datetime>"} |
filterAnd | list / array | Conditions de filtre AND, et/ou le marqueur latest (voir ci-dessous) |
columns | list / array | Colonnes à renvoyer (optionnel). tsp, device_key et authid sont toujours inclus ; omettez ce champ pour obtenir toutes les colonnes |
Opérateurs de filtre : =, !=, >, <, >=, <=, LIKE, ILIKE, IN, NOT IN, IS, IS NOT
Chaque filtre est un objet avec les clés column, operator et value.
Lire les valeurs actuelles. Une entrée {"latest": true} dans filterAnd n’est pas une condition de filtre mais un changement de mode : le backend de données ne renvoie que la ligne la plus récente de chaque entité, dérivée en SQL à partir de la clé d’entité que la table déclare avec maintainLatestFlagFor. Une table sans clé d’entité renvoie son unique ligne la plus récente.
Les autres conditions se combinent au marqueur comme on peut s’y attendre : les conditions portant sur les colonnes de la clé d’entité restreignent quelles entités sont renvoyées, tandis que toutes les autres conditions et timeRange s’appliquent aux lignes les plus récentes obtenues. Ainsi, combiner {"latest": true} avec un filtre sur deleted masque les entités supprimées au lieu de faire remonter leur ligne précédente.
Les versions précédentes d’IronFlock stockaient une colonne latest_flag physique. Elle n’existe plus — un filtre hérité latest_flag = true est toujours accepté et traité comme le marqueur, mais le nouveau code doit utiliser {"latest": true}. Le marqueur latest n’est pas disponible dans getSeriesHistory.
getSeriesHistory / get_series_history
Récupère des données de séries temporelles sous-échantillonnées d’une table de flotte : des colonnes numériques agrégées par intervalles de temps (par ex. des moyennes horaires). Idéal pour les graphiques couvrant de longues périodes. Disponible pour les tables (pas pour les transforms).
Python
series = await ironflock.get_series_history("sensordata", {
"metrics": ["temperature", "humidity"],
"method": "AVG",
"limit": 500,
"timeRange": ["2026-01-01T00:00:00Z", "2026-03-01T00:00:00Z"],
"groupBy": ["device_id"]
})Paramètres de requête :
| Champ | Type | Description |
|---|---|---|
metrics | list / array | Colonnes numériques à sous-échantillonner |
method | str / string | Agrégation par intervalle : "AVG", "SUM", "COUNT", "MIN", "MAX", "FIRST" ou "LAST" |
limit | int / number | Nombre maximum d’intervalles (1–10 000) |
timeRange | list / array | [start, end] — chaînes ISO datetime ou nombres epoch-ms ; null = borne ouverte (obligatoire) |
groupBy | list / array | Colonnes selon lesquelles grouper la série (optionnel) |
filterAnd | list / array | Conditions de filtre AND (optionnel). Conditions de filtre uniquement — le marqueur latest n’est pas pris en charge ici ; utilisez getHistory pour lire les valeurs actuelles |
S’abonner aux données
subscribeToTable / subscribe_to_table
S’abonne aux mises à jour en temps réel d’une table de flotte. Le gestionnaire est appelé chaque fois que de nouvelles données sont publiées dans la table. Les lignes écrites via le chemin d’insertion en masse (publishRowsToTable / appendRowsToTable) sont transmises à votre gestionnaire une à la fois, de sorte que le code du gestionnaire reste identique quelle que soit la manière dont les données ont été écrites.
Python
def on_sensor_data(*args, **kwargs):
print("New reading:", args, kwargs)
await ironflock.subscribe_to_table("sensordata", on_sensor_data)subscribe
S’abonne à n’importe quel topic WAMP pour de la messagerie en temps réel personnalisée.
Python
def on_alert(*args, **kwargs):
print("Alert received:", args, kwargs)
await ironflock.subscribe("com.myapp.alerts", on_alert)Accès aux données inter-apps
Lisez les données de flotte d’une autre app depuis votre propre app, au sein du même projet. L’app fournisseur doit déclarer votre app dans la section consumes: de son data-template.yml, et l’utilisateur du projet doit accorder l’accès. L’accès est en lecture seule : vous pouvez interroger l’historique et vous abonner en temps réel aux lignes des tables et des transforms que le fournisseur partage, mais vous ne pouvez pas y écrire. Les connexions aux apps consommées sont mises en cache par app et fermées automatiquement lorsque votre instance s’arrête.
Si votre app détient l’autorisation wildcard (consumes: [{ app: "*" }]), vous pouvez découvrir et ouvrir dynamiquement les fournisseurs avec listConsumableApps / list_consumable_apps et connectToAllApps / connect_to_all_apps (ci-dessous).
connectToApp / connect_to_app
Ouvre une connexion en lecture seule au backend de données d’une autre app et renvoie un handle. Le handle expose getHistory / get_history, subscribeToTable / subscribe_to_table et getSeriesHistory / get_series_history (tables uniquement) — les mêmes interrogations et abonnements que vous utilisez sur vos propres tables — ainsi que close et les catalogues partagés tables / transforms.
Python
# Open a read-only handle on another app's data backend
weather = await ironflock.connect_to_app("weather-app")
# Inspect what the provider shares
print([t["tablename"] for t in weather.tables])
# Query history and subscribe, just like your own tables
rows = await weather.get_history("forecasts", {"limit": 100})
def on_forecast(*args, **kwargs):
print("New forecast:", args)
await weather.subscribe_to_table("forecasts", on_forecast)Paramètres :
| Paramètre | Type | Description |
|---|---|---|
app_name / appName | str / string | Nom de l’app fournisseur, tel que déclaré dans votre section consumes: |
stage | str / string, optionnel | Stage du fournisseur : "dev" ou "prod". Par défaut, le stage de votre propre app |
on_error / onError | callable, optionnel | Appelé avec un CrossAppAccessError si l’accès est refusé après l’établissement de la connexion (par ex. si l’autorisation est révoquée ultérieurement) |
Si l’accès est refusé ou mal utilisé, un CrossAppAccessError est levé (Python) / lancé (JavaScript) avec un champ code : NO_GRANT, PROVIDER_NOT_INSTALLED, UNKNOWN_APP, PRIVATE_TABLE ou NOT_AUTHORIZED.
En Python,
stageeton_errorsont des arguments nommés ; en JavaScript, ils sont passés via un objet d’options (connectToApp(appName, { stage, onError })).
listConsumableApps / list_consumable_apps
Liste chaque fournisseur non privé du projet — la primitive de découverte pour les apps qui détiennent l’autorisation de consommation wildcard (consumes: [{ app: "*" }] dans votre data-template.yml, accordée par l’utilisateur du projet). Elle effectue un seul appel et n’ouvre aucune connexion : affichez les catalogues renvoyés dans un sélecteur, puis appelez connectToApp / connect_to_app pour ceux que vous souhaitez — ou connectToAllApps / connect_to_all_apps pour tous les ouvrir en une seule fois.
Remarque : Déclarez l’autorisation dans le
data-template.ymlde votre app, et mettez le*entre guillemets — un*nu est un alias YAML et ne sera pas analysé :
consumes:
- app: "*"Python
providers = await ironflock.list_consumable_apps()
for p in providers:
print(p["app"], list(p["stages"].keys())) # e.g. "weather-app" ["dev", "prod"]Chaque entrée décrit un fournisseur :
| Champ | Type | Description |
|---|---|---|
app | str / string | Nom de l’app fournisseur |
provider_app_key | int / number | La clé d’app du fournisseur |
stages | dict / object | Catalogue par stage { dev?, prod? } ; un stage n’est présent que si le fournisseur dispose d’un backend de données pour celui-ci. Chaque catalogue contient les tables et transforms non privés qu’il partage |
Lève (Python) / lance (JavaScript) un CrossAppAccessError avec code: NO_GRANT si votre app ne détient aucune autorisation wildcard.
connectToAllApps / connect_to_all_apps
Ouvre des handles en lecture seule vers chaque fournisseur non privé du projet en un seul appel (consommateurs wildcard uniquement). Énumère les fournisseurs via listConsumableApps / list_consumable_apps et ouvre chacun d’eux, en ignorant ceux qui n’ont pas de backend de données pour le stage demandé. Chaque handle est mis en cache sous la même clé que connectToApp / connect_to_app, de sorte qu’un connectToApp(name) ultérieur renvoie le handle déjà préchauffé. Les handles renvoyés sont fermés ensemble lorsque votre instance s’arrête.
Python
apps = await ironflock.connect_to_all_apps(
on_error=lambda err: print("Provider skipped:", err)
)
for app in apps:
rows = await app.get_history(app.tables[0]["tablename"], {"limit": 10})
print(app.app, rows)Paramètres :
| Paramètre | Type | Description |
|---|---|---|
stage | str / string, optionnel | Stage du fournisseur : "dev" ou "prod". Par défaut, le stage de votre propre app |
continue_on_error / continueOnError | bool / boolean, optionnel | Lorsque true (la valeur par défaut), un fournisseur qui échoue à s’ouvrir est signalé à on_error / onError et omis du résultat. Lorsque false, le premier échec est levé/lancé |
on_error / onError | callable, optionnel | Appelé avec chaque fournisseur qui n’a pas pu être ouvert (tant que continue_on_error / continueOnError vaut true), et avec un CrossAppAccessError si une connexion déjà établie est refusée ultérieurement (par ex. si l’autorisation est révoquée) |
Renvoie les handles de fournisseurs ouverts avec succès (même type de handle que connectToApp / connect_to_app). Lève (Python) / lance (JavaScript) un CrossAppAccessError avec code: NO_GRANT si votre app ne détient aucune autorisation wildcard.
En Python,
stage,on_erroretcontinue_on_errorsont des arguments nommés ; en JavaScript, ils sont passés via un objet d’options (connectToAllApps({ stage, onError, continueOnError })).
Stockage de fichiers managé
Chaque backend de données d’app dispose, à côté de ses tables, d’un stockage d’objets privé accessible via la propriété files. Utilisez-le pour des images, des PDF, des images de caméra, des blobs de firmware — tout ce qui n’a pas sa place dans une ligne de table. Aucune configuration n’est nécessaire : une app dépourvue de section files: dans son data-template obtient malgré tout un espace de noms nommé default.
L’idée clé est que stocker un objet vous renvoie une URL permanente que vous pouvez écrire directement dans une colonne de table, de sorte qu’un widget de tableau de bord puisse l’afficher sans travail supplémentaire :
Python
# Store an object and get a permanent URL back in the same call
info = await ironflock.files.put("part-1.jpg", jpeg_bytes, content_type="image/jpeg")
# The URL is safe to store in a table column — a dashboard widget can then
# render <img src="{{photo_url}}"> without any extra round trip
await ironflock.publish_to_table("inspections", part_id="1", photo_url=info.url)
# Read it back
data = await ironflock.files.get("part-1.jpg")
# Walk every object under a prefix (pages are fetched for you)
async for obj in ironflock.files.iter(prefix="2026/"):
print(obj.key, obj.size)Cette URL n’expire jamais, mais ce n’est pas un lien public : elle ne reste lisible que par un demandeur authentifié disposant de l’accès READ sur ce backend de données, et un proxy d’authentification le revérifie à chaque requête. Elle peut donc être stockée sans risque dans la base de données.
Espaces de noms
Un espace de noms est un préfixe de clé porteur de règles — rétention, règles de partage, types de contenu autorisés. Ce n’est pas un bucket distinct ; tous les espaces de noms d’une app vivent dans l’unique zone de stockage de cette app. N’en déclarez un que lorsqu’un ensemble d’objets a besoin de règles différentes ; sinon, restez dans default et organisez vos objets avec des chemins de clés comme 2026/03/part-1.jpg.
Déclarez des espaces de noms supplémentaires dans data-template.yml :
files:
# Storage budget the app suggests for itself. The project user can change it,
# and their setting is the one that gets enforced.
quotaBytes: 5368709120
namespaces:
- name: frames
description: Raw camera frames, one JPEG per inspected part.
contentTypes: ["image/jpeg"]
maxObjectBytes: 20971520
retention: { deleteAfter: 30 days }Notez que le budget est déclaré une seule fois pour toute l’app, et non par espace de noms. Un espace de noms n’est qu’un préfixe de clé à l’intérieur de l’unique zone de stockage de l’app : il n’y a donc rien contre quoi un budget par préfixe pourrait être appliqué. maxObjectBytes, lui, est défini par espace de noms — il plafonne un objet unique, pas un total.
Chaque méthode ci-dessous accepte l’espace de noms comme argument optionnel et utilise default par défaut.
Stocker et lire des objets
Python
# Bytes in, bytes out
info = await ironflock.files.put("reports/march.pdf", pdf_bytes, content_type="application/pdf")
data = await ironflock.files.get("reports/march.pdf")
# Or straight from/to a local file — these stream on the large-object path,
# so a multi-gigabyte file never has to fit in memory
await ironflock.files.put_file("firmware/v2.bin", "/data/build/v2.bin")
await ironflock.files.get_to_file("firmware/v2.bin", "/tmp/v2.bin")| Méthode | Description |
|---|---|
put(key, data, …) | Stocke un objet (bytes en Python, Uint8Array en JavaScript). Renvoie les métadonnées de l’objet, dont son url |
get(key, namespace?) | Renvoie le contenu de l’objet |
put_file(key, path, …) / get_to_file(key, path, …) | Python uniquement. Stocke depuis un fichier local, ou écrit dans un fichier local. Transfère en flux sur le chemin des objets volumineux |
delete(key, namespace?) | Supprime un objet |
copy(key, to, …) | Copie un objet, éventuellement vers un autre espace de noms |
move(key, to, …) | Copie puis suppression. Non atomique — le service ne dispose d’aucun verbe move, une suppression échouée laisse donc les deux copies en place |
JavaScript n’offre pas d’assistants de chemins de fichiers, car le paquet est livré en un seul build pour Node comme pour le navigateur — lisez et écrivez vous-même les fichiers locaux avec fs.
put accepte : content_type / contentType (le type MIME ; l’espace de noms peut restreindre ceux qui sont autorisés) et namespace. En Python, ce sont des arguments nommés ; en JavaScript, ils sont passés dans un objet d’options.
Lister et inspecter
| Méthode | Description |
|---|---|
list(…) | Une page d’objets. Renvoie objects, prefixes, is_truncated / isTruncated ainsi qu’un cursor à repasser pour la page suivante |
iter(…) / iterate(…) | Itérateur asynchrone sur chaque objet situé sous un préfixe, avec pagination automatique. Nommé iter en Python, iterate en JavaScript |
stat(key, namespace?) | Métadonnées d’un objet sans transférer son contenu |
exists(key, namespace?) | Indique si un objet existe |
namespaces() | Les espaces de noms que cette app peut utiliser |
usage(…) | Quelle quantité de stockage l’app utilise — voir ci-dessous |
catalog() | Espaces de noms, ainsi que les limites et quotas émis par le serveur. Mis en cache après le premier appel |
Les objets sont décrits par les mêmes champs dans les deux SDK, selon le style de nommage propre à chaque langage : namespace, key, size, etag, content_type / contentType, last_modified / lastModified, checksum_sha256 / checksumSha256 et url.
Utilisation du stockage et quota
usage obtient sa réponse du magasin d’objets en un seul appel : les totaux sont donc exacts, et non additionnés par le SDK :
Python
u = await ironflock.files.usage()
print(u.size_bytes, u.object_count, u.quota_bytes, u.free_bytes)
# Break the total down per namespace (costs one listing per namespace)
detailed = await ironflock.files.usage(detail=True)
print(detailed.per_namespace) # {"default": 1048576, "frames": 73400320}| Champ | Signification |
|---|---|
size_bytes / sizeBytes | Octets actuellement stockés |
object_count / objectCount | Nombre d’objets stockés |
quota_bytes / quotaBytes | Le budget appliqué. 0 signifie illimité |
free_bytes / freeBytes | Octets restants. -1 signifie illimité — indiquer 0 ici se lirait comme « plein » |
per_namespace / perNamespace | Octets par espace de noms. Présent uniquement lorsque vous demandez le détail |
La ventilation par espace de noms est désactivée par défaut, car le magasin ne sait pas y répondre directement : il comptabilise par zone de stockage, et un espace de noms n’est qu’un préfixe ; le SDK doit donc lister chaque espace de noms pour en additionner les tailles. Demandez-la quand vous en avez besoin, pas sur un chemin critique.
Deux quotas différents apparaissent, et il vaut la peine de ne pas les confondre. catalog() renvoie les deux :
| Champ | Signification |
|---|---|
quota_bytes / quotaBytes | Ce qui est réellement appliqué, lu depuis le magasin d’objets — le réglage de l’utilisateur du projet |
suggested_quota_bytes / suggestedQuotaBytes | Ce que le data template de l’app a demandé. 0 s’il n’a rien demandé |
Ils diffèrent dès qu’un utilisateur a augmenté ou réduit le budget de l’app, et c’est précisément pourquoi la valeur appliquée est lue depuis le magasin plutôt que depuis le template — redéployer l’app ne doit pas réinitialiser silencieusement le choix d’un utilisateur. Une interface peut afficher les deux (« l’app suggère X, vous avez réglé Y »). L’application du quota s’appuie toujours sur la première valeur.
Partager des objets
Il existe deux types de liens, et la différence a son importance :
| Méthode | Durée de vie | Qui peut le lire |
|---|---|---|
url(key, …) | Permanente | Uniquement un demandeur authentifié disposant de READ sur ce backend de données — revérifié à chaque requête. Peut être stockée sans risque dans une colonne de table |
share_url / shareUrl | Expirante (15 min par défaut, plafonnée par le serveur) | Quiconque détient le lien. Rien ne revérifie l’autorisation au moment de son utilisation |
share_url / shareUrl est une capacité au porteur : remettez-la à une personne qui a besoin d’un accès temporaire, et ne la stockez pas dans la base de données. Utilisez url pour tout ce qu’affiche un tableau de bord.
url renvoie None / undefined lorsque le déploiement n’a pas de frontal HTTP (par exemple une appliance en HTTP simple) — c’est le signal qu’il faut se rabattre sur get. Passer l’etag d’un objet comme argument version permet aux navigateurs de mettre la réponse en cache de façon immuable.
upload_url / uploadUrl génère une URL expirante qui accepte un envoi direct, et renvoie url, method, headers et expires_in / expiresIn. Envoyez exactement les en-têtes qu’elle renvoie, sinon la signature ne sera pas vérifiée.
Objets volumineux
Le SDK choisit le transport en fonction de la taille, automatiquement — il n’y a rien à configurer :
| Taille de l’objet | Comment il transite |
|---|---|
| Jusqu’à la limite inline (actuellement 6 Mio) | Un seul appel via le routeur de messages |
| Au-delà | Directement vers le stockage d’objets en HTTPS, en contournant le routeur |
La limite exacte est indiquée à l’exécution par le serveur sous la forme inline_max_bytes / inlineMaxBytes dans catalog() ; elle peut donc être relevée sans nouvelle version du SDK.
Deux plafonds subsistent, et tous deux signalent TOO_LARGE avec une raison précisant lequel a été atteint :
- 5 Gio — la limite d’envoi en une seule fois du stockage d’objets. L’envoi multipart n’est pas encore implémenté.
- La limite inline, lorsqu’il n’existe pas de point de terminaison direct — une appliance isolée du réseau ne peut tout simplement pas transférer un objet volumineux. Aucun nouvel essai ni découpage plus fin n’y changera quoi que ce soit, et le message le précise.
Le chemin direct exige que l’appareil puisse joindre l’hôte du stockage d’objets, et pas seulement le routeur. Deux défaillances courantes sur le terrain reçoivent leurs propres codes au lieu de ressembler à des problèmes d’autorisation : PRESIGN_UNREACHABLE (un proxy qui n’autorise que le routeur) et CLOCK_SKEW (le stockage d’objets rejette les requêtes décalées de plus de 15 minutes — vérifiez NTP sur l’appareil).
Erreurs de stockage de fichiers
Chaque opération sur les fichiers lève (Python) / lance (JavaScript) une FileStoreError portant un code stable et une reason lisible par un humain. Faites vos branchements sur code, jamais sur reason.
Python
from ironflock.filestore import FileStoreError
try:
await ironflock.files.put("huge.bin", payload)
except FileStoreError as e:
if e.code == "QUOTA_EXCEEDED":
print("Filestore is full:", e.reason)
else:
raise| Code | Signification |
|---|---|
NOT_AUTHORIZED | L’appelant n’a pas le droit d’effectuer cette opération |
NO_SUCH_NAMESPACE | L’espace de noms n’est pas déclaré dans le data-template |
NO_SUCH_OBJECT | La clé n’existe pas |
TOO_LARGE | Dépasse la limite de transfert en un seul appel |
OBJECT_TOO_LARGE | Dépasse le maxObjectBytes propre à l’espace de noms |
QUOTA_EXCEEDED | Le filestore est plein |
CONTENT_TYPE_NOT_ALLOWED | L’espace de noms restreint les contentTypes |
NOT_SUPPORTED | Le backend ne sait pas faire cela |
NOT_AVAILABLE | Ce déploiement n’a pas de service de fichiers |
PRESIGN_UNREACHABLE | Le stockage d’objets n’est pas joignable directement (un proxy ?) |
CLOCK_SKEW | L’horloge de l’appareil est trop décalée |
INTERNAL | Tout le reste |
Un serveur plus récent peut introduire des codes que cette version du SDK ne connaît pas. Ils sont transmis tels quels dans code plutôt que ramenés à une valeur générique : traitez donc une valeur non reconnue comme un échec générique.
En Python,
FileStoreErrorest importée depuisironflock.filestore; en JavaScript, elle est exportée depuis la racine du paquet (import { FileStoreError } from "ironflock").
Communication inter-appareils
registerDeviceFunction / register_device_function
Enregistre une procédure que d’autres appareils du même projet peuvent appeler. Le SDK ajoute automatiquement un espace de noms à la procédure pour l’appareil actuel.
Python
def add(a, b):
return a + b
await ironflock.register_device_function("com.myapp.add", add)
register()est un alias deregister_device_function().
callDeviceFunction / call_device_function
Appelle une procédure enregistrée par un autre appareil. Le SDK assemble automatiquement le topic WAMP complet en utilisant la clé de l’appareil cible.
Python
result = await ironflock.call_device_function(
42, # target device key
"com.myapp.add", # procedure name
args=[3, 5] # arguments
)
print(result) # 8call
Appelle une procédure distante en utilisant une URI WAMP complète. Utilisez ceci pour des appels directs lorsque vous connaissez le topic exact.
Python
result = await ironflock.call("some.full.wamp.topic", args=[42])Métadonnées de l’appareil
setDeviceLocation / set_device_location
Met à jour la localisation GPS de l’appareil dans la plateforme. Les modifications sont reflétées en temps réel sur les cartes IronFlock.
Python
await ironflock.set_device_location(long=8.6821, lat=50.1109)| Paramètre | Plage |
|---|---|
long | -180 à 180 |
lat | -90 à 90 |
L’historique de localisation n’est pas stocké. Pour suivre la localisation dans le temps, créez une table dédiée et utilisez
publish_to_table/publishToTable.
getRemoteAccessUrlForPort
Renvoie l’URL publique d’accès distant pour un port donné sur l’appareil.
Python
url = ironflock.getRemoteAccessUrlForPort(8080)
# "https://<device_key>-<app_name>-8080.app.ironflock.com"Propriétés & cycle de vie de la connexion
Python
| Propriété | Type | Description |
|---|---|---|
is_connected | bool | Indique si la connexion à la plateforme est active |
connection | CrossbarConnection | L’instance de connexion sous-jacente (usage avancé) |
| Méthode | Description |
|---|---|
run() | Démarre la connexion et exécute mainFunc (bloquant) |
await start() | Démarre la connexion de manière asynchrone |
await stop() | Arrête la connexion et annule les tâches en cours |
await run_async() | Démarre et maintient la connexion en cours d’exécution de manière asynchrone |
Gestion des erreurs
Chaque méthode du SDK échoue de manière explicite : en cas d’arguments invalides, de connexion perdue ou de rejet par la plateforme, elle lève une exception (Python) ou rejette (JavaScript) avec un message indiquant l’opération, le topic et la raison. Rien n’est ignoré silencieusement : encadrez donc d’un bloc try les appels auxquels votre code doit survivre.
Python
try:
rows = await ironflock.getHistory("sensordata", {"limit": 100})
except ValueError as e:
# Invalid parameters — e.g. limit out of range, or a malformed filter
print(f"Bad query: {e}")
except RuntimeError as e:
# Not connected, table not in the data-template, or the platform rejected the call
print(f"Query failed: {e}")En JavaScript, les échecs provenant de la plateforme sont des instances de WampError — une sous-classe normale d’Error qui transporte en plus l’URI d’erreur WAMP dans error et la charge utile de l’erreur dans args / kwargs. Tout le reste (paramètres invalides, absence de connexion) est une Error ordinaire.
Migration : les anciennes versions du SDK journalisaient un message et renvoyaient
None/nulllorsqu’un appel échouait. Elles lèvent désormais une erreur : un code de la formeif result is None:ne détecte donc plus les échecs — utiliseztry/except(outry/catch).
Utilisation dans le navigateur (JavaScript uniquement)
Le SDK JavaScript fonctionne dans les navigateurs modernes. Comme les navigateurs n’ont pas de variables d’environnement, passez toute la configuration via le constructeur :
import { IronFlock } from "ironflock";
const ironflock = new IronFlock({
serialNumber: "device-serial-from-server",
deviceKey: "my-device-key",
appName: "MyWebApp",
swarmKey: 10,
appKey: 20,
env: "PROD",
});
await ironflock.start();
await ironflock.publishToTable("sensordata", [{ temperature: 22 }]);Utilisez IronFlock.fromServer() pour récupérer la configuration depuis votre backend au lieu de coder en dur les identifiants :
const ironflock = await IronFlock.fromServer("/api/ironflock-config");
await ironflock.start();Votre point de terminaison backend doit renvoyer un objet JSON contenant les options de connexion (serialNumber, deviceKey, appName, swarmKey, appKey, env).
Enregistrement de fonctions pour agents IA
Le SDK peut enregistrer des fonctions appelables par des agents IA. Enregistrez une procédure et référencez son topic dans votre ai-template.yml :
Python
def get_sensor_reading(sensor_id):
"""Returns the latest reading from a sensor."""
reading = read_from_hardware(sensor_id)
return {
"sensor_id": sensor_id,
"temperature": reading.temp,
"humidity": reading.hum,
"timestamp": reading.ts
}
await ironflock.register_device_function("sensors.get_latest", get_sensor_reading)L’agent IA peut alors appeler cette fonction lorsqu’un utilisateur pose une question nécessitant des données de capteur en temps réel.
Pour relier le topic WAMP enregistré à un agent IA, référencez-le dans le .ironflock/ai-template.yml de votre app :
sensor_agent:
tool_description: |
Delegate to this agent when the user asks about sensor readings,
live device data, or current environmental conditions.
system_prompt: |
You are a sensor data specialist. Use get_current to retrieve
the latest reading from any sensor. Always include the unit in
your response.
main: true
max_context_tokens: 30000
max_iterations: 5
tools:
get_current:
description: Returns the latest reading from a sensor.
topic: sensors.get_latest
parameters:
sensor_id:
type: string
description: The sensor identifier to query.
required: trueLa valeur topic (sensors.get_latest) doit correspondre au nom passé à register_device_function / registerDeviceFunction dans votre code edge. IronFlock achemine automatiquement l’appel vers l’appareil où la fonction est enregistrée.
Pour la référence complète de ai-template.yml, consultez Définir des agents et des outils.
Variables d’environnement
Ces variables sont définies automatiquement par l’environnement d’exécution IronFlock dans les conteneurs d’applications :
| Variable | Description |
|---|---|
DEVICE_NAME | Nom d’affichage de l’appareil |
DEVICE_SERIAL_NUMBER | Identifiant unique et immuable de l’appareil |
DEVICE_KEY | Clé de l’appareil pour l’authentification |
SWARM_KEY | Identifiant du projet |
APP_KEY | Identifiant de l’application |
APP_NAME | Nom de l’application |
ENV | Environnement : DEV ou PROD |
Python
import os
device_name = os.environ.get("DEVICE_NAME")
serial = os.environ.get("DEVICE_SERIAL_NUMBER")
project_key = os.environ.get("SWARM_KEY")