IronFlock SDK
L’SDK IronFlock consente alle tue applicazioni edge di interagire con la piattaforma IronFlock. Gestisce l’autenticazione automaticamente quando viene eseguito su un dispositivo registrato e fornisce funzioni per pubblicare dati, interrogare la cronologia, chiamare procedure remote tra dispositivi e aggiornare i metadati dei dispositivi.
| SDK | Pacchetto | Richiede |
|---|---|---|
| Python | ironflock su PyPI | Python 3.8+ |
| JavaScript | ironflock su npm | Node.js 18+ o browser moderno |
Installazione
Python
pip install ironflockOppure aggiungi ironflock al file requirements.txt della tua app.
Avvio Rapido
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()Quando viene usato all’interno di un container dell’app IronFlock, l’SDK legge automaticamente le credenziali di connessione dall’ambiente — nessuna configurazione manuale necessaria.
Opzioni del Costruttore
Python
ironflock = IronFlock(
mainFunc=main, # async function to run after connecting
serial_number="abc123" # override device serial (optional)
)| Parametro | Descrizione |
|---|---|
mainFunc | Una funzione asincrona che viene eseguita una volta stabilita la connessione |
serial_number | Sovrascrive il numero seriale del dispositivo. Predefinito: variabile d’ambiente DEVICE_SERIAL_NUMBER |
Pubblicazione dei Dati
publishToTable / publish_to_table
Pubblica un record di dati in una tabella della flotta. Il nome della tabella deve corrispondere a una tabella definita nel file data-template.yml della tua app. L’SDK instrada automaticamente i dati al database del progetto corretto.
Python
await ironflock.publish_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60,
"device_id": "sensor-001"
})appendToTable / append_to_table
Aggiunge dati a una tabella della flotta tramite una chiamata di procedura remota invece di pub/sub. Usalo quando hai bisogno della conferma che i dati siano stati persistiti.
Python
result = await ironflock.append_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60
})publishRowsToTable / publish_rows_to_table
Pubblica molte righe in un singolo messaggio (inserimento in blocco) in una tabella della flotta. La piattaforma inserisce l’intero batch in modo atomico (tutto-o-niente) in un’unica operazione. Usalo per dati ad alta frequenza dove un round-trip per riga sarebbe troppo costoso. Come publishToTable, è fire-and-forget — la conferma attesta la consegna al router, non l’inserimento nel database.
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},
])Il secondo argomento è una lista non vuota di oggetti riga da inserire.
appendRowsToTable / append_rows_to_table
Aggiunge molte righe in una singola chiamata di procedura remota (inserimento in blocco) a una tabella della flotta. La piattaforma inserisce l’intero batch in modo atomico (tutto-o-niente): se una qualsiasi riga non è valida, l’intero batch viene rifiutato e nulla viene persistito. Preferiscilo a publishRowsToTable / publish_rows_to_table quando hai bisogno dell’esito dell’inserimento.
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
Segnala un errore dell’applicazione nella tabella error-logs della tua flotta. Si tratta di un wrapper di comodità su publishToTable / appendToTable: aggiunge alla riga source: "app", un level di gravità e un timestamp, quindi la scrive come qualsiasi normale riga di tabella. L’errore finisce nella stessa tabella error-logs usata dagli errori di sistema di fleetdb (contrassegnati con source: "system"), perciò è interrogabile con getHistory, trasmissibile in streaming con subscribeToTable / subscribe_to_table, utilizzabile nei board-templates e consegnato in tempo reale su transformed.error-logs — senza far comparire il toast di errore di sistema della piattaforma.
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)Parametri:
| Parametro | Tipo | Descrizione |
|---|---|---|
error | str / string o eccezione / Error | Il messaggio di errore, oppure un’eccezione il cui traceback/stack (o messaggio) viene registrato |
level | str / string, opzionale | Gravità: "error", "warn", "info" o "debug". Il valore predefinito è "error" |
append | bool / boolean, opzionale | Quando è true, usa la procedura remota di append (restituisce l’esito dell’inserimento). Il valore predefinito è false (pubblicazione fire-and-forget) |
tsp | str / string, opzionale | Sovrascrittura del timestamp ISO-8601. Il valore predefinito è l’ora corrente |
In Python le opzioni sono argomenti con parola chiave (
report_error(error, level=..., append=..., tsp=...)); in JavaScript vengono passate tramite un oggetto di opzioni (reportError(error, { level, append, tsp })).
publish
Pubblica un messaggio su qualsiasi topic WAMP. Usalo per messaggi personalizzati o eventi che non si mappano su una tabella del database.
Python
await ironflock.publish("com.myapp.alerts", {
"level": "warning",
"message": "Temperature threshold exceeded"
})Interrogazione dei Dati Storici
getHistory
Recupera dati storici da una tabella della flotta. Supporta filtraggio, intervalli di tempo e paginazione.
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}]
})Parametri di query:
| Campo | Tipo | Descrizione |
|---|---|---|
limit | int / number | Numero massimo di righe da restituire (1–10.000, obbligatorio) |
offset | int / number | Offset per la paginazione |
timeRange | dict / object | {"start": "<ISO datetime>", "end": "<ISO datetime>"} |
filterAnd | list / array | Condizioni di filtro AND e/o il marcatore latest (vedi sotto) |
columns | list / array | Colonne da restituire (opzionale). tsp, device_key e authid sono sempre incluse; ometti per avere tutte le colonne |
Operatori di filtro: =, !=, >, <, >=, <=, LIKE, ILIKE, IN, NOT IN, IS, IS NOT
Ogni filtro è un oggetto con le chiavi column, operator e value.
Leggere i valori correnti. Una voce {"latest": true} in filterAnd non è una condizione di filtro ma un cambio di modalità: il Data Backend restituisce solo la riga più recente per ogni entità, derivata in SQL dalla chiave di entità che la tabella dichiara con maintainLatestFlagFor. Una tabella senza chiave di entità restituisce la sua singola riga più recente.
Le altre condizioni si combinano con il marcatore come ci si aspetta: le condizioni sulle colonne della chiave di entità restringono quali entità vengono restituite, mentre tutte le altre condizioni e timeRange vengono applicate alle righe più recenti risultanti. Quindi combinare {"latest": true} con un filtro su deleted nasconde le entità eliminate invece di far riemergere la loro riga precedente.
Le versioni precedenti di IronFlock memorizzavano una colonna fisica latest_flag. Non esiste più — un filtro legacy latest_flag = true viene ancora accettato e trattato come il marcatore, ma il codice nuovo dovrebbe usare {"latest": true}. Il marcatore latest non è disponibile in getSeriesHistory.
getSeriesHistory / get_series_history
Recupera dati di serie temporali sotto-campionati da una tabella di flotta: colonne numeriche aggregate in intervalli di tempo (ad es. medie orarie). Ideale per grafici su intervalli di tempo lunghi. Disponibile per le tabelle (non per i transform).
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"]
})Parametri di query:
| Campo | Tipo | Descrizione |
|---|---|---|
metrics | list / array | Colonne numeriche da sotto-campionare |
method | str / string | Aggregazione per intervallo: "AVG", "SUM", "COUNT", "MIN", "MAX", "FIRST" o "LAST" |
limit | int / number | Numero massimo di intervalli (1–10.000) |
timeRange | list / array | [start, end] — stringhe ISO datetime o numeri epoch-ms; null = estremo aperto (obbligatorio) |
groupBy | list / array | Colonne per cui raggruppare la serie (opzionale) |
filterAnd | list / array | Condizioni di filtro AND (opzionale). Solo condizioni di filtro — il marcatore latest non è supportato qui; usa getHistory per leggere i valori correnti |
Sottoscrizione ai Dati
subscribeToTable / subscribe_to_table
Sottoscrive agli aggiornamenti in tempo reale di una tabella della flotta. Il gestore viene chiamato ogni volta che nuovi dati vengono pubblicati nella tabella. Le righe scritte tramite il percorso di inserimento in blocco (publishRowsToTable / appendRowsToTable) vengono consegnate al tuo gestore una alla volta, quindi il codice del gestore rimane lo stesso indipendentemente da come i dati sono stati scritti.
Python
def on_sensor_data(*args, **kwargs):
print("New reading:", args, kwargs)
await ironflock.subscribe_to_table("sensordata", on_sensor_data)subscribe
Sottoscrive a qualsiasi topic WAMP per la messaggistica in tempo reale personalizzata.
Python
def on_alert(*args, **kwargs):
print("Alert received:", args, kwargs)
await ironflock.subscribe("com.myapp.alerts", on_alert)Colonne Segrete
Una colonna dichiarata secret: true nel data template dell’app viene cifrata a riposo. Nessun percorso di lettura ordinario restituisce il valore in chiaro, nemmeno all’app che lo ha scritto:
| Percorso di lettura | Come appare una colonna segreta |
|---|---|
getHistory, subscribeToTable / subscribe_to_table e tutto il resto attraverso il router dei messaggi | il segnaposto __secret__, esportato come SECRET_PLACEHOLDER |
| Accesso SQL (il login Postgres di FleetDB Access) | il testo cifrato memorizzato, ifsec:1:… |
revealSecrets / reveal_secrets | il valore decifrato in chiaro |
NULL viene propagato come NULL su ogni percorso, così “impostato ma nascosto” rimane distinguibile da “mai scritto”.
Su una tabella entità, il ciclo lettura-modifica-scrittura è sicuro: ripubblicare una riga che contiene ancora il segnaposto __secret__ — oppure omettere del tutto la colonna segreta — mantiene il segreto memorizzato invece di sovrascriverlo, e un null esplicito lo cancella. Consulta Colonne Segrete nella guida al Data Backend per le regole esatte.
Python
# Written like any other column — the data backend encrypts it on insert
await ironflock.publish_to_table("credentials", {"device_id": "d-1", "api_token": "hunter2"})
# A normal read redacts it
rows = await ironflock.getHistory("credentials", {"limit": 1})
# [{"tsp": "...", "device_id": "d-1", "api_token": "__secret__"}]Entrambe le funzioni descritte di seguito sono richiamabili solo dai container dell’app stessa. Il router dei messaggi le nega a ogni ruolo browser e alle app consumatrici, ed è per questo che un handle ConsumedApp non ha un equivalente: le colonne segrete di un’altra app arrivano oscurate e tali restano.
revealSecrets / reveal_secrets
Legge le righe di una delle tue tabelle con le colonne segrete decifrate.
Python
rows = await ironflock.reveal_secrets("credentials", {
"limit": 1,
"filterAnd": [{"column": "device_id", "operator": "=", "value": "d-1"}],
})
# [{"tsp": "...", "device_id": "d-1", "api_token": "hunter2"}]I parametri di query sono gli stessi di getHistory, con una differenza: limit non può superare 100. Questo tetto esiste per impedire che una singola chiamata decifri un’intera tabella, e il data backend rifiuta un valore superiore invece di ridurlo silenziosamente.
verifySecret / verify_secret
Verifica un valore candidato rispetto a una colonna segreta senza rivelarla — la controparte “verifica della password” della funzione di rivelazione. Il confronto viene eseguito all’interno del data backend, in tempo costante, e torna indietro soltanto la risposta.
Python
ok = await ironflock.verify_secret(
"credentials", "api_token", submitted_token,
{"limit": 1, "filterAnd": [{"column": "device_id", "operator": "=", "value": "d-1"}]},
)
if ok:
...Parametri:
| Parametro | Tipo | Descrizione |
|---|---|---|
tablename | str / string | La tabella su cui effettuare la verifica |
column | str / string | La colonna segreta da confrontare |
candidate | str / string | Il valore in chiaro da verificare. Viene confrontato lato server e non viene mai scritto nella tabella |
query_params / queryParams | SecretQueryParams | Quali righe partecipano al confronto. Per impostazione predefinita la riga più recente; limit non può superare 100 |
JavaScript restituisce { match, checked }; Python restituisce un semplice bool. Dove hai checked, vale la pena usarlo: distingue un candidato sbagliato (checked > 0) da un selettore che non ha trovato alcuna riga (checked === 0), due errori molto diversi da segnalare all’utente.
Poiché ogni riga è cifrata con un proprio valore casuale, due righe che contengono lo stesso segreto memorizzano testi cifrati diversi. È per questo che un filtro = su una colonna segreta non può funzionare ed esiste invece questa funzione.
Accesso ai Dati tra App
Leggi i dati di flotta di un’altra app dall’interno della tua app, all’interno dello stesso progetto. L’app fornitrice deve dichiarare la tua app nella sezione consumes: del suo data-template.yml, e l’utente del progetto deve concedere l’accesso. L’accesso è di sola lettura: puoi interrogare lo storico e sottoscrivere in tempo reale le righe delle tabelle e dei transform condivisi dal fornitore, ma non puoi scrivervi. Le connessioni alle app consumate vengono memorizzate nella cache per app e chiuse automaticamente quando la tua istanza si arresta.
Se la tua app possiede l’autorizzazione wildcard (consumes: [{ app: "*" }]), puoi scoprire e aprire dinamicamente i fornitori con listConsumableApps / list_consumable_apps e connectToAllApps / connect_to_all_apps (di seguito).
connectToApp / connect_to_app
Apre una connessione di sola lettura al data backend di un’altra app e restituisce un handle. L’handle espone getHistory / get_history, subscribeToTable / subscribe_to_table e getSeriesHistory / get_series_history (solo tabelle) — le stesse interrogazioni e sottoscrizioni che usi sulle tue tabelle — oltre a close e ai cataloghi condivisi 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)Parametri:
| Parametro | Tipo | Descrizione |
|---|---|---|
app_name / appName | str / string | Nome dell’app fornitrice, come dichiarato nella tua sezione consumes: |
stage | str / string, opzionale | Stage del fornitore: "dev" o "prod". Il valore predefinito è lo stage della tua app |
on_error / onError | callable, opzionale | Viene invocato con un CrossAppAccessError se l’accesso viene negato dopo che la connessione è stata stabilita (ad es. se l’autorizzazione viene revocata in seguito) |
Se l’accesso viene negato o usato in modo improprio, viene sollevato (Python) / lanciato (JavaScript) un CrossAppAccessError con un campo code: NO_GRANT, PROVIDER_NOT_INSTALLED, UNKNOWN_APP, PRIVATE_TABLE o NOT_AUTHORIZED.
In Python,
stageeon_errorsono argomenti con parola chiave; in JavaScript vengono passati tramite un oggetto di opzioni (connectToApp(appName, { stage, onError })).
listConsumableApps / list_consumable_apps
Elenca tutti i fornitori non privati nel progetto — la primitiva di scoperta per le app che possiedono l’autorizzazione di consumo wildcard (consumes: [{ app: "*" }] nel tuo data-template.yml, concessa dall’utente del progetto). Esegue una singola chiamata e non apre nessuna connessione: mostra i cataloghi restituiti in un selettore, quindi chiama connectToApp / connect_to_app per quelli che desideri — oppure connectToAllApps / connect_to_all_apps per aprirli tutti in una volta.
Nota: Dichiara l’autorizzazione nel
data-template.ymldella tua app, e metti tra virgolette il*— un*da solo è un alias YAML e non verrà interpretato:
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"]Ogni voce descrive un fornitore:
| Campo | Tipo | Descrizione |
|---|---|---|
app | str / string | Nome dell’app fornitrice |
provider_app_key | int / number | La chiave dell’app del fornitore |
stages | dict / object | Catalogo per stage { dev?, prod? }; uno stage è presente solo se il fornitore dispone di un data backend per esso. Ogni catalogo contiene le tables e i transforms non privati che condivide |
Viene sollevato (Python) / lanciato (JavaScript) un CrossAppAccessError con code: NO_GRANT se la tua app non possiede alcuna autorizzazione wildcard.
connectToAllApps / connect_to_all_apps
Apre handle di sola lettura verso tutti i fornitori non privati nel progetto in un’unica chiamata (solo per i consumatori wildcard). Enumera i fornitori tramite listConsumableApps / list_consumable_apps e apre ciascuno di essi, saltando quelli senza un data backend per lo stage richiesto. Ogni handle viene memorizzato nella cache con la stessa chiave di connectToApp / connect_to_app, così una successiva chiamata a connectToApp(name) restituisce l’handle già pronto. Gli handle restituiti vengono chiusi insieme quando la tua istanza si arresta.
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)Parametri:
| Parametro | Tipo | Descrizione |
|---|---|---|
stage | str / string, opzionale | Stage del fornitore: "dev" o "prod". Il valore predefinito è lo stage della tua app |
continue_on_error / continueOnError | bool / boolean, opzionale | Quando è true (il valore predefinito), un fornitore che non riesce ad aprirsi viene segnalato a on_error / onError e omesso dal risultato. Quando è false, il primo errore viene sollevato/lanciato |
on_error / onError | callable, opzionale | Viene invocato con ogni fornitore che non è stato possibile aprire (mentre continue_on_error / continueOnError è true), e con un CrossAppAccessError se una connessione già aperta viene successivamente negata (ad es. se l’autorizzazione viene revocata) |
Restituisce gli handle dei fornitori aperti con successo (stesso tipo di handle di connectToApp / connect_to_app). Viene sollevato (Python) / lanciato (JavaScript) un CrossAppAccessError con code: NO_GRANT se la tua app non possiede alcuna autorizzazione wildcard.
In Python,
stage,on_errorecontinue_on_errorsono argomenti con parola chiave; in JavaScript vengono passati tramite un oggetto di opzioni (connectToAllApps({ stage, onError, continueOnError })).
Archiviazione Gestita dei File
Ogni data backend di un’app dispone di un’archiviazione a oggetti privata accanto alle sue tabelle, raggiungibile tramite la proprietà files. Usala per immagini, PDF, fotogrammi di telecamere, blob di firmware — tutto ciò che non appartiene alla riga di una tabella. Non è richiesta alcuna configurazione: un’app senza una sezione files: nel suo data template ottiene comunque un namespace chiamato default.
L’idea chiave è che memorizzare un oggetto ti restituisce un URL permanente che puoi scrivere direttamente in una colonna di una tabella, così che un widget di una dashboard possa renderizzarlo senza ulteriore lavoro:
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)Quell’URL non scade mai, ma non è un link pubblico: resta leggibile soltanto a un richiedente autenticato che possiede l’accesso READ su questo data backend, e un proxy di autenticazione lo riverifica a ogni richiesta. È quindi sicuro memorizzarlo nel database.
Namespace
Un namespace è un prefisso di chiave che porta con sé delle policy — conservazione, regole di condivisione, tipi di contenuto ammessi. Non è un bucket separato: ogni namespace di un’app risiede all’interno dell’unica area di archiviazione di quell’app. Dichiarane uno solo quando un insieme di oggetti ha bisogno di regole diverse; altrimenti resta in default e organizza i tuoi oggetti con percorsi di chiave come 2026/03/part-1.jpg.
Dichiara i namespace aggiuntivi nel 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 }Nota che il budget viene dichiarato una sola volta per l’intera app, non per singolo namespace. Un namespace è soltanto un prefisso di chiave all’interno dell’unica area di archiviazione dell’app, quindi non ci sarebbe nulla su cui far valere un budget per prefisso. maxObjectBytes è invece per namespace — limita un singolo oggetto, non un totale.
Ogni metodo elencato di seguito accetta il namespace come argomento opzionale e usa default come valore predefinito.
Memorizzare e leggere gli oggetti
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")| Metodo | Descrizione |
|---|---|
put(key, data, …) | Memorizza un oggetto (bytes in Python, Uint8Array in JavaScript). Restituisce i metadati dell’oggetto, incluso il suo url |
get(key, namespace?) | Restituisce il contenuto dell’oggetto |
put_file(key, path, …) / get_to_file(key, path, …) | Solo Python. Memorizza a partire da un file locale, o scrive su di esso. Trasmette in streaming sul percorso per oggetti di grandi dimensioni |
delete(key, namespace?) | Elimina un oggetto |
copy(key, to, …) | Copia un oggetto, opzionalmente in un altro namespace |
move(key, to, …) | Copia-poi-elimina. Non atomico — il servizio non dispone di un verbo di spostamento, quindi un’eliminazione fallita lascia entrambe le copie |
JavaScript non offre helper per i percorsi dei file perché il pacchetto viene distribuito con un’unica build sia per Node sia per il browser — leggi e scrivi i file locali autonomamente con fs.
put accetta: content_type / contentType (il tipo MIME; il namespace può limitare quali sono ammessi) e namespace. In Python sono argomenti con parola chiave; in JavaScript vengono passati in un oggetto di opzioni.
Elencare e ispezionare
| Metodo | Descrizione |
|---|---|
list(…) | Una pagina di oggetti. Restituisce objects, prefixes, is_truncated / isTruncated e un cursor da ripassare per la pagina successiva |
iter(…) / iterate(…) | Iteratore asincrono su tutti gli oggetti sotto un prefisso, con paginazione automatica. Si chiama iter in Python e iterate in JavaScript |
stat(key, namespace?) | Metadati di un singolo oggetto senza trasferirne il contenuto |
exists(key, namespace?) | Indica se un oggetto esiste |
namespaces() | I namespace che questa app può usare |
usage(…) | Quanto spazio di archiviazione sta usando l’app — vedi sotto |
catalog() | I namespace più i limiti e le quote emessi dal server. Memorizzato nella cache dopo la prima chiamata |
Gli oggetti sono descritti dagli stessi campi in entrambi gli SDK, secondo lo stile di denominazione di ciascun linguaggio: namespace, key, size, etag, content_type / contentType, last_modified / lastModified, checksum_sha256 / checksumSha256 e url.
Utilizzo dello spazio e quota
usage risponde interrogando l’archiviazione a oggetti con una sola chiamata, quindi i totali sono esatti anziché sommati dall’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 | Significato |
|---|---|
size_bytes / sizeBytes | Byte attualmente memorizzati |
object_count / objectCount | Numero di oggetti memorizzati |
quota_bytes / quotaBytes | Il budget applicato. 0 significa illimitato |
free_bytes / freeBytes | Byte rimanenti. -1 significa illimitato — riportare 0 verrebbe letto come “pieno” |
per_namespace / perNamespace | Byte per namespace. Presente solo se richiedi il dettaglio |
Il dettaglio per namespace è disattivato per impostazione predefinita perché l’archiviazione a oggetti non è in grado di fornirlo direttamente: conteggia per area di archiviazione, e un namespace è soltanto un prefisso, quindi l’SDK deve elencare ogni namespace per sommarne le dimensioni. Richiedilo quando ti serve, non su un percorso critico.
Compaiono due quote diverse, ed è utile tenerle distinte. catalog() riporta entrambe:
| Campo | Significato |
|---|---|
quota_bytes / quotaBytes | Ciò che viene effettivamente applicato, letto dall’archiviazione a oggetti — l’impostazione dell’utente del progetto |
suggested_quota_bytes / suggestedQuotaBytes | Ciò che il data template dell’app ha richiesto. 0 se non ha richiesto nulla |
Differiscono ogni volta che un utente ha aumentato o ridotto il budget dell’app, ed è per questo che il valore applicato viene letto dall’archiviazione anziché dal template — ridistribuire l’app non deve azzerare silenziosamente la scelta di un utente. Un’interfaccia può mostrare entrambi (“l’app suggerisce X, tu hai impostato Y”). L’applicazione della quota usa sempre il primo.
Condividere gli oggetti
Esistono due tipi di link, e la differenza è importante:
| Metodo | Durata | Chi può leggerlo |
|---|---|---|
url(key, …) | Permanente | Solo un richiedente autenticato con accesso READ su questo data backend — riverificato a ogni richiesta. Sicuro da memorizzare in una colonna di una tabella |
share_url / shareUrl | A scadenza (15 minuti per impostazione predefinita, limitati dal server) | Chiunque possieda il link. Nulla riverifica l’autorizzazione nel momento in cui viene usato |
share_url / shareUrl è una capability al portatore: consegnalo a una persona che ha bisogno di accesso temporaneo, e non memorizzarlo nel database. Usa url per tutto ciò che viene renderizzato da una dashboard.
url restituisce None / undefined quando il deployment non dispone di un edge HTTP (ad esempio un’appliance in solo HTTP) — è il segnale per ripiegare su get. Passando l’etag di un oggetto come argomento version, i browser possono memorizzare la risposta nella cache in modo immutabile.
upload_url / uploadUrl genera un URL a scadenza che accetta un upload diretto, restituendo url, method, headers ed expires_in / expiresIn. Invia esattamente gli header che restituisce, altrimenti la firma non verrà verificata.
Oggetti di grandi dimensioni
L’SDK sceglie il trasporto in base alla dimensione, automaticamente — non c’è nulla da configurare:
| Dimensione dell’oggetto | Come viaggia |
|---|---|
| Fino al limite inline (attualmente 6 MiB) | Una singola chiamata attraverso il router dei messaggi |
| Superiore | Direttamente verso l’archiviazione a oggetti tramite HTTPS, bypassando il router |
Il limite esatto viene comunicato dal server a runtime come inline_max_bytes / inlineMaxBytes in catalog(), così può essere alzato senza una nuova release dell’SDK.
Restano due tetti massimi, ed entrambi segnalano TOO_LARGE con un motivo che indica quale dei due hai raggiunto:
- 5 GiB — il limite di upload singolo dell’archiviazione a oggetti. L’upload multipart non è ancora implementato.
- Il limite inline, dove non esiste un endpoint diretto — un’appliance air-gapped non può trasferire affatto un oggetto di grandi dimensioni. Nessun nuovo tentativo né chunk più piccolo servirà a qualcosa, e il messaggio lo dice esplicitamente.
Il percorso diretto richiede che il dispositivo raggiunga l’host dell’archiviazione a oggetti, non solo il router. Due fallimenti comuni sul campo hanno codici propri invece di apparire come problemi di autorizzazione: PRESIGN_UNREACHABLE (un proxy che consente soltanto il router) e CLOCK_SKEW (l’archiviazione a oggetti rifiuta le richieste sfasate di più di 15 minuti — controlla l’NTP sul dispositivo).
Errori di archiviazione dei file
Ogni operazione sui file solleva (Python) / lancia (JavaScript) un FileStoreError che trasporta un code stabile e un reason leggibile. Basa le tue condizioni sul code, mai sul 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| Codice | Significato |
|---|---|
NOT_AUTHORIZED | Il chiamante non può eseguire questa operazione |
NO_SUCH_NAMESPACE | Il namespace non è dichiarato nel data template |
NO_SUCH_OBJECT | La chiave non esiste |
TOO_LARGE | Supera il limite di trasferimento in una singola chiamata |
OBJECT_TOO_LARGE | Supera il maxObjectBytes proprio del namespace |
QUOTA_EXCEEDED | Il filestore è pieno |
CONTENT_TYPE_NOT_ALLOWED | Il namespace limita i contentTypes |
NOT_SUPPORTED | Il backend non può farlo |
NOT_AVAILABLE | Questo deployment non dispone di un servizio file |
PRESIGN_UNREACHABLE | L’archiviazione a oggetti non è raggiungibile direttamente (un proxy?) |
CLOCK_SKEW | L’orologio del dispositivo è troppo sfasato |
INTERNAL | Qualsiasi altra cosa |
Un server più recente può introdurre codici che questa release dell’SDK non conosce. Vengono propagati così come sono in code invece di essere accorpati, quindi tratta un valore non riconosciuto come un fallimento generico.
In Python,
FileStoreErrorviene importato daironflock.filestore; in JavaScript viene esportato dalla radice del pacchetto (import { FileStoreError } from "ironflock").
Comunicazione tra Dispositivi
registerDeviceFunction / register_device_function
Registra una procedura che altri dispositivi nello stesso progetto possono chiamare. L’SDK assegna automaticamente il namespace della procedura al dispositivo corrente.
Python
def add(a, b):
return a + b
await ironflock.register_device_function("com.myapp.add", add)
register()è un alias diregister_device_function().
callDeviceFunction / call_device_function
Chiama una procedura registrata da un altro dispositivo. L’SDK assembla automaticamente il topic WAMP completo usando la chiave del dispositivo di destinazione.
Python
result = await ironflock.call_device_function(
42, # target device key
"com.myapp.add", # procedure name
args=[3, 5] # arguments
)
print(result) # 8call
Chiama una procedura remota usando un URI WAMP completo. Usalo per chiamate dirette quando conosci il topic esatto.
Python
result = await ironflock.call("some.full.wamp.topic", args=[42])Metadati del Dispositivo
setDeviceLocation / set_device_location
Aggiorna la posizione GPS del dispositivo nella piattaforma. Le modifiche vengono riflesse in tempo reale sulle mappe IronFlock.
Python
await ironflock.set_device_location(long=8.6821, lat=50.1109)| Parametro | Intervallo |
|---|---|
long | da -180 a 180 |
lat | da -90 a 90 |
La cronologia della posizione non viene memorizzata. Per tracciare la posizione nel tempo, crea una tabella dedicata e usa
publish_to_table/publishToTable.
getRemoteAccessUrlForPort
Restituisce l’URL pubblico di accesso remoto per una determinata porta sul dispositivo.
Python
url = ironflock.getRemoteAccessUrlForPort(8080)
# "https://<device_key>-<app_name>-8080.app.ironflock.com"Proprietà della Connessione e Ciclo di Vita
Python
| Proprietà | Tipo | Descrizione |
|---|---|---|
is_connected | bool | Indica se la connessione alla piattaforma è attiva |
connection | CrossbarConnection | L’istanza di connessione sottostante (uso avanzato) |
| Metodo | Descrizione |
|---|---|
run() | Avvia la connessione ed esegue mainFunc (bloccante) |
await start() | Avvia la connessione in modo asincrono |
await stop() | Interrompe la connessione e annulla i task in esecuzione |
await run_async() | Avvia e mantiene la connessione in esecuzione in modo asincrono |
Affidabilità della Connessione
L’SDK si riconnette da solo. Quando la connessione cade, riprova finché il router non torna disponibile, poi ripristina ogni sottoscrizione e ogni funzione del dispositivo registrata — non devi sottoscriverti di nuovo dopo una riconnessione. Se un topic non può essere ripristinato (un permesso cambiato, un errore temporaneo del router), gli altri lo sono comunque, e quello fallito viene ritentato alla riconnessione successiva.
Un caso particolare è un router raggiungibile che non ha ancora un realm per l’app. Nella race dell’installazione concorrente il container dell’app si avvia prima che il Data Backend abbia provisionato il suo realm, quindi i primi tentativi di join vengono rifiutati con wamp.error.no_such_realm; l’SDK continua a riprovare ogni pochi secondi e l’app parte nel momento in cui il realm esiste. Un realm ancora assente dopo un minuto molto probabilmente non comparirà mai — l’app è stata eliminata o non ha un Data Backend per questo stage —, quindi da quel momento l’SDK rallenta a un tentativo ogni due minuti invece di interrogare il router ogni secondo per sempre. Il ritmo veloce torna non appena il realm compare, o quando il router stesso cade e ritorna.
Il caso più difficile è una connessione che muore silenziosamente: un NAT o un proxy la interrompe per inattività senza avvisare nessuna delle due estremità, oppure un router si riavvia dietro un load balancer. Non arriva nulla che segnali la perdita. Un’app che si limita a pubblicare se ne accorge alla successiva scrittura fallita, ma un’app che si limita a sottoscrivere non scrive mai, quindi senza aiuto resta morta finché il container non viene riavviato.
Entrambi gli SDK rilevano questa situazione. Entrambi sono attivi per impostazione predefinita e non richiedono alcuna configurazione.
Python
L’SDK Python usa il keepalive WebSocket: invia un ping al router ogni 30 secondi mentre il collegamento è inattivo e si aspetta un pong entro 10 secondi. Se non arriva, la connessione viene chiusa e ristabilita. Il traffico in entrata vale come prova di vita, quindi una connessione attiva non viene mai sondata: il ping parte solo quando il collegamento è davvero rimasto in silenzio.
Le connessioni tra app aperte con
connectToApp/connect_to_appdi solito si limitano a sottoscrivere, che è esattamente il caso protetto da questo meccanismo. Sono coperte automaticamente.
Gestione degli Errori
Ogni metodo dell’SDK fallisce in modo esplicito: in caso di argomenti non validi, di connessione persa o di rifiuto da parte della piattaforma, solleva un’eccezione (Python) o va in reject (JavaScript) con un messaggio che indica l’operazione, il topic e il motivo. Nulla viene silenziosamente ignorato, quindi racchiudi in un blocco try le chiamate che vuoi far sopravvivere agli errori.
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}")In JavaScript, i fallimenti provenienti dalla piattaforma sono istanze di WampError — una normale sottoclasse di Error che trasporta in più l’URI dell’errore WAMP in error e il payload dell’errore in args / kwargs. Tutto il resto (parametri non validi, nessuna connessione) è un semplice Error.
Migrazione: le versioni precedenti dell’SDK registravano un messaggio nei log e restituivano
None/nullquando una chiamata falliva. Ora sollevano invece un errore, quindi il codice scritto comeif result is None:non rileva più i fallimenti — usatry/except(otry/catch).
Utilizzo nel Browser (solo JavaScript)
L’SDK JavaScript funziona nei browser moderni. Poiché i browser non hanno variabili d’ambiente, passa tutta la configurazione tramite il costruttore:
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() per recuperare la configurazione dal tuo backend invece di inserire le credenziali nel codice:
const ironflock = await IronFlock.fromServer("/api/ironflock-config");
await ironflock.start();L’endpoint del tuo backend deve restituire un oggetto JSON con le opzioni di connessione (serialNumber, deviceKey, appName, swarmKey, appKey, env).
Registrazione delle Funzioni per Agenti AI
L’SDK può registrare funzioni chiamabili dagli agenti AI. Registra una procedura e fai riferimento al suo topic nel tuo 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’agente AI può quindi chiamare questa funzione quando un utente fa una domanda che richiede dati live dai sensori.
Per collegare il topic WAMP registrato a un agente AI, fai riferimento ad esso nel file .ironflock/ai-template.yml della tua 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: trueIl valore di topic (sensors.get_latest) deve corrispondere al nome passato a register_device_function / registerDeviceFunction nel codice edge. IronFlock instrada automaticamente la chiamata al dispositivo in cui la funzione è registrata.
Per il riferimento completo di ai-template.yml, consulta Definire Agenti e Strumenti.
Variabili d’Ambiente
Queste variabili vengono impostate automaticamente dal runtime IronFlock all’interno dei container dell’app:
| Variabile | Descrizione |
|---|---|
DEVICE_NAME | Nome visualizzato del dispositivo |
DEVICE_SERIAL_NUMBER | Identificatore univoco e immutabile del dispositivo |
DEVICE_KEY | Chiave del dispositivo per l’autenticazione |
SWARM_KEY | Identificatore del progetto |
APP_KEY | Identificatore dell’app |
APP_NAME | Nome dell’app |
ENV | Ambiente: 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")