IronFlock SDK
IronFlock SDK を使用すると、エッジアプリケーションから IronFlock プラットフォームと連携できます。SDK は Python、JavaScript、Go の 3 つの言語で提供されており、機能は同じです。以下では並べて説明します。登録済みデバイスで実行する場合、認証を自動的に処理し、データの公開、履歴のクエリ、デバイス間のリモートプロシージャ呼び出し、デバイスメタデータの更新のための関数を提供します。
| SDK | パッケージ | 必要環境 |
|---|---|---|
| Python | ironflock(PyPI) | Python 3.11+ |
| JavaScript | ironflock(npm) | Node.js 22+ またはモダンブラウザ |
| Go | github.com/ironflock/sdk-go | Go 1.27.2+ |
このページは 3 つの SDK のガイドです。各 SDK の完全なリファレンスは、GitHub にあるそれぞれの README です:IronFlock/sdk-py、IronFlock/sdk-js、IronFlock/sdk-go。
インストール
Python
pip install ironflockまたは、アプリの requirements.txt に ironflock を追加してください。
クイックスタート
Python
import asyncio
from datetime import datetime, timezone
from ironflock import IronFlock
async def main():
while True:
await ironflock.publish_to_table("sensordata", {
"tsp": datetime.now(timezone.utc).isoformat(),
"temperature": 22.5,
"humidity": 60
})
await asyncio.sleep(5)
ironflock = IronFlock(mainFunc=main)
ironflock.run()IronFlock アプリコンテナ内で使用する場合、SDK は環境から接続資格情報を自動的に読み取ります — 手動設定は不要です。テーブルに書き込むすべての行は、そのタイムスタンプである tsp を持ちます(publishToTable を参照)。
コンストラクタオプション
Python
ironflock = IronFlock(
mainFunc=main, # async function to run after connecting
serial_number="abc123", # override device serial (optional)
reconnect_window=60, # seconds an operation rides out a platform restart (optional)
)| パラメータ | 説明 |
|---|---|
mainFunc | 接続が確立された後に実行される非同期関数(async def)。この関数が戻るか例外を送出すると、run() と run_async() は接続を停止します(接続プロパティとライフサイクル を参照) |
ironFlockUrl | ルーターの WebSocket URL(例:wss://cbw.ironflock.com/ws-ua-usr)。指定しない場合、SDK は URL を環境から取得します(環境変数 を参照) |
serial_number | 公開やテーブル書き込みとともに送信されるデバイスのシリアル番号を上書きします。デフォルトでは環境変数 DEVICE_SERIAL_NUMBER が使用されます。デバイスエージェントがアプリごとの資格情報を注入している間は、接続が認証に使う ID は変わりません |
reconnect_window | テーブル操作、publish、subscribe、register_device_function が、再起動後にプラットフォームが復帰するのを待ってから例外を送出するまでの秒数。デフォルトは 60。0 で無効になります(接続の信頼性 を参照)。IronFlock() は、負の値、NaN、無限大に対しては ValueError を、数値でない値に対しては TypeError を送出します |
データの公開
publishToTable / publish_to_table
フリートテーブルにデータレコードを書き込みます。テーブル名はアプリの data-template.yml で定義されたテーブルと一致する必要があります。SDK はデータを適切なプロジェクトデータベースに自動的にルーティングします。
appendToTable / append_to_table と同じです:テーブルの追加プロシージャを呼び出すため、プラットフォームの再起動を乗り切ります — 接続を最大で再接続ウィンドウの間待ち、データバックエンドがまだテーブルを提供していない間は再試行します — また、データバックエンドが行を拒否した場合は例外を送出(リジェクト、エラーを返却)します。プラットフォームの再起動中に行が失われることはありません。
3 つの SDK のバージョン 1.9.1 以前は、代わりに行をテーブルの書き込みトピックに公開していました:データバックエンドの再起動中に公開された行は失われ、拒否された行はエラーなしに破棄されていました。現在は、そのような行で呼び出しが失敗します — たとえば
tspのない行や、データテンプレートにないテーブル(再接続ウィンドウの経過後)です。
Python
from datetime import datetime, timezone
await ironflock.publish_to_table("sensordata", {
"tsp": datetime.now(timezone.utc).isoformat(),
"temperature": 22.5,
"humidity": 60,
"device_id": "sensor-001"
})データバックエンドは最初の引数から行を読み取ります:Python では dict、JavaScript では配列の最初の要素、Go では最初の位置引数(マップまたは構造体)です。追加のキーワード引数(Go では引数の中にある ironflock.Kwargs の値)は、データテンプレートがそれらをマッピングしているカラム(kwargs.<name>)についてのみ読み取られます。
すべての行に tsp が必要です。 すべてのテーブルには必須の tsp カラム、つまり行のタイムスタンプ(例:ISO 8601 文字列、Go では UTC の RFC 3339 として送信される time.Time)があり、データテンプレートが別の場所にマッピングしていない限り、データバックエンドはそれを行から読み取ります。appendToTable / append_to_table で説明しているとおり、データバックエンドは tsp のない行を拒否します。
エンティティテーブルへの部分的な行。 エンティティキー(maintainLatestFlagFor)を持つテーブルでは、行にエンティティキー、tsp、変更するカラムだけを含めれば済みます。行が省略したカラムはエンティティの直前の値を保持し、None / null / nilとして送信したカラムはクリアされます。フィールドが省略扱いになるかどうかは、行の組み立て方によって決まります:
- Python: 値が
Noneのキーはnullとして送信され、カラムをクリアします。古い値を保持するには、そのキーをdictから削除してください。 - JavaScript: 値が
undefinedのプロパティはペイロードから除外されるため、古い値が保持されます。カラムをクリアするにはnullを設定してください。 - Go:
omitemptyまたはomitzeroタグ付きの構造体フィールドは、空のとき省略されるため、古い値が保持されます。それ以外の場合、nilポインタのフィールドは、ironflock.Row内のnil値と同様にnullとして送信され、カラムをクリアします。
これは4つの書き込みメソッドすべてに当てはまります。エンティティキーのないテーブルでは、行が省略したカラムはNULLとして保存されます。
appendToTable / append_to_table
リモートプロシージャコールを使用してフリートテーブルにデータを追加します:拒否された行はエラーになります。publishToTable / publish_to_table も同じことを行います。違いは、エラーメッセージが示す操作名だけです。
Python
result = await ironflock.append_to_table("sensordata", {
"tsp": datetime.now(timezone.utc).isoformat(),
"temperature": 22.5,
"humidity": 60
})publishToTable と同様に、行には tsp が必要です。データバックエンドは tsp のない行を、理由を示さずに wamp.error.runtime_error で拒否します。その場合、SDK のエラーには “the data backend refused the row without a reason — a missing tsp is the usual cause; see the error-logs table” という一文が追加されます。アプライアンスのディスクがいっぱいの場合など、型付きの拒否は データバックエンドによる拒否 に一覧されています。
publishRowsToTable / publish_rows_to_table
複数の行を 1 つのメッセージで(一括挿入)フリートテーブルに書き込みます。プラットフォームはバッチ全体を 1 回の操作でアトミックに(オール・オア・ナッシングで)挿入します。1 行ごとにラウンドトリップを行うとコストが高すぎる高頻度データに使用してください。appendRowsToTable / append_rows_to_table と同じです:一括追加プロシージャを呼び出し、publishToTable と同様にプラットフォームの再起動を乗り切り、データバックエンドがバッチを拒否した場合は失敗します。
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},
])第 2 引数は、挿入する行オブジェクトの空でないリストで、各行がそれぞれ独自の tsp を持ちます(Go では行のスライス:[]ironflock.Row、[]map[string]any、または構造体のスライス)。データバックエンドが拒否したバッチ(たとえば行に tsp がない場合)は呼び出しを失敗させ、そのバッチの行は 1 つも保存されません。(SDK のバージョン 1.9.1 以前は、バッチをテーブルの一括挿入用トピックに公開しており、拒否されたバッチはそこでエラーなしに破棄されていました。)
appendRowsToTable / append_rows_to_table
複数の行を 1 つのリモートプロシージャ呼び出しで(一括挿入)フリートテーブルに追加します。プラットフォームはバッチ全体をアトミックに(オール・オア・ナッシングで)挿入します。いずれかの行が無効な場合、バッチ全体が拒否され、何も永続化されません。publishRowsToTable / publish_rows_to_table も同じことを行います。すべての行にそれぞれの tsp が必要です。拒否の扱いは appendToTable と同じで、理由なしに拒否されたバッチに対するヒントも含まれます。
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
# Writes a row to the error-logs table
await ironflock.report_error("Sensor read timed out", level="warning")
# Pass an exception to capture its traceback (falls back to the message)
try:
risky_operation()
except Exception as err:
await ironflock.report_error(err)
# Show the operator a friendlier line than the raw error
await ironflock.report_error(
"Calibration failed: offset 0.42 out of range",
user_message="Please recalibrate the sensor",
)行は { tsp, msg, user_message, source: "app", level } です:msg には技術的なテキスト(トレースバックまたはスタック)が、user_message にはボードのエラートーストに表示される行が入ります — 渡したユーザーメッセージ、指定しなかった場合はメッセージそのものです。
パラメータ:
| パラメータ | 型 | 説明 |
|---|---|---|
error | str / string または例外 / Error | エラーメッセージ、またはトレースバック/スタック(もしくはメッセージ)が記録される例外 |
level | str / string、オプション | 重大度:"error"、"warning"、"info" または "debug"。これらはボードのトーストウィジェットが表示する種類です。デフォルトは "error"。SDK 1.9.1 以前の表記である "warn" は "warning" として送信されます |
append | bool / boolean、オプション | 互換性のために残されています:publishToTable と appendToTable はどちらも追加を行うため、行はどちらの場合も追加されます。エラーメッセージが 2 つのうちどちらの名前を示すかを選ぶだけです |
tsp | str / string、オプション | ISO-8601 タイムスタンプの上書き。デフォルトは現在時刻 |
user_message / userMessage | str / string、オプション | オペレーター向けのテキストで、user_message として保存されます。デフォルトは記録されるメッセージ |
Python ではオプションはキーワード引数です(
report_error(error, level=..., append=..., tsp=..., user_message=...))。JavaScript ではオプションオブジェクトを介して渡します(reportError(error, { level, append, tsp, userMessage }))。Go では構造体を介して渡します(ReportError(ctx, errOrMsg, ironflock.ReportErrorOptions{Level, Append, Tsp, UserMessage}))。ここでerrOrMsgはerrorまたは文字列、Levelはironflock.LevelError、LevelWarning、LevelInfo、LevelDebugのいずれかで、返される*Resultは挿入結果です。
publish
独自の WAMP トピックにメッセージを公開します。データベーステーブルにマッピングされないカスタムメッセージングやイベントに使用します。ルーターは一部の名前をプラットフォーム用に予約しています — アプリが使用できる URI を参照してください。
テーブル書き込みと同様に、publish もデバイスのメタデータ(DEVICE_SERIAL_NUMBER、DEVICE_KEY、DEVICE_NAME)をキーワード引数に追加します。これらのいずれかと同じ名前の独自のキーワード引数を渡した場合は、そちらが優先されます。
接続がない場合、publish は最大で再接続ウィンドウの間(デフォルトは 60 秒。接続の信頼性 を参照)接続を待つため、短い切断であれば公開はそれを乗り切ります。その後、メッセージは 1 回だけ送信され、再試行されることはありません。ルーターは誰も購読していないメッセージにも確認応答を返すため、その間停止している購読者はそのメッセージを受け取れません。
Python
await ironflock.publish("com.myapp.alerts", {
"level": "warning",
"message": "Temperature threshold exceeded"
})履歴データのクエリ
getHistory
フリートテーブルまたは transform から履歴データを取得します:条件に一致する最新の行を、tsp の昇順で返します。フィルタリング、時間範囲、ページネーションをサポートしています。
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": ["2026-01-01T00:00:00Z", "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}]
})
# One field of a large json column: the column comes back pruned to its paths
readings = await ironflock.getHistory("sensordata", {
"limit": 100,
"columns": ["json_data"],
"columnPaths": ["json_data.sensor.temp"],
})クエリパラメータ:
| フィールド | 型 | 説明 |
|---|---|---|
limit | int / number | 返す最大行数(1〜10,000、必須。transform の場合は 1〜3,000) |
offset | int / number | ページネーション用のオフセット |
timeRange | list / array | [start, end] — ISO 日時文字列またはエポックミリ秒の数値(混在は不可)。開いた端には None / null。レガシーな {"start": ..., "end": ...} オブジェクトはこのペアに変換されます |
filterAnd | list / array | AND で結合されるフィルタエントリ:条件、グループ、および/または latest マーカー(下記参照) |
columns | list / array | 返すカラム(オプション)。tsp、device_key、authid は常に含まれます。すべてのカラムが必要な場合は省略してください |
columnPaths | list / array | json カラムを絞り込みます(オプション):フィルタと同じ記法の JSON パス("json_data.a.b"、"json_data['key']")。columns に列挙された json カラムは、そのパスの和集合だけを保持した状態で返されます。ここにパスがないカラムは全体がそのまま返されます。transform、および fleetdb v1.0.52 より古いデータバックエンドでは無視されます |
クエリパラメータを渡す場合は limit が必須です。まったく渡さない場合(Go では nil)、getHistory は最新の 10 行を読み取ります。
Go では、これらは ironflock.TableQueryParams のフィールド Limit、Offset、TimeRange、FilterAnd、Columns、ColumnPaths です。TimeRange は *ironflock.TimeRange{Start, End} で、各境界値は time.Time、ISO 8601 文字列、エポックミリ秒の数値、または開いた端を表す nil です(ironflock.Between、Since、Until は time.Time の値からこれを作成します)。FilterAnd は []ironflock.Filter です。レガシーなオブジェクト形式はありません。
フィルタ演算子: =, !=, <>, >, <, >=, <=, LIKE, ILIKE, NOT LIKE, NOT ILIKE, IN, NOT IN, IS NULL, IS NOT NULL。JavaScript はこの一覧を SQL_OPERATORS としてエクスポートしており、Go では ironflock.SQLOperators() がこれを返します。
各フィルタ条件は column、operator、value のキーを持つオブジェクトです — Go では ironflock.Where(column, operator, value)、ironflock.IsNull(column)、ironflock.IsNotNull(column) で作成します。演算子は大文字・小文字を問わず、前後の空白を無視して照合され、上に挙げた形式 — データバックエンドが受け付ける唯一の形式 — で送信されます。そのため "like" は、グループの中でも "LIKE" として送られます。一覧にない演算子は、警告とともにそのまま送信されます。IS NULL と IS NOT NULL は value を取らず、NULL について問い合わせる唯一の方法です:それ以外の演算子はすべて、SQL とまったく同じく、カラムが NULL の行を除外します。IN と NOT IN はリスト(Go ではスライス)を取ります。LIKE は大文字と小文字を区別します。区別せずに照合するには ILIKE を使用してください。
OR による組み合わせ。 filterAnd のエントリは AND で結合されます。グループエントリは、自身のエントリ(条件、latest マーカー、またはさらに別のグループ)を 1 つの演算子 "AND" または "OR" で組み合わせます:
Python
# Rows that are not soft-deleted: deleted IS NULL OR deleted = false
"filterAnd": [
{
"combinator": "OR",
"filters": [
{"column": "deleted", "operator": "IS NULL"},
{"column": "deleted", "operator": "=", "value": False},
],
}
]グループに対応していないデータバックエンド(v1.2.0 より前の fleetdb)は、この形の一部だけを適用するのではなく、この形そのものを拒否します。そのため、フィルタが除外するはずの行を黙って返すことはなく、明示的に失敗します。
現在の値の読み取り。 filterAnd 内の {"latest": true} エントリ(Go では ironflock.Latest())はフィルタ条件ではなくモードの切り替えです。データバックエンドは、テーブルが maintainLatestFlagFor で宣言したエンティティキーをもとに SQL で導出した、エンティティごとの最新の行のみを返します。エンティティキーを持たないテーブルの場合は、最新の 1 行だけが返されます。
その他の条件は、想定どおりにマーカーと組み合わされます。エンティティキーのカラムに対する条件はどのエンティティを返すかを絞り込み、それ以外のすべての条件と timeRange は、得られた最新の行に対して適用されます。したがって {"latest": true} と deleted フィルタを組み合わせると、削除済みのエンティティは非表示になり、その前の行が再び現れることはありません。
以前のバージョンの IronFlock は、物理的な latest_flag カラムを保存していました。このカラムはもう存在しません。レガシーな latest_flag = true フィルタは引き続き受け付けられ、マーカーとして扱われますが、新しいコードでは {"latest": true} を使用してください。latest マーカーは getSeriesHistory では利用できません。
時間範囲。 データバックエンドは文字列の境界値を JavaScript の Date で読み取ります。そのため、ISO 8601 の日付または日時(2026-07-01、2026-07-01T12:30:15.250Z)で記述し、日付と時刻は T で区切ってください。SDK は、0〜99 年について日付と時刻の間に空白を入れた値を拒否します。Date がそれを誤って解釈するためです:"0001-01-01 00:00:00"(Python の str(datetime.min) が返す値)は 2001 年として読み取られてしまいます。Python SDK は、datetime.fromisoformat で読み取れない文字列も拒否します。タイムゾーンのない時刻は UTC として読み取られます。数値の境界値はエポックミリ秒であり、有限でなければなりません:NaN と ±Infinity はデータバックエンドに開いた境界として届き、時間フィルタを黙って無効にしてしまうため、SDK は送信前にこれらを拒否します。Go では、time.Time の境界値は UTC の RFC 3339 として送信され、文字列の境界値は Date が正しく読み取れる ISO 8601 形式のいずれかでなければなりません — 日付、または日付と時刻(省略可能な Z または UTC オフセット(+02:00 または +0200)付き)です。それ以外の文字列、たとえば基本形式の 20260701T123000Z、時間だけのオフセット、その月に存在しない日(2026-02-30)などは、何も送信される前に ironflock.ErrInvalidArgument で失敗します。
transform の読み取り。 transform(データテンプレートの SQL ビュー。変換テーブル を参照)は、テーブルとは異なる方法で読み取られます。データバックエンドが適用するのは limit(最大 3000。それより大きい値は URI_INVALID_LIMIT で拒否されます)、offset、filterAnd だけです。timeRange、columns、columnPaths、latest マーカーは無視されます。また、transform が宣言していないカラムに対するフィルタは破棄され(transform には暗黙の tsp、device_key、authid はありません)、その分だけ結果が広がります。行はビュー自身の順序を逆にした順で返されます:最新の行が古い順に得られるのは、ビューが新しい順に並べている場合だけです。transform の時間範囲は、その SQL の中で絞り込んでください。
大きな結果。 データバックエンドの 1 メッセージ(JSON で 8 MiB)に収まらない大きな結果はチャンクに分割されて届き、SDK がメモリ上で再構成します:1 回の読み取りの行が、同じ順序で得られます。チャンクのつじつまが合わない場合(欠落や重複がある、または行数がデータバックエンドのサマリーと異なる場合)は、行の一部を返すのではなく読み取りが失敗します。8 MiB を超える単一の行、またはデータバックエンドの 1 GiB の上限を超える結果は URI_RESULT_TOO_LARGE で拒否されます:limit を下げる、timeRange を狭める、columns を減らす、または columnPaths で絞り込んでください。
getSeriesHistory / get_series_history
フリートテーブルから ダウンサンプリングされた時系列データ を取得します。カラムを時間バケットごとに集約します(例:1 時間ごとの平均)。長期間にわたるグラフに最適です。テーブルで利用可能で(transform では利用できません)、fleetdb v1.0.58 以降のデータバックエンドが必要です。
Python
from datetime import datetime, timedelta, timezone
from ironflock import SeriesMetric
now = datetime.now(timezone.utc)
avg = SeriesMetric(ref="temperature", method="AVG") # AVG, SUM, COUNT, MIN, MAX, FIRST, LAST
series = await ironflock.get_series_history("sensordata", {
"metrics": [avg, {"ref": "humidity", "method": "MAX"}],
"bucketMs": 3_600_000, # one-hour buckets; omit to divide the range into `limit` buckets
"limit": 24, # the bucket budget, 1–10000
"timeRange": [(now - timedelta(hours=24)).isoformat(), now.isoformat()], # required, with a start
"groupBy": ["device_key"],
})
for row in series:
print(row["tsp"], row["device_key"], row[avg.column]) # avg.column is "AVG:temperature"クエリパラメータ:
| フィールド | 型 | 説明 |
|---|---|---|
metrics | list / array | バケットごとに計算される集約で、少なくとも 1 つ必要です:{"ref": ..., "method": ...}。ref はカラム、または json カラム内の JSON パス("json_data.temp")です。method は "AVG"、"SUM"、"COUNT"、"MIN"、"MAX"、"FIRST" または "LAST" です。各メトリクスは "<METHOD>:<ref>" という名前で返され、この名前は 63 バイト以内に収まる必要があります |
bucketMs | int / number | バケットの幅(ミリ秒単位の整数、1000 以上、オプション)。省略すると、時間範囲が limit 個のバケットに分割されます |
limit | int / number | バケットの予算(1〜10,000) — 行数の上限ではありません |
timeRange | list / array | [start, end] — ISO 日時文字列またはエポックミリ秒の数値。必須で、開始値が必要です。終了値に None / null を指定すると現在時刻を意味します。終了は開始より後でなければなりません |
groupBy | list / array | 各バケットをグループごとの行に分割するカラム(オプション) |
filterAnd | list / array | getHistory と同様に AND で結合される条件とグループ(オプション)。ここでは latest マーカーは、グループの中であっても拒否されます。現在の値を読み取るには getHistory を使用してください |
行。 各行は 1 つのバケットです(groupBy を指定した場合は、1 つのグループの 1 つのバケット)。tsp はエポックミリ秒で表したバケットの開始時刻で、各 groupBy カラムはそれ自身の名前で、各メトリクスは "<METHOD>:<ref>"(Python では SeriesMetric.column、JavaScript では seriesColumn(metric)、Go では SeriesMetric.Column())という名前で返されます。例:row["AVG:temperature"]。行は tsp の昇順に並び、行を含まないバケットは結果に現れません(ギャップの補完はありません)。AVG と SUM は数値カラムを受け付け、MIN と MAX は文字列やタイムスタンプも受け付けます。FIRST と LAST はバケット内で最も早い行と最も遅い行の値を返し、COUNT は NULL でない値の数を数えます(tsp の場合は行数)。大きな結果は、getHistory と同様にチャンクから再構成されます。
バケットと開いた終端。 limit は行ではなくバケットを数えます。bucketMs を指定しない場合、時間範囲は limit 個のバケットに分割されます。指定した場合、範囲内のバケット数が limit を超えると、データバックエンドはバケットを bucketMs の倍数に広げます — そして開いた終端は、データバックエンド自身の現在時刻、つまりあなたの現在時刻より少し後まで達します。開いた終端を持つ直近 24 時間には 1 時間のバケットが 25 個含まれるため、limit: 24 では 2 時間のバケットになります。例のように終端を指定するか、バケットを 1 つ多く許容してください:bucketMs と開いた終端を使うと、limit + 1 個のバケットが返ることがあります。
SDK は、データバックエンドが拒否するはずの内容を送信前に確認します:少なくとも 1 つのメトリクス、空でない ref、既知のメソッド、63 バイト以内のカラム名、開始値、開始より後の終了値、そして 1000 以上の整数である bucketMs(Go では、ミリ秒単位の整数で 1 秒以上の Bucket)。データバックエンドは、応答できないクエリを URI_INVALID_TIME_RANGE、URI_INVALID_METRIC、URI_INVALID_GROUP_BY、または URI_SERIES_TOO_MANY_GROUPS(50,000 行超)で拒否します — データバックエンドによる拒否 を参照してください。
非推奨の形式。 1.9.0 までの SDK リリースは、
metricsをカラム名として、トップレベルのmethodとともに送信していました({"metrics": ["temperature"], "method": "AVG", ...})。この形式は、fleetdb v1.0.58 以降のデータバックエンドでは拒否されます。それでもこの形式は引き続き動作します:SDK はカラムごとに 1 つの{ref, method}メトリクスを送信し、警告を出し(Python ではDeprecationWarning、JavaScript ではプロセスごとに 1 回のコンソール警告)、結果のカラム名を元のカラム名に戻します。そのため、古いコードは引き続きrow["temperature"]を読み取れます — ただし、行にすでにその名前のカラムがある場合(tspのCOUNTにおけるtspなど)は除きます。{ref, method}メトリクスに移行し、row["AVG:temperature"]を読み取るようにしてください。Go SDK には現在の形式しかありません:メトリクスごとのメソッド(SeriesMetric)です。
データの購読
subscribeToTable / subscribe_to_table
フリートテーブルのリアルタイム更新を購読します。データバックエンドが新しい行を保存するたびに、保存されたとおりの行(値はデータテンプレートのカラムの型に変換され、シークレットカラムはマスクされます)を最初の引数としてハンドラが呼び出されます。一括挿入パス(publishRowsToTable / appendRowsToTable)で書き込まれた行は、1 行ずつハンドラに配信されるため、データの書き込み方法に関係なくハンドラのコードは変わりません。
Python
def on_sensor_data(*args, **kwargs):
row = args[0]
print("New reading:", row["tsp"], row["temperature"])
await ironflock.subscribe_to_table("sensordata", on_sensor_data)subscribeToTable / subscribe_to_table は、両方のフィードのハンドルである TableSubscription を返します:その unsubscribe() は両方のフィードを削除し、後述のキャッチアップを終了します(unsubscribe / unregister を参照)。
接続が切れている間に保存された行は、再接続後に届きます。 ルーターは接続が切れている間に公開されたものを再送しないため、SDK は再接続のたびに、その間に保存された行を読み取ってハンドラに配信します(キャッチアップ)。配信は古い順に行われ、ライブで届く行よりも先になります。対象となるのは、tsp がハンドラの受け取った最新の行より新しいすべての行です。ハンドラがまだ 1 行も受け取っていない場合は、購読した時点で保存されていた最新の行が起点になります:SDK は購読の直後にその行を読み取り、その読み取りに失敗した場合は、購読した時刻(デバイスの時計による)を起点にします。その時点で空だったテーブルは、先頭から読み取られます。その間に古い tsp で書き込まれた行(バッファしておいた測定値をデバイスが送信する場合など)は、キャッチアップの対象になりません。1 回のキャッチアップで配信されるのは最大で最新の 10,000 行で、その上限まで読み取った場合は警告がログに記録されます。キャッチアップの読み取りは、ほかの履歴の読み取りと同様に、最大で再接続ウィンドウの間データバックエンドを待ちます。ライブの行は、その読み取りが終わるまで待たされます。読み取りに失敗した場合、その間に保存された行は配信されず、警告がログに記録されます。キャッチアップされた行はイベントではなく読み取りから得られるため、ハンドラはそれぞれの行をイベントの詳細なしで受け取ります:Python では行のみ、JavaScript では ([row], {})、Go では Topic と Args を持つ Event で、これはサブスクリプションの DeliveryGroup 上で順番に配信されます。キャッチアップは SDK のバージョン 1.10.0 から含まれています。
transform には独自の行がありません:スケジュールの各ティックで、データバックエンドはビュー全体(最大 3000 行、ビューの順序を逆にしたもの)を、最初の引数が行のリストである 1 つの イベントとして公開します。transform にキャッチアップは必要ありません:再接続後の次のティックで、ビュー全体が改めて公開されるためです。Python の event_rows(args[0])、JavaScript の eventRows(args)、Go の ev.Rows() は両方の形を読み取ります:テーブルの行からは [row] が、transform のリストからはそのリスト(空のビューなら [])が得られ、それ以外は None / undefined / nil になります。Go では、transform のイベントに対する ev.Row() は nil です。
Python
from ironflock import event_rows
def on_hourly(*args, **kwargs):
for row in event_rows(args[0]) or []:
print(row)
await ironflock.subscribe_to_table("hourly_averages", on_hourly)subscribe
カスタムリアルタイムメッセージング用に独自の WAMP トピックを購読します。テーブルの行には subscribeToTable / subscribe_to_table を使用してください:ルーターは、テーブルへの生の書き込みトピックの購読を拒否します(アプリが使用できる URI を参照)。
Python
def on_alert(*args, **kwargs):
print("Alert received:", args, kwargs)
alerts = await ironflock.subscribe("com.myapp.alerts", on_alert)
# Later: the handler is called for no further event
await alerts.unsubscribe()subscribe は、そのサブスクリプションのハンドルである Subscription を返します(unsubscribe / unregister を参照)。呼び出しごとに専用のハンドルが返されます。すでに購読しているトピックに対しても同様です。
接続がない場合、subscribe は publish と同様に、最大で再接続ウィンドウの間(デフォルトは 60 秒)接続を待ちます。subscribeToTable / subscribe_to_table も同様です(接続の信頼性 を参照)。
トピックは完全一致で照合されます。ハンドラの後に渡す省略可能な引数である購読オプションで、これを変えられます:match に "prefix" を指定すると、指定したトピックで始まるすべてのトピックを購読します。オプションは、Python では autobahn の SubscribeOptions(SubscribeOptions(match="prefix")、autobahn.wamp.types から)、JavaScript ではオブジェクト({ match: "prefix" })、Go では ironflock.SubscribeOptions{Match: "prefix"} です。サブスクリプションは、再接続のたびに同じオプションで復元されます。subscribeToTable / subscribe_to_table も同じオプションを受け取り、両方のフィードをそのオプションで購読します。
unsubscribe / unregister
subscribe、subscribeToTable / subscribe_to_table、registerDeviceFunction / register_device_function は、それぞれハンドルを返します。ハンドルは、削除するまで再接続をまたいで有効なままです:SDK は、ハンドルが表すものを再接続のたびに復元します。
| ハンドル | 返すメソッド | メンバー |
|---|---|---|
Subscription | subscribe | unsubscribe()、topic、active |
TableSubscription | 自分のアプリおよび 消費先アプリ の subscribeToTable / subscribe_to_table | unsubscribe()、table、active |
Registration | registerDeviceFunction / register_device_function とそのエイリアス | unregister()、procedure、active |
topic、table、procedure は、そのハンドルが表すものの名前です。procedure は完全な WAMP URI です。active は、サブスクリプションや登録が現在ルーター上で有効かどうかを示します:接続が切れている間、復元に失敗してから再試行が成功するまでの間、およびハンドルが削除された後は false になります。TableSubscription は、両方のフィードが有効な間だけ有効です。ironflock.unsubscribe(handle) は Subscription または TableSubscription を、ironflock.unregister(registration) は Registration を受け取ります。どちらも、ハンドル自身のメソッドと同じ処理を行います。
Python
status = await ironflock.subscribe("com.myapp.status", on_status)
readings = await ironflock.subscribe_to_table("sensordata", on_row)
add = await ironflock.register_device_function("com.myapp.add", add_numbers)
print(readings.table, readings.active) # sensordata True
await status.unsubscribe() # the same as: await ironflock.unsubscribe(status)
await ironflock.unsubscribe(readings) # both feeds of the table, and its catch-up
await ironflock.unregister(add) # the same as: await add.unregister()unsubscribe() と unregister() は、呼び出した時点ですぐに作用し、awaitable を返します:ルーターが応答したことを知るには、それを await してください。拒否された場合は、そこで例外が送出されます。async ではない通常の関数であるハンドラは、subscription.unsubscribe() を await せずに呼び出せます。3 つのハンドルクラスは ironflock からエクスポートされています。
削除は、3 つの SDK すべてで同じルールに従います:
- すぐに効果を発揮します。 ハンドラは、それ以降のイベントでは一切呼び出されません:まだキューにあるイベントでも、一括挿入の残りの行でも、キャッチアップが配信するはずだった行でも呼び出されません。削除された関数は、もう呼び出されません。ルーターが登録を破棄する前にまだその関数へルーティングした呼び出しは、
wamp.error.no_such_procedureで拒否されます。すでに実行中の呼び出しは待たれないため、ハンドラは自身のサブスクリプションを削除できます。削除したものが再接続後に復元されることはありません。 - 接続を待ちません。 接続が切れている間は、ルーターがセッションとともにサブスクリプションや登録を破棄しているため、それらはローカルで削除され、呼び出しはすぐに戻ります。接続している間はルーターに通知され、ルーターが応答した時点で呼び出しが完了します。拒否された場合は、例外が送出(Python、
RuntimeError)、スロー(JavaScript、WampError)、または返却(Go)されますが、削除はそのまま有効です。 - 冪等です。 2 回目の呼び出しは何も行わず、ハンドルなし(
None、nullまたはundefined、nil)の呼び出しも同様です。stop()/Stopの後の呼び出しは、サブスクリプションや登録をローカルで削除するだけです:Python と JavaScript では、次のstart()でもそれは復元されません。 - ハンドルは互いに独立しています。 あるハンドルを削除しても、同じトピックやテーブルであっても、別のハンドルのハンドラが削除されることはありません。ルーター上のサブスクリプションは、そのトピックの最後のハンドルとともに終了します。
TableSubscriptionは両方のフィードを削除します。transformed.<table>とtransformed.bulk.<table>の両方が削除され、そのキャッチアップも終了します。
移行時の注意: バージョン 1.10.0 より前の Python と JavaScript の SDK は、autobahn のサブスクリプションおよび登録オブジェクトを返していました。また、
subscribe_to_table/subscribeToTableは行フィードのサブスクリプションしか返さなかったため、それを削除しても一括フィードは購読されたままでした。返された値に対してunsubscribe()やunregister()を呼び出すコードは引き続き動作し、削除したものは再接続後に復元されなくなります。idなど、autobahn のオブジェクトのその他のメンバーはなくなりました。
シークレットカラム
アプリの データテンプレート で secret: true と宣言されたカラムは、保存時に暗号化されます。通常の読み取り経路が平文を返すことは一切ありません。そのデータを書き込んだアプリ自身に対してもです:
| 読み取り経路 | シークレットカラムの見え方 |
|---|---|
getHistory、subscribeToTable / subscribe_to_table、および消費先アプリの読み取り(ボードも使う履歴の読み取りとリアルタイムの行) | プレースホルダ __secret__(SECRET_PLACEHOLDER としてエクスポートされています。Go では ironflock.SecretPlaceholder) |
| SQL(SQL クエリコンソール、データエクスポート、FleetDB Access の Postgres ログイン)とデータベースのバックアップ | 保存された暗号文 ifsec:1:… |
revealSecrets / reveal_secrets | 復号された平文 |
NULL はすべての経路でそのまま NULL として渡されるため、「設定済みだが非表示」と「一度も書き込まれていない」は引き続き区別できます。
エンティティテーブル では、読み取り・変更・書き戻し(read-modify-write)を安全に行えます。__secret__ プレースホルダを保持したままの行を再送信しても — あるいはシークレットカラムを丸ごと省略しても — 保存済みのシークレットは上書きされずに維持され、明示的な null はそれをクリアします。正確なルールについては、データバックエンドガイドの シークレットカラム を参照してください。
シークレットにできるのは string カラムだけで、tsp とエンティティキーはシークレットにできません。データバックエンドは各値を AES-256-GCM と値ごとに固有のランダムな IV で暗号化するため、同じシークレットでも行ごとに異なる暗号文として保存されます:シークレットカラムでフィルタ、グループ化、ソートを行うことはできません。シークレットカラムに対するフィルタは URI_SECRET_COLUMN で拒否されます。値を照合するには verifySecret を使用してください。
Python
from datetime import datetime, timezone
# Written like any other column — the data backend encrypts it on insert
await ironflock.publish_to_table("credentials", {
"tsp": datetime.now(timezone.utc).isoformat(),
"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__"}]以下の 2 つの関数は、アプリ自身のコンテナからのみ 呼び出せます。メッセージルーターは、あらゆるブラウザロールおよび消費側アプリに対してこれらを拒否します。そのため ConsumedApp ハンドルには対応するものがありません。別のアプリのシークレットカラムは秘匿された状態で届き、そのままです。
revealSecrets / reveal_secrets
自分のテーブルの行を、シークレットカラムを復号した状態で読み取ります。
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"}]クエリパラメータは getHistory と同じですが、limit は 100 を超えられません。この上限は、1 回の呼び出しでテーブル全体が復号されるのを防ぐためのものであり、データバックエンドはより大きな値を黙って切り詰めるのではなく 拒否 します。クエリパラメータを指定しない場合、この呼び出しは最新の 10 行を読み取ります。getHistory とは異なり、行は新しい順に返されます。
データバックエンドが応答する reveal は、デバイス上のアプリ資格情報ごとに 1 分あたり最大 30 回です。それを超えると、呼び出しは URI_RATE_LIMITED で失敗します。シークレットは使うたびに復号するのではなく、一度復号したら保持してください。シークレットカラムに対するフィルタは URI_SECRET_COLUMN で拒否されます。データバックエンドがこの関数を提供するのは、シークレットカラムを宣言しているテーブルに対してだけです:それ以外のテーブルに対しては、ルーターが wamp.error.no_such_procedure を返します。SDK はこれを再接続ウィンドウの間再試行し、その後、プロシージャが登録されていないことを示すエラーで呼び出しが失敗します。このエラーには、考えられる原因が可能性の高い順に示されます:テーブルがシークレットカラムを宣言していない(データテンプレートの secret: true)、テーブルが宣言されていない、またはデータバックエンドがシークレットカラム導入前のものである、のいずれかです。
verifySecret / verify_secret
候補の値をシークレットカラムと照合します。シークレットを明かすことは ありません — reveal 関数に対するパスワード照合版です。比較はデータバックエンド内部で一定時間で実行され、返ってくるのは結果だけです。
Python
ok = await ironflock.verify_secret(
"credentials", "api_token", submitted_token,
{"limit": 1, "filterAnd": [{"column": "device_id", "operator": "=", "value": "d-1"}]},
)
if ok:
...パラメータ:
| パラメータ | 型 | 説明 |
|---|---|---|
tablename | str / string | 照合先のテーブル |
column | str / string | 比較するシークレットカラム |
candidate | str / string | 検証する平文。サーバー側で比較され、テーブルに書き込まれることはありません |
query_params / queryParams | SecretQueryParams | どの行を対象にするか。デフォルトは最新の 1 行。limit は 100 を超えられません |
Go では、パラメータはコンテキストの後に続く位置引数の文字列で、クエリは *ironflock.TableQueryParams です(最新の 1 行には nil)。
JavaScript は { match, checked } を、Go は Match と Checked を持つ *ironflock.SecretVerifyResult を返し、Python は単なる bool を返します。checked が得られる場合は活用する価値があります:候補が誤っていた場合(checked > 0)と、セレクタがどの行にも一致しなかった場合(checked === 0)を区別できるためです。この 2 つは、ユーザーに伝えるべき内容がまったく異なる失敗です。
各行はそれぞれ独自のランダムな値で暗号化されるため、同じシークレットを保持する 2 つの行は異なる暗号文を保存します。これが、シークレットカラムに対する = フィルタが機能し得ない理由であり(データバックエンドはシークレットカラムに対するあらゆるフィルタを URI_SECRET_COLUMN で拒否します)、この関数が代わりに存在する理由です。
データバックエンドが応答する照合は、デバイス上のアプリ資格情報ごとに 1 分あたり最大 120 回です。それを超えると、呼び出しは URI_RATE_LIMITED で失敗します。リクエストのたびに照合する場合(API キーなど)は、成功した結果を短時間キャッシュしてください。シークレットカラムではないカラムは URI_NOT_A_SECRET_COLUMN で拒否されます。シークレットカラムを 1 つも持たないテーブルでは、この呼び出しは revealSecrets / reveal_secrets の場合と同じように失敗します。
アプリ間データアクセス
同じプロジェクト内で、別のアプリのフリートデータを自分のアプリから読み取ります。あなたのアプリは、自身の data-template.yml の consumes: セクションで提供側のアプリを宣言する(または下記のワイルドカード許可を保持する)必要があり、プロジェクトユーザーがアクセスを許可する必要があります。アクセスは 読み取り専用 です。提供側が共有するテーブルおよび transform の履歴をクエリしたり、行をリアルタイムに購読したりできますが、書き込むことはできません。消費先アプリへの接続はアプリとステージごとにキャッシュされ、インスタンスが停止すると自動的に閉じられます。
接続には、デバイスエージェントが注入するアプリごとの資格情報(APP_AUTH_ID、APP_AUTH_SECRET)が必要です。プラットフォームは、別のアプリの realm ではレガシーなデバイス資格情報 — アプリごとの資格情報を受け取らなかったアプリ(古いデバイスエージェントの下で動作している場合など)が代わりに使うシリアル番号 — を拒否します(NOT_AUTHORIZED。メッセージにその旨が示されます)。ただし、提供側のアプリが同じデバイス上で動作している場合は例外で、その場合は読み取り専用ではなく、そのアプリ自身の完全な権限でその realm に参加します。
アプリが ワイルドカード 許可(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 カタログも提供します。このハンドルの subscribeToTable / subscribe_to_table は、自分のテーブルの場合と同じ TableSubscription ハンドルを返し、ironflock.unsubscribe もこれを受け取ります(unsubscribe / unregister を参照)。また、そのテーブル購読も、再接続後に同じようにキャッチアップします(subscribeToTable を参照)。
ハンドルは、提供側の名前とステージ(app と stage。Go では App と Stage)、および提供側の realm への接続も保持しています:is_connected / isConnected / IsConnected() は接続が開いているかどうかを示し、connection / Connection() は接続そのものです。JavaScript では、close() は接続が閉じた時点で(5 秒以内に)解決されます。
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、オプション | 提供側のステージ:"dev" または "prod"。デフォルトは自分のアプリのステージ |
on_error / onError | callable、オプション | 開いている接続が最終的に拒否された場合(例:許可が後で取り消された場合 — 拒否がいつ最終と見なされるかは下記を参照)に CrossAppAccessError とともに呼び出されます |
アクセスが拒否された、または誤って使用された場合、code フィールド(Go では Code。ironflock.CodeNoGrant などの定数があります)を持つ CrossAppAccessError が送出(Python)/スロー(JavaScript)/返却(Go。errors.As で判定する *ironflock.CrossAppAccessError)されます:NO_GRANT、PROVIDER_NOT_INSTALLED、UNKNOWN_APP、PRIVATE_TABLE、SECRET_COLUMN(提供側がシークレットとしてマークしたカラムに対して — グループの中も含めて — フィルタをかける、またはそのカラムを選択するクエリ)、または NOT_AUTHORIZED(許可が取り消された、またはその時点で検証できなかった場合 — あるいはアプリがレガシーなデバイス資格情報を提示した場合で、その場合はメッセージにその旨が示されます)。提供側のルーターが拒否した(wamp.error.not_authorized、wamp.error.authorization_failed、または wamp.error.authentication_failed)消費先アプリの subscribeToTable / subscribe_to_table も、拒否された読み取りと同様に、NOT_AUTHORIZED を持つ CrossAppAccessError で失敗します。
connectToApp / connect_to_app の待機中に拒否された場合、呼び出しは即座に失敗します。ハンドルが開いた後は、拒否が続いた場合にのみ最終と見なされます — 接続が最後に確立されて以降に 3 回以上の拒否があり(ルーターが認証上の理由でセッションを閉じた場合も、拒否された再接続も、それぞれ 1 回と数えます)、その最初の拒否が 60 秒以上前である場合です。これは、プラットフォームがアクセスを一時的に検証できない間も同じ方法で拒否するためです。それまでの間、ハンドルは再接続を続け、再接続に成功すれば回復します。拒否が最終となると、on_error / onError / OnError が呼び出され、ハンドルは破棄されます:次の connectToApp / connect_to_app / ConnectToApp は新しい接続を開きます。それ以降、そのハンドルの読み取りと購読は、on_error / onError が受け取ったエラーで即座に失敗します — Go では、wamp.ErrStopped をラップしたエラーで失敗します。
Python では
stageとon_errorはキーワード引数です。JavaScript ではオプションオブジェクトを介して渡します(connectToApp(appName, { stage, onError }))。Go では構造体を介して渡します(ConnectToApp(ctx, appName, ironflock.ConnectToAppOptions{Stage, OnError}))。ここでOnErrorはfunc(*ironflock.CrossAppAccessError)です。Go では、ctxが制限するのは呼び出し元の待機だけです:同じアプリに対する並行した呼び出しは 1 回の試行を共有し、Close(ctx)でハンドルを閉じます。Go はアプリ名を大文字・小文字を問わず照合します。また、ConsumedApp.Stageは、ifl.Stage()とは異なり、小文字のステージ("dev"または"prod")です。
listConsumableApps / list_consumable_apps
プロジェクト内の すべての 非プライベートな提供側を一覧します — ワイルドカード 消費許可(data-template.yml 内の consumes: [{ app: "*" }]、プロジェクトユーザーによって許可される)を保持するアプリのための検出プリミティブです。1 回の呼び出しを実行し、接続は 一切 開きません:返されたカタログをピッカーに表示し、必要なものについて 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"]各エントリは 1 つの提供側を表します:
| フィールド | 型 | 説明 |
|---|---|---|
app | str / string | 提供側アプリの名前 |
provider_app_key | int / number | 提供側のアプリキー |
stages | dict / object | ステージごとのカタログ { dev?, prod? }。提供側がそのステージのデータバックエンドを持つ場合にのみ、ステージが含まれます。各カタログは、共有される非プライベートな tables と transforms を保持します |
Go では、各エントリは App、ProviderAppKey、Stages.Dev / Stages.Prod(Tables と Transforms を持つ *StageCatalog。存在しないステージでは nil)を持つ ironflock.ConsumedAppInfo です。Catalog(stage) は名前でカタログを返します。
アプリがワイルドカード許可を保持していない場合、code: NO_GRANT を持つ CrossAppAccessError が送出(Python)/スロー(JavaScript)/返却(Go)されます。
connectToAllApps / connect_to_all_apps
プロジェクト内の すべての 非プライベートな提供側への読み取り専用ハンドルを 1 回の呼び出しで開きます(ワイルドカード消費側のみ)。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、オプション | 提供側のステージ:"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 と同じハンドル型)。アプリがワイルドカード許可を保持していない場合、code: NO_GRANT を持つ CrossAppAccessError が送出(Python)/スロー(JavaScript)/返却(Go)されます。
Python では
stage、on_error、continue_on_errorはキーワード引数です。JavaScript ではオプションオブジェクトを介して渡します(connectToAllApps({ stage, onError, continueOnError }))。Go では構造体を介して渡します(ConnectToAllApps(ctx, ironflock.ConnectToAllAppsOptions{Stage, OnError, StopOnError}))。ここでOnErrorはfunc(error)で、StopOnError: trueはcontinueOnError: falseに相当します。
マネージドファイルストレージ
すべてのアプリのデータバックエンドは、テーブルと並んでプライベートなオブジェクトストレージを備えており、files プロパティ(Go では ifl.Files()。*filestore.FileStore)からアクセスできます。このプロパティは I/O を行わないため、3 つの SDK のいずれでも、接続を開始する前に読み取れます。画像、PDF、カメラフレーム、ファームウェアのバイナリなど、テーブルの行に収めるべきでないものはすべてここに保存します。セットアップは不要です:データテンプレートに files: セクションがないアプリでも、default という名前空間が 1 つ用意されます(独自の名前空間を宣言するテンプレートでは、これが置き換えられます — 名前空間 を参照)。
このセクションはクライアント側のリファレンスです。プラットフォーム側 — 名前空間・クォータ・保持期間の宣言、アプリ間でのファイル共有、アプリごとの S3 資格情報、オンプレミスでの動作 — については、ファイルストレージ を参照してください。
重要なのは、オブジェクトを保存すると 永続的な URL が返され、それをそのままテーブルのカラムに書き込めるという点です。これにより、ダッシュボードのウィジェットは追加の作業なしにそのオブジェクトを表示できます:
Python
from datetime import datetime, timezone
# 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", {
"tsp": datetime.now(timezone.utc).isoformat(),
"part_id": "1",
"photo_url": info.url,
})
# Read it back
data = await ironflock.files.get("part-1.jpg")
# Every object in the folder 2026/, in its subfolders too (pages are fetched for you)
async for obj in ironflock.files.iter(prefix="2026/"):
print(obj.key, obj.size)この URL が期限切れになることはありませんが、公開リンクでは ありません:このデータバックエンドに対する READ 権限を持つ認証済みのリクエスト元だけが読み取ることができ、認証プロキシがリクエストのたびにそれを確認し、その判定を最大 5 分間キャッシュします。したがって、データベースに保存しても安全です。
名前空間
名前空間 は、ポリシー(保持期間、共有ルール、許可されるコンテンツタイプ)を伴うキーのプレフィックスです。個別のバケットではありません。アプリのすべての名前空間は、そのアプリの単一のストレージ領域の中に存在します。宣言するのは、あるオブジェクト群に 異なるルール が必要な場合だけにしてください。それ以外の場合は default のままにして、2026/03/part-1.jpg のようなキーのパスでオブジェクトを整理します。
名前空間は data-template.yml で宣言します。いずれかを宣言すると、暗黙の default は 置き換えられます:default という名前の名前空間も併せて列挙してください。そうしないと、名前空間を指定しないすべての呼び出しが NO_SUCH_NAMESPACE で失敗します。
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:
# Keep default: declaring namespaces replaces the implicit one.
- name: default
- name: frames
description: Raw camera frames, one JPEG per inspected part.
contentTypes: ["image/jpeg"]
maxObjectBytes: 20971520
retention: { deleteAfter: 30 days }この予算は名前空間ごとではなく、アプリ全体に対して一度だけ宣言される点に注意してください。名前空間はアプリの単一のストレージ領域の中にあるキーのプレフィックスにすぎないため、プレフィックスごとの予算を課す対象が存在しません。maxObjectBytes は 名前空間ごと です — これは合計ではなく、単一のオブジェクトの上限を定めます:指定しない場合は 100 MiB、最大 5 GiB です(それを超えると OBJECT_TOO_LARGE)。
以下のすべてのメソッドは名前空間をオプション引数として受け取り、デフォルトは default です。空の名前空間("")も 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、Go では []byte)。url を含むオブジェクトのメタデータを返します |
get(key, namespace?) | オブジェクトの内容を返します。インライン制限を超えるオブジェクトはオブジェクトストレージから直接取得され、それができない場合はルーター経由で範囲ごとに取得されます(ラージオブジェクト を参照) |
put_file(key, path, …) / get_to_file(key, path, …) | Python と Go(PutFile / GetToFile。Go は PutReader で io.Reader から、GetTo で io.Writer へのストリーミングも行えます)。ローカルファイルから保存、またはローカルファイルへ書き出します。ラージオブジェクトのパスではストリーミングされます |
delete(key, namespace?) | オブジェクトを削除します |
copy(key, to, …) | オブジェクトをコピーします。別の名前空間へコピーすることもできます |
move(key, to, …) | コピーしてから削除します。アトミックではありません — サービスに move の動詞がないため、削除に失敗すると両方のコピーが残ります |
put と get はオブジェクト全体をメモリ上に保持します。大きなオブジェクトには、ストリーミングで処理するファイル版のメソッドを使用してください(Go では PutReader と GetTo も使えます)。JavaScript にファイルパス用のヘルパーがないのは、パッケージが Node とブラウザの両方に対して単一のビルドを提供しているためです — ローカルファイルの読み書きは fs で自分で行ってください。
Python では、put_file と get_to_file のファイル処理はイベントループの外で実行されます。get_to_file は存在しない親ディレクトリを作成し、オブジェクトの最初のバイトを受け取った時点でファイルをその場で書き込みます:拒否された読み取り(オブジェクトが存在しない、認可されていない)ではパスに一切手が加えられませんが、途中で失敗した読み取りでは、それまでに書き込まれた内容が残ります。FIFO はオブジェクトのストリーミングに合わせて書き込まれ、FIFO を開く処理は読み手を待つため、誰も読まない FIFO は呼び出しをブロックします(イベントループはブロックしません)。asyncio.wait_for でこれを打ち切ることができます。put_file は FIFO を最後まで読んでから保存します。
Go でも GetToFile は存在しない親ディレクトリを作成しますが、ターゲットの隣にある一時ファイルにダウンロードし、完了した時点でそれをリネームして配置します:失敗またはキャンセルされたダウンロードが不完全なファイルを残すことはなく、以前のファイルはそのまま保持されます。シンボリックリンクはたどられます(リンク先のファイルが書き込まれ、リンクはそのまま残ります)。置き換え後のファイルは、プロセスに可能な範囲で、元のファイルの所有者、グループ、パーミッションビットを引き継ぎます。FIFO やデバイスは、オブジェクトのストリーミングに合わせてその場で書き込まれます。隣に新しいファイルを作成できないターゲット(読み取り専用や疑似ファイルシステム上のものなど)には、自分で開いたファイルに対して GetTo を使用してください。
put が受け取るもの: content_type / contentType(MIME タイプ。名前空間によって許可されるものが制限される場合があります)と namespace。Python ではこれらはキーワード引数です。JavaScript ではオプションオブジェクトに渡します。Go ではオプションです(filestore.ContentType(…)、filestore.Namespace(…))。
一覧取得と検査
| メソッド | 説明 |
|---|---|
list(…) | ファイルサービスの一覧の 1 ページ分で、デフォルトではフォルダ形式です:objects にはキーが / を含まずにプレフィックスに続くオブジェクトが、prefixes にはその下のフォルダが入ります。delimiter に "" を指定するとフラットに一覧します — プレフィックス配下のすべてのオブジェクトを、サブフォルダ内のものも含めて返します。別の limit を渡さない限り、1 ページには最大 200 個のオブジェクトが含まれます。objects、prefixes、is_truncated / isTruncated / IsTruncated、および次のページを取得するために渡す cursor を返します |
iter(…) / iterate(…) | プレフィックス配下の すべての オブジェクトを、サブフォルダ内のものも含めて走査する非同期イテレータで、ページングは自動的に行われます。Python では iter、JavaScript では iterate、Go では Iter(range ループ用の iter.Seq2[*ObjectInfo, error])という名前です |
stat(key, namespace?) | 内容を転送せずに、1 つのオブジェクトのメタデータを取得します |
exists(key, namespace?) | オブジェクトが存在するかどうか |
namespaces() | このアプリが使用できる名前空間 |
usage(…) | アプリがどれだけのストレージを使用しているか — 下記を参照 |
catalog() | 名前空間と、サーバーが発行する制限値およびクォータ。一度取得されてキャッシュされます:catalog(refresh=True) / catalog(true) / RefreshCatalog(ctx) で再取得します |
フォルダ。 / で終わるプレフィックスはフォルダを表します:iter(prefix="2026/") / iterate({ prefix: "2026/" }) / Iter(ctx, filestore.Prefix("2026/")) は 2026/a.txt と 2026/sub/c.txt を返しますが、2026-notes.txt は返しません。list はフォルダを名前で開くことができません。ファイルサービスは / で終わる一覧用プレフィックスを拒否するためです(FleetFiles v0.2 は INTERNAL を返します):プレフィックス 2026 で list を呼び出すと、オブジェクトとして 2026-notes.txt が、プレフィックスとしてフォルダ 2026/ が返されます。サービスはキーを Unicode 正規化形式 C で保存するため、フォルダのプレフィックスも NFC で指定してください。プレフィックスの別の正規化形式の下にあるキーに遭遇したイテレーションは、すべてのキーを読み飛ばすのではなく失敗します。
オブジェクトは、3 つの SDK すべてで同じフィールドで表現され、名前は各言語の命名スタイルに従います:namespace、key、size、etag、content_type / contentType、last_modified / lastModified、checksum_sha256 / checksumSha256、url — Go では filestore.ObjectInfo のフィールド Namespace、Key、Size、ETag、ContentType、LastModified、ChecksumSHA256、URL です。
ストレージ使用量とクォータ
usage は 1 回の呼び出しでオブジェクトストアから直接回答するため、合計値は 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}Go では、フィールドは filestore.StorageUsage の SizeBytes、ObjectCount、QuotaBytes、FreeBytes、PerNamespace です。
| フィールド | 意味 |
|---|---|
size_bytes / sizeBytes | 現在保存されているバイト数 |
object_count / objectCount | 保存されているオブジェクトの数 |
quota_bytes / quotaBytes | 実際に課される 予算。0 は無制限を意味します |
free_bytes / freeBytes | 残りのバイト数。-1 は無制限を意味します — ここで 0 を返すと「いっぱい」と読めてしまうためです |
per_namespace / perNamespace | 名前空間ごとのバイト数。詳細な内訳を要求したときに のみ 含まれます |
名前空間ごとの内訳がデフォルトで無効になっているのは、ストアがそれに直接答えられないためです:ストアはストレージ領域単位で集計しており、名前空間は単なるプレフィックスにすぎないため、SDK が各名前空間を一覧してサイズを足し合わせる必要があります。必要なときに要求してください。ホットパスでは使わないでください。
2 つの異なるクォータが登場するので、区別しておく価値があります。catalog() はその両方を返します:
| フィールド | 意味 |
|---|---|
quota_bytes / quotaBytes | 実際に 課される 値で、オブジェクトストアから読み取られます — プロジェクトユーザーの設定です |
suggested_quota_bytes / suggestedQuotaBytes | アプリのデータテンプレートが要求した値。何も要求していない場合は 0 |
Go では、Catalog(ctx) は QuotaBytes と SuggestedQuotaBytes を持つ *filestore.Catalog を返します。
この 2 つは、ユーザーがアプリの予算を引き上げたり引き下げたりした場合に食い違います。実際に課される値をテンプレートではなくストアから読み取るのはそのためです — アプリを再デプロイしたときに、ユーザーの選択が黙って上書きされてはいけません。UI では両方を表示できます(「アプリの推奨は X ですが、あなたは Y に設定しています」)。実際の制限には常に前者が使われます。ユーザーはいつでも予算を変更でき、catalog() は最後に取得した時点の値を報告します:現在の値は usage() から読み取ってください。
オブジェクトの共有
リンクには 2 種類あり、その違いが重要です:
| メソッド | 有効期間 | 読み取れる相手 |
|---|---|---|
url(key, …) | 永続的 | このデータバックエンドに対する READ 権限を持つ認証済みのリクエスト元のみ — リクエストのたびに確認されます(判定は最大 5 分間キャッシュされます)。テーブルのカラムに保存しても安全です |
share_url / shareUrl / ShareURL | 有効期限あり(デフォルト 15 分、サーバー側で上限が適用されます) | リンクを持っている人なら誰でも。 使用時に認可が再確認されることはありません |
share_url / shareUrl はベアラー型のケーパビリティです:一時的なアクセスが必要な人に渡すためのものであり、データベースに保存しないでください。ダッシュボードが表示するものには url を使用してください。
デプロイに HTTP エッジがない場合(たとえばプレーン HTTP のアプライアンス)、url は None / undefined / ""(Go)を返します — これは get にフォールバックすべきという合図です。share: none と宣言された名前空間でも永続的な URL は機能しませんが、カタログは名前空間の共有方法を報告しないため、url はそれを判別できず、それでも URL を返します。ファイルエッジはその URL に 404 で応答します。そうしたオブジェクトは get で読み取ってください。share_url / shareUrl / ShareURL のリンクは share に左右されません。オブジェクトの etag を version 引数として渡すと(Go では filestore.Version(etag))、ブラウザはレスポンスをイミュータブルなものとしてキャッシュできます。
Python と Go では、cloud_url(key, …) / CloudURL(ctx, key, …) がアプライアンスのクラウドファイルトンネルを経由する永続的な URL(https://i<instanceKey>-files.<cloud-edge>)を返します:アプライアンスがオンラインである間はインターネットから到達でき、認可は url と同じです。クラウドホスト型のデプロイ(url がすでにインターネットから到達可能な場合)、HTTP エッジがない場合、およびデータバックエンドのクラウド共有がオフになっている間は None(Go では "")を返します。ユーザーはクラウド共有をいつでも切り替えられるため、cloud_url / CloudURL はキャッシュされたカタログが 30 秒より古い場合にカタログを読み直します。
upload_url / uploadUrl / UploadURL は、直接アップロードを受け付ける有効期限付きの URL(デフォルトでは 1 時間有効)を発行し、url、method、headers、expires_in / expiresIn を返します(Go では URL、Method、Headers、ExpiresIn を持つ *filestore.UploadTarget)。オブジェクトが発行時のコンテンツタイプで保存されるよう、ヘッダーは返されたとおりに送信してください。名前空間のコンテンツタイプとサイズ上限、宣言された size、およびクォータは、URL の使用時ではなく発行時に確認されます:URL はホストに対してのみ署名されているため、URL を持っている人は誰でも、有効期限が切れるまでそのキーに任意の内容を書き込めます。share_url / shareUrl と同様に、これはベアラー型のケーパビリティです — 書き込みのためのものです。
ラージオブジェクト
SDK は サイズ に応じて転送方法を自動的に選択します — 設定するものは何もありません:
| オブジェクトのサイズ | 転送経路 |
|---|---|
| インライン制限まで(現在は 6 MiB) | メッセージルーターを経由する 1 回の呼び出し |
| それより大きい場合 | ルーターを経由せず、HTTPS でオブジェクトストレージへ直接転送 |
| それより大きく、直接転送のパスがない環境で読み取る場合 | メッセージルーターを経由して範囲ごとに転送 |
正確な制限値は、実行時にサーバーから catalog() の inline_max_bytes / inlineMaxBytes / InlineMaxBytes として報告されるため、SDK のリリースなしに引き上げることができます。
直接転送のパスには、署名付き URL を発行できるデプロイと、ルーターだけでなくオブジェクトストレージのホストにも到達できるデバイスが必要です。そのいずれかが欠けている場合 — そのデプロイでは署名付き URL を発行できないとカタログが示している場合(オンプレミスアプライアンスではデフォルトでそうです)、読み取り用 URL の発行が NOT_SUPPORTED を返した場合、またはバイトが 1 つも届く前にオブジェクトストレージに到達できなかった場合 — get(および Python の get_to_file、Go の GetTo と GetToFile)は代わりにルーター経由でオブジェクトを範囲ごとに読み取ります(FleetFiles v0.1.33 以降)。各範囲の大きさは最大でインライン制限です。各範囲はそれぞれ独立した読み取りです:そのような読み取りの最中にオブジェクトが置き換えられると、読み取りは INTERNAL(“object changed while it was read”)で失敗します。書き込みにはこのようなフォールバックはありません。
書き込みには上限が 2 つ残っており、どちらの場合も、どちらの上限に達したのかを示す理由とともに TOO_LARGE が報告されます:
- 5 GiB — オブジェクトストアの単一アップロードの上限です。マルチパートアップロードはまだ実装されていません。
- 直接エンドポイントがない環境でのインライン制限 — 署名付き URL を発行できないデプロイ(アプライアンスではデフォルトでそうです)では、それより大きなオブジェクトをそもそも保存できません。リトライしても、チャンクを小さくしても解決せず、メッセージにもそのように記載されます。
現場でよく起きる、直接転送のパスの 2 つの失敗には、認可の問題のように見えてしまわないよう、専用のコードが用意されています:PRESIGN_UNREACHABLE(ルーターのみを許可しているプロキシ — 読み取りは範囲ごとの転送にフォールバックし、書き込みは失敗します)と CLOCK_SKEW(オブジェクトストアは 15 分以上ずれたリクエストを拒否します — デバイスの NTP を確認してください)。
ファイルストレージのエラー
ファイルサービスまたは直接転送の失敗は、安定した code と人間が読める reason を持つ FileStoreError を送出(Python)/スロー(JavaScript)/返却(Go)します。分岐は必ず code で行い、reason では決して行わないでください。 ローカルファイルのエラー、キャンセルされた呼び出し、および下記でマッピングされているもの以外のルーターレベルの WAMP エラーは、そのまま送出されます。
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 | 1 回の呼び出しでの転送制限を超えています |
OBJECT_TOO_LARGE | 名前空間自身の maxObjectBytes を超えています |
QUOTA_EXCEEDED | ファイルストアが満杯です |
CONTENT_TYPE_NOT_ALLOWED | 名前空間が contentTypes を制限しています |
INVALID_KEY | 形式が不正なキーまたはプレフィックス(下記参照。FleetFiles v0.2 はまだ INTERNAL として報告します) |
INVALID_RANGE | オブジェクトの範囲外のバイト範囲。SDK がそれを要求することはありません |
NOT_SUPPORTED | バックエンドはこれを実行できません |
NOT_AVAILABLE | このデプロイにはファイルサービスがありません — あるいはまだありません:プラットフォームの再起動後や、ファイルサービスがプロシージャを登録している間です。呼び出しはどこでも実行されていないため、繰り返して構いません |
PRESIGN_UNREACHABLE | オブジェクトストレージに直接到達できません(プロキシが原因かもしれません) |
CLOCK_SKEW | デバイスのクロックのずれが大きすぎます |
INTERNAL | それ以外のすべて — 範囲ごとの読み取り中に変更されたオブジェクトも含みます |
新しいサーバーは、この SDK リリースが知らないコードを導入する場合があります。それらはまとめられることなく code としてそのまま渡されるため、認識できない値は一般的な失敗として扱ってください。
ファイル呼び出しは接続を最大 10 秒待ちますが(start() / Start より前に発行された呼び出しは、その開始も同じ時間内で待ちます)、再試行はされません:テーブル操作とは異なり、プラットフォームの再起動を乗り切ることはないため、再起動中の呼び出しは、ファイルサービスが再登録を終えるまで NOT_AVAILABLE で失敗することがあります。そのような呼び出しはどこでも実行されていないため、アプリから繰り返して構いません。
キー。 キーの正否を判断するのはファイルサービスであり、SDK はキーとプレフィックスを指定されたとおりに送信します。形式が不正なキーまたはプレフィックス — 空のもの、先頭または末尾に / があるもの(iter / iterate のフォルダプレフィックスは除く)、空・.・.. のセグメントや、. で終わるセグメント、空白で始まるか終わるセグメントを含むもの、\ : * ? " < > | ^ ~ のいずれか、制御文字、または Unicode の書式文字を含むもの、900 バイト・20 セグメント・セグメントあたり 255 バイトのいずれかを超えるもの、あるいは予約済みの _sys/ プレフィックスの下にあるもの — は、キーのエラーを分類するファイルサービスでは INVALID_KEY で、FleetFiles v0.2 では INTERNAL で拒否されます。タイムスタンプに注意してください:2026-10-09T12:00:00Z は : を含むため、有効なキーのセグメントではありません。
Python では
FileStoreErrorはironflock.filestoreからインポートします。JavaScript ではパッケージルートからエクスポートされています(import { FileStoreError } from "ironflock")。Go では*filestore.Error(ironflock.FileStoreErrorとしても利用可能)で、コードはfilestore.CodeQuotaExceededなどの定数として提供されています。
デバイス間通信
registerDeviceFunction / register_device_function
同じアプリを(同じステージで)実行している他のデバイスが呼び出せるプロシージャを登録します。ダッシュボードウィジェットのアクションからも呼び出せます。SDK は完全な URI <SWARM_KEY>.<DEVICE_KEY>.<APP_KEY>.<STAGE>.<topic> を自動的に組み立てます。ここで STAGE は、ENV が PROD(大文字・小文字は問いません)の場合は PROD、それ以外の場合は DEV です。SDK は何かを送信する前に、空のトピックと、存在しないか 0 であるデバイスキー、プロジェクトキー、アプリキーを拒否します。callDeviceFunction / call_device_function も同様です。ルーターはアプリからのそれ以外の登録を受け付けず、また単一登録しか受け付けません(アプリが使用できる URI を参照)。接続がない場合、このメソッドは最大で再接続ウィンドウの間(デフォルトは 60 秒。接続の信頼性 を参照)接続を待ちます。登録は再接続のたびに復元されます。このメソッドは、登録のハンドルである Registration を返します:その unregister() で登録を削除できます(unsubscribe / unregister を参照)。
Python
def add(a, b):
return a + b
await ironflock.register_device_function("com.myapp.add", add)
register()はregister_device_function()のエイリアスです。
以前の名前である register_function / registerFunction / RegisterFunction と call_function / callFunction / CallFunction も引き続き使用できますが、非推奨です。
ENVを上書きするアプリ: Python では 1.8.6 まで、JavaScript では 1.8.4 までの SDK バージョンは、ENVの値をそのままステージのセグメントに入れていました。違いが生じるのは、アプリがENVをprodのような別の表記で自ら設定している場合だけです:その場合、これらのバージョンのデバイスと新しいバージョンのデバイスは、互いの関数を呼び出せません。
callDeviceFunction / call_device_function
同じアプリの別のデバイスが登録したプロシージャを呼び出します。SDK はターゲットデバイスのキーを使用して完全な WAMP トピックを自動的に組み立てます:<SWARM_KEY>.<device_key>.<APP_KEY>.<STAGE>.<topic> で、これはターゲットデバイス上で registerDeviceFunction / register_device_function が登録するのと同じ URI です。
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 を使用してリモートプロシージャを呼び出します。アプリは some.full.wamp.topic のような名前を登録できないため、このような名前で到達できるのは、プラットフォームのサービスがアプリのデータ realm に登録したプロシージャ(データバックエンドやファイルストレージのものなど)だけです。アプリの別のデバイスが登録した関数を呼び出すには、callDeviceFunction / call_device_function を使用してください。
Python
result = await ironflock.call("some.full.wamp.topic", args=[42])アプリが使用できる URI
アプリコンテナは、ルーターロール app で自身のデータ realm realm-<SWARM_KEY>-<APP_KEY>-<dev|prod> に参加します。ルーターは、そこでのすべての公開、購読、呼び出し、登録を、そのロールの許可リストと照合します。SDK 自身のメソッドはこの範囲内にとどまります。このルールが問題になるのは、publish、subscribe、call に独自の URI を渡す場合や、connection を直接使用する場合(Go では Connection())です。拒否された操作は wamp.error.not_authorized で失敗します。Python ではメソッドが RuntimeError を送出し、wamp_error_uri(err) がその URI を返します。JavaScript ではエラーは WampError で、その error が URI です。Go では ironflock.WampURI(err) がこれを返します。
| 自分の realm 上の URI | 許可される操作 | 使用するもの |
|---|---|---|
独自の名前(例:com.myapp.status) | 公開、購読(プレフィックス購読も可)、呼び出し — 登録は不可 | publish、subscribe、call |
<SWARM_KEY>.<APP_KEY>.<table>、bulk.<SWARM_KEY>.<APP_KEY>.<table> | 公開 — 購読は不可 | –(テーブルの書き込みトピック。SDK のテーブル書き込みは代わりに追加を行います) |
transformed.<table>、transformed.bulk.<table> | 購読のみ | subscribeToTable / subscribe_to_table |
databackend.errors.…、files.events… | 購読のみ | データバックエンドのエラーイベントとファイルイベント |
<SWARM_KEY>.<DEVICE_KEY>.<APP_KEY>.<STAGE>.<name> | 登録(単一)、呼び出し、公開 — 購読は不可 | registerDeviceFunction / register_device_function、callDeviceFunction / call_device_function |
append.…、appendBulk.…、history.transformed.…、secret.reveal.…、secret.verify.…、files.read.…、files.write.… | 呼び出し(SDK が呼び出します) | テーブル、履歴、シークレット、ファイルの各メソッド |
sys.appaccess.list、sys.appaccess.resolve | 呼び出し | listConsumableApps / list_consumable_apps、connectToApp / connect_to_app、connectToAllApps / connect_to_all_apps |
wamp.session.get | 呼び出し | JavaScript SDK のハートビート |
ID チェックされる名前。 数字で始まる名前 — テーブルへの書き込みトピックとデバイス関数の URI — は、プラットフォームの ID チェックも通過する必要があり、このチェックによって realm のプロジェクトキー(SWARM_KEY)に結び付けられます。登録が受け付けられるのは、自分の realm のプロジェクトキー、アプリキー、ステージを持つ、自分のデバイスの関数 URI に対してだけです。プレフィックス登録やワイルドカード登録は、これら 4 つのセグメントを固定している場合にのみ受け付けられます。公開と呼び出しに必要なのは realm のプロジェクトキーだけです:別のアプリキーを持つテーブルトピックに公開された行は受け付けられますが、どのデータバックエンドにも届かずに失われます。これらの URI の組み立ては SDK に任せてください。
予約済みの名前。 ルーターは、上記の 2 つのアプリ間プロシージャを除く sys. と、wamp.session.get を除く wamp. を拒否します。表にあるその他の名前はプラットフォームのものです:独自の名前を、数字、bulk.、transformed.、databackend.errors.、files.、append.、appendBulk.、history.、secret. で始めないでください。それ以外の先頭セグメントはすべて、アプリ独自のトピック空間です。
生のテーブルトピックは誰も購読しません。 これらのトピックは送信されたとおりの行を運び、シークレットカラムの平文も含みます。データバックエンドがシークレットカラムを暗号化するのは、行を保存するときだけだからです。subscribeToTable / subscribe_to_table は代わりに transformed.<table> から保存済みの行を読み取ります。その行は型付けされ、シークレットカラムはマスクされています。
他のアプリのデータは読み取り専用です。 提供側の realm では、あなたのアプリ(ルーターロール app_reader)は、ハートビートの wamp.session.get のほかには、transformed.… と files.events… の購読、history.transformed.… と files.read.… の呼び出ししかできません — 公開、登録、書き込み、シークレット用のプロシージャはいずれも利用できません。
デバイスメタデータ
setDeviceLocation / set_device_location
デバイスの GPS 位置の更新をプラットフォームに要求します。位置は IronFlock マップにリアルタイムで表示されます。
現在は利用できません: アプリのデータ realm 上でこの呼び出しに応答するプラットフォームサービスはまだないため、呼び出しは
wamp.error.no_such_procedureで失敗します。利用できるようになるまでは、位置を独自のテーブルに保存してください。
Python
await ironflock.set_device_location(long=8.6821, lat=50.1109)| パラメータ | 範囲 |
|---|---|
long | -180 〜 180 |
lat | -90 〜 90 |
位置履歴は保存されません。位置を時系列で追跡するには、専用テーブルを作成して
publish_to_table/publishToTableを使用してください。
getRemoteAccessUrlForPort
アプリの port-template.yml で宣言されたポートが、そのトンネルがアクティブになったときに到達可能となる URL を返します。
Python
url = ironflock.getRemoteAccessUrlForPort(8080)
# "https://<device_key>-<app_name>-8080.app.ironflock.com"
vnc = ironflock.getRemoteAccessUrlForPort(5900, protocol="tcp")
# "tcp://app.ironflock.com:<the tunnel's remote port>"| パラメータ | 型 | 説明 |
|---|---|---|
port | int / number | port-template.yml で宣言したポート |
protocol | str / string、オプション | ポートのプロトコル:"http"(デフォルト)、"https"、"tcp" または "udp" で、大文字・小文字は問いません。前後の空白は無視され、空の場合は "http" になります |
remote_port_environment / remotePortEnvironment | str / string、オプション | tcp/udp のみ:REMOTE_PORT_FOR_<port> の代わりにリモートポートを保持する変数 — テンプレートにあるそのポート自身の remote_port_environment で、0.19.8 より前のデバイスエージェントではこれが必要です |
http と https のポートは、デバイスエージェントが注入するトンネルドメイン(TUNNEL_DOMAIN)上で、トンネルラベル <device_key>-<app name, lower case>-<port>(https ポートの場合はプラットフォームの secure- プレフィックス付き)の下で提供され、常に https:// URL になります。オンプレミスアプライアンスに属するデバイス(エージェントが INSTANCE_KEY を注入します)では、代わりにクラウド経由で転送されるルート https://i<INSTANCE_KEY>-<label>.<CLOUD_TUNNEL_DOMAIN> が返されます。
tcp または udp の URL には、トンネルが割り当てたパブリックポートが含まれます。このポートは、デバイスエージェントが REMOTE_PORT_FOR_<port> として通知します(エージェント 0.19.8 以降):<protocol>://<TUNNEL_DOMAIN>:<remote port>。アプライアンスのデバイスでは、エージェントが通知している間はインターネット側のポート REMOTE_PORT_FOR_<port>_CLOUD が優先されます。それが現れる前、およびエージェントがそれを削除した後(クラウド転送がオフになった場合)は、アプライアンスローカルの URL が返されます。ポートが 0 の場合は、まだ割り当てられていません。
ポートの値は /data/env からその都度読み取られるため、結果をキャッシュせずにメソッドを再度呼び出してください。この URL では対応できないケースもあります:
- デバイスエージェントがトンネルするのは、本番(PROD)インストールのポートだけです。開発(DEV)コンテナでは、http(s) の URL はこのデバイス上にある同じアプリの PROD インストールの URL になり、tcp/udp では URL は得られません。DEV アプリには、代わりにデバイスの LAN アドレス(
DEVICE_LAN_IP、DEVICE_PORT_FOR_<port>)でアクセスしてください。 - ドメインなしでインストールされたアプライアンス(プレーンモード)のエッジデバイスでは、エージェントが
TUNNEL_DOMAINをlocalhostに設定するため、そこで組み立てられる URL は localhost を指します:このようなアプライアンスはアプリのポートを自身のホストポートで提供しますが、それはトンネル URL では表現できません。 - 未設定または空のトンネルドメインは、デフォルトで
app.ironflock.comになります。現在、プラットフォームはCLOUD_TUNNEL_DOMAINを設定しません:アプリが環境設定でこの変数を自ら設定しない限り、アプライアンスの URL はapp.ironflock.comを使用します。 - root 以外のユーザーとして実行されるコンテナでは、値はコンテナの起動時のものになります(環境変数 を参照)。
URL を組み立てられない場合、メソッドは None / null(Go では "" と false)を返します:デバイスキーまたはアプリ名がない場合、tcp/udp ポートのリモートポートがまだ通知されていない(または 0 である)場合、プロトコルが不明な場合、またはラベルが有効な DNS ラベルではない場合(63 文字を超える、またはアプリ名にドットが含まれる)です。ブラウザには環境がないため、http(s) の URL は deviceKey と appName オプションから app.ironflock.com 上に組み立てられます。
接続プロパティとライフサイクル
Python
| プロパティ | 型 | 説明 |
|---|---|---|
is_connected | bool | プラットフォームへの接続がアクティブかどうか |
connection | CrossbarConnection | 基盤となる接続インスタンス(上級者向け) |
files | FileStore | アプリのマネージドファイルストレージ(マネージドファイルストレージ を参照) |
| メソッド | 説明 |
|---|---|
run() | 接続を開始して mainFunc を実行し(ブロッキング)、その後、接続を停止します |
await start() | 接続を非同期で開始します。それより前に発行された操作は、それぞれの待機時間の範囲内でその開始を待ちます(接続の信頼性 を参照) |
await stop() | 接続と消費先アプリへの接続を停止し、メインタスク(mainFunc)をキャンセルします。接続はそれぞれの realm から離脱し、その後は再接続しません。start() で新たに接続します。接続または start() をまだ待っているすべての操作は即座に失敗し、まだ待機中の start() や connect_to_app も同様に失敗します。stop() の後に発行された操作は、次の start() を待ちます(接続の信頼性 を参照) |
await run_async() | 接続を開始し、非同期で維持します |
run() と run_async() は、mainFunc が戻るか例外を送出するとすぐに接続を停止します。送出された例外は、停止の後にあらためて送出されます。そのため、購読やデバイス関数の登録だけを行う mainFunc は、たとえば await asyncio.Event().wait() で実行を続ける必要があります。mainFunc は非同期関数(async def)でなければなりません。mainFunc を指定しない場合、run() と run_async() はプロセスが終了するまで接続を開いたままにします。
接続の信頼性
SDK は自動的に再接続します。ソケットが切断されると、ルーターが復帰するまで再試行し、その後すべてのサブスクリプションと登録済みのデバイス関数を復元します。再接続後に購読し直す必要はなく、受け取ったハンドルも有効なままです:各ハンドルの active は、それが現在ルーター上で有効かどうかを示します(unsubscribe / unregister を参照)。テーブルの購読では、接続が切れている間に保存された行も配信されます(subscribeToTable を参照)。一方、独自のトピックのメッセージは保存されないため、その間に公開されたものは失われます。いずれかを復元できなかった場合でも残りは復元され、失敗したものはセッションが続く間再試行されます — 1 秒後に始まり、待ち時間は 2 倍ずつ最大 30 秒まで延び、各待ち時間は最大 15 % の範囲で変動します — そして再接続のたびにも再試行されます:ルーターは以前にそれを受け付けていたので、その拒否は一時的なものです。たとえば、デバイス関数に対するプラットフォームの ID チェックが一瞬フェイルクローズした場合などです。最初の失敗は警告としてログに記録され、復元に成功するとその旨もログに記録されます。
テーブル操作 — テーブル書き込み、履歴・系列・シークレットの読み取り、および消費先アプリの履歴・系列の読み取り — も、ルーターやプラットフォームの更新などによるプラットフォームの再起動を乗り切ります。これらの操作は、接続が戻るのを最大 reconnect_window(Python、秒単位)/ reconnectWindowMs(JavaScript)/ WithReconnectWindow(Go、time.Duration)の間(デフォルトは 60 秒)待ちます。start() / Start より前に発行された操作は、同じウィンドウの範囲内でその開始を待ちます。また、データバックエンドがまだプロシージャを提供していない間は再試行します:ルーターが wamp.error.no_such_procedure を返す場合や、データバックエンドが自身の登録より先に届いた呼び出しを “client has no handler for registration” で拒否する場合です。このような呼び出しはハンドラに到達していないため、再試行によって行が二重に書き込まれることはありません。それ以外のエラーは発生したとおりに送出されます。テーブル購読のキャッチアップの読み取りも、これらの読み取りの 1 つです。
テーブル書き込みには、publishToTable / publish_to_table、publishRowsToTable / publish_rows_to_table、reportError / report_error も含まれます:これらは appendToTable や appendRowsToTable と同じ追加プロシージャを呼び出すため、データバックエンドの再起動中に書き込まれた行が失われることはありません。
publish、subscribe、subscribeToTable / subscribe_to_table(消費先アプリのものも含む)、registerDeviceFunction / register_device_function も、再接続ウィンドウの範囲内で接続を待つため、短い切断で失敗することはありません。これらは再試行されません。サブスクリプションや登録は、その後の再接続のたびに復元されます。
その他のすべての操作 — call、callDeviceFunction / call_device_function、setDeviceLocation / set_device_location、ファイル呼び出し、およびアプリ間の検索と、接続を開く処理 — は、接続を最大 10 秒待ち、再試行されません。start() / Start より前に発行された操作は、その 10 秒の範囲内でその開始を待ちます。待機時間内に接続が得られなかった操作は、待機の秒数を示すエラーで失敗します:Python では、IronFlock のメソッドは原因(cause)が TimeoutError である RuntimeError を送出します。JavaScript ではプレーンな Error、Go では wamp.ErrNotConnected をラップしたエラーです。unsubscribe と unregister は、まったく待ちません:接続が切れている間も含め、即座に反映されます(unsubscribe / unregister を参照)。
0 はウィンドウを無効にします:その場合、これらの操作は他のすべての操作と同じように動作します。接続を 10 秒間待ち、再試行されないため、プラットフォームの再起動直後のキャッチアップも再試行されません。負のウィンドウ、および Python と JavaScript では NaN や無限大も、IronFlock オブジェクトの作成時に拒否されます(コンストラクタオプション を参照)。
送信済みのリクエストは、そのセッションが閉じると(ルーターがセッションを閉じた場合、リンクが切断された場合、または stop() が呼び出された場合)、応答を待たずに即座に失敗し、再試行されません:すでにハンドラに到達している可能性があるためです。Python では、エラーの原因(cause)は、クローズ理由(例:wamp.close.system_shutdown)を持つ autobahn の ApplicationError、またはリンクが切断された場合は TransportLost です。JavaScript では、エラーは WampError で、その error がクローズ理由です:ルーターが閉じた場合はルーターの理由、stop() の後は wamp.close.goodbye_and_out、リンクが切断された場合は wamp.close.transport_lost です。Go では、このようなリクエストは、wamp.ErrNotConnected(Stop の後は wamp.ErrStopped)をラップしたエラーで失敗します。
特殊なケースは、ルーターには到達できるものの、アプリ用の realm がまだ存在しない場合です。同時インストール時の競合により、データバックエンドが realm をプロビジョニングする前にアプリコンテナが起動するため、最初の接続は wamp.error.no_such_realm で拒否されます。SDK は数秒ごとに再試行し、realm が作成された瞬間にアプリは立ち上がります。1 分経っても realm が存在しない場合、それはおそらく今後も作成されません(アプリが削除されたか、このステージ用のデータバックエンドがない)。そのため SDK はそれ以降、ルーターを永遠に毎秒叩き続けるのではなく、2 分に 1 回の試行に切り替えます。realm が現れるか、ルーター自体が落ちて復帰すれば、すぐに元の速いペースに戻ります。
stop() / Stop は、あらゆる待機をすぐに終わらせます。接続を — あるいは start() / Start より前に発行された場合はその開始を — まだ待っている操作は、SDK の停止エラーで失敗します。2 回の再試行の間で待機しているテーブル呼び出しも同様です:Python では IronFlock の接続が停止されたことを示す ConnectionError(IronFlock のメソッドは、これを原因とする RuntimeError を送出します)、JavaScript では ConnectionStoppedError、Go では wamp.ErrStopped をラップしたエラーです。realm への最初の参加をまだ待っている start()(たとえば、realm がまだ存在しない場合)は、Connection to realm <realm> stopped before it opened で失敗します。Go では、Start は wamp.ErrStopped をラップしたエラーを返します。提供側への接続をまだ開いている途中の connect_to_app / connectToApp は Connection to app '<app>' (<stage>) aborted by stop() で失敗します。それでも接続が realm に参加した場合は、その接続を閉じたうえで Connection to app '<app>' (<stage>) closed by stop() で失敗します。Go では、ConnectToApp は ironflock: connection to app '<app>' (<stage>) aborted by Stop(または closed by Stop)で失敗します。stop() と競合した拒否のエラーは、CrossAppAccessError のままです。Python と JavaScript では、stop() から戻った時点で接続を試みているものは何も残っておらず、離脱に応答しないルーターとの接続は 5 秒後に切断されます。Go の Stop は ctx の範囲内で離脱を待ち、その後ソケットを閉じます。Run は 10 秒の猶予を与えます。
Python と JavaScript では、インスタンスを stop() の後に再び開始できます:start() で新たに接続し、stop() の後に発行された操作は、最初の start() より前に発行された操作と同様に、次の start() を待ちます。Go の Stop は最終的です:その後に発行された操作は、即座に wamp.ErrStopped で失敗します。
より厄介なのは、接続が何も知らせずに死ぬケースです。アイドル状態の接続を NAT やプロキシが両端に通知せずに切断したり、ロードバランサーの背後でルーターが再起動したりする場合です。切断を知らせるものは何も届きません。データを publish するだけのアプリは次の書き込み失敗で気づきますが、購読するだけのアプリはそもそも書き込みを行わないため、何もしなければコンテナが再起動するまで停止したままになります。
3 つの SDK すべてがこれを検出します。検出はデフォルトで有効で、設定は不要です。
Python
Python SDK は WebSocket キープアライブを使用します。リンクがアイドル状態のあいだ 30 秒ごとにルーターへ ping を送り、10 秒以内に pong が返ることを期待します。返らなければ接続を破棄して再接続します。受信トラフィックも生存の証拠とみなされるため、通信中の接続が探索されることはありません。ping が送られるのは、リンクが実際に静かになったときだけです。
connectToApp/connect_to_appで開いたアプリ間接続は通常は購読専用であり、まさにこの仕組みが守る対象です。これらは自動的に対象となり、ルーターに拒否された後も、拒否が続くまでは再接続します(connectToAppを参照)。
エラー処理
失敗した SDK のメソッドは — 引数が不正な場合、接続が失われた場合、プラットフォームから拒否された場合 — 操作名・トピック・理由を示すメッセージとともに例外を送出(Python)、リジェクト(JavaScript)、またはエラーを返却(Go)します。そのため、処理を継続させたい呼び出しは try ブロックで囲んでください — Go では、返されたエラーを確認してください。ただし、コードに届かない失敗もあります。独自のトピックに公開されたメッセージは、その間停止している購読者には届きません。再接続後のキャッチアップの読み取りの失敗は、ログに記録されるだけです(subscribeToTable を参照)。
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 のサブクラスで、加えて WAMP エラー URI を error に、エラーペイロードを args / kwargs に保持します。次の 3 種類の呼び出しは異なる形で失敗します:プロシージャが登録されていない履歴・系列・reveal・verify の呼び出しは、その旨と考えられる原因を示すプレーンな Error で失敗し、しかも wamp.error.no_such_procedure は再試行されるため、失敗するのは再接続ウィンドウ全体が経過した後です。ファイル呼び出しは FileStoreError で失敗します(ファイルストレージのエラー を参照)。拒否されたアプリ間の呼び出しは、消費先アプリの subscribeToTable も含めて、CrossAppAccessError で失敗します。接続を待っている間に stop() によって終了させられた操作は、エクスポートされている Error のサブクラスである ConnectionStoppedError で失敗します。それ以外(パラメータの不正、未接続)はプレーンな Error です。
Go では、失敗した操作は *ironflock.OperationError を返します。そのメッセージは操作と原因を示し、原因をラップしています。errors.Is と errors.As はこれを透過して判定します。ルーターまたは呼び出し先による拒否は、URI、Args、Kwargs を持つ *ironflock.WampError(*wamp.Error)です。ironflock.WampURI(err) は URI を返し、なければ "" を返します。その他のセンチネルは ironflock.ErrMissingConfig(操作に必要な ID がない)、ironflock.ErrAlreadyStarted、wamp.ErrStopped(Stop の後の操作、または Stop によって中断された操作)です。
移行時の注意: 以前のバージョンの Python および JavaScript SDK は、呼び出しが失敗するとメッセージをログに出力して
None/nullを返していました。現在は代わりに例外を送出するため、if result is None:のような形のコードでは失敗を検出できません。try/except(またはtry/catch)を使用してください。
データバックエンドによる拒否
データバックエンドが拒否した操作は、以下のいずれかの WAMP エラー URI で失敗し、その最初の引数が理由です。3 つの SDK すべてが、これらを定数としてエクスポートしています。Go では Go のスタイルで命名されています(ironflock.URIRateLimited、ironflock.URIStorageFull、…)。Python では、IronFlock のメソッドは autobahn の ApplicationError を原因(cause)とする RuntimeError を送出し(消費先アプリの読み取りは ApplicationError そのものを送出します)、wamp_error_uri(err) はどちらからでも URI を読み取ります。JavaScript では、エラーは WampError で、その error が URI です。Go では、*ironflock.WampError を包む *ironflock.OperationError で、ironflock.WampURI(err) が URI を読み取ります。SDK はこれらのいずれも再試行しません。公開メソッドを含め、すべてのテーブル書き込みがこれらを報告します:公開メソッドも追加を行うためです。
Python
from datetime import datetime, timezone
from ironflock import URI_STORAGE_FULL, wamp_error_uri
row = {"tsp": datetime.now(timezone.utc).isoformat(), "temperature": 22.5}
try:
await ironflock.append_to_table("sensordata", row)
except RuntimeError as err:
if wamp_error_uri(err) == URI_STORAGE_FULL:
pending.append(row) # the appliance's disk is nearly full: append the row later
else:
raise| 定数 | 拒否される内容 |
|---|---|
URI_RATE_LIMITED | デバイス上のアプリ資格情報ごとに 1 分あたり 30 回を超える revealSecrets / reveal_secrets、120 回を超える verifySecret / verify_secret の呼び出し |
URI_RESULT_TOO_LARGE | 8 MiB のメッセージ予算を超える単一の行を含む読み取り、または 1 GiB の上限を超える結果(その他の大きな結果はチャンクに分割されて届き、SDK が再構成します)。キーワード引数で最も大きなカラムが示されます:limit を下げる、timeRange を狭める、columns を減らす、または columnPaths で絞り込んでください |
URI_INVALID_LIMIT | limit が 3000 を超える transform の読み取り |
URI_INVALID_TIME_RANGE、URI_INVALID_METRIC、URI_INVALID_GROUP_BY、URI_SERIES_TOO_MANY_GROUPS | テーブルが応答できない系列クエリ:開始値がない、または終了値が開始値より後でない。テーブルが集約できないメトリクス(存在しないカラム、シークレットカラム、カラムの型が受け付けないメソッド、COUNT 以外のメソッドを指定した tsp)。グループ化できない groupBy カラム。50,000 行を超える結果 |
URI_SECRET_COLUMN、URI_NOT_A_SECRET_COLUMN | シークレットカラムに対するフィルタ。シークレットではないカラムに対する verifySecret / verify_secret。シークレットカラムを持たないテーブルには、reveal 関数も verify 関数もありません(revealSecrets を参照) |
URI_STORAGE_FULL | アプライアンスのディスクがほぼいっぱいの状態での追加(空き容量が約 3 GiB 未満。4 GiB 以上で再び受け付けられます) — 一時的なものです:行は後で追加してください |
URI_STORAGE_OVERUSAGE | アカウントのストレージ割り当てを超える追加。すぐに再試行しても解決しません |
URI_ENTITY_KEY_CONFLICT | エンティティキー(maintainLatestFlagFor)と tsp が、バッチ内または保存済みの別の行とすでに同じである行:すべての行にそれぞれ独自の tsp を与えてください |
URI_SECRET_SENTINEL_UNRESOLVABLE、URI_SECRET_CIPHERTEXT_REJECTED | シークレットの以前の値を維持する(__secret__)書き込みで、その値を見つけるためのエンティティキーがないもの。すでに暗号化された値の書き込み |
データバックエンドが理由なしに拒否した追加 — tsp のない行に対するデータバックエンドの応答である wamp.error.runtime_error — は、型付きの拒否ではありません:SDK のエラーメッセージは、欠落している tsp と、データバックエンドが原因を記録するアプリの error-logs テーブルを指し示します。
ブラウザでの使用(JavaScript のみ)
JavaScript SDK はモダンブラウザで動作します。ブラウザには環境変数がないため、すべての設定をコンストラクタで渡してください:
import { IronFlock } from "ironflock";
const ironflock = new IronFlock({
serialNumber: "device-serial-from-server",
deviceKey: "12",
appName: "MyWebApp",
swarmKey: 10,
appKey: 20,
env: "PROD",
});
await ironflock.start();
await ironflock.publishToTable("sensordata", [{ tsp: new Date().toISOString(), temperature: 22 }]);資格情報をハードコードする代わりに、IronFlock.fromServer() を使用してバックエンドから設定を取得できます:
const ironflock = await IronFlock.fromServer("/api/ironflock-config");
await ironflock.start();バックエンドのエンドポイントは、接続オプション(serialNumber、deviceKey、appName、swarmKey、appKey、env)を含む JSON オブジェクトを返す必要があります。
AI エージェント関数の登録
SDK は AI エージェントから呼び出し可能な関数を登録できます。プロシージャを登録し、そのトピックを 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 エージェントはこの関数を呼び出すことができます。
登録した 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 / RegisterDeviceFunction に渡した名前と一致している必要があります。IronFlock は、関数が登録されているデバイスに自動的に呼び出しをルーティングします。
ai-template.yml の完全なリファレンスについては、エージェントとツールの定義を参照してください。
環境変数
以下の変数は、アプリコンテナ内で IronFlock ランタイムによって自動的に設定されます:
| 変数 | 説明 |
|---|---|
DEVICE_NAME | デバイスの表示名 |
DEVICE_SERIAL_NUMBER | 一意で不変のデバイス識別子 |
DEVICE_KEY | デバイスの数値キー。SDK はこれをもとにデバイス関数の URI とリモートアクセス用トンネルのラベルを組み立て、公開やテーブル書き込みとともにメタデータとして送信します。認証には使用しません |
SWARM_KEY | プロジェクト識別子 |
APP_KEY | アプリ識別子 |
APP_NAME | アプリ名 |
ENV | 環境:DEV または PROD |
APP_AUTH_ID、APP_AUTH_SECRET | SDK がルーターに提示する、アプリ自身の資格情報。アプリ間アクセスにはこれが必要です。これがない場合(古いデバイスエージェント)、SDK はデバイスのシリアル番号にフォールバックします |
DEVICE_ENDPOINT_URL | SDK が接続するルーターの URL(パスは /ws-ua-usr に置き換えられます)。RESWARM_URL より優先されます。オプションとして渡したルーターの URL(ironFlockUrl、Go では WithURL)は、その両方より優先されます |
RESWARM_URL | スタジオの URL。DEVICE_ENDPOINT_URL が設定されていない場合、SDK はここからルーターの URL を導出します。SDK が知らないスタジオの URL を指定すると、start() / Start は失敗します。どちらの変数もない場合、SDK は wss://cbw.ironflock.com/ws-ua-usr に接続します |
Python
import os
device_name = os.environ.get("DEVICE_NAME")
serial = os.environ.get("DEVICE_SERIAL_NUMBER")
project_key = os.environ.get("SWARM_KEY")リモートアクセス用の変数(TUNNEL_DOMAIN、DEVICE_LAN_IP、DEVICE_PORT_FOR_<port>、REMOTE_PORT_FOR_<port>、…)は、ポートの設定 に記載されています。
デバイスエージェントはこれらの値を /data/env にも 1 つずつ <NAME>.txt ファイルとしてミラーし、アプリの実行中はそのミラーを最新の状態に保ちます。SDK は、変更が実行中のアプリに届くよう、2 種類の値をまずミラーのファイルから読み取ります。1 つはアプリごとの資格情報(APP_AUTH_ID、APP_AUTH_SECRET)で、接続を試みるたびに読み直されるため、アプリの実行中にエージェントがローテーションした資格情報は、次の再接続で提示されます。もう 1 つはリモートポート(REMOTE_PORT_FOR_<port> と REMOTE_PORT_FOR_<port>_CLOUD)で、getRemoteAccessUrlForPort を呼び出すたびに読み取られます。それ以外のすべての値 — ID(DEVICE_KEY、DEVICE_NAME、…)、ENV、トンネルのドメイン — は、コンテナの起動時の環境から取得されます(Go は ID と ENV を New で 1 回だけ読み取ります)。古いデバイスエージェントでは /data/env を root だけが読み取れます:その場合、別のユーザーとして実行されるコンテナでは、SDK はコンテナの起動時の値を参照し、警告を 1 回ログに記録します。JavaScript でミラーを読み取るには、Node.js 22.3 以降が必要です。それより古い Node.js では、環境変数だけが参照されます。