SDK do IronFlock
O SDK do IronFlock permite que suas aplicações de borda interajam com a plataforma IronFlock. Ele cuida da autenticação automaticamente quando executado em um dispositivo registrado e fornece funções para publicar dados, consultar histórico, chamar procedimentos remotos entre dispositivos e atualizar os metadados do dispositivo.
| SDK | Pacote | Requer |
|---|---|---|
| Python | ironflock no PyPI | Python 3.8+ |
| JavaScript | ironflock no npm | Node.js 18+ ou navegador moderno |
Instalação
Python
pip install ironflockOu adicione ironflock ao requirements.txt da sua aplicação.
Início 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()Quando usado dentro de um contêiner de aplicação do IronFlock, o SDK lê as credenciais de conexão do ambiente automaticamente — nenhuma configuração manual é necessária.
Opções do Construtor
Python
ironflock = IronFlock(
mainFunc=main, # async function to run after connecting
serial_number="abc123" # override device serial (optional)
)| Parâmetro | Descrição |
|---|---|
mainFunc | Uma função assíncrona que é executada assim que a conexão é estabelecida |
serial_number | Sobrescreve o número de série do dispositivo. O padrão é a variável de ambiente DEVICE_SERIAL_NUMBER |
Publicando Dados
publishToTable / publish_to_table
Publica um registro de dados em uma tabela da frota. O nome da tabela deve corresponder a uma tabela definida no data-template.yml da sua aplicação. O SDK roteia automaticamente os dados para o banco de dados correto do projeto.
Python
await ironflock.publish_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60,
"device_id": "sensor-001"
})appendToTable / append_to_table
Acrescenta dados a uma tabela da frota usando uma chamada de procedimento remoto em vez de pub/sub. Use isto quando você precisar de confirmação de que os dados foram persistidos.
Python
result = await ironflock.append_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60
})publishRowsToTable / publish_rows_to_table
Publica várias linhas em uma única mensagem (inserção em lote) em uma tabela da frota. A plataforma insere todo o lote atomicamente (tudo ou nada) em uma única operação. Use isto para dados de alta frequência, onde uma ida e volta por linha seria custosa demais. Assim como publishToTable, isto é fire-and-forget — a confirmação atesta a entrega ao roteador, não a inserção no banco de dados.
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},
])O segundo argumento é uma lista não vazia de objetos de linha a inserir.
appendRowsToTable / append_rows_to_table
Acrescenta várias linhas em uma única chamada de procedimento remoto (inserção em lote) a uma tabela da frota. A plataforma insere todo o lote atomicamente (tudo ou nada): se qualquer linha for inválida, todo o lote é rejeitado e nada é persistido. Prefira isto a publishRowsToTable / publish_rows_to_table quando você precisar do resultado da inserção.
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 um erro da aplicação na tabela error-logs da sua frota. Isto é um invólucro de conveniência sobre publishToTable / appendToTable: ele carimba a linha com source: "app", um nível de severidade level e um timestamp, e então a escreve como qualquer linha de tabela normal. O erro chega na mesma tabela error-logs que os erros de sistema do fleetdb usam (marcados com source: "system"), de modo que ele pode ser consultado com getHistory, transmitido com subscribeToTable / subscribe_to_table, usado em board-templates e entregue em tempo real em transformed.error-logs — sem disparar o toast de erro de sistema da 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 | Descrição |
|---|---|---|
error | str / string ou exceção / Error | A mensagem de erro, ou uma exceção cujo traceback/stack (ou mensagem) é registrado |
level | str / string, opcional | Severidade: "error", "warn", "info" ou "debug". O padrão é "error" |
append | bool / boolean, opcional | Quando true, usa a RPC de acréscimo (retorna o resultado da inserção). O padrão é false (publicação fire-and-forget) |
tsp | str / string, opcional | Sobrescrita de timestamp ISO-8601. O padrão é o horário atual |
Em Python as opções são argumentos nomeados (
report_error(error, level=..., append=..., tsp=...)); em JavaScript elas são passadas via um objeto de opções (reportError(error, { level, append, tsp })).
publish
Publica uma mensagem em qualquer tópico WAMP. Use isto para mensagens ou eventos personalizados que não correspondem a uma tabela do banco de dados.
Python
await ironflock.publish("com.myapp.alerts", {
"level": "warning",
"message": "Temperature threshold exceeded"
})Consultando Dados Históricos
getHistory
Recupera dados históricos de uma tabela da frota. Suporta filtragem, intervalos de tempo e paginação.
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 da consulta:
| Campo | Tipo | Descrição |
|---|---|---|
limit | int / number | Número máximo de linhas a retornar (1–10.000, obrigatório) |
offset | int / number | Deslocamento para paginação |
timeRange | dict / object | {"start": "<ISO datetime>", "end": "<ISO datetime>"} |
filterAnd | list / array | Condições de filtro AND e/ou o marcador latest (veja abaixo) |
columns | list / array | Colunas a retornar (opcional). tsp, device_key e authid são sempre incluídas; omita para obter todas as colunas |
Operadores de filtro: =, !=, >, <, >=, <=, LIKE, ILIKE, IN, NOT IN, IS, IS NOT
Cada filtro é um objeto com as chaves column, operator e value.
Lendo os valores atuais. Uma entrada {"latest": true} em filterAnd não é uma condição de filtro, e sim uma troca de modo: o backend de dados retorna apenas a linha mais recente de cada entidade, derivada em SQL a partir da chave de entidade que a tabela declara com maintainLatestFlagFor. Uma tabela sem chave de entidade retorna a sua única linha mais recente.
As demais condições se combinam com o marcador como você esperaria: condições sobre colunas da chave de entidade restringem quais entidades são retornadas, enquanto todas as outras condições e o timeRange são aplicados às linhas mais recentes resultantes. Assim, combinar {"latest": true} com um filtro de deleted oculta as entidades excluídas em vez de fazer a linha anterior delas reaparecer.
Versões anteriores do IronFlock armazenavam uma coluna física latest_flag. Ela não existe mais — um filtro legado latest_flag = true continua sendo aceito e tratado como o marcador, mas código novo deve usar {"latest": true}. O marcador latest não está disponível em getSeriesHistory.
getSeriesHistory / get_series_history
Recupera dados de séries temporais reduzidos (down-sampled) de uma tabela de frota: colunas numéricas agregadas em intervalos de tempo (por exemplo, médias por hora). Ideal para gráficos que abrangem longos intervalos de tempo. Disponível para tabelas (não 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 da consulta:
| Campo | Tipo | Descrição |
|---|---|---|
metrics | list / array | Colunas numéricas a serem reduzidas |
method | str / string | Agregação por intervalo: "AVG", "SUM", "COUNT", "MIN", "MAX", "FIRST" ou "LAST" |
limit | int / number | Número máximo de intervalos (1–10.000) |
timeRange | list / array | [start, end] — strings ISO datetime ou números epoch-ms; null = extremidade aberta (obrigatório) |
groupBy | list / array | Colunas para agrupar a série (opcional) |
filterAnd | list / array | Condições de filtro AND (opcional). Apenas condições de filtro — o marcador latest não é suportado aqui; use getHistory para ler os valores atuais |
Inscrevendo-se em Dados
subscribeToTable / subscribe_to_table
Inscreve-se em atualizações em tempo real de uma tabela da frota. O handler é chamado sempre que novos dados são publicados na tabela. As linhas escritas pelo caminho de inserção em lote (publishRowsToTable / appendRowsToTable) são entregues ao seu handler uma de cada vez, de modo que o código do handler permanece o mesmo independentemente de como os dados foram escritos.
Python
def on_sensor_data(*args, **kwargs):
print("New reading:", args, kwargs)
await ironflock.subscribe_to_table("sensordata", on_sensor_data)subscribe
Inscreve-se em qualquer tópico WAMP para mensagens personalizadas em tempo real.
Python
def on_alert(*args, **kwargs):
print("Alert received:", args, kwargs)
await ironflock.subscribe("com.myapp.alerts", on_alert)Acesso a Dados Entre Apps
Leia os dados de frota de outro app de dentro do seu próprio app, no mesmo projeto. O app provedor deve declarar o seu app na seção consumes: do seu data-template.yml, e o usuário do projeto deve conceder o acesso. O acesso é somente leitura: você pode consultar o histórico e se inscrever em tempo real nas linhas das tabelas e transforms que o provedor compartilha, mas não pode escrever nelas. As conexões com apps consumidos são armazenadas em cache por app e fechadas automaticamente quando a sua instância para.
Se o seu app possuir a concessão curinga (consumes: [{ app: "*" }]), você pode descobrir e abrir provedores dinamicamente com listConsumableApps / list_consumable_apps e connectToAllApps / connect_to_all_apps (abaixo).
connectToApp / connect_to_app
Abre uma conexão somente leitura ao backend de dados de outro app e retorna um handle. O handle expõe getHistory / get_history, subscribeToTable / subscribe_to_table e getSeriesHistory / get_series_history (apenas tabelas) — as mesmas consultas e inscrições que você usa nas suas próprias tabelas — além de close e dos catálogos compartilhados 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 | Descrição |
|---|---|---|
app_name / appName | str / string | Nome do app provedor, conforme declarado na sua seção consumes: |
stage | str / string, opcional | Stage do provedor: "dev" ou "prod". O padrão é o stage do seu próprio app |
on_error / onError | callable, opcional | Chamado com um CrossAppAccessError se o acesso for negado após a conexão ter sido estabelecida (por exemplo, se a concessão for revogada mais tarde) |
Se o acesso for negado ou usado incorretamente, um CrossAppAccessError é levantado (Python) / lançado (JavaScript) com um campo code: NO_GRANT, PROVIDER_NOT_INSTALLED, UNKNOWN_APP, PRIVATE_TABLE ou NOT_AUTHORIZED.
Em Python,
stageeon_errorsão argumentos nomeados; em JavaScript eles são passados via um objeto de opções (connectToApp(appName, { stage, onError })).
listConsumableApps / list_consumable_apps
Lista todos os provedores não privados do projeto — a primitiva de descoberta para apps que possuem a concessão de consumo curinga (consumes: [{ app: "*" }] no seu data-template.yml, concedida pelo usuário do projeto). Realiza uma única chamada e não abre nenhuma conexão: renderize os catálogos retornados em um seletor, então chame connectToApp / connect_to_app para os que você deseja — ou connectToAllApps / connect_to_all_apps para abrir todos de uma vez.
Nota: Declare a concessão no
data-template.ymldo seu app, e coloque o*entre aspas — um*sem aspas é um alias YAML e não será interpretado:
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 descreve um provedor:
| Campo | Tipo | Descrição |
|---|---|---|
app | str / string | Nome do app provedor |
provider_app_key | int / number | A chave de app do provedor |
stages | dict / object | Catálogo por stage { dev?, prod? }; um stage está presente apenas se o provedor tiver um backend de dados para ele. Cada catálogo contém as tables e transforms não privadas que ele compartilha |
Levanta (Python) / lança (JavaScript) um CrossAppAccessError com code: NO_GRANT se o seu app não possuir a concessão curinga.
connectToAllApps / connect_to_all_apps
Abre handles somente leitura para todos os provedores não privados do projeto em uma única chamada (apenas consumidores curinga). Enumera os provedores via listConsumableApps / list_consumable_apps e abre cada um, ignorando qualquer um sem um backend de dados para o stage solicitado. Cada handle é armazenado em cache sob a mesma chave que connectToApp / connect_to_app, de modo que uma chamada posterior a connectToApp(name) retorna o handle já aquecido. Os handles retornados são fechados em conjunto quando a sua instância para.
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 | Descrição |
|---|---|---|
stage | str / string, opcional | Stage do provedor: "dev" ou "prod". O padrão é o stage do seu próprio app |
continue_on_error / continueOnError | bool / boolean, opcional | Quando true (o padrão), um provedor que falha ao abrir é reportado para on_error / onError e omitido do resultado. Quando false, a primeira falha é levantada/lançada |
on_error / onError | callable, opcional | Chamado com cada provedor que não pôde ser aberto (enquanto continue_on_error / continueOnError for true), e com um CrossAppAccessError se uma conexão já aberta for negada mais tarde (por exemplo, se a concessão for revogada) |
Retorna os handles de provedor abertos com sucesso (o mesmo tipo de handle que connectToApp / connect_to_app). Levanta (Python) / lança (JavaScript) um CrossAppAccessError com code: NO_GRANT se o seu app não possuir a concessão curinga.
Em Python,
stage,on_errorecontinue_on_errorsão argumentos nomeados; em JavaScript eles são passados via um objeto de opções (connectToAllApps({ stage, onError, continueOnError })).
Armazenamento Gerenciado de Arquivos
Todo backend de dados de app recebe um armazenamento de objetos privado ao lado das suas tabelas, acessível através da propriedade files. Use-o para imagens, PDFs, frames de câmera, blobs de firmware — qualquer coisa que não pertença a uma linha de tabela. Nenhuma configuração é necessária: um app sem uma seção files: no seu data template ainda recebe um namespace chamado default.
A ideia central é que armazenar um objeto lhe devolve uma URL permanente que você pode escrever diretamente em uma coluna de tabela, de modo que um widget de dashboard consiga renderizá-la sem nenhum trabalho 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)Essa URL nunca expira, mas ela não é um link público: ela permanece legível apenas para um solicitante autenticado que possua acesso READ neste backend de dados, e um proxy de autenticação reverifica isso a cada requisição. Por isso, ela é segura para armazenar no banco de dados.
Namespaces
Um namespace é um prefixo de chave que carrega política — retenção, regras de compartilhamento, tipos de conteúdo permitidos. Ele não é um bucket separado; todo namespace de um app vive dentro da única área de armazenamento daquele app. Declare um apenas quando um conjunto de objetos precisar de regras diferentes; caso contrário, permaneça em default e organize os seus objetos com caminhos de chave como 2026/03/part-1.jpg.
Declare namespaces adicionais no 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 }Note que o orçamento é declarado uma única vez para o app inteiro, e não por namespace. Um namespace é apenas um prefixo de chave dentro da única área de armazenamento do app, então não há nada contra o que um orçamento por prefixo pudesse ser imposto. maxObjectBytes é por namespace — ele limita um único objeto, não um total.
Todos os métodos abaixo aceitam o namespace como um argumento opcional e usam default como padrão.
Armazenando e lendo 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 | Descrição |
|---|---|
put(key, data, …) | Armazena um objeto (bytes em Python, Uint8Array em JavaScript). Retorna os metadados do objeto, incluindo a sua url |
get(key, namespace?) | Retorna o conteúdo do objeto |
put_file(key, path, …) / get_to_file(key, path, …) | Apenas Python. Armazena a partir de, ou escreve para, um arquivo local. Faz streaming no caminho de objetos grandes |
delete(key, namespace?) | Exclui um objeto |
copy(key, to, …) | Copia um objeto, opcionalmente para outro namespace |
move(key, to, …) | Copiar e depois excluir. Não é atômico — o serviço não tem um verbo de movimentação, então uma exclusão que falhe deixa as duas cópias |
O JavaScript não possui helpers de caminho de arquivo porque o pacote entrega um único build tanto para o Node quanto para o navegador — leia e escreva os arquivos locais você mesmo com fs.
put aceita: content_type / contentType (o tipo MIME; o namespace pode restringir quais são permitidos) e namespace. Em Python estes são argumentos nomeados; em JavaScript eles vão em um objeto de opções.
Listando e inspecionando
| Método | Descrição |
|---|---|
list(…) | Uma página de objetos. Retorna objects, prefixes, is_truncated / isTruncated e um cursor para ser passado de volta na próxima página |
iter(…) / iterate(…) | Iterador assíncrono sobre todos os objetos sob um prefixo, paginando automaticamente. Chamado iter em Python e iterate em JavaScript |
stat(key, namespace?) | Metadados de um objeto sem transferir o seu conteúdo |
exists(key, namespace?) | Se um objeto existe |
namespaces() | Os namespaces que este app pode usar |
usage(…) | Quanto armazenamento o app está usando — veja abaixo |
catalog() | Namespaces mais os limites e as cotas emitidos pelo servidor. Armazenado em cache após a primeira chamada |
Os objetos são descritos pelos mesmos campos em ambos os SDKs, no estilo de nomenclatura de cada linguagem: namespace, key, size, etag, content_type / contentType, last_modified / lastModified, checksum_sha256 / checksumSha256 e url.
Uso de armazenamento e cota
usage responde a partir do object store em uma única chamada, então os totais são exatos em vez de somados pelo 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 atualmente armazenados |
object_count / objectCount | Número de objetos armazenados |
quota_bytes / quotaBytes | O orçamento imposto. 0 significa ilimitado |
free_bytes / freeBytes | Bytes restantes. -1 significa ilimitado — reportar 0 aí seria lido como “cheio” |
per_namespace / perNamespace | Bytes por namespace. Presente apenas quando você pede o detalhamento por namespace |
O detalhamento por namespace vem desligado por padrão porque o store não consegue respondê-lo diretamente: ele contabiliza por área de armazenamento, e um namespace é apenas um prefixo, então o SDK precisa listar cada namespace para somar os tamanhos. Peça-o quando você quiser, não em um caminho crítico de desempenho.
Duas cotas diferentes aparecem, e vale a pena mantê-las separadas. catalog() reporta as duas:
| Campo | Significado |
|---|---|
quota_bytes / quotaBytes | O que é de fato imposto, lido do object store — a configuração do usuário do projeto |
suggested_quota_bytes / suggestedQuotaBytes | O que o data template do app pediu. 0 se ele não pediu nada |
Elas divergem sempre que um usuário aumentou ou diminuiu o orçamento do app, e é por isso que o valor imposto é lido do store em vez do template — reimplantar o app não pode redefinir silenciosamente a escolha de um usuário. Uma UI pode mostrar ambos (“o app sugere X, você definiu Y”). A imposição sempre usa o primeiro.
Compartilhando objetos
Existem dois tipos de link, e a diferença importa:
| Método | Tempo de vida | Quem pode lê-lo |
|---|---|---|
url(key, …) | Permanente | Apenas um solicitante autenticado com READ neste backend de dados — reverificado a cada requisição. Seguro para armazenar em uma coluna de tabela |
share_url / shareUrl | Expirável (padrão de 15 min, limitado pelo servidor) | Qualquer pessoa que possua o link. Nada reverifica a autorização quando ele é usado |
share_url / shareUrl é uma capacidade ao portador (bearer): entregue-o a uma pessoa que precise de acesso temporário, e não o armazene no banco de dados. Use url para qualquer coisa que um dashboard renderize.
url retorna None / undefined onde a implantação não possui uma borda HTTP (por exemplo, um appliance em HTTP puro) — esse é o sinal para recorrer a get. Passar o etag de um objeto como argumento version permite que os navegadores armazenem a resposta em cache de forma imutável.
upload_url / uploadUrl gera uma URL expirável que aceita um upload direto, retornando url, method, headers e expires_in / expiresIn. Envie exatamente os headers que ela retorna, ou a assinatura não será verificada.
Objetos grandes
O SDK escolhe o transporte pelo tamanho, automaticamente — não há nada para configurar:
| Tamanho do objeto | Como ele trafega |
|---|---|
| Até o limite inline (atualmente 6 MiB) | Uma única chamada através do roteador de mensagens |
| Maior | Diretamente para o armazenamento de objetos via HTTPS, contornando o roteador |
O limite exato é reportado pelo servidor em tempo de execução como inline_max_bytes / inlineMaxBytes em catalog(), de modo que ele pode ser elevado sem um novo release do SDK.
Dois tetos permanecem, e ambos reportam TOO_LARGE com um motivo que nomeia qual deles você atingiu:
- 5 GiB — o limite de upload único do armazenamento de objetos. O upload multipart ainda não foi implementado.
- O limite inline, onde não há endpoint direto — um appliance isolado (air-gapped) não consegue transferir um objeto grande de forma alguma. Nenhuma nova tentativa nem chunk menor vai ajudar, e a mensagem diz isso.
O caminho direto exige que o dispositivo alcance o host do armazenamento de objetos, não apenas o roteador. Duas falhas comuns em campo recebem os seus próprios códigos em vez de parecerem problemas de autorização: PRESIGN_UNREACHABLE (um proxy que permite apenas o roteador) e CLOCK_SKEW (o armazenamento de objetos rejeita requisições com mais de 15 minutos de defasagem — verifique o NTP no dispositivo).
Erros de armazenamento de arquivos
Toda operação de arquivo levanta (Python) / lança (JavaScript) um FileStoreError que carrega um code estável e um reason legível por humanos. Ramifique pelo code, nunca pelo 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 | O chamador não pode realizar esta operação |
NO_SUCH_NAMESPACE | O namespace não está declarado no data template |
NO_SUCH_OBJECT | A chave não existe |
TOO_LARGE | Excede o limite de transferência em uma única chamada |
OBJECT_TOO_LARGE | Excede o maxObjectBytes do próprio namespace |
QUOTA_EXCEEDED | O filestore está cheio |
CONTENT_TYPE_NOT_ALLOWED | O namespace restringe os contentTypes |
NOT_SUPPORTED | O backend não consegue fazer isto |
NOT_AVAILABLE | Esta implantação não possui serviço de arquivos |
PRESIGN_UNREACHABLE | O armazenamento de objetos não é alcançável diretamente (um proxy?) |
CLOCK_SKEW | O relógio do dispositivo está defasado demais |
INTERNAL | Qualquer outra coisa |
Um servidor mais novo pode introduzir códigos que este release do SDK não conhece. Eles são repassados como code em vez de serem colapsados, portanto trate um valor não reconhecido como uma falha genérica.
Em Python,
FileStoreErroré importado deironflock.filestore; em JavaScript ele é exportado a partir da raiz do pacote (import { FileStoreError } from "ironflock").
Comunicação Entre Dispositivos
registerDeviceFunction / register_device_function
Registra um procedimento que outros dispositivos no mesmo projeto podem chamar. O SDK automaticamente atribui um namespace ao procedimento para o dispositivo atual.
Python
def add(a, b):
return a + b
await ironflock.register_device_function("com.myapp.add", add)
register()é um alias pararegister_device_function().
callDeviceFunction / call_device_function
Chama um procedimento registrado por outro dispositivo. O SDK monta o tópico WAMP completo automaticamente usando a chave do 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
Chama um procedimento remoto usando um URI WAMP completo. Use isto para chamadas diretas quando você conhece o tópico exato.
Python
result = await ironflock.call("some.full.wamp.topic", args=[42])Metadados do Dispositivo
setDeviceLocation / set_device_location
Atualiza a localização GPS do dispositivo na plataforma. As alterações são refletidas em tempo real nos mapas do IronFlock.
Python
await ironflock.set_device_location(long=8.6821, lat=50.1109)| Parâmetro | Intervalo |
|---|---|
long | -180 a 180 |
lat | -90 a 90 |
O histórico de localização não é armazenado. Para rastrear a localização ao longo do tempo, crie uma tabela dedicada e use
publish_to_table/publishToTable.
getRemoteAccessUrlForPort
Retorna a URL pública de acesso remoto para uma determinada porta no dispositivo.
Python
url = ironflock.getRemoteAccessUrlForPort(8080)
# "https://<device_key>-<app_name>-8080.app.ironflock.com"Propriedades de Conexão e Ciclo de Vida
Python
| Propriedade | Tipo | Descrição |
|---|---|---|
is_connected | bool | Se a conexão com a plataforma está ativa |
connection | CrossbarConnection | A instância de conexão subjacente (uso avançado) |
| Método | Descrição |
|---|---|
run() | Inicia a conexão e executa mainFunc (bloqueante) |
await start() | Inicia a conexão de forma assíncrona |
await stop() | Encerra a conexão e cancela as tarefas em execução |
await run_async() | Inicia e mantém a conexão em execução de forma assíncrona |
Tratamento de Erros
Todo método do SDK falha de forma explícita: diante de argumentos inválidos, de uma conexão perdida ou de uma rejeição da plataforma, ele levanta uma exceção (Python) ou rejeita (JavaScript) com uma mensagem que nomeia a operação, o tópico e o motivo. Nada é silenciosamente engolido, portanto envolva em um bloco try as chamadas que você deseja que sobrevivam.
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}")Em JavaScript, as falhas provenientes da plataforma são instâncias de WampError — uma subclasse normal de Error que adicionalmente carrega o URI de erro WAMP em error e a carga do erro em args / kwargs. Todo o restante (parâmetros inválidos, ausência de conexão) é um Error comum.
Migrando: versões mais antigas do SDK registravam uma mensagem e retornavam
None/nullquando uma chamada falhava. Agora elas levantam o erro, de modo que código no formatoif result is None:não detecta mais as falhas — usetry/except(outry/catch).
Uso no Navegador (apenas JavaScript)
O SDK em JavaScript funciona em navegadores modernos. Como os navegadores não possuem variáveis de ambiente, passe toda a configuração pelo construtor:
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 }]);Use IronFlock.fromServer() para buscar a configuração do seu backend em vez de embutir as credenciais no código:
const ironflock = await IronFlock.fromServer("/api/ironflock-config");
await ironflock.start();O endpoint do seu backend deve retornar um objeto JSON com as opções de conexão (serialNumber, deviceKey, appName, swarmKey, appKey, env).
Registrando Funções para Agentes de IA
O SDK pode registrar funções que podem ser chamadas por agentes de IA. Registre um procedimento e referencie o seu tópico no seu 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)O agente de IA pode então chamar esta função quando um usuário fizer uma pergunta que exija dados de sensor ao vivo.
Para conectar o tópico WAMP registrado a um agente de IA, referencie-o no .ironflock/ai-template.yml da sua aplicação:
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: trueO valor de topic (sensors.get_latest) deve corresponder ao nome passado para register_device_function / registerDeviceFunction no seu código de borda. O IronFlock roteia automaticamente a chamada para o dispositivo onde a função está registrada.
Para a referência completa do ai-template.yml, consulte Definindo Agentes e Ferramentas.
Variáveis de Ambiente
Estas variáveis são definidas automaticamente pelo runtime do IronFlock dentro dos contêineres de aplicação:
| Variável | Descrição |
|---|---|
DEVICE_NAME | Nome de exibição do dispositivo |
DEVICE_SERIAL_NUMBER | Identificador único e imutável do dispositivo |
DEVICE_KEY | Chave do dispositivo para autenticação |
SWARM_KEY | Identificador do projeto |
APP_KEY | Identificador da aplicação |
APP_NAME | Nome da aplicação |
ENV | Ambiente: DEV ou PROD |
Python
import os
device_name = os.environ.get("DEVICE_NAME")
serial = os.environ.get("DEVICE_SERIAL_NUMBER")
project_key = os.environ.get("SWARM_KEY")