IronFlock SDK
El SDK de IronFlock permite que tus aplicaciones de borde interactúen con la plataforma IronFlock. Gestiona la autenticación automáticamente cuando se ejecuta en un dispositivo registrado y proporciona funciones para publicar datos, consultar historial, llamar procedimientos remotos entre dispositivos y actualizar metadatos de dispositivos.
| SDK | Paquete | Requisitos |
|---|---|---|
| Python | ironflock en PyPI | Python 3.8+ |
| JavaScript | ironflock en npm | Node.js 18+ o navegador moderno |
Instalación
Python
pip install ironflockO añade ironflock al archivo requirements.txt de tu app.
Inicio rápido
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()Cuando se usa dentro de un contenedor de app de IronFlock, el SDK lee las credenciales de conexión del entorno automáticamente — no se necesita configuración manual.
Opciones del constructor
Python
ironflock = IronFlock(
mainFunc=main, # async function to run after connecting
serial_number="abc123" # override device serial (optional)
)| Parámetro | Descripción |
|---|---|
mainFunc | Una función asíncrona que se ejecuta una vez establecida la conexión |
serial_number | Sobrescribe el número de serie del dispositivo. Por defecto usa la variable de entorno DEVICE_SERIAL_NUMBER |
Publicación de datos
publishToTable / publish_to_table
Publica un registro de datos en una tabla de flota. El nombre de la tabla debe coincidir con una tabla definida en el archivo data-template.yml de tu app. El SDK enruta los datos automáticamente a la base de datos del proyecto correspondiente.
Python
await ironflock.publish_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60,
"device_id": "sensor-001"
})appendToTable / append_to_table
Añade datos a una tabla de flota usando una llamada a procedimiento remoto en lugar de pub/sub. Usa esto cuando necesites confirmación de que los datos fueron persistidos.
Python
result = await ironflock.append_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60
})publishRowsToTable / publish_rows_to_table
Publica muchas filas en un solo mensaje (inserción masiva) en una tabla de flota. La plataforma inserta todo el lote de forma atómica (todo o nada) en una sola operación. Usa esto para datos de alta frecuencia donde una ida y vuelta por fila sería demasiado costosa. Al igual que publishToTable, esto es fire-and-forget — la confirmación valida la entrega al router, no la inserción en la base de datos.
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},
])El segundo argumento es una lista no vacía de objetos de fila a insertar.
appendRowsToTable / append_rows_to_table
Añade muchas filas en una sola llamada a procedimiento remoto (inserción masiva) en una tabla de flota. La plataforma inserta todo el lote de forma atómica (todo o nada): si cualquier fila es inválida, se rechaza el lote completo y no se persiste nada. Prefiere esto sobre publishRowsToTable / publish_rows_to_table cuando necesites conocer el resultado de la inserción.
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
Reporta un error de aplicación en la tabla error-logs de tu flota. Esto es un envoltorio de conveniencia sobre publishToTable / appendToTable: marca la fila con source: "app", un level de severidad y una marca de tiempo, y luego la escribe como cualquier fila normal de tabla. El error llega a la misma tabla error-logs que usan los errores de sistema de fleetdb (etiquetados con source: "system"), por lo que se puede consultar con getHistory, transmitir con subscribeToTable / subscribe_to_table, usar en plantillas de tablero y entregar en tiempo real en transformed.error-logs — sin disparar el toast de error de sistema de la plataforma.
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)Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
error | str / string o excepción / Error | El mensaje de error, o una excepción cuyo traceback/stack (o mensaje) se registra |
level | str / string, opcional | Severidad: "error", "warn", "info" o "debug". Por defecto "error" |
append | bool / boolean, opcional | Cuando es true, usa la RPC de append (devuelve el resultado de la inserción). Por defecto false (publicación fire-and-forget) |
tsp | str / string, opcional | Sobrescritura de marca de tiempo ISO-8601. Por defecto la hora actual |
En Python las opciones son argumentos con nombre (
report_error(error, level=..., append=..., tsp=...)); en JavaScript se pasan a través de un objeto de opciones (reportError(error, { level, append, tsp })).
publish
Publica un mensaje en cualquier topic WAMP. Usa esto para mensajería personalizada o eventos que no se corresponden con una tabla de base de datos.
Python
await ironflock.publish("com.myapp.alerts", {
"level": "warning",
"message": "Temperature threshold exceeded"
})Consulta de datos históricos
getHistory
Recupera datos históricos de una tabla de flota. Soporta filtrado, rangos de tiempo y paginación.
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}]
})Parámetros de consulta:
| Campo | Tipo | Descripción |
|---|---|---|
limit | int / number | Máximo de filas a devolver (1–10.000, obligatorio) |
offset | int / number | Desplazamiento para paginación |
timeRange | dict / object | {"start": "<fecha ISO>", "end": "<fecha ISO>"} |
filterAnd | list / array | Condiciones de filtro AND, y/o el marcador latest (ver abajo) |
columns | list / array | Columnas a devolver (opcional). tsp, device_key y authid siempre se incluyen; omítelo para obtener todas las columnas |
Operadores de filtro: =, !=, >, <, >=, <=, LIKE, ILIKE, IN, NOT IN, IS, IS NOT
Cada filtro es un objeto con las claves column, operator y value.
Leer los valores actuales. Una entrada {"latest": true} en filterAnd no es una condición de filtro sino un cambio de modo: el backend de datos devuelve solo la fila más reciente de cada entidad, derivada en SQL a partir de la clave de entidad que la tabla declara con maintainLatestFlagFor. Una tabla sin clave de entidad devuelve su única fila más reciente.
Las demás condiciones se combinan con el marcador como cabría esperar: las condiciones sobre columnas de la clave de entidad acotan qué entidades se devuelven, mientras que todas las demás condiciones y timeRange se aplican a las filas más recientes resultantes. Así, combinar {"latest": true} con un filtro sobre deleted oculta las entidades eliminadas en lugar de hacer reaparecer su fila anterior.
Las versiones anteriores de IronFlock almacenaban una columna física latest_flag. Ya no existe — un filtro heredado latest_flag = true se sigue aceptando y se trata como el marcador, pero el código nuevo debería usar {"latest": true}. El marcador latest no está disponible en getSeriesHistory.
getSeriesHistory / get_series_history
Recupera datos de series temporales submuestreados de una tabla de flota: columnas numéricas agregadas en intervalos de tiempo (p. ej., promedios por hora). Ideal para gráficos que abarcan rangos de tiempo largos. Disponible para tablas (no para 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"]
})Parámetros de consulta:
| Campo | Tipo | Descripción |
|---|---|---|
metrics | list / array | Columnas numéricas a submuestrear |
method | str / string | Agregación por intervalo: "AVG", "SUM", "COUNT", "MIN", "MAX", "FIRST" o "LAST" |
limit | int / number | Número máximo de intervalos (1–10 000) |
timeRange | list / array | [start, end] — cadenas ISO datetime o números epoch-ms; null = extremo abierto (obligatorio) |
groupBy | list / array | Columnas por las que agrupar la serie (opcional) |
filterAnd | list / array | Condiciones de filtro AND (opcional). Solo condiciones de filtro — el marcador latest no se admite aquí; usa getHistory para leer los valores actuales |
Suscripción a datos
subscribeToTable / subscribe_to_table
Se suscribe a actualizaciones en tiempo real de una tabla de flota. El handler se llama cada vez que se publican nuevos datos en la tabla. Las filas escritas a través de la ruta de inserción masiva (publishRowsToTable / appendRowsToTable) se entregan a tu handler una por una, por lo que el código del handler permanece igual sin importar cómo se escribieron los datos.
Python
def on_sensor_data(*args, **kwargs):
print("New reading:", args, kwargs)
await ironflock.subscribe_to_table("sensordata", on_sensor_data)subscribe
Se suscribe a cualquier topic WAMP para mensajería personalizada en tiempo real.
Python
def on_alert(*args, **kwargs):
print("Alert received:", args, kwargs)
await ironflock.subscribe("com.myapp.alerts", on_alert)Acceso a datos entre apps
Lee los datos de flota de otra app desde dentro de tu propia app, en el mismo proyecto. La app proveedora debe declarar tu app en la sección consumes: de su data-template.yml, y el usuario del proyecto debe conceder el acceso. El acceso es de solo lectura: puedes consultar el historial y suscribirte en tiempo real a las filas de las tablas y transforms que comparte el proveedor, pero no puedes escribir en ellas. Las conexiones a apps consumidas se almacenan en caché por app y se cierran automáticamente cuando tu instancia se detiene.
Si tu app posee la concesión comodín (consumes: [{ app: "*" }]), puedes descubrir y abrir proveedores de forma dinámica con listConsumableApps / list_consumable_apps y connectToAllApps / connect_to_all_apps (más abajo).
connectToApp / connect_to_app
Abre una conexión de solo lectura al backend de datos de otra app y devuelve un manejador (handle). El manejador expone getHistory / get_history, subscribeToTable / subscribe_to_table y getSeriesHistory / get_series_history (solo tablas) — las mismas consultas y suscripciones que usas en tus propias tablas — además de close y los catálogos compartidos 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)Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
app_name / appName | str / string | Nombre de la app proveedora, tal como se declara en tu sección consumes: |
stage | str / string, opcional | Stage del proveedor: "dev" o "prod". Por defecto, el stage de tu propia app |
on_error / onError | callable, opcional | Se invoca con un CrossAppAccessError si el acceso se deniega después de establecer la conexión (p. ej., si la concesión se revoca más tarde) |
Si el acceso se deniega o se usa incorrectamente, se lanza un CrossAppAccessError (Python / JavaScript) con un campo code: NO_GRANT, PROVIDER_NOT_INSTALLED, UNKNOWN_APP, PRIVATE_TABLE o NOT_AUTHORIZED.
En Python,
stageyon_errorson argumentos con nombre; en JavaScript se pasan a través de un objeto de opciones (connectToApp(appName, { stage, onError })).
listConsumableApps / list_consumable_apps
Lista todos los proveedores no privados del proyecto — la primitiva de descubrimiento para apps que poseen la concesión de consumo comodín (consumes: [{ app: "*" }] en tu data-template.yml, concedida por el usuario del proyecto). Realiza una única llamada y no abre ninguna conexión: renderiza los catálogos devueltos en un selector, luego llama a connectToApp / connect_to_app para los que quieras — o a connectToAllApps / connect_to_all_apps para abrirlos todos a la vez.
Nota: Declara la concesión en el
data-template.ymlde tu app, y entrecomilla el*— un*sin comillas es un alias de YAML y no se parseará:
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"]Cada entrada describe un proveedor:
| Campo | Tipo | Descripción |
|---|---|---|
app | str / string | Nombre de la app proveedora |
provider_app_key | int / number | La clave de app del proveedor |
stages | dict / object | Catálogo por stage { dev?, prod? }; un stage está presente solo si el proveedor tiene un backend de datos para él. Cada catálogo contiene las tables y transforms no privadas que comparte |
Genera (Python) / lanza (JavaScript) un CrossAppAccessError con code: NO_GRANT si tu app no posee ninguna concesión comodín.
connectToAllApps / connect_to_all_apps
Abre manejadores de solo lectura a todos los proveedores no privados del proyecto en una sola llamada (solo para consumidores comodín). Enumera los proveedores mediante listConsumableApps / list_consumable_apps y abre cada uno, omitiendo los que no tengan un backend de datos para el stage solicitado. Cada manejador se almacena en caché bajo la misma clave que connectToApp / connect_to_app, de modo que una llamada posterior a connectToApp(name) devuelve el manejador ya preparado. Los manejadores devueltos se cierran juntos cuando tu instancia se detiene.
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)Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
stage | str / string, opcional | Stage del proveedor: "dev" o "prod". Por defecto, el stage de tu propia app |
continue_on_error / continueOnError | bool / boolean, opcional | Cuando es true (el valor por defecto), un proveedor que no se puede abrir se reporta a on_error / onError y se omite del resultado. Cuando es false, el primer fallo se lanza |
on_error / onError | callable, opcional | Se invoca con cada proveedor que no se pudo abrir (mientras continue_on_error / continueOnError sea true), y con un CrossAppAccessError si una conexión ya abierta se deniega posteriormente (p. ej., si la concesión se revoca) |
Devuelve los manejadores de proveedor abiertos correctamente (el mismo tipo de manejador que connectToApp / connect_to_app). Genera (Python) / lanza (JavaScript) un CrossAppAccessError con code: NO_GRANT si tu app no posee ninguna concesión comodín.
En Python,
stage,on_errorycontinue_on_errorson argumentos con nombre; en JavaScript se pasan a través de un objeto de opciones (connectToAllApps({ stage, onError, continueOnError })).
Almacenamiento de archivos gestionado
Cada backend de datos de app dispone de almacenamiento de objetos privado junto a sus tablas, accesible a través de la propiedad files. Úsalo para imágenes, PDF, fotogramas de cámara, blobs de firmware — cualquier cosa que no pertenezca a una fila de una tabla. No requiere configuración alguna: una app sin sección files: en su plantilla de datos también obtiene un espacio de nombres llamado default.
La idea clave es que almacenar un objeto te devuelve una URL permanente que puedes escribir directamente en una columna de tabla, de modo que un widget de tablero pueda renderizarla sin ningún trabajo adicional:
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)Esa URL nunca caduca, pero no es un enlace público: solo permanece legible para un solicitante autenticado que tenga acceso READ sobre este backend de datos, y un proxy de autenticación lo vuelve a comprobar en cada petición. Por eso es seguro almacenarla en la base de datos.
Espacios de nombres
Un espacio de nombres es un prefijo de clave que lleva asociada una política — retención, reglas de compartición, tipos de contenido permitidos. No es un bucket aparte; todos los espacios de nombres de una app viven dentro de la única área de almacenamiento de esa app. Declara uno solo cuando un conjunto de objetos necesite reglas distintas; en caso contrario quédate en default y organiza tus objetos con rutas de clave como 2026/03/part-1.jpg.
Declara espacios de nombres adicionales en 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 }Ten en cuenta que el presupuesto se declara una sola vez para toda la app, no por espacio de nombres. Un espacio de nombres es solo un prefijo de clave dentro de la única área de almacenamiento de la app, así que no hay nada contra lo que aplicar un presupuesto por prefijo. maxObjectBytes sí es por espacio de nombres — limita un objeto individual, no un total.
Todos los métodos siguientes aceptan el espacio de nombres como argumento opcional y, por defecto, usan default.
Almacenar y leer objetos
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étodo | Descripción |
|---|---|
put(key, data, …) | Almacena un objeto (bytes en Python, Uint8Array en JavaScript). Devuelve los metadatos del objeto, incluida su url |
get(key, namespace?) | Devuelve el contenido del objeto |
put_file(key, path, …) / get_to_file(key, path, …) | Solo Python. Almacena desde un archivo local, o escribe en él. Usa streaming en la ruta de objetos grandes |
delete(key, namespace?) | Elimina un objeto |
copy(key, to, …) | Copia un objeto, opcionalmente a otro espacio de nombres |
move(key, to, …) | Copia y luego elimina. No es atómico — el servicio no tiene un verbo de movimiento, así que un borrado fallido deja ambas copias |
JavaScript no tiene utilidades para rutas de archivo porque el paquete se distribuye con una única compilación para Node y el navegador — lee y escribe los archivos locales tú mismo con fs.
put acepta: content_type / contentType (el tipo MIME; el espacio de nombres puede restringir cuáles se permiten) y namespace. En Python son argumentos con nombre; en JavaScript van en un objeto de opciones.
Listar e inspeccionar
| Método | Descripción |
|---|---|
list(…) | Una página de objetos. Devuelve objects, prefixes, is_truncated / isTruncated y un cursor que puedes volver a pasar para la página siguiente |
iter(…) / iterate(…) | Iterador asíncrono sobre todos los objetos bajo un prefijo, paginando automáticamente. Se llama iter en Python e iterate en JavaScript |
stat(key, namespace?) | Metadatos de un objeto sin transferir su contenido |
exists(key, namespace?) | Si un objeto existe o no |
namespaces() | Los espacios de nombres que esta app puede usar |
usage(…) | Cuánto almacenamiento está usando la app — véase más abajo |
catalog() | Espacios de nombres más los límites y cuotas emitidos por el servidor. Se almacena en caché tras la primera llamada |
Los objetos se describen con los mismos campos en ambos SDK, siguiendo el estilo de nomenclatura de cada lenguaje: namespace, key, size, etag, content_type / contentType, last_modified / lastModified, checksum_sha256 / checksumSha256 y url.
Uso de almacenamiento y cuota
usage responde desde el almacén de objetos en una sola llamada, así que los totales son exactos en lugar de sumados por el 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}| Campo | Significado |
|---|---|
size_bytes / sizeBytes | Bytes almacenados actualmente |
object_count / objectCount | Número de objetos almacenados |
quota_bytes / quotaBytes | El presupuesto aplicado. 0 significa ilimitado |
free_bytes / freeBytes | Bytes restantes. -1 significa ilimitado — indicar 0 ahí se leería como “lleno” |
per_namespace / perNamespace | Bytes por espacio de nombres. Presente solo cuando pides el desglose detallado |
El desglose por espacio de nombres está desactivado por defecto porque el almacén no puede responderlo directamente: lleva la contabilidad por área de almacenamiento, y un espacio de nombres es solo un prefijo, así que el SDK tiene que listar cada espacio de nombres para sumar los tamaños. Pídelo cuando lo necesites, no en una ruta crítica.
Aparecen dos cuotas distintas, y conviene no confundirlas. catalog() informa de ambas:
| Campo | Significado |
|---|---|
quota_bytes / quotaBytes | Lo que realmente se aplica, leído del almacén de objetos — el ajuste del usuario del proyecto |
suggested_quota_bytes / suggestedQuotaBytes | Lo que pidió la plantilla de datos de la app. 0 si no pidió nada |
Difieren siempre que un usuario ha subido o bajado el presupuesto de la app, y por eso el valor aplicado se lee del almacén y no de la plantilla — volver a desplegar la app no debe restablecer en silencio la elección de un usuario. Una interfaz puede mostrar ambos (“la app sugiere X, tú has establecido Y”). La aplicación efectiva siempre usa el primero.
Compartir objetos
Hay dos tipos de enlace, y la diferencia importa:
| Método | Vigencia | Quién puede leerlo |
|---|---|---|
url(key, …) | Permanente | Solo un solicitante autenticado con READ sobre este backend de datos — se vuelve a comprobar en cada petición. Seguro para almacenar en una columna de tabla |
share_url / shareUrl | Caduca (15 min por defecto, acotado por el servidor) | Cualquiera que tenga el enlace. Nada vuelve a comprobar la autorización cuando se usa |
share_url / shareUrl es una capacidad al portador: entrégalo a una persona que necesite acceso temporal, y no lo almacenes en la base de datos. Usa url para todo aquello que renderice un tablero.
url devuelve None / undefined cuando el despliegue no tiene un borde HTTP (por ejemplo, un appliance de HTTP simple) — esa es la señal para recurrir a get. Pasar el etag de un objeto como argumento version permite que los navegadores cacheen la respuesta de forma inmutable.
upload_url / uploadUrl emite una URL con caducidad que acepta una subida directa, y devuelve url, method, headers y expires_in / expiresIn. Envía exactamente las cabeceras que devuelve, o la firma no se verificará.
Objetos grandes
El SDK elige el transporte según el tamaño, de forma automática — no hay nada que configurar:
| Tamaño del objeto | Cómo viaja |
|---|---|
| Hasta el límite inline (actualmente 6 MiB) | Una única llamada a través del router de mensajes |
| Mayor | Directamente al almacenamiento de objetos por HTTPS, sin pasar por el router |
El servidor informa del límite exacto en tiempo de ejecución como inline_max_bytes / inlineMaxBytes en catalog(), de modo que se puede subir sin publicar una nueva versión del SDK.
Quedan dos topes, y ambos reportan TOO_LARGE con un motivo que indica cuál de los dos has alcanzado:
- 5 GiB — el límite de subida en una sola operación del almacén de objetos. La subida multiparte aún no está implementada.
- El límite inline, allí donde no hay endpoint directo — un appliance aislado (air-gapped) no puede transferir un objeto grande en absoluto. Ningún reintento ni fragmento más pequeño ayudará, y el mensaje lo dice.
La ruta directa necesita que el dispositivo alcance el host del almacenamiento de objetos, no solo el router. Dos fallos habituales en campo tienen sus propios códigos en lugar de parecer problemas de autorización: PRESIGN_UNREACHABLE (un proxy que solo permite el router) y CLOCK_SKEW (el almacén de objetos rechaza las peticiones desfasadas más de 15 minutos — revisa el NTP del dispositivo).
Errores de almacenamiento de archivos
Toda operación de archivos genera (Python) / lanza (JavaScript) un FileStoreError que lleva un code estable y un reason legible para humanos. Ramifica según code, nunca según 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| Código | Significado |
|---|---|
NOT_AUTHORIZED | El llamador no puede realizar esta operación |
NO_SUCH_NAMESPACE | El espacio de nombres no está declarado en la plantilla de datos |
NO_SUCH_OBJECT | La clave no existe |
TOO_LARGE | Supera el límite de transferencia en una sola llamada |
OBJECT_TOO_LARGE | Supera el maxObjectBytes propio del espacio de nombres |
QUOTA_EXCEEDED | El filestore está lleno |
CONTENT_TYPE_NOT_ALLOWED | El espacio de nombres restringe los contentTypes |
NOT_SUPPORTED | El backend no puede hacer esto |
NOT_AVAILABLE | Este despliegue no tiene servicio de archivos |
PRESIGN_UNREACHABLE | El almacenamiento de objetos no es accesible directamente (¿un proxy?) |
CLOCK_SKEW | El reloj del dispositivo está demasiado desfasado |
INTERNAL | Cualquier otra cosa |
Un servidor más nuevo puede introducir códigos que esta versión del SDK no conoce. Se propagan tal cual en code en lugar de colapsarse, así que trata un valor no reconocido como un fallo genérico.
En Python,
FileStoreErrorse importa desdeironflock.filestore; en JavaScript se exporta desde la raíz del paquete (import { FileStoreError } from "ironflock").
Comunicación entre dispositivos
registerDeviceFunction / register_device_function
Registra un procedimiento que otros dispositivos del mismo proyecto pueden llamar. El SDK asigna automáticamente un espacio de nombres al procedimiento para el dispositivo actual.
Python
def add(a, b):
return a + b
await ironflock.register_device_function("com.myapp.add", add)
register()es un alias deregister_device_function().
callDeviceFunction / call_device_function
Llama a un procedimiento registrado por otro dispositivo. El SDK construye el topic WAMP completo automáticamente usando la clave del dispositivo de destino.
Python
result = await ironflock.call_device_function(
42, # target device key
"com.myapp.add", # procedure name
args=[3, 5] # arguments
)
print(result) # 8call
Llama a un procedimiento remoto usando una URI WAMP completa. Usa esto para llamadas directas cuando conoces el topic exacto.
Python
result = await ironflock.call("some.full.wamp.topic", args=[42])Metadatos del dispositivo
setDeviceLocation / set_device_location
Actualiza la ubicación GPS del dispositivo en la plataforma. Los cambios se reflejan en tiempo real en los mapas de IronFlock.
Python
await ironflock.set_device_location(long=8.6821, lat=50.1109)| Parámetro | Rango |
|---|---|
long | -180 a 180 |
lat | -90 a 90 |
El historial de ubicación no se almacena. Para rastrear la ubicación a lo largo del tiempo, crea una tabla dedicada y usa
publish_to_table/publishToTable.
getRemoteAccessUrlForPort
Devuelve la URL pública de acceso remoto para un puerto determinado del dispositivo.
Python
url = ironflock.getRemoteAccessUrlForPort(8080)
# "https://<device_key>-<app_name>-8080.app.ironflock.com"Propiedades de conexión y ciclo de vida
Python
| Propiedad | Tipo | Descripción |
|---|---|---|
is_connected | bool | Indica si la conexión con la plataforma está activa |
connection | CrossbarConnection | La instancia de conexión subyacente (uso avanzado) |
| Método | Descripción |
|---|---|
run() | Inicia la conexión y ejecuta mainFunc (bloqueante) |
await start() | Inicia la conexión de forma asíncrona |
await stop() | Detiene la conexión y cancela tareas en ejecución |
await run_async() | Inicia y mantiene la conexión de forma asíncrona |
Manejo de errores
Todos los métodos del SDK fallan de forma explícita: ante argumentos inválidos, una conexión perdida o un rechazo de la plataforma, lanzan una excepción (Python) o rechazan la promesa (JavaScript) con un mensaje que indica la operación, el topic y el motivo. Nada se descarta en silencio, así que envuelve en un bloque try las llamadas que quieras que sobrevivan.
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, los fallos que provienen de la plataforma son instancias de WampError — una subclase normal de Error que además lleva la URI de error WAMP en error y la carga útil del error en args / kwargs. Todo lo demás (parámetros inválidos, sin conexión) es un Error normal.
Migración: las versiones anteriores del SDK registraban un mensaje y devolvían
None/nullcuando una llamada fallaba. Ahora lanzan un error en su lugar, por lo que el código con la formaif result is None:ya no detecta los fallos — usatry/except(otry/catch).
Uso en navegador (solo JavaScript)
El SDK de JavaScript funciona en navegadores modernos. Como los navegadores no tienen variables de entorno, pasa toda la configuración a través del constructor:
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 }]);Usa IronFlock.fromServer() para obtener la configuración desde tu backend en lugar de codificar las credenciales:
const ironflock = await IronFlock.fromServer("/api/ironflock-config");
await ironflock.start();Tu endpoint del backend debe devolver un objeto JSON con las opciones de conexión (serialNumber, deviceKey, appName, swarmKey, appKey, env).
Registro de funciones para agentes de IA
El SDK puede registrar funciones que son invocables por agentes de IA. Registra un procedimiento y referencia su topic en tu 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)El agente de IA puede entonces llamar a esta función cuando un usuario haga una pregunta que requiera datos de sensores en tiempo real.
Para conectar el topic WAMP registrado con un agente de IA, referencíalo en el archivo .ironflock/ai-template.yml de tu app:
sensor_agent:
tool_description: |
Delega a este agente cuando el usuario pregunte sobre lecturas de sensores,
datos en vivo del dispositivo o condiciones ambientales actuales.
system_prompt: |
Eres un especialista en datos de sensores. Usa get_current para obtener
la lectura más reciente de cualquier sensor. Incluye siempre la unidad
en tu respuesta.
main: true
max_context_tokens: 30000
max_iterations: 5
tools:
get_current:
description: Devuelve la lectura más reciente de un sensor.
topic: sensors.get_latest
parameters:
sensor_id:
type: string
description: El identificador del sensor a consultar.
required: trueEl valor de topic (sensors.get_latest) debe coincidir con el nombre indicado en register_device_function / registerDeviceFunction en el código edge. IronFlock enruta automáticamente la llamada al dispositivo donde está registrada la función.
Para la referencia completa de ai-template.yml, consulta Definición de agentes y herramientas.
Variables de entorno
Estas variables son establecidas automáticamente por el runtime de IronFlock dentro de los contenedores de apps:
| Variable | Descripción |
|---|---|
DEVICE_NAME | Nombre visible del dispositivo |
DEVICE_SERIAL_NUMBER | Identificador único e inmutable del dispositivo |
DEVICE_KEY | Clave del dispositivo para autenticación |
SWARM_KEY | Identificador del proyecto |
APP_KEY | Identificador de la app |
APP_NAME | Nombre de la app |
ENV | Entorno: DEV o 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")