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)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 |
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")