IronFlock SDK
IronFlock SDK 让您的边缘应用程序能够与 IronFlock 平台交互。在已注册的设备上运行时,它会自动处理身份验证,并提供用于发布数据、查询历史记录、跨设备调用远程过程以及更新设备元数据的函数。
| SDK | 包 | 要求 |
|---|---|---|
| Python | ironflock(PyPI) | Python 3.8+ |
| JavaScript | ironflock(npm) | Node.js 18+ 或现代浏览器 |
安装
Python
pip install ironflock或将 ironflock 添加到应用的 requirements.txt 中。
快速开始
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()在 IronFlock 应用容器内使用时,SDK 会自动从环境中读取连接凭据——无需手动配置。
构造函数选项
Python
ironflock = IronFlock(
mainFunc=main, # async function to run after connecting
serial_number="abc123" # override device serial (optional)
)| 参数 | 描述 |
|---|---|
mainFunc | 连接建立后执行的异步函数 |
serial_number | 覆盖设备序列号。默认使用环境变量 DEVICE_SERIAL_NUMBER |
发布数据
publishToTable / publish_to_table
向舰队表发布数据记录。表名必须与应用的 data-template.yml 中定义的表匹配。SDK 会自动将数据路由到正确的项目数据库。
Python
await ironflock.publish_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60,
"device_id": "sensor-001"
})appendToTable / append_to_table
使用远程过程调用而非发布/订阅方式向舰队表追加数据。当需要确认数据已持久化时使用此方法。
Python
result = await ironflock.append_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60
})publishRowsToTable / publish_rows_to_table
在单条消息中发布多行(批量插入)到舰队表。平台会在一次操作中原子地插入整个批次(全部成功或全部失败)。对于高频数据,如果每行都进行一次往返通信开销过大,请使用此方法。与 publishToTable 一样,这是即发即忘(fire-and-forget)的——确认信息仅表示消息已送达路由器,而非确认数据库已完成插入。
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},
])第二个参数是一个非空的待插入行对象列表。
appendRowsToTable / append_rows_to_table
通过单次远程过程调用追加多行(批量插入)到舰队表。平台会原子地插入整个批次(全部成功或全部失败):如果任何一行无效,整个批次都将被拒绝,且不会持久化任何数据。当您需要获知插入结果时,应优先使用此方法而非 publishRowsToTable / publish_rows_to_table。
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
将应用程序错误报告到舰队的 error-logs 表中。这是对 publishToTable / appendToTable 的便捷封装:它会为该行打上 source: "app" 标记、一个严重级别 level 以及一个时间戳,然后像写入任何普通表行一样将其写入。该错误会落入与 fleetdb 系统错误相同的 error-logs 表中(系统错误标记为 source: "system"),因此可以使用 getHistory 查询,使用 subscribeToTable / subscribe_to_table 流式订阅,在看板模板中使用,并通过 transformed.error-logs 实时传递——而不会触发平台的系统错误提示框。
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)参数:
| 参数 | 类型 | 描述 |
|---|---|---|
error | str / string 或异常 / Error | 错误消息,或一个异常对象,其 traceback/stack(或消息)将被记录 |
level | str / string,可选 | 严重级别:"error"、"warn"、"info" 或 "debug"。默认为 "error" |
append | bool / boolean,可选 | 当为 true 时,使用 append 远程过程调用(返回插入结果)。默认为 false(即发即弃式发布) |
tsp | str / string,可选 | ISO-8601 时间戳覆盖值。默认为当前时间 |
在 Python 中,这些选项以关键字参数形式传入(
report_error(error, level=..., append=..., tsp=...));在 JavaScript 中,它们通过一个选项对象传入(reportError(error, { level, append, tsp }))。
publish
向任意 WAMP 主题发布消息。用于不映射到数据库表的自定义消息传递或事件。
Python
await ironflock.publish("com.myapp.alerts", {
"level": "warning",
"message": "Temperature threshold exceeded"
})查询历史数据
getHistory
从舰队表中检索历史数据。支持过滤、时间范围和分页。
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}]
})查询参数:
| 字段 | 类型 | 描述 |
|---|---|---|
limit | int / number | 返回的最大行数(1–10,000,必填) |
offset | int / number | 分页偏移量 |
timeRange | dict / object | {"start": "<ISO 日期时间>", "end": "<ISO 日期时间>"} |
filterAnd | list / array | AND 过滤条件,以及/或 latest 标记(见下文) |
columns | list / array | 要返回的列(可选)。tsp、device_key 和 authid 始终包含在内;省略则返回所有列 |
过滤运算符: =, !=, >, <, >=, <=, LIKE, ILIKE, IN, NOT IN, IS, IS NOT
每个过滤条件是包含 column、operator 和 value 键的对象。
读取当前值。 filterAnd 中的 {"latest": true} 条目并不是一个过滤条件,而是一个模式开关:数据后端只返回每个实体的最新行,该结果由表通过 maintainLatestFlagFor 声明的实体键在 SQL 中推导得出。没有实体键的表则返回其唯一的最新一行。
其他条件与该标记的组合方式符合预期:作用于实体键列的条件用于缩小返回哪些实体的范围,而所有其他条件和 timeRange 都会应用到得出的最新行上。因此,将 {"latest": true} 与 deleted 过滤条件结合使用,会隐藏已删除的实体,而不会让它们此前的行重新浮现。
IronFlock 的早期版本会存储一个物理的 latest_flag 列。该列已不再存在——旧式的 latest_flag = true 过滤条件仍被接受并按该标记处理,但新代码应使用 {"latest": true}。latest 标记在 getSeriesHistory 中不可用。
getSeriesHistory / get_series_history
从舰队表中检索降采样的时间序列数据:将数值列聚合到时间桶中(例如每小时平均值)。非常适合跨越长时间范围的图表。适用于表(不适用于 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"]
})查询参数:
| 字段 | 类型 | 描述 |
|---|---|---|
metrics | list / array | 要降采样的数值列 |
method | str / string | 每个时间桶的聚合:"AVG"、"SUM"、"COUNT"、"MIN"、"MAX"、"FIRST" 或 "LAST" |
limit | int / number | 桶的最大数量(1–10,000) |
timeRange | list / array | [start, end] — ISO 日期时间字符串或 epoch 毫秒数字;null = 开区间(必填) |
groupBy | list / array | 用于对序列分组的列(可选) |
filterAnd | list / array | AND 过滤条件(可选)。仅支持过滤条件——此处不支持 latest 标记;要读取当前值请使用 getHistory |
订阅数据
subscribeToTable / subscribe_to_table
订阅舰队表的实时更新。每当有新数据发布到表中时,处理函数将被调用。通过批量插入路径(publishRowsToTable / appendRowsToTable)写入的行会逐行依次传递给您的处理函数,因此无论数据以何种方式写入,处理函数的代码都保持不变。
Python
def on_sensor_data(*args, **kwargs):
print("New reading:", args, kwargs)
await ironflock.subscribe_to_table("sensordata", on_sensor_data)subscribe
订阅任意 WAMP 主题以实现自定义实时消息传递。
Python
def on_alert(*args, **kwargs):
print("Alert received:", args, kwargs)
await ironflock.subscribe("com.myapp.alerts", on_alert)跨应用数据访问
在同一个项目中,从你自己的应用内部读取另一个应用的舰队数据。提供方应用必须在其 data-template.yml 的 consumes: 部分声明你的应用,并且项目用户必须授予访问权限。访问是只读的:你可以查询提供方共享的表和 transform 的历史数据,并实时订阅其行,但不能写入它们。对被消费应用的连接会按应用缓存,并在你的实例停止时自动关闭。
如果你的应用持有通配符授权(consumes: [{ app: "*" }]),你可以使用 listConsumableApps / list_consumable_apps 和 connectToAllApps / connect_to_all_apps(见下文)动态地发现并打开提供方。
connectToApp / connect_to_app
打开到另一个应用数据后端的只读连接,并返回一个句柄。该句柄提供 getHistory / get_history、subscribeToTable / subscribe_to_table 和 getSeriesHistory / get_series_history(仅限表)——与你在自己的表上使用的查询和订阅完全相同——此外还提供 close 以及共享的 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)参数:
| 参数 | 类型 | 描述 |
|---|---|---|
app_name / appName | str / string | 提供方应用的名称,即你在 consumes: 部分中声明的名称 |
stage | str / string,可选 | 提供方的阶段(stage):"dev" 或 "prod"。默认为你自己应用的阶段 |
on_error / onError | callable,可选 | 如果在连接建立之后访问被拒绝(例如授权随后被撤销),则会以一个 CrossAppAccessError 调用它 |
如果访问被拒绝或使用不当,将引发(Python)/抛出(JavaScript)一个带有 code 字段的 CrossAppAccessError:NO_GRANT、PROVIDER_NOT_INSTALLED、UNKNOWN_APP、PRIVATE_TABLE 或 NOT_AUTHORIZED。
在 Python 中,
stage和on_error是关键字参数;在 JavaScript 中,它们通过一个选项对象传入(connectToApp(appName, { stage, onError }))。
listConsumableApps / list_consumable_apps
列出项目中每一个非私有的提供方——这是为持有通配符消费授权(在你的 data-template.yml 中声明 consumes: [{ app: "*" }],并由项目用户授予)的应用提供的发现原语。它只执行一次调用,且不打开任何连接:将返回的目录渲染到一个选择器中,然后对你想要的提供方调用 connectToApp / connect_to_app——或使用 connectToAllApps / connect_to_all_apps 一次性打开所有提供方。
注意:在你应用的
data-template.yml中声明该授权,并为*加上引号——不带引号的*是一个 YAML 别名,无法解析:
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"]每个条目描述一个提供方:
| 字段 | 类型 | 描述 |
|---|---|---|
app | str / string | 提供方应用的名称 |
provider_app_key | int / number | 提供方的应用密钥 |
stages | dict / object | 按阶段(stage)划分的目录 { dev?, prod? };只有当提供方针对该阶段有数据后端时,该阶段才会出现。每个目录包含它所共享的非私有 tables 和 transforms |
如果你的应用没有持有通配符授权,将引发(Python)/抛出(JavaScript)一个带有 code: NO_GRANT 的 CrossAppAccessError。
connectToAllApps / connect_to_all_apps
在一次调用中打开到项目中每一个非私有提供方的只读句柄(仅限通配符消费方)。它通过 listConsumableApps / list_consumable_apps 枚举提供方并逐个打开,跳过任何针对所请求阶段没有数据后端的提供方。每个句柄都以与 connectToApp / connect_to_app 相同的键进行缓存,因此之后调用 connectToApp(name) 会返回那个已经预热好的句柄。返回的这些句柄会在你的实例停止时一起关闭。
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)参数:
| 参数 | 类型 | 描述 |
|---|---|---|
stage | str / string,可选 | 提供方的阶段(stage):"dev" 或 "prod"。默认为你自己应用的阶段 |
continue_on_error / continueOnError | bool / boolean,可选 | 当为 true 时(默认值),打开失败的提供方会被报告给 on_error / onError 并从结果中省略。当为 false 时,第一个失败会被引发/抛出 |
on_error / onError | callable,可选 | 对每个无法打开的提供方调用它(当 continue_on_error / continueOnError 为 true 时),以及在一个已经打开的连接随后被拒绝(例如授权被撤销)时,以一个 CrossAppAccessError 调用它 |
返回成功打开的提供方句柄(与 connectToApp / connect_to_app 的句柄类型相同)。如果你的应用没有持有通配符授权,将引发(Python)/抛出(JavaScript)一个带有 code: NO_GRANT 的 CrossAppAccessError。
在 Python 中,
stage、on_error和continue_on_error是关键字参数;在 JavaScript 中,它们通过一个选项对象传入(connectToAllApps({ stage, onError, continueOnError }))。
托管文件存储
每个应用的数据后端在表之外还会获得一块私有的对象存储,通过 files 属性访问。可以用它来存放图片、PDF、相机帧、固件二进制块——任何不适合放进表行里的内容。无需任何配置:即使应用的数据模板中没有 files: 段,它仍会获得一个名为 default 的命名空间。
其核心思路在于:存储一个对象时会直接返回一个永久 URL,你可以把它原样写入表的某一列,这样仪表板组件无需任何额外工作就能把它渲染出来:
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)该 URL 永不过期,但它不是公开链接:只有在此数据后端上持有 READ 权限、且已通过身份验证的请求方才能读取它,鉴权代理会在每一次请求时重新校验这一点。因此把它存入数据库是安全的。
命名空间
命名空间是一个带有策略的键前缀——保留期、共享规则、允许的内容类型。它并不是一个独立的存储桶;一个应用的所有命名空间都位于该应用唯一的那块存储区域之内。只有当一组对象需要不同的规则时才去声明它;否则就留在 default 中,并用类似 2026/03/part-1.jpg 这样的键路径来组织你的对象。
在 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 }请注意,该预算是为整个应用声明一次的,而不是按命名空间声明的。命名空间只是应用那块唯一存储区域内部的一个键前缀,因此并不存在什么东西可以让按前缀划分的预算去强制执行。maxObjectBytes 则确实是按命名空间生效的——它限制的是单个对象的大小,而不是总量。
下面的每个方法都接受命名空间作为可选参数,默认为 default。
存储与读取对象
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")| 方法 | 描述 |
|---|---|
put(key, data, …) | 存储一个对象(Python 中为 bytes,JavaScript 中为 Uint8Array)。返回该对象的元数据,其中包含它的 url |
get(key, namespace?) | 返回该对象的内容 |
put_file(key, path, …) / get_to_file(key, path, …) | 仅限 Python。 从本地文件存储,或写入到本地文件。在大对象路径上以流式方式传输 |
delete(key, namespace?) | 删除一个对象 |
copy(key, to, …) | 复制一个对象,也可复制到另一个命名空间 |
move(key, to, …) | 先复制再删除。非原子操作——服务端没有 move 这个动作,因此一旦删除失败,两份副本都会留存 |
JavaScript 没有提供文件路径辅助方法,因为该包为 Node 和浏览器发布的是同一个构建产物——请自行使用 fs 读写本地文件。
put 接受: content_type / contentType(MIME 类型;命名空间可能会限制允许哪些类型)和 namespace。在 Python 中它们是关键字参数;在 JavaScript 中它们放在一个选项对象里。
列举与查看
| 方法 | 描述 |
|---|---|
list(…) | 返回一页对象。包含 objects、prefixes、is_truncated / isTruncated,以及一个 cursor——把它传回即可获取下一页 |
iter(…) / iterate(…) | 遍历某个前缀下每一个对象的异步迭代器,自动翻页。在 Python 中名为 iter,在 JavaScript 中名为 iterate |
stat(key, namespace?) | 获取单个对象的元数据,而不传输其内容 |
exists(key, namespace?) | 某个对象是否存在 |
namespaces() | 本应用可以使用的命名空间 |
usage(…) | 本应用占用了多少存储空间——见下文 |
catalog() | 命名空间,以及服务器下发的各项限制和配额。首次调用后会被缓存 |
两个 SDK 用相同的字段来描述对象,只是各自遵循本语言的命名风格:namespace、key、size、etag、content_type / contentType、last_modified / lastModified、checksum_sha256 / checksumSha256 和 url。
存储用量与配额
usage 由对象存储在一次调用中直接给出答案,因此这些总量是精确的,而不是由 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}| 字段 | 含义 |
|---|---|
size_bytes / sizeBytes | 当前已存储的字节数 |
object_count / objectCount | 已存储的对象数量 |
quota_bytes / quotaBytes | 实际强制执行的预算。0 表示不限量 |
free_bytes / freeBytes | 剩余的字节数。-1 表示不限量——在这里报告 0 会被读作“已满” |
per_namespace / perNamespace | 按命名空间划分的字节数。只有在你请求详细分项时才会出现 |
按命名空间划分的分项默认是关闭的,因为对象存储无法直接回答这个问题:它是按存储区域来记账的,而命名空间只是一个前缀,所以 SDK 必须逐个列举每个命名空间才能把大小累加起来。需要的时候再去请求它,不要放在热路径上。
这里会出现两种不同的配额,把它们区分开是值得的。catalog() 会同时报告这两者:
| 字段 | 含义 |
|---|---|
quota_bytes / quotaBytes | 实际被强制执行的值,读取自对象存储——也就是项目用户的设置 |
suggested_quota_bytes / suggestedQuotaBytes | 应用的数据模板所请求的值。如果它没有请求任何值,则为 0 |
只要用户调高或调低了应用的预算,这两者就会不同;这也正是为什么强制执行的值是从对象存储读取的,而不是取自模板——重新部署应用绝不能悄无声息地重置用户的选择。UI 可以把两者都显示出来(“应用建议 X,你设置的是 Y”)。强制执行时始终使用前者。
共享对象
链接有两种,二者的区别很重要:
| 方法 | 有效期 | 谁能读取它 |
|---|---|---|
url(key, …) | 永久 | 只有在此数据后端上持有 READ 权限、且已通过身份验证的请求方——每次请求都会重新校验。可以安全地存入表的列中 |
share_url / shareUrl | 会过期(默认 15 分钟,上限由服务器强制限定) | 任何持有该链接的人。 使用它时不会再对授权做任何校验 |
share_url / shareUrl 是一种 bearer 凭据,即持有链接者即可访问:把它交给需要临时访问的人,不要把它存进数据库。凡是仪表板要渲染的内容,一律使用 url。
在部署环境没有 HTTP 边缘入口时(例如一台仅有纯 HTTP 的一体机设备),url 会返回 None / undefined——这就是应当回退到 get 的信号。把对象的 etag 作为 version 参数传入,可以让浏览器以不可变(immutable)的方式缓存响应。
upload_url / uploadUrl 会签发一个会过期的 URL,它接受直接上传,并返回 url、method、headers 和 expires_in / expiresIn。请原样发送它返回的那些请求头,否则签名将无法通过校验。
大对象
SDK 会根据对象大小自动选择传输方式——没有任何需要配置的地方:
| 对象大小 | 传输方式 |
|---|---|
| 不超过内联上限(目前为 6 MiB) | 经由消息路由器的一次调用 |
| 更大 | 绕过路由器,通过 HTTPS 直接传输到对象存储 |
确切的上限由服务器在运行时给出,即 catalog() 中的 inline_max_bytes / inlineMaxBytes,因此无需发布新的 SDK 版本就能调高它。
仍有两个硬上限,二者都会报告 TOO_LARGE,并在原因说明中指出你触碰的是哪一个:
- 5 GiB——对象存储的单次上传上限。分片上传尚未实现。
- 在没有直连端点时的内联上限——物理隔离的一体机设备根本无法传输大对象。重试或改用更小的分块都无济于事,错误消息中也会这样写明。
直连路径要求设备能够访问对象存储主机,而不只是路由器。现场常见的两类故障各有专属的错误码,而不会看起来像是授权问题:PRESIGN_UNREACHABLE(代理只放行通往路由器的流量)和 CLOCK_SKEW(对象存储会拒绝时间偏差超过 15 分钟的请求——请检查设备上的 NTP)。
文件存储错误
每个文件操作都会引发(Python)/抛出(JavaScript)一个 FileStoreError,它携带一个稳定的 code 和一段人类可读的 reason。请依据 code 分支判断,绝不要依据 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| 错误码 | 含义 |
|---|---|
NOT_AUTHORIZED | 调用方无权执行此操作 |
NO_SUCH_NAMESPACE | 该命名空间未在数据模板中声明 |
NO_SUCH_OBJECT | 该键不存在 |
TOO_LARGE | 超出单次调用的传输上限 |
OBJECT_TOO_LARGE | 超出该命名空间自身的 maxObjectBytes |
QUOTA_EXCEEDED | 文件存储已满 |
CONTENT_TYPE_NOT_ALLOWED | 该命名空间限制了 contentTypes |
NOT_SUPPORTED | 后端无法完成此操作 |
NOT_AVAILABLE | 此部署环境没有文件服务 |
PRESIGN_UNREACHABLE | 无法直接访问对象存储(是不是有代理?) |
CLOCK_SKEW | 设备时钟偏差过大 |
INTERNAL | 其他所有情况 |
较新的服务器可能会引入本次 SDK 发布尚不认识的错误码。它们会作为 code 原样透传,而不会被归并,因此请把无法识别的值当作一般性失败来处理。
在 Python 中,
FileStoreError从ironflock.filestore导入;在 JavaScript 中,它由包的根导出(import { FileStoreError } from "ironflock")。
跨设备通信
registerDeviceFunction / register_device_function
注册一个同项目中其他设备可以调用的过程。SDK 会自动为当前设备的过程添加命名空间。
Python
def add(a, b):
return a + b
await ironflock.register_device_function("com.myapp.add", add)
register()是register_device_function()的别名。
callDeviceFunction / call_device_function
调用另一台设备注册的过程。SDK 使用目标设备的密钥自动组装完整的 WAMP 主题。
Python
result = await ironflock.call_device_function(
42, # target device key
"com.myapp.add", # procedure name
args=[3, 5] # arguments
)
print(result) # 8call
使用完整的 WAMP URI 调用远程过程。当您知道确切的主题时,可使用此方法进行直接调用。
Python
result = await ironflock.call("some.full.wamp.topic", args=[42])设备元数据
setDeviceLocation / set_device_location
更新设备在平台上的 GPS 位置。更改会实时反映在 IronFlock 地图上。
Python
await ironflock.set_device_location(long=8.6821, lat=50.1109)| 参数 | 范围 |
|---|---|
long | -180 到 180 |
lat | -90 到 90 |
位置历史不会被存储。要跟踪位置随时间的变化,请创建专用表并使用
publish_to_table/publishToTable。
getRemoteAccessUrlForPort
返回设备上指定端口的公共远程访问 URL。
Python
url = ironflock.getRemoteAccessUrlForPort(8080)
# "https://<device_key>-<app_name>-8080.app.ironflock.com"连接属性与生命周期
Python
| 属性 | 类型 | 描述 |
|---|---|---|
is_connected | bool | 平台连接是否处于活动状态 |
connection | CrossbarConnection | 底层连接实例(高级用法) |
| 方法 | 描述 |
|---|---|
run() | 启动连接并运行 mainFunc(阻塞式) |
await start() | 异步启动连接 |
await stop() | 停止连接并取消正在运行的任务 |
await run_async() | 异步启动并保持连接 |
错误处理
每个 SDK 方法在失败时都会明确报错:遇到无效参数、连接丢失或平台拒绝时,它会引发异常(Python)或拒绝(JavaScript),并给出一条包含操作名称、主题和原因的消息。没有任何错误会被悄悄吞掉,因此请将您希望能够继续运行的调用包裹在 try 块中。
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}")在 JavaScript 中,来自平台的失败是 WampError 实例——它是一个普通的 Error 子类,并额外在 error 中携带 WAMP 错误 URI,在 args / kwargs 中携带错误负载。其他所有情况(参数错误、未连接)都是普通的 Error。
**升级说明:**较早版本的 SDK 在调用失败时会记录一条日志并返回
None/null。现在它们会改为引发错误,因此形如if result is None:的代码已无法再检测到失败——请改用try/except(或try/catch)。
浏览器使用(仅 JavaScript)
JavaScript SDK 可在现代浏览器中使用。由于浏览器没有环境变量,需要通过构造函数传入所有配置:
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 }]);使用 IronFlock.fromServer() 从后端获取配置,而不是硬编码凭据:
const ironflock = await IronFlock.fromServer("/api/ironflock-config");
await ironflock.start();后端接口应返回包含连接选项(serialNumber、deviceKey、appName、swarmKey、appKey、env)的 JSON 对象。
注册 AI Agent 函数
SDK 可以注册供 AI Agent 调用的函数。注册一个过程并在 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)当用户提出需要实时传感器数据的问题时,AI Agent 可以调用此函数。
要将注册的 WAMP 主题与 AI 代理连接,请在应用的 .ironflock/ai-template.yml 中引用它:
sensor_agent:
tool_description: |
当用户询问传感器读数、设备实时数据或当前环境条件时,
请委托给此代理。
system_prompt: |
你是传感器数据专家。使用 get_current 获取任意传感器的最新读数。
在回答中始终包含单位。
main: true
max_context_tokens: 30000
max_iterations: 5
tools:
get_current:
description: 返回传感器的最新读数。
topic: sensors.get_latest
parameters:
sensor_id:
type: string
description: 要查询的传感器标识符。
required: truetopic 的值(sensors.get_latest)必须与边缘代码中传递给 register_device_function / registerDeviceFunction 的名称一致。IronFlock 会自动将调用路由到注册了该函数的设备。
有关 ai-template.yml 的完整参考,请参阅定义代理与工具。
环境变量
以下变量由 IronFlock 运行时在应用容器内自动设置:
| 变量 | 描述 |
|---|---|
DEVICE_NAME | 设备显示名称 |
DEVICE_SERIAL_NUMBER | 唯一且不可变的设备标识符 |
DEVICE_KEY | 用于认证的设备密钥 |
SWARM_KEY | 项目标识符 |
APP_KEY | 应用标识符 |
APP_NAME | 应用名称 |
ENV | 环境:DEV 或 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")