Skip to Content
IoT-App-EntwicklungIronFlock SDK

IronFlock SDK

Das IronFlock SDK ermöglicht Ihren Edge-Anwendungen die Interaktion mit der IronFlock-Plattform. Es gibt es in drei Sprachen — Python, JavaScript und Go — mit demselben Funktionsumfang, unten nebeneinander dokumentiert. Es übernimmt die Authentifizierung automatisch, wenn es auf einem registrierten Gerät ausgeführt wird, und bietet Funktionen zum Veröffentlichen von Daten, Abfragen von Verlaufsdaten, Aufrufen von Remote-Prozeduren über Geräte hinweg sowie zum Aktualisieren von Gerätemetadaten.

SDKPaketVoraussetzung
Pythonironflock auf PyPIPython 3.11+
JavaScriptironflock auf npmNode.js 22+ oder moderner Browser
Gogithub.com/ironflock/sdk-goGo 1.27.2+

Diese Seite ist der Leitfaden zu den drei SDKs. Die vollständige Referenz jedes SDK ist sein README auf GitHub: IronFlock/sdk-py, IronFlock/sdk-js und IronFlock/sdk-go.

Installation

pip install ironflock

Oder fügen Sie ironflock zur requirements.txt Ihrer App hinzu.

Schnellstart

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

Bei Verwendung innerhalb eines IronFlock-App-Containers liest das SDK die Verbindungsdaten automatisch aus der Umgebung — keine manuelle Konfiguration erforderlich. Jede Zeile, die Sie in eine Tabelle schreiben, trägt einen tsp, ihren Zeitstempel (siehe publishToTable).

Konstruktor-Optionen

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) )
ParameterBeschreibung
mainFuncEine asynchrone Funktion (async def), die ausgeführt wird, sobald die Verbindung hergestellt ist. Wenn sie zurückkehrt oder eine Exception auslöst, stoppen run() und run_async() die Verbindung (siehe Verbindungseigenschaften & Lebenszyklus)
ironFlockUrlDie WebSocket-URL des Routers, z. B. wss://cbw.ironflock.com/ws-ua-usr. Ohne diesen Parameter entnimmt das SDK die URL der Umgebung (siehe Umgebungsvariablen)
serial_numberÜberschreibt die Geräte-Seriennummer, die mit Veröffentlichungen und Tabellenschreibvorgängen gesendet wird. Standardmäßig wird die Umgebungsvariable DEVICE_SERIAL_NUMBER verwendet. Die Identität, mit der sich die Verbindung authentifiziert, ändert sie nicht, solange der Geräte-Agent Zugangsdaten pro App injiziert
reconnect_windowSekunden, die die Tabellenoperationen, publish, subscribe und register_device_function nach einem Neustart auf die Rückkehr der Plattform warten, bevor sie einen Fehler auslösen. Standardwert ist 60; 0 schaltet dies ab (siehe Verbindungszuverlässigkeit). IronFlock() löst bei einem negativen Wert, NaN oder einem unendlichen Wert einen ValueError aus und bei einem Wert, der keine Zahl ist, einen TypeError

Daten veröffentlichen

publishToTable / publish_to_table

Schreibt einen Datensatz in eine Fleet-Tabelle. Der Tabellenname muss einer in der data-template.yml Ihrer App definierten Tabelle entsprechen. Das SDK leitet die Daten automatisch an die richtige Projektdatenbank weiter.

Die Methode entspricht appendToTable / append_to_table: Sie ruft die Append-Prozedur der Tabelle auf und überbrückt daher einen Plattform-Neustart — sie wartet längstens für die Dauer des Neuverbindungsfensters auf die Verbindung und versucht es erneut, solange das Daten-Backend die Tabelle noch nicht bereitstellt —, und sie löst einen Fehler aus (liefert ein abgelehntes Promise, gibt einen Fehler zurück), wenn das Daten-Backend die Zeile ablehnt. Während die Plattform neu startet, geht keine Zeile verloren.

Bis einschließlich Version 1.9.1 veröffentlichten die drei SDKs die Zeile stattdessen im Schreib-Topic der Tabelle: Eine Zeile, die veröffentlicht wurde, während das Daten-Backend neu startete, ging verloren, und eine abgelehnte Zeile wurde ohne Fehler verworfen. Jetzt lässt eine solche Zeile den Aufruf fehlschlagen — etwa eine Zeile ohne tsp oder eine Zeile für eine Tabelle, die im Data Template fehlt (nach Ablauf des Neuverbindungsfensters).

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

Das Daten-Backend liest die Zeile aus dem ersten Argument: einem Dict in Python, dem ersten Element des Arrays in JavaScript, dem ersten positionalen Argument in Go (einer Map oder einem Struct). Zusätzliche Keyword-Argumente — in Go ein ironflock.Kwargs-Wert unter den Argumenten — werden nur für Spalten gelesen, die das Data Template ihnen zuordnet (kwargs.<name>).

Jede Zeile braucht einen tsp. Jede Tabelle hat die Pflichtspalte tsp, den Zeitstempel der Zeile — z. B. einen ISO-8601-String oder in Go ein time.Time, das als RFC 3339 in UTC gesendet wird —, den das Daten-Backend aus der Zeile liest, sofern das Data Template ihn nicht anderweitig zuordnet. Eine Zeile ohne tsp lehnt es ab, wie unter appendToTable / append_to_table beschrieben.

Teilzeilen in Entitätstabellen. In einer Tabelle mit Entitätsschlüssel (maintainLatestFlagFor) genügt eine Zeile mit dem Entitätsschlüssel, tsp und den Spalten, die sich ändern. Eine Spalte, die die Zeile weglässt, behält den vorherigen Wert der Entität; eine als None / null / nil gesendete Spalte wird geleert. Ob ein Feld als weggelassen gilt, hängt davon ab, wie Sie die Zeile aufbauen:

  • Python: Ein Schlüssel mit dem Wert None wird als null gesendet und leert die Spalte. Entfernen Sie den Schlüssel aus dem Dict, um den alten Wert zu behalten.
  • JavaScript: Eine Eigenschaft mit dem Wert undefined fällt aus dem Payload heraus und behält daher den alten Wert. Setzen Sie sie auf null, um die Spalte zu leeren.
  • Go: Ein Struct-Feld mit omitempty oder omitzero im Tag wird weggelassen, wenn es leer ist, und behält daher den alten Wert. Sonst wird ein nil-Pointer-Feld, wie ein nil-Wert in einer ironflock.Row, als null gesendet und leert die Spalte.

Das gilt für alle vier Schreibmethoden. In einer Tabelle ohne Entitätsschlüssel wird eine Spalte, die die Zeile weglässt, als NULL gespeichert.

appendToTable / append_to_table

Fügt Daten über einen Remote Procedure Call an eine Fleet-Tabelle an: Eine abgelehnte Zeile führt zu einem Fehler. publishToTable / publish_to_table tut dasselbe; der einzige Unterschied ist der Name, den die Fehlermeldung der Operation gibt.

result = await ironflock.append_to_table("sensordata", { "tsp": datetime.now(timezone.utc).isoformat(), "temperature": 22.5, "humidity": 60 })

Die Zeile braucht einen tsp, wie bei publishToTable. Eine Zeile ohne tsp lehnt das Daten-Backend mit wamp.error.runtime_error und ohne Begründung ab; der Fehler des SDK ergänzt dann “the data backend refused the row without a reason — a missing tsp is the usual cause; see the error-logs table”. Die typisierten Ablehnungen, etwa eine volle Festplatte auf einer Appliance, sind unter Ablehnungen durch das Daten-Backend aufgeführt.

publishRowsToTable / publish_rows_to_table

Schreibt mehrere Zeilen in einer einzigen Nachricht (Bulk-Insert) in eine Fleet-Tabelle. Die Plattform fügt den gesamten Stapel atomar (Alles-oder-nichts) in einem einzigen Vorgang ein. Verwenden Sie dies für hochfrequente Daten, bei denen ein Roundtrip pro Zeile zu kostspielig wäre. Die Methode entspricht appendRowsToTable / append_rows_to_table: Sie ruft die Bulk-Append-Prozedur auf, überbrückt wie publishToTable einen Plattform-Neustart und schlägt fehl, wenn das Daten-Backend den Stapel ablehnt.

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}, ])

Das zweite Argument ist eine nicht leere Liste von einzufügenden Zeilenobjekten, jedes mit einem eigenen tsp — in Go ein Slice von Zeilen ([]ironflock.Row, []map[string]any oder ein Slice von Structs). Ein Stapel, den das Daten-Backend ablehnt — etwa weil einer Zeile ihr tsp fehlt —, lässt den Aufruf fehlschlagen, und keine seiner Zeilen wird gespeichert. (Bis einschließlich Version 1.9.1 veröffentlichten die SDKs den Stapel im Bulk-Topic der Tabelle, wo ein abgelehnter Stapel ohne Fehler verworfen wurde.)

appendRowsToTable / append_rows_to_table

Hängt mehrere Zeilen in einem einzigen Remote Procedure Call (Bulk-Insert) an eine Fleet-Tabelle an. Die Plattform fügt den gesamten Stapel atomar (Alles-oder-nichts) ein: Ist eine Zeile ungültig, wird der gesamte Stapel abgelehnt und nichts wird gespeichert. publishRowsToTable / publish_rows_to_table tut dasselbe. Jede Zeile braucht einen eigenen tsp; die Ablehnungen sind die von appendToTable, einschließlich des Hinweises für einen ohne Begründung abgelehnten Stapel.

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

Meldet einen Anwendungsfehler in die error-logs-Tabelle Ihrer Fleet. Dies ist ein komfortabler Wrapper über publishToTable / appendToTable: Er versieht die Zeile mit source: "app", einem Schweregrad level und einem Zeitstempel und schreibt sie anschließend wie jede normale Tabellenzeile. Der Fehler landet in derselben error-logs-Tabelle, die auch fleetdb-Systemfehler verwenden (gekennzeichnet mit source: "system"), und ist somit mit getHistory abfragbar, mit subscribeToTable / subscribe_to_table streambar, in Board-Templates nutzbar und wird in Echtzeit auf transformed.error-logs zugestellt — ohne den System-Fehler-Toast der Plattform auszulösen.

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

Die Zeile ist { tsp, msg, user_message, source: "app", level }: msg enthält den technischen Text (einen Traceback oder Stack), user_message die Zeile, die der Fehler-Toast des Boards anzeigt — die von Ihnen übergebene Benutzermeldung oder, ohne eine solche, die Meldung selbst.

Parameter:

ParameterTypBeschreibung
errorstr / string oder Exception / ErrorDie Fehlermeldung oder eine Exception, deren Traceback/Stack (oder Meldung) aufgezeichnet wird
levelstr / string, optionalSchweregrad: "error", "warning", "info" oder "debug" — die Typen, die das Toast-Widget des Boards anzeigt. Standardwert ist "error". "warn", die Schreibweise von SDK 1.9.1 und älter, wird als "warning" gesendet
appendbool / boolean, optionalAus Kompatibilitätsgründen beibehalten: Die Zeile wird in jedem Fall per Append geschrieben, da publishToTable und appendToTable beide per Append schreiben. Es bestimmt nur, welche der beiden Methoden die Fehlermeldung nennt
tspstr / string, optionalÜberschreibt den Zeitstempel im ISO-8601-Format. Standardwert ist die aktuelle Zeit
user_message / userMessagestr / string, optionalText für den Bediener, gespeichert als user_message. Standardwert ist die aufgezeichnete Meldung

In Python werden die Optionen als Keyword-Argumente übergeben (report_error(error, level=..., append=..., tsp=..., user_message=...)); in JavaScript werden sie über ein Optionsobjekt übergeben (reportError(error, { level, append, tsp, userMessage })); in Go über ein Struct (ReportError(ctx, errOrMsg, ironflock.ReportErrorOptions{Level, Append, Tsp, UserMessage})), wobei errOrMsg ein error oder ein String ist, Level einer von ironflock.LevelError, LevelWarning, LevelInfo, LevelDebug, und das zurückgegebene *Result ist das Ergebnis des Inserts.

publish

Veröffentlicht eine Nachricht an ein eigenes WAMP-Topic. Verwenden Sie dies für benutzerdefinierte Nachrichten oder Ereignisse, die keiner Datenbanktabelle zugeordnet sind. Der Router reserviert einige Namen für die Plattform — siehe URIs, die eine App verwenden darf.

Wie die Tabellenschreibvorgänge fügt publish den Keyword-Argumenten die Gerätemetadaten hinzu: DEVICE_SERIAL_NUMBER, DEVICE_KEY und DEVICE_NAME. Ein eigenes Keyword-Argument mit einem dieser Namen hat Vorrang.

Besteht keine Verbindung, wartet publish längstens für die Dauer des Neuverbindungsfensters auf eine (standardmäßig 60 Sekunden, siehe Verbindungszuverlässigkeit), sodass ein Publish eine kurze Unterbrechung überbrückt. Die Nachricht wird dann genau einmal gesendet und nie wiederholt. Der Router bestätigt auch eine Nachricht, die niemand abonniert hat, daher verpasst sie ein Abonnent, der in der Zwischenzeit nicht läuft.

await ironflock.publish("com.myapp.alerts", { "level": "warning", "message": "Temperature threshold exceeded" })

Historische Daten abfragen

getHistory

Ruft historische Daten aus einer Fleet-Tabelle oder einem Transform ab: die neuesten passenden Zeilen, aufsteigend nach tsp sortiert. Unterstützt Filterung, Zeitbereiche und Paginierung.

# 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"], })

Abfrageparameter:

FeldTypBeschreibung
limitint / numberMaximale Anzahl zurückgegebener Zeilen (1–10.000, erforderlich; 1–3.000 bei einem Transform)
offsetint / numberOffset für Paginierung
timeRangelist / array[start, end] — ISO-Datetime-Strings oder Epoch-Millisekunden-Zahlen, nie gemischt; None / null für ein offenes Ende. Ein Legacy-Objekt {"start": ..., "end": ...} wird in das Paar umgewandelt
filterAndlist / arrayUND-verknüpfte Filtereinträge: Bedingungen, Gruppen und/oder der latest-Marker (siehe unten)
columnslist / arrayZurückzugebende Spalten (optional). tsp, device_key und authid sind immer enthalten; weglassen für alle Spalten
columnPathslist / arrayBeschneidet json-Spalten (optional): JSON-Pfade in der Notation, die auch Filter verwenden ("json_data.a.b", "json_data['key']"). Eine in columns aufgeführte json-Spalte enthält dann nur die Vereinigung ihrer Pfade; eine Spalte ohne Pfad hier kommt vollständig zurück. Wird von Transforms und von Daten-Backends vor fleetdb v1.0.52 ignoriert

Wenn Sie Abfrageparameter übergeben, ist limit erforderlich; ohne Parameter (nil in Go) liest getHistory die 10 neuesten Zeilen.

In Go sind dies die Felder Limit, Offset, TimeRange, FilterAnd, Columns und ColumnPaths von ironflock.TableQueryParams. TimeRange ist ein *ironflock.TimeRange{Start, End}: jede Grenze ein time.Time, ein ISO-8601-String, eine Zahl in Epoch-Millisekunden oder nil für ein offenes Ende (ironflock.Between, Since und Until bilden einen aus time.Time-Werten). FilterAnd ist ein []ironflock.Filter. Eine veraltete Objektform gibt es nicht.

Filteroperatoren: =, !=, <>, >, <, >=, <=, LIKE, ILIKE, NOT LIKE, NOT ILIKE, IN, NOT IN, IS NULL, IS NOT NULL. JavaScript exportiert diese Liste als SQL_OPERATORS; in Go liefert ironflock.SQLOperators() sie.

Jede Filterbedingung ist ein Objekt mit den Schlüsseln column, operator und value — in Go gebildet mit ironflock.Where(column, operator, value), ironflock.IsNull(column) und ironflock.IsNotNull(column). Operatoren werden unabhängig von Groß- und Kleinschreibung erkannt, umgebende Leerzeichen werden ignoriert, und gesendet werden sie in der oben aufgeführten Form — der einzigen, die das Daten-Backend akzeptiert —, sodass "like" als "LIKE" hinausgeht, auch innerhalb von Gruppen. Ein Operator außerhalb der Liste wird unverändert und mit einer Warnung gesendet. IS NULL und IS NOT NULL erwarten keinen value und sind die einzige Möglichkeit, nach NULL zu fragen: Jeder andere Operator schließt eine Zeile aus, deren Spalte NULL ist, genau wie in SQL. IN und NOT IN erwarten eine Liste (in Go ein Slice). LIKE unterscheidet Groß- und Kleinschreibung; verwenden Sie ILIKE für einen Vergleich ohne Beachtung der Groß- und Kleinschreibung.

Mit OR kombinieren. Die Einträge von filterAnd werden UND-verknüpft. Ein Gruppeneintrag verknüpft seine eigenen Einträge — Bedingungen, den latest-Marker oder weitere Gruppen — mit einem einzigen Operator, "AND" oder "OR":

# 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}, ], } ]

Ein Daten-Backend, das noch keine Gruppen kennt (fleetdb vor v1.2.0), lehnt diese Form ab, statt nur einen Teil davon anzuwenden; der Aufruf schlägt also lautstark fehl, statt stillschweigend Zeilen zurückzugeben, die der Filter ausschließt.

Aktuelle Werte lesen. Ein Eintrag {"latest": true} in filterAnd (ironflock.Latest() in Go) ist keine Filterbedingung, sondern ein Modusschalter: Das Daten-Backend gibt nur die neueste Zeile pro Entität zurück, in SQL abgeleitet aus dem Entitätsschlüssel, den die Tabelle mit maintainLatestFlagFor deklariert. Eine Tabelle ohne Entitätsschlüssel gibt ihre einzelne aktuellste Zeile zurück.

Andere Bedingungen lassen sich wie erwartet mit dem Marker kombinieren: Bedingungen auf Spalten des Entitätsschlüssels schränken ein, welche Entitäten zurückgegeben werden, während alle übrigen Bedingungen und timeRange auf die resultierenden neuesten Zeilen angewendet werden. Die Kombination von {"latest": true} mit einem deleted-Filter blendet gelöschte Entitäten also aus, statt deren vorherige Zeile wieder auftauchen zu lassen.

Frühere Versionen von IronFlock speicherten eine physische Spalte latest_flag. Diese existiert nicht mehr — ein alter Filter latest_flag = true wird weiterhin akzeptiert und als Marker behandelt, neuer Code sollte aber {"latest": true} verwenden. Der latest-Marker steht in getSeriesHistory nicht zur Verfügung.

Zeitbereiche. Das Daten-Backend liest eine Zeitgrenze als String mit JavaScripts Date; schreiben Sie daher ein ISO-8601-Datum oder eine ISO-8601-Datums- und Uhrzeitangabe (2026-07-01, 2026-07-01T12:30:15.250Z) und trennen Sie Datum und Uhrzeit mit T. Die SDKs lehnen ein Leerzeichen zwischen Datum und Uhrzeit in den Jahren 0–99 ab, weil Date es falsch liest: "0001-01-01 00:00:00" — was Pythons str(datetime.min) liefert — würde als 2001 gelesen. Das Python-SDK lehnt außerdem einen String ab, den datetime.fromisoformat nicht lesen kann. Eine Uhrzeit ohne Zeitzone wird als UTC gelesen. Eine numerische Grenze ist in Epoch-Millisekunden angegeben und muss endlich sein: NaN und ±Infinity kämen beim Daten-Backend als offene Grenze an und ließen den Zeitfilter stillschweigend wegfallen, daher lehnen die SDKs sie vor dem Senden ab. In Go wird eine time.Time-Grenze als RFC 3339 in UTC gesendet, und eine String-Grenze muss eine der ISO-8601-Formen sein, die Date korrekt liest — ein Datum oder ein Datum mit Uhrzeit und optionalem Z oder UTC-Offset (+02:00 oder +0200); andere Strings, etwa das Basisformat 20260701T123000Z, ein Offset nur in Stunden oder ein Tag, den der Monat nicht hat (2026-02-30), schlagen mit ironflock.ErrInvalidArgument fehl, bevor etwas gesendet wird.

Transforms. Ein Transform (eine SQL-View eines Data Templates, siehe Transformationstabellen) wird anders gelesen als eine Tabelle. Das Daten-Backend wendet nur limit — höchstens 3000, ein größerer Wert wird mit URI_INVALID_LIMIT abgelehnt —, offset und filterAnd an. Es ignoriert timeRange, columns, columnPaths und den latest-Marker und verwirft einen Filter auf eine Spalte, die der Transform nicht deklariert (ein Transform hat keine impliziten Spalten tsp, device_key oder authid), wodurch das Ergebnis größer wird. Seine Zeilen kommen in der eigenen Reihenfolge der View, umgekehrt: Die neuesten Zeilen, die ältesten zuerst, erhalten Sie nur, wenn die View die neuesten zuerst sortiert. Begrenzen Sie den Zeitbereich eines Transforms in seinem SQL.

Große Ergebnisse. Ein Ergebnis, das für eine Nachricht des Daten-Backends zu groß ist (8 MiB JSON), kommt in Chunks an, die das SDK im Speicher wieder zusammensetzt: Sie erhalten die Zeilen eines einzigen Lesevorgangs in derselben Reihenfolge. Chunks, die nicht aufgehen — einer fehlt oder ist doppelt, oder die Zeilenzahl weicht von der Zusammenfassung des Daten-Backends ab —, lassen den Lesevorgang fehlschlagen, statt einen Teil der Zeilen zurückzugeben. Eine einzelne Zeile über 8 MiB oder ein Ergebnis über der 1-GiB-Schutzgrenze des Daten-Backends wird mit URI_RESULT_TOO_LARGE abgelehnt: Senken Sie limit, verengen Sie timeRange, wählen Sie weniger columns aus oder beschneiden Sie mit columnPaths.

getSeriesHistory / get_series_history

Ruft heruntergerechnete Zeitreihendaten aus einer Fleet-Tabelle ab: Spalten, die pro Zeitfenster aggregiert werden (z. B. stündliche Durchschnittswerte). Ideal für Diagramme über lange Zeiträume. Für Tabellen verfügbar (nicht für Transforms), auf Daten-Backends ab fleetdb v1.0.58.

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"

Abfrageparameter:

FeldTypBeschreibung
metricslist / arrayDie pro Zeitfenster berechneten Aggregate, mindestens eines: {"ref": ..., "method": ...}. ref ist eine Spalte oder ein JSON-Pfad in eine json-Spalte ("json_data.temp"); method ist "AVG", "SUM", "COUNT", "MIN", "MAX", "FIRST" oder "LAST". Jede Metrik kommt unter "<METHOD>:<ref>" zurück, was in 63 Bytes passen muss
bucketMsint / numberBreite eines Zeitfensters in ganzen Millisekunden, mindestens 1000 (optional). Ohne Angabe wird der Zeitbereich in limit Zeitfenster aufgeteilt
limitint / numberDas Budget an Zeitfenstern (1–10.000) — kein Zeilenlimit
timeRangelist / array[start, end] — ISO-Datetime-Strings oder Epoch-ms-Zahlen. Erforderlich, mit einem Start; None / null als Ende bedeutet „jetzt“. Das Ende muss nach dem Start liegen
groupBylist / arraySpalten, die jedes Zeitfenster in eine Zeile pro Gruppe aufteilen (optional)
filterAndlist / arrayUND-verknüpfte Bedingungen und Gruppen, wie bei getHistory (optional). Der latest-Marker wird hier abgelehnt, auch innerhalb einer Gruppe; verwenden Sie getHistory, um aktuelle Werte zu lesen

Zeilen. Jede Zeile ist ein Zeitfenster — mit groupBy das Zeitfenster einer Gruppe. tsp ist der Beginn des Zeitfensters in Epoch-Millisekunden, jede groupBy-Spalte kommt unter ihrem eigenen Namen zurück und jede Metrik unter "<METHOD>:<ref>" (SeriesMetric.column in Python, seriesColumn(metric) in JavaScript, SeriesMetric.Column() in Go), z. B. row["AVG:temperature"]. Die Zeilen sind aufsteigend nach tsp sortiert; ein Zeitfenster ohne Zeilen fehlt (keine Lückenfüllung). AVG und SUM nehmen numerische Spalten, MIN und MAX auch Strings und Zeitstempel; FIRST und LAST liefern den Wert der frühesten bzw. spätesten Zeile des Zeitfensters; COUNT zählt die Werte, die nicht NULL sind (bei tsp: die Zeilen). Ein großes Ergebnis wird wie bei getHistory aus Chunks zusammengesetzt.

Zeitfenster und ein offenes Ende. limit zählt Zeitfenster, nicht Zeilen. Ohne bucketMs wird der Zeitbereich in limit Zeitfenster aufgeteilt. Mit bucketMs verbreitert das Daten-Backend die Zeitfenster auf ein Vielfaches von bucketMs, wenn der Bereich mehr als limit davon enthält — und ein offenes Ende reicht bis zum eigenen Jetzt des Daten-Backends, ein wenig nach Ihrem. Die letzten 24 Stunden mit offenem Ende enthalten 25 einstündige Zeitfenster, sodass limit: 24 zweistündige liefert. Begrenzen Sie das Ende wie im Beispiel oder lassen Sie ein Zeitfenster mehr zu: Mit bucketMs und offenem Ende können limit + 1 Zeitfenster zurückkommen.

Die SDKs prüfen vor dem Senden, was das Daten-Backend ablehnen würde: mindestens eine Metrik, nicht leere Refs, bekannte Methoden, Spaltennamen innerhalb von 63 Bytes, einen Start, ein Ende nach dem Start und bucketMs als ganze Zahl von mindestens 1000 (in Go Bucket von mindestens 1 Sekunde, in ganzen Millisekunden). Was es nicht beantworten kann, lehnt das Daten-Backend mit URI_INVALID_TIME_RANGE, URI_INVALID_METRIC, URI_INVALID_GROUP_BY oder URI_SERIES_TOO_MANY_GROUPS (mehr als 50.000 Zeilen) ab — siehe Ablehnungen durch das Daten-Backend.

Veraltete Form. SDK-Versionen bis 1.9.0 sendeten metrics als Spaltennamen plus ein method auf oberster Ebene ({"metrics": ["temperature"], "method": "AVG", ...}), was Daten-Backends seit fleetdb v1.0.58 ablehnen. Diese Form funktioniert weiterhin: Das SDK sendet eine {ref, method}-Metrik pro Spalte, warnt (eine DeprecationWarning in Python, eine Konsolenwarnung einmal pro Prozess in JavaScript) und benennt die Ergebnisspalten zurück in die Spaltennamen, sodass alter Code weiterhin row["temperature"] liest — es sei denn, die Zeile hat bereits eine Spalte dieses Namens, etwa tsp bei COUNT von tsp. Steigen Sie auf {ref, method}-Metriken um und lesen Sie row["AVG:temperature"]. Das Go-SDK kennt nur die aktuelle Form: eine Methode pro Metrik (SeriesMetric).

Daten abonnieren

subscribeToTable / subscribe_to_table

Abonniert Echtzeit-Updates einer Fleet-Tabelle. Der Handler wird immer dann aufgerufen, wenn das Daten-Backend eine neue Zeile speichert, und erhält als erstes Argument die Zeile so, wie sie gespeichert wurde — Werte typisiert gemäß den Spalten des Data Templates, geheime Spalten maskiert. Zeilen, die über den Bulk-Insert-Pfad (publishRowsToTable / appendRowsToTable) geschrieben werden, werden Ihrem Handler einzeln zugestellt, sodass der Handler-Code unabhängig davon, wie die Daten geschrieben wurden, gleich bleibt.

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 gibt eine TableSubscription zurück, das Handle für beide Feeds; deren unsubscribe() entfernt beide und beendet die unten beschriebene Nachlieferung (siehe unsubscribe / unregister).

Zeilen, die gespeichert wurden, während die Verbindung unterbrochen war, kommen nach der Neuverbindung an. Der Router spielt nicht erneut ab, was veröffentlicht wurde, während die Verbindung unterbrochen war; daher liest das SDK nach jeder Neuverbindung die in der Zwischenzeit gespeicherten Zeilen und stellt sie Ihrem Handler zu, die ältesten zuerst, vor den Zeilen, die live eintreffen: jede Zeile, deren tsp nach dem Zeitstempel der neuesten Zeile liegt, die Ihr Handler erhalten hat. Solange Ihr Handler noch keine Zeile erhalten hat, ist der Ausgangspunkt die neueste Zeile, die beim Abonnieren gespeichert war: Das SDK liest sie direkt nach dem Abonnieren, und schlägt dieser Lesevorgang fehl, gilt der Zeitpunkt des Abonnierens als Ausgangspunkt (nach der Uhr des Geräts); eine Tabelle, die damals leer war, wird von Anfang an gelesen. Eine in der Zwischenzeit geschriebene Zeile mit einem älteren tsp (etwa von einem Gerät, das gepufferte Messwerte sendet) wird nicht nachgeliefert. Eine Nachlieferung liefert höchstens die neuesten 10.000 Zeilen und protokolliert eine Warnung, wenn sie so viele gelesen hat. Der Lesevorgang der Nachlieferung wartet wie jeder Verlaufslesevorgang auf das Daten-Backend, längstens für die Dauer des Neuverbindungsfensters, und die Live-Zeilen warten auf ihn; schlägt er fehl, werden die in der Zwischenzeit gespeicherten Zeilen nicht zugestellt, und eine Warnung wird protokolliert. Nachgelieferte Zeilen stammen aus einem Lesevorgang, nicht aus Ereignissen, daher erhält der Handler jede einzelne ohne Ereignisdetails: in Python nur die Zeile, in JavaScript ([row], {}), in Go ein Event mit Topic und Args, das der Reihe nach über die DeliveryGroup der Subscription zugestellt wird. Die Nachlieferung ist ab Version 1.10.0 Teil des SDK.

Ein Transform hat keine eigenen Zeilen: Bei jedem Takt seines Zeitplans veröffentlicht das Daten-Backend die gesamte View — höchstens 3000 Zeilen, in umgekehrter Reihenfolge der View — als ein Ereignis, dessen erstes Argument die Liste der Zeilen ist. Ein Transform braucht keine Nachlieferung: Sein nächster Takt nach einer Neuverbindung veröffentlicht wieder die gesamte View. event_rows(args[0]) in Python, eventRows(args) in JavaScript und ev.Rows() in Go lesen beide Formen: Die Zeile einer Tabelle ergibt [row], die Liste eines Transforms die Liste ([] bei einer leeren View), alles andere None / undefined / nil. In Go ist ev.Row() beim Ereignis eines Transforms nil.

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

Abonniert ein eigenes WAMP-Topic für benutzerdefinierte Echtzeit-Kommunikation. Für Tabellenzeilen verwenden Sie subscribeToTable / subscribe_to_table: Der Router lehnt Subscriptions auf die rohen Schreib-Topics der Tabellen ab (siehe URIs, die eine App verwenden darf).

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 gibt eine Subscription zurück, das Handle der Subscription (siehe unsubscribe / unregister). Jeder Aufruf gibt ein eigenes Handle zurück, auch für ein Topic, das bereits abonniert ist.

Besteht keine Verbindung, wartet subscribe wie publish längstens für die Dauer des Neuverbindungsfensters auf eine (standardmäßig 60 Sekunden), und ebenso subscribeToTable / subscribe_to_table (siehe Verbindungszuverlässigkeit).

Das Topic wird exakt abgeglichen. Subscribe-Optionen, ein optionales Argument nach dem Handler, können das ändern: Ein match von "prefix" abonniert jedes Topic, das mit dem angegebenen beginnt. In Python sind die Optionen die SubscribeOptions von autobahn (SubscribeOptions(match="prefix"), aus autobahn.wamp.types), in JavaScript ein Objekt ({ match: "prefix" }) und in Go ironflock.SubscribeOptions{Match: "prefix"}. Die Subscription wird nach jeder Neuverbindung mit denselben Optionen wiederhergestellt. subscribeToTable / subscribe_to_table nimmt dieselben Optionen entgegen und abonniert damit beide Feeds.

unsubscribe / unregister

subscribe, subscribeToTable / subscribe_to_table und registerDeviceFunction / register_device_function geben jeweils ein Handle zurück. Ein Handle bleibt über Neuverbindungen hinweg gültig, bis Sie es entfernen: Das SDK stellt nach jeder Neuverbindung wieder her, wofür es steht.

HandleZurückgegeben vonElemente
Subscriptionsubscribeunsubscribe(), topic, active
TableSubscriptionsubscribeToTable / subscribe_to_table Ihrer eigenen App und einer konsumierten Appunsubscribe(), table, active
RegistrationregisterDeviceFunction / register_device_function und ihre Aliaseunregister(), procedure, active

topic, table und procedure benennen, wofür das Handle steht; procedure ist die vollständige WAMP-URI. active gibt an, ob die Subscription oder Registrierung gerade beim Router aktiv ist: Der Wert ist false, solange die Verbindung unterbrochen ist, nach einer fehlgeschlagenen Wiederherstellung, bis ein erneuter Versuch gelingt, und sobald das Handle entfernt ist. Eine TableSubscription ist aktiv, solange es beide ihrer Feeds sind. ironflock.unsubscribe(handle) nimmt eine Subscription oder eine TableSubscription entgegen und ironflock.unregister(registration) eine Registration; jede tut dasselbe wie die eigene Methode des Handles.

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() und unregister() wirken sofort, beim Aufruf, und geben ein Awaitable zurück: Warten Sie darauf, um zu wissen, dass der Router geantwortet hat; eine Ablehnung wird dort ausgelöst. Ein Handler, der eine gewöhnliche Funktion ist und nicht async, kann subscription.unsubscribe() aufrufen, ohne darauf zu warten. Die drei Handle-Klassen werden von ironflock exportiert.

Für das Entfernen gelten in allen drei SDKs dieselben Regeln:

  • Es wirkt sofort. Der Handler wird für kein weiteres Ereignis mehr aufgerufen: nicht für eines, das noch in der Warteschlange steht, nicht für die restlichen Zeilen eines Bulk-Inserts, nicht für die Zeilen, die eine Nachlieferung zustellen würde. Eine entfernte Funktion wird nicht mehr aufgerufen; ein Aufruf, den der Router noch an sie weiterleitet, bevor er die Registrierung verworfen hat, wird mit wamp.error.no_such_procedure abgelehnt. Auf einen bereits laufenden Aufruf wird nicht gewartet, daher darf ein Handler seine eigene Subscription entfernen. Was Sie entfernen, wird nach einer Neuverbindung nie wiederhergestellt.
  • Es wartet nicht auf eine Verbindung. Solange die Verbindung unterbrochen ist, wird die Subscription oder Registrierung lokal entfernt, weil der Router sie mit der Session verworfen hat, und der Aufruf kehrt sofort zurück. Solange die Verbindung besteht, wird der Router benachrichtigt, und der Aufruf ist abgeschlossen, sobald der Router geantwortet hat. Eine Ablehnung wird ausgelöst (Python, ein RuntimeError), geworfen (JavaScript, ein WampError) oder zurückgegeben (Go), das Entfernen bleibt aber trotzdem bestehen.
  • Es ist idempotent. Ein zweiter Aufruf bewirkt nichts, ebenso ein Aufruf ohne Handle (None, null oder undefined, nil). Nach stop() / Stop entfernt ein Aufruf die Subscription oder Registrierung nur lokal: In Python und JavaScript stellt das nächste start() sie dann nicht wieder her.
  • Handles sind unabhängig. Das Entfernen eines Handles entfernt nie den Handler eines anderen, auch nicht auf demselben Topic oder derselben Tabelle. Die Subscription beim Router endet mit dem letzten Handle ihres Topics.
  • Eine TableSubscription entfernt beide Feeds, transformed.<table> und transformed.bulk.<table>, und beendet ihre Nachlieferung.

Migration: Vor Version 1.10.0 gaben das Python- und das JavaScript-SDK die Subscription- und Registrierungsobjekte von autobahn zurück, und subscribe_to_table / subscribeToTable gab nur die Subscription des Zeilen-Feeds zurück, sodass deren Entfernen den Bulk-Feed abonniert ließ. Code, der unsubscribe() oder unregister() auf dem zurückgegebenen Wert aufruft, funktioniert weiterhin, und was er entfernt, wird nach einer Neuverbindung nicht mehr wiederhergestellt. Andere Elemente der autobahn-Objekte, etwa id, gibt es nicht mehr.

Geheime Spalten

Eine Spalte, die in der data-template.yml der App mit secret: true deklariert ist, wird verschlüsselt gespeichert. Kein gewöhnlicher Lesepfad gibt den Klartext zurück, nicht einmal an die App, die ihn geschrieben hat:

LesepfadWie eine geheime Spalte aussieht
getHistory, subscribeToTable / subscribe_to_table und die Lesevorgänge einer konsumierten App (die Verlaufslesevorgänge und Echtzeit-Zeilen, die auch Boards nutzen)der Platzhalter __secret__, exportiert als SECRET_PLACEHOLDER (ironflock.SecretPlaceholder in Go)
SQL (die SQL-Abfragekonsole, Datenexporte und der Postgres-Login FleetDB Access) und Datenbank-Backupsdas gespeicherte Chiffrat, ifsec:1:…
revealSecrets / reveal_secretsder entschlüsselte Klartext

NULL wird auf jedem Pfad als NULL durchgereicht, sodass „gesetzt, aber verborgen“ von „nie geschrieben“ unterscheidbar bleibt.

Auf einer Entitätstabelle ist Read-Modify-Write sicher: Eine Zeile erneut zu veröffentlichen, die noch den Platzhalter __secret__ trägt — oder die geheime Spalte ganz wegzulassen — behält das gespeicherte Geheimnis, statt es zu überschreiben, und ein explizites null löscht es. Die genauen Regeln finden Sie unter Geheime Spalten im Daten-Backend-Leitfaden.

Nur eine string-Spalte kann geheim sein, und weder tsp noch ein Entitätsschlüssel kann es sein. Das Daten-Backend verschlüsselt jeden Wert mit AES-256-GCM und einem eigenen zufälligen Initialisierungsvektor (IV), sodass dasselbe Geheimnis in jeder Zeile als anderes Chiffrat gespeichert wird: Eine geheime Spalte lässt sich weder filtern noch gruppieren noch sortieren. Ein Filter auf eine solche Spalte wird mit URI_SECRET_COLUMN abgelehnt; um einen Wert zu prüfen, verwenden Sie verifySecret.

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__"}]

Beide nachfolgenden Funktionen sind ausschließlich aus den eigenen Containern der App heraus aufrufbar. Der Message Router verweigert sie jeder Browser-Rolle und allen konsumierenden Apps — deshalb hat ein ConsumedApp-Handle auch keine Entsprechung dazu: Die geheimen Spalten einer fremden App kommen geschwärzt an und bleiben es auch.

revealSecrets / reveal_secrets

Liest Zeilen einer Ihrer eigenen Tabellen mit entschlüsselten geheimen Spalten.

rows = await ironflock.reveal_secrets("credentials", { "limit": 1, "filterAnd": [{"column": "device_id", "operator": "=", "value": "d-1"}], }) # [{"tsp": "...", "device_id": "d-1", "api_token": "hunter2"}]

Die Abfrageparameter sind dieselben wie bei getHistory, außer dass limit 100 nicht überschreiten darf. Diese Obergrenze verhindert, dass ein einzelner Aufruf eine ganze Tabelle entschlüsselt, und das Daten-Backend weist einen größeren Wert zurück, statt ihn stillschweigend zu kappen. Ohne Abfrageparameter liest der Aufruf die 10 neuesten Zeilen. Anders als getHistory gibt er die neuesten Zeilen zuerst zurück.

Das Daten-Backend beantwortet höchstens 30 Reveal-Aufrufe pro Minute und App-Zugangsdaten auf einem Gerät; darüber hinaus schlägt der Aufruf mit URI_RATE_LIMITED fehl. Lesen Sie ein Geheimnis einmal entschlüsselt aus und behalten Sie es, statt es bei jeder Verwendung erneut abzurufen. Ein Filter auf eine geheime Spalte wird mit URI_SECRET_COLUMN abgelehnt. Das Daten-Backend stellt diese Funktion nur für Tabellen bereit, die eine geheime Spalte deklarieren: Für jede andere Tabelle antwortet der Router mit wamp.error.no_such_procedure, worauf das SDK den Aufruf für die Dauer des Neuverbindungsfensters wiederholt; danach schlägt der Aufruf mit einem Fehler fehl, der besagt, dass die Prozedur nicht registriert ist. Der Fehler nennt die möglichen Ursachen, die wahrscheinlichste zuerst: Die Tabelle deklariert keine geheime Spalte (secret: true im Data Template), die Tabelle ist nicht deklariert, oder das Daten-Backend ist älter als die Unterstützung geheimer Spalten.

verifySecret / verify_secret

Prüft einen Kandidatenwert gegen eine geheime Spalte, ohne sie preiszugeben — das Passwortprüfungs-Gegenstück zur Reveal-Funktion. Der Vergleich läuft innerhalb des Daten-Backends, in konstanter Zeit, und zurück kommt nur die Antwort.

ok = await ironflock.verify_secret( "credentials", "api_token", submitted_token, {"limit": 1, "filterAnd": [{"column": "device_id", "operator": "=", "value": "d-1"}]}, ) if ok: ...

Parameter:

ParameterTypBeschreibung
tablenamestr / stringDie Tabelle, gegen die geprüft wird
columnstr / stringDie zu vergleichende geheime Spalte
candidatestr / stringDer zu prüfende Klartext. Wird serverseitig verglichen und niemals in die Tabelle geschrieben
query_params / queryParamsSecretQueryParamsWelche Zeilen teilnehmen. Standardmäßig die neueste Zeile; limit darf 100 nicht überschreiten

In Go sind die Parameter positionale Strings nach dem Kontext, und die Abfrage ist ein *ironflock.TableQueryParams (nil für die neueste Zeile).

JavaScript gibt { match, checked } zurück und Go ein *ironflock.SecretVerifyResult mit Match und Checked; Python gibt einen einfachen bool zurück. Wo Ihnen checked zur Verfügung steht, lohnt es sich, den Wert zu nutzen: Er trennt einen falschen Kandidaten (checked > 0) von einem Selektor, auf den überhaupt keine Zeile passte (checked === 0) — zwei sehr unterschiedliche Fehler, die einem Benutzer auch unterschiedlich gemeldet werden sollten.

Da jede Zeile unter einem eigenen Zufallswert verschlüsselt wird, speichern zwei Zeilen mit demselben Geheimnis unterschiedliche Chiffrate. Genau deshalb kann ein =-Filter auf einer geheimen Spalte nicht funktionieren — das Daten-Backend lehnt jeden Filter auf einer solchen Spalte mit URI_SECRET_COLUMN ab —, und genau deshalb gibt es diese Funktion.

Das Daten-Backend beantwortet höchstens 120 Prüfungen pro Minute und App-Zugangsdaten auf einem Gerät; darüber hinaus schlägt der Aufruf mit URI_RATE_LIMITED fehl. Für eine Prüfung bei jeder Anfrage (etwa eines API-Schlüssels) speichern Sie ein positives Ergebnis kurz zwischen. Eine Spalte, die keine geheime Spalte ist, wird mit URI_NOT_A_SECRET_COLUMN abgelehnt; auf einer Tabelle ganz ohne geheime Spalte schlägt der Aufruf so fehl wie dort revealSecrets / reveal_secrets.

App-übergreifender Datenzugriff

Lesen Sie die Fleet-Daten einer anderen App aus Ihrer eigenen App heraus – innerhalb desselben Projekts. Ihre App muss die bereitstellende App im Abschnitt consumes: ihrer eigenen data-template.yml deklarieren (oder die Wildcard-Consume-Berechtigung besitzen, siehe unten), und der Projektbenutzer muss den Zugriff gewähren. Der Zugriff ist schreibgeschützt: Sie können den Verlauf abfragen und Zeilen der vom Anbieter freigegebenen Tabellen und Transforms in Echtzeit abonnieren, aber nicht in sie schreiben. Verbindungen zu konsumierten Apps werden pro App und Stage zwischengespeichert und automatisch geschlossen, wenn Ihre Instanz stoppt.

Die Verbindung benötigt die Zugangsdaten pro App, die der Geräte-Agent injiziert (APP_AUTH_ID, APP_AUTH_SECRET). Die Plattform lehnt die alten Gerätezugangsdaten — den Seriennummern-Fallback einer App, die keine eigenen Zugangsdaten erhalten hat, z. B. unter einem älteren Geräte-Agenten — auf dem Realm einer anderen App ab (NOT_AUTHORIZED, mit einer Meldung, die genau das sagt), es sei denn, die bereitstellende App läuft auf demselben Gerät: Dann tritt die Verbindung deren Realm mit den vollen eigenen Rechten dieser App bei statt schreibgeschützt.

Wenn Ihre App die Wildcard-Berechtigung besitzt (consumes: [{ app: "*" }]), können Sie Anbieter dynamisch mit listConsumableApps / list_consumable_apps und connectToAllApps / connect_to_all_apps (siehe unten) auffinden und öffnen.

connectToApp / connect_to_app

Öffnet eine schreibgeschützte Verbindung zum Data Backend einer anderen App und gibt ein Handle zurück. Das Handle stellt getHistory / get_history, subscribeToTable / subscribe_to_table und getSeriesHistory / get_series_history (nur Tabellen) bereit – dasselbe Abfragen und Abonnieren wie bei Ihren eigenen Tabellen, mit denselben Parametern und Ergebnissen – sowie close und die freigegebenen Kataloge tables / transforms. Sein subscribeToTable / subscribe_to_table gibt dasselbe TableSubscription-Handle zurück wie bei Ihren eigenen Tabellen, das auch ironflock.unsubscribe entgegennimmt (siehe unsubscribe / unregister), und seine Tabellen-Subscriptions erhalten nach einer Neuverbindung auf dieselbe Weise eine Nachlieferung (siehe subscribeToTable).

Das Handle enthält außerdem den Namen und die Stage des Anbieters (app und stage, in Go App und Stage) sowie seine Verbindung zum Realm des Anbieters: is_connected / isConnected / IsConnected() gibt an, ob sie offen ist, connection / Connection() ist die Verbindung selbst. In JavaScript wird das Promise von close() erfüllt, sobald die Verbindung geschlossen ist, innerhalb von 5 Sekunden.

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

Parameter:

ParameterTypBeschreibung
app_name / appNamestr / stringName der bereitstellenden App, wie in Ihrem consumes:-Abschnitt deklariert
stagestr / string, optionalStage des Anbieters: "dev" oder "prod". Standardmäßig die Stage Ihrer eigenen App
on_error / onErrorCallback, optionalWird mit einem CrossAppAccessError aufgerufen, wenn der geöffneten Verbindung der Zugriff endgültig verweigert wird (z. B. wenn die Berechtigung später widerrufen wird — wann eine Verweigerung als endgültig gilt, steht unten)

Bei verweigertem oder falsch verwendetem Zugriff wird ein CrossAppAccessError ausgelöst (Python) bzw. geworfen (JavaScript) bzw. zurückgegeben (Go, ein *ironflock.CrossAppAccessError, den Sie mit errors.As prüfen) mit einem code-Feld (Code in Go, mit Konstanten wie ironflock.CodeNoGrant): NO_GRANT, PROVIDER_NOT_INSTALLED, UNKNOWN_APP, PRIVATE_TABLE, SECRET_COLUMN (eine Abfrage, die auf eine vom Anbieter als geheim markierte Spalte filtert — auch innerhalb einer Gruppe — oder sie auswählt) oder NOT_AUTHORIZED (die Berechtigung wurde widerrufen oder konnte gerade nicht überprüft werden — oder Ihre App hat die alten Gerätezugangsdaten vorgelegt; dann sagt die Meldung genau das). Ein subscribeToTable / subscribe_to_table einer konsumierten App, das der Router des Anbieters ablehnt (wamp.error.not_authorized, wamp.error.authorization_failed oder wamp.error.authentication_failed), schlägt ebenfalls mit einem CrossAppAccessError mit NOT_AUTHORIZED fehl, wie ein abgelehnter Lesevorgang.

Eine Verweigerung, während connectToApp / connect_to_app wartet, lässt den Aufruf sofort fehlschlagen. Ist ein Handle erst geöffnet, gilt eine Verweigerung nur dann als endgültig, wenn sie anhält — 3 oder mehr Ablehnungen, seit die Verbindung zuletzt stand (das Schließen der Session durch den Router aus einem Authentifizierungsgrund zählt als eine, ebenso jede abgelehnte Neuverbindung), die erste vor mindestens 60 Sekunden —, denn die Plattform lehnt genauso ab, während sie den Zugriff kurzzeitig nicht überprüfen kann. Bis dahin verbindet sich das Handle immer wieder neu und erholt sich, wenn eine Neuverbindung gelingt. Ist die Verweigerung endgültig, wird on_error / onError / OnError aufgerufen und das Handle verworfen: Der nächste Aufruf von connectToApp / connect_to_app / ConnectToApp öffnet eine neue Verbindung. Lesevorgänge und Subscriptions des Handles schlagen von da an sofort fehl, mit dem Fehler, den on_error / onError erhalten hat — in Go mit einem Fehler, der wamp.ErrStopped umhüllt.

In Python sind stage und on_error Keyword-Argumente; in JavaScript werden sie über ein Optionsobjekt übergeben (connectToApp(appName, { stage, onError })); in Go über ein Struct (ConnectToApp(ctx, appName, ironflock.ConnectToAppOptions{Stage, OnError})), wobei OnError eine func(*ironflock.CrossAppAccessError) ist. In Go begrenzt ctx nur das Warten des Aufrufers: Gleichzeitige Aufrufe für dieselbe App teilen sich einen Versuch, und Close(ctx) schließt ein Handle. Go vergleicht App-Namen ohne Rücksicht auf Groß- und Kleinschreibung, und ConsumedApp.Stage ist die Stage in Kleinbuchstaben ("dev" oder "prod"), anders als ifl.Stage().

listConsumableApps / list_consumable_apps

Listet jeden nicht privaten Anbieter im Projekt auf – das Discovery-Primitiv für Apps, die die Wildcard-Consume-Berechtigung besitzen (consumes: [{ app: "*" }] in Ihrer data-template.yml, gewährt durch den Projektbenutzer). Sie führt einen einzigen Aufruf aus und öffnet keine Verbindungen: Stellen Sie die zurückgegebenen Kataloge in einem Auswahlmenü dar und rufen Sie dann connectToApp / connect_to_app für die gewünschten auf – oder connectToAllApps / connect_to_all_apps, um alle auf einmal zu öffnen.

Hinweis: Deklarieren Sie die Berechtigung in der data-template.yml Ihrer App und setzen Sie das * in Anführungszeichen – ein bloßes * ist ein YAML-Alias und kann nicht geparst werden:

consumes: - app: "*"
providers = await ironflock.list_consumable_apps() for p in providers: print(p["app"], list(p["stages"].keys())) # e.g. "weather-app" ["dev", "prod"]

Jeder Eintrag beschreibt einen Anbieter:

FeldTypBeschreibung
appstr / stringName der bereitstellenden App
provider_app_keyint / numberDer App-Schlüssel des Anbieters
stagesdict / objectKatalog pro Stage { dev?, prod? }; eine Stage ist nur vorhanden, wenn der Anbieter dafür ein Data Backend besitzt. Jeder Katalog enthält die nicht privaten tables und transforms, die er freigibt

In Go ist jeder Eintrag ein ironflock.ConsumedAppInfo mit App, ProviderAppKey und Stages.Dev / Stages.Prod (ein *StageCatalog mit Tables und Transforms, nil für eine fehlende Stage); Catalog(stage) liefert einen davon über seinen Namen.

Es wird ein CrossAppAccessError mit code: NO_GRANT ausgelöst (Python) bzw. geworfen (JavaScript) bzw. zurückgegeben (Go), wenn Ihre App keine Wildcard-Berechtigung besitzt.

connectToAllApps / connect_to_all_apps

Öffnet in einem einzigen Aufruf schreibgeschützte Handles zu jedem nicht privaten Anbieter im Projekt (nur für Wildcard-Consumer). Zählt die Anbieter über listConsumableApps / list_consumable_apps auf und öffnet jeden einzelnen, wobei alle ohne Data Backend für die angeforderte Stage übersprungen werden. Jedes Handle wird unter demselben Schlüssel wie bei connectToApp / connect_to_app zwischengespeichert, sodass ein späteres connectToApp(name) das bereits vorgewärmte Handle zurückgibt. Die zurückgegebenen Handles werden gemeinsam geschlossen, wenn Ihre Instanz stoppt.

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)

Parameter:

ParameterTypBeschreibung
stagestr / string, optionalStage des Anbieters: "dev" oder "prod". Standardmäßig die Stage Ihrer eigenen App
continue_on_error / continueOnErrorbool / boolean, optionalBei true (Standardwert) wird ein Anbieter, der nicht geöffnet werden kann, an on_error / onError gemeldet und aus dem Ergebnis ausgelassen. Bei false wird der erste Fehler ausgelöst/geworfen
on_error / onErrorcallable, optionalWird mit dem Fehler jedes Anbieters aufgerufen, der nicht geöffnet werden konnte (solange continue_on_error / continueOnError true ist), und mit einem CrossAppAccessError, wenn einer bereits geöffneten Verbindung später der Zugriff endgültig verweigert wird (z. B. wenn die Berechtigung widerrufen wird)

Gibt die erfolgreich geöffneten Anbieter-Handles zurück (derselbe Handle-Typ wie bei connectToApp / connect_to_app). Es wird ein CrossAppAccessError mit code: NO_GRANT ausgelöst (Python) bzw. geworfen (JavaScript) bzw. zurückgegeben (Go), wenn Ihre App keine Wildcard-Berechtigung besitzt.

In Python sind stage, on_error und continue_on_error Keyword-Argumente; in JavaScript werden sie über ein Optionsobjekt übergeben (connectToAllApps({ stage, onError, continueOnError })); in Go über ein Struct (ConnectToAllApps(ctx, ironflock.ConnectToAllAppsOptions{Stage, OnError, StopOnError})), wobei OnError eine func(error) ist und StopOnError: true continueOnError: false entspricht.

Verwalteter Dateispeicher

Jedes App-Data-Backend erhält neben seinen Tabellen einen privaten Objektspeicher, erreichbar über die Eigenschaft files (ifl.Files() in Go, ein *filestore.FileStore). Die Eigenschaft führt keine I/O aus, daher kann sie in allen drei SDKs schon gelesen werden, bevor die Verbindung gestartet ist. Verwenden Sie ihn für Bilder, PDFs, Kamerabilder, Firmware-Blobs — alles, was nicht in eine Tabellenzeile gehört. Es ist keine Einrichtung nötig: Auch eine App ohne files:-Abschnitt in ihrer data-template.yml erhält einen Namespace mit dem Namen default (ein Template, das eigene Namespaces deklariert, ersetzt ihn — siehe Namespaces).

Dieser Abschnitt ist die clientseitige Referenz. Für die Plattformseite — das Deklarieren von Namespaces, Kontingenten und Aufbewahrung, das Teilen von Dateien zwischen Apps, S3-Zugangsdaten pro App und das Verhalten auf On-Premises-Appliances — siehe Dateispeicher.

Der zentrale Gedanke: Das Ablegen eines Objekts liefert Ihnen eine permanente URL zurück, die Sie direkt in eine Tabellenspalte schreiben können, sodass ein Dashboard-Widget sie ohne weiteres Zutun darstellen kann:

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)

Diese URL läuft nie ab, ist aber kein öffentlicher Link: Sie bleibt nur für einen authentifizierten Anfragenden lesbar, der READ-Zugriff auf dieses Data Backend besitzt, und ein Auth-Proxy prüft das bei jeder Anfrage, wobei er seine Entscheidung bis zu 5 Minuten zwischenspeichert. Sie kann daher gefahrlos in der Datenbank gespeichert werden.

Namespaces

Ein Namespace ist ein Schlüsselpräfix, das Richtlinien mitführt — Aufbewahrung, Freigaberegeln, erlaubte Content-Types. Er ist kein separater Bucket; jeder Namespace einer App liegt innerhalb des einen Speicherbereichs dieser App. Deklarieren Sie einen eigenen Namespace nur, wenn eine Menge von Objekten andere Regeln benötigt; bleiben Sie andernfalls in default und organisieren Sie Ihre Objekte über Schlüsselpfade wie 2026/03/part-1.jpg.

Namespaces deklarieren Sie in der data-template.yml. Sobald Sie welche deklarieren, ersetzen diese den impliziten default: Führen Sie auch einen Namespace namens default auf, sonst schlägt jeder Aufruf ohne Namespace mit NO_SUCH_NAMESPACE fehl.

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 }

Beachten Sie, dass das Budget einmal für die gesamte App deklariert wird, nicht pro Namespace. Ein Namespace ist lediglich ein Schlüsselpräfix innerhalb des einen Speicherbereichs der App, sodass es nichts gibt, wogegen sich ein Budget pro Präfix durchsetzen ließe. maxObjectBytes gilt dagegen pro Namespace — es begrenzt ein einzelnes Objekt, keine Summe: 100 MiB, sofern nicht gesetzt, höchstens 5 GiB (OBJECT_TOO_LARGE darüber).

Jede der folgenden Methoden nimmt den Namespace als optionales Argument entgegen und verwendet standardmäßig default; ein leerer Namespace ("") bedeutet ebenfalls default.

Objekte ablegen und lesen

# 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")
MethodeBeschreibung
put(key, data, …)Legt ein Objekt ab (bytes in Python, Uint8Array in JavaScript, []byte in Go). Gibt die Metadaten des Objekts inklusive seiner url zurück
get(key, namespace?)Gibt den Inhalt des Objekts zurück. Ein Objekt über dem Inline-Limit kommt direkt aus dem Objektspeicher oder, wo das nicht möglich ist, in Byte-Bereichen über den Router (siehe Große Objekte)
put_file(key, path, …) / get_to_file(key, path, …)Python und Go (PutFile / GetToFile; Go streamt außerdem mit PutReader aus einem io.Reader und mit GetTo in einen io.Writer). Aus einer lokalen Datei ablegen bzw. in eine lokale Datei schreiben. Streamt auf dem Pfad für große Objekte
delete(key, namespace?)Löscht ein Objekt
copy(key, to, …)Kopiert ein Objekt, optional in einen anderen Namespace
move(key, to, …)Kopieren und anschließend löschen. Nicht atomar — der Dienst kennt kein Move-Verb, daher hinterlässt ein fehlgeschlagenes Löschen beide Kopien

put und get halten das gesamte Objekt im Arbeitsspeicher. Verwenden Sie für ein großes Objekt die Dateivarianten, die streamen (in Go auch PutReader und GetTo). JavaScript besitzt keine Helfer für Dateipfade, weil das Paket einen einzigen Build für Node und den Browser ausliefert — lesen und schreiben Sie lokale Dateien selbst mit fs.

In Python läuft die Dateiarbeit von put_file und get_to_file außerhalb der Event-Loop. get_to_file legt fehlende übergeordnete Verzeichnisse an und schreibt die Datei an Ort und Stelle, sobald die ersten Bytes des Objekts vorliegen: Ein abgelehnter Lesevorgang (kein solches Objekt, nicht autorisiert) lässt den Pfad unberührt, während ein Lesevorgang, der mittendrin fehlschlägt, das bis dahin Geschriebene hinterlässt. Ein FIFO wird beschrieben, während das Objekt hereinströmt, und sein Öffnen wartet auf einen Leser, sodass ein FIFO, den niemand liest, den Aufruf blockiert (nicht die Event-Loop); mit asyncio.wait_for lässt er sich aufgeben. put_file liest einen FIFO bis zum Ende, bevor es ihn speichert.

In Go legt auch GetToFile fehlende übergeordnete Verzeichnisse an, lädt aber in eine temporäre Datei neben dem Ziel herunter und benennt sie an ihren Platz um, sobald der Download vollständig ist: Ein fehlgeschlagener oder abgebrochener Download hinterlässt nie eine unvollständige Datei und behält die vorherige. Symbolischen Links wird gefolgt (die Datei, auf die sie verweisen, wird geschrieben, und die Links bleiben bestehen); die Ersatzdatei erhält, soweit der Prozess das darf, Besitzer, Gruppe und Berechtigungsbits der alten Datei. Ein FIFO oder ein Gerät wird an Ort und Stelle beschrieben, während das Objekt hereinströmt. Für ein Ziel, neben dem keine neue Datei angelegt werden kann, etwa auf einem schreibgeschützten oder einem Pseudo-Dateisystem, verwenden Sie GetTo mit einer Datei, die Sie selbst öffnen.

put akzeptiert: content_type / contentType (den MIME-Typ; der Namespace kann einschränken, welche erlaubt sind) und namespace. In Python sind dies Keyword-Argumente; in JavaScript stehen sie in einem Optionsobjekt; in Go sind es Optionen (filestore.ContentType(…), filestore.Namespace(…)).

Auflisten und inspizieren

MethodeBeschreibung
list(…)Eine Seite der Auflistung des Dateidienstes, standardmäßig ordnerartig: objects enthält die Objekte, deren Schlüssel das Präfix ohne / fortsetzen, prefixes die Ordner darunter. Ein delimiter von "" listet flach — jedes Objekt unterhalb des Präfixes, auch in Unterordnern. Eine Seite enthält bis zu 200 Objekte, sofern Sie kein anderes limit übergeben. Gibt objects, prefixes, is_truncated / isTruncated / IsTruncated und einen cursor zurück, den Sie für die nächste Seite wieder übergeben
iter(…) / iterate(…)Asynchroner Iterator über jedes Objekt unterhalb eines Präfixes, auch in Unterordnern, mit automatischer Paginierung. In Python heißt er iter, in JavaScript iterate, in Go Iter (ein iter.Seq2[*ObjectInfo, error] für eine range-Schleife)
stat(key, namespace?)Metadaten zu einem Objekt, ohne dessen Inhalt zu übertragen
exists(key, namespace?)Ob ein Objekt existiert
namespaces()Die Namespaces, die diese App verwenden darf
usage(…)Wie viel Speicher die App belegt — siehe unten
catalog()Namespaces sowie die vom Server vorgegebenen Limits und Kontingente. Wird einmal abgerufen und zwischengespeichert: catalog(refresh=True) / catalog(true) / RefreshCatalog(ctx) ruft ihn erneut ab

Ordner. Ein Präfix, das auf / endet, bezeichnet einen Ordner: iter(prefix="2026/") / iterate({ prefix: "2026/" }) / Iter(ctx, filestore.Prefix("2026/")) liefert 2026/a.txt und 2026/sub/c.txt, aber nicht 2026-notes.txt. list kann einen Ordner nicht über seinen Namen öffnen, weil der Dateidienst ein Auflistungspräfix ablehnt, das auf / endet (FleetFiles v0.2 antwortet mit INTERNAL): list mit dem Präfix 2026 liefert 2026-notes.txt unter den Objekten und den Ordner 2026/ unter den Präfixen. Der Dienst speichert Schlüssel in der Unicode-Normalisierungsform C, geben Sie ein Ordnerpräfix daher ebenfalls in NFC an; eine Iteration, die auf einen Schlüssel unter einer anderen Form des Präfixes trifft, schlägt fehl, statt jeden Schlüssel zu überspringen.

Objekte werden in allen drei SDKs durch dieselben Felder beschrieben, jeweils in der Namenskonvention der Sprache: namespace, key, size, etag, content_type / contentType, last_modified / lastModified, checksum_sha256 / checksumSha256 und url — in Go die Felder Namespace, Key, Size, ETag, ContentType, LastModified, ChecksumSHA256 und URL von filestore.ObjectInfo.

Speicherbelegung und Kontingent

usage beantwortet die Frage in einem einzigen Aufruf direkt aus dem Objektspeicher, sodass die Summen exakt sind und nicht vom SDK zusammengezählt werden:

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}

In Go sind die Felder SizeBytes, ObjectCount, QuotaBytes, FreeBytes und PerNamespace von filestore.StorageUsage.

FeldBedeutung
size_bytes / sizeBytesAktuell gespeicherte Bytes
object_count / objectCountAnzahl der gespeicherten Objekte
quota_bytes / quotaBytesDas durchgesetzte Budget. 0 bedeutet unbegrenzt
free_bytes / freeBytesVerbleibende Bytes. -1 bedeutet unbegrenzt — eine 0 würde hier als „voll“ gelesen
per_namespace / perNamespaceBytes pro Namespace. Nur vorhanden, wenn Sie die detaillierte Aufschlüsselung anfordern

Die Aufschlüsselung pro Namespace ist standardmäßig abgeschaltet, weil der Speicher sie nicht direkt beantworten kann: Er rechnet pro Speicherbereich ab, und ein Namespace ist nur ein Präfix, sodass das SDK jeden Namespace auflisten muss, um die Größen aufzusummieren. Fordern Sie sie an, wenn Sie sie brauchen, aber nicht auf einem heißen Pfad.

Es tauchen zwei verschiedene Kontingente auf, und es lohnt sich, sie auseinanderzuhalten. catalog() meldet beide:

FeldBedeutung
quota_bytes / quotaBytesWas tatsächlich durchgesetzt wird, aus dem Objektspeicher gelesen — die Einstellung des Projektnutzers
suggested_quota_bytes / suggestedQuotaBytesWas das Data Template der App angefordert hat. 0, wenn es nichts angefordert hat

In Go liefert Catalog(ctx) einen *filestore.Catalog mit QuotaBytes und SuggestedQuotaBytes.

Sie unterscheiden sich immer dann, wenn ein Nutzer das Budget der App erhöht oder gesenkt hat. Genau deshalb wird der durchgesetzte Wert aus dem Speicher gelesen und nicht aus dem Template — ein erneutes Deployment der App darf die Entscheidung eines Nutzers nicht stillschweigend zurücksetzen. Eine Oberfläche kann beide anzeigen („die App schlägt X vor, Sie haben Y eingestellt“). Durchgesetzt wird immer der erste Wert. Der Nutzer kann das Budget jederzeit ändern, und catalog() meldet es mit dem Stand seines letzten Abrufs: Lesen Sie den aktuellen Wert aus usage().

Objekte freigeben

Es gibt zwei Arten von Links, und der Unterschied ist wesentlich:

MethodeLebensdauerWer kann ihn lesen
url(key, …)PermanentNur ein authentifizierter Anfragender mit READ auf diesem Data Backend — bei jeder Anfrage geprüft (eine Entscheidung wird bis zu 5 Minuten zwischengespeichert). Kann gefahrlos in einer Tabellenspalte gespeichert werden
share_url / shareUrl / ShareURLAblaufend (standardmäßig 15 Minuten, serverseitig begrenzt)Jeder, der den Link besitzt. Bei seiner Verwendung prüft nichts die Autorisierung erneut

share_url / shareUrl ist eine Bearer-Berechtigung: Geben Sie den Link an eine Person weiter, die vorübergehend Zugriff benötigt, und speichern Sie ihn nicht in der Datenbank. Verwenden Sie url für alles, was ein Dashboard darstellt.

url gibt None / undefined / "" (Go) zurück, wenn das Deployment keinen HTTP-Edge besitzt (zum Beispiel eine Appliance mit reinem HTTP) — das ist das Signal, auf get zurückzufallen. In einem Namespace, der mit share: none deklariert ist, funktioniert die permanente URL ebenfalls nicht, doch url kann das nicht erkennen, weil der Katalog nicht meldet, wie ein Namespace freigegeben ist: Die Methode liefert die URL trotzdem, und der Datei-Edge beantwortet sie mit 404. Lesen Sie solche Objekte mit get; Links von share_url / shareUrl / ShareURL hängen nicht von share ab. Wird der etag eines Objekts als version-Argument übergeben (filestore.Version(etag) in Go), können Browser die Antwort unveränderlich zwischenspeichern.

In Python und Go liefert cloud_url(key, …) / CloudURL(ctx, key, …) die permanente URL über den Cloud-Datei-Tunnel einer Appliance (https://i<instanceKey>-files.<cloud-edge>): aus dem Internet erreichbar, solange die Appliance online ist, mit derselben Autorisierung wie url. Sie gibt None ("" in Go) zurück bei in der Cloud gehosteten Deployments (wo url bereits aus dem Internet erreichbar ist), wo es keinen HTTP-Edge gibt, und solange die Cloud-Freigabe des Daten-Backends ausgeschaltet ist. Der Nutzer kann die Freigabe jederzeit umschalten, daher liest cloud_url / CloudURL den Katalog neu, wenn der zwischengespeicherte älter als 30 Sekunden ist.

upload_url / uploadUrl / UploadURL erzeugt eine ablaufende URL (standardmäßig 1 Stunde gültig), die einen direkten Upload entgegennimmt, und gibt url, method, headers sowie expires_in / expiresIn zurück (in Go ein *filestore.UploadTarget mit URL, Method, Headers und ExpiresIn). Senden Sie die Header unverändert mit, damit das Objekt mit dem beim Erzeugen festgelegten Content-Type gespeichert wird. Die Content-Types und die Größenobergrenze des Namespace, eine deklarierte size und das Kontingent werden beim Erzeugen der URL geprüft, nicht bei ihrer Verwendung: Die URL ist nur für den Host signiert, sodass jeder, der sie besitzt, bis zu ihrem Ablauf beliebige Inhalte unter ihren Schlüssel schreiben kann. Wie share_url / shareUrl ist sie eine Bearer-Berechtigung — eine, die schreibt.

Große Objekte

Das SDK wählt den Transportweg automatisch anhand der Größe — es gibt nichts zu konfigurieren:

ObjektgrößeÜbertragungsweg
Bis zum Inline-Limit (derzeit 6 MiB)Ein einzelner Aufruf über den Message Router
DarüberDirekt in den Objektspeicher über HTTPS, unter Umgehung des Routers
Darüber, beim Lesen ohne direkten PfadIn Byte-Bereichen über den Message Router

Das genaue Limit meldet der Server zur Laufzeit als inline_max_bytes / inlineMaxBytes / InlineMaxBytes in catalog(), sodass es ohne ein SDK-Release angehoben werden kann.

Der direkte Pfad setzt ein Deployment voraus, das URLs vorsignieren kann, und ein Gerät, das den Host des Objektspeichers erreicht, nicht nur den Router. Wo eines davon fehlt — der Katalog meldet, dass das Deployment nicht vorsignieren kann (standardmäßig bei einer On-Premises-Appliance), das Erzeugen der Lese-URL antwortet mit NOT_SUPPORTED, oder der Objektspeicher ist nicht erreichbar, bevor ein Byte angekommen ist —, lesen get (und Pythons get_to_file, Gos GetTo und GetToFile) das Objekt stattdessen in Byte-Bereichen über den Router (FleetFiles v0.1.33 und neuer), jeder höchstens so groß wie das Inline-Limit. Jeder Bereich ist ein eigener Lesevorgang: Ein Objekt, das während eines solchen Lesens ersetzt wird, lässt es mit INTERNAL fehlschlagen (“object changed while it was read”). Schreibvorgänge haben keinen solchen Fallback.

Für Schreibvorgänge bleiben zwei Obergrenzen bestehen, und beide melden TOO_LARGE mit einem Grund, der benennt, welche davon Sie erreicht haben:

  • 5 GiB — das Limit des Objektspeichers für einen einzelnen Upload. Multipart-Upload ist noch nicht implementiert.
  • Das Inline-Limit, wo es keinen direkten Endpunkt gibt — ein Deployment, das nicht vorsignieren kann (standardmäßig eine Appliance), kann ein größeres Objekt überhaupt nicht speichern. Kein Retry und keine kleinere Chunk-Größe hilft, und die Meldung sagt das auch.

Zwei im Feld häufige Fehler des direkten Pfads erhalten eigene Codes, statt wie Autorisierungsprobleme auszusehen: PRESIGN_UNREACHABLE (ein Proxy, der nur den Router zulässt — ein Lesevorgang weicht auf Byte-Bereiche aus, ein Schreibvorgang schlägt fehl) und CLOCK_SKEW (der Objektspeicher weist Anfragen zurück, die mehr als 15 Minuten abweichen — prüfen Sie NTP auf dem Gerät).

Fehler beim Dateispeicher

Ein Fehler des Dateidienstes oder einer direkten Übertragung löst einen FileStoreError aus (Python) bzw. wirft ihn (JavaScript) bzw. gibt ihn zurück (Go), der einen stabilen code und einen menschenlesbaren reason trägt. Verzweigen Sie über code, niemals über reason. Lokale Dateifehler, ein abgebrochener Aufruf und WAMP-Fehler auf Router-Ebene außer den unten zugeordneten werden unverändert ausgelöst.

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
CodeBedeutung
NOT_AUTHORIZEDDer Aufrufer darf diese Operation nicht ausführen
NO_SUCH_NAMESPACEDer Namespace ist in der data-template.yml nicht deklariert
NO_SUCH_OBJECTDer Schlüssel existiert nicht
TOO_LARGEÜberschreitet das Übertragungslimit für einen einzelnen Aufruf
OBJECT_TOO_LARGEÜberschreitet das maxObjectBytes des jeweiligen Namespace
QUOTA_EXCEEDEDDer Dateispeicher ist voll
CONTENT_TYPE_NOT_ALLOWEDDer Namespace schränkt contentTypes ein
INVALID_KEYEin fehlerhafter Schlüssel oder ein fehlerhaftes Präfix (siehe unten; FleetFiles v0.2 meldet dies noch als INTERNAL)
INVALID_RANGEEin Byte-Bereich außerhalb des Objekts; die SDKs fordern nie einen solchen an
NOT_SUPPORTEDDas Backend kann dies nicht
NOT_AVAILABLEDieses Deployment besitzt keinen Dateidienst — oder noch nicht: nach einem Plattform-Neustart oder während der Dateidienst seine Prozeduren registriert. Der Aufruf wurde nirgends ausgeführt und darf wiederholt werden
PRESIGN_UNREACHABLEDer Objektspeicher ist nicht direkt erreichbar (ein Proxy?)
CLOCK_SKEWDie Geräteuhr weicht zu stark ab
INTERNALAlles Übrige — auch ein Objekt, das sich während eines Lesens in Byte-Bereichen geändert hat

Ein neuerer Server kann Codes einführen, die dieses SDK-Release noch nicht kennt. Sie werden unverändert als code durchgereicht statt zusammengefasst zu werden; behandeln Sie einen unbekannten Wert daher als generischen Fehler.

Dateiaufrufe warten bis zu 10 Sekunden auf die Verbindung (vor start() / Start abgesetzt, auch auf den Start) und werden nicht wiederholt: Anders als Tabellenoperationen überbrücken sie keinen Plattform-Neustart, sodass ein Aufruf währenddessen mit NOT_AVAILABLE fehlschlagen kann, bis sich der Dateidienst wieder registriert hat. Ein solcher Aufruf wurde nirgends ausgeführt, Ihre App darf ihn also wiederholen.

Schlüssel. Maßgeblich für Schlüssel ist der Dateidienst; die SDKs senden Schlüssel und Präfixe unverändert. Ein fehlerhafter Schlüssel oder ein fehlerhaftes Präfix — leer; mit führendem oder abschließendem / (abgesehen vom Ordnerpräfix von iter / iterate); mit einem leeren, .- oder ..-Segment oder einem Segment, das auf . endet oder mit einem Leerzeichen beginnt oder endet; mit einem der Zeichen \ : * ? " < > | ^ ~, einem Steuerzeichen oder einem Unicode-Formatzeichen; über 900 Bytes, 20 Segmente oder 255 Bytes pro Segment; oder unter dem reservierten Präfix _sys/ — wird von einem Dateidienst, der Schlüsselfehler klassifiziert, mit INVALID_KEY abgelehnt und von FleetFiles v0.2 mit INTERNAL. Achten Sie auf Zeitstempel: 2026-10-09T12:00:00Z ist wegen des : kein gültiges Schlüsselsegment.

In Python wird FileStoreError aus ironflock.filestore importiert; in JavaScript wird er aus dem Paket-Root exportiert (import { FileStoreError } from "ironflock"); in Go ist es *filestore.Error (auch ironflock.FileStoreError), mit den Codes als Konstanten wie filestore.CodeQuotaExceeded.

Geräteübergreifende Kommunikation

registerDeviceFunction / register_device_function

Registriert eine Prozedur, die andere Geräte aufrufen können, auf denen dieselbe App (in derselben Stage) läuft, ebenso Aktionen von Dashboard-Widgets. Das SDK bildet die vollständige URI <SWARM_KEY>.<DEVICE_KEY>.<APP_KEY>.<STAGE>.<topic> für Sie, wobei STAGE den Wert PROD hat, wenn ENV in beliebiger Schreibweise PROD lautet, und sonst DEV. Bevor es etwas sendet, lehnt das SDK ein leeres Topic sowie einen Geräte-, Projekt- oder App-Schlüssel ab, der fehlt oder 0 ist; dasselbe gilt für callDeviceFunction / call_device_function. Der Router akzeptiert von einer App keine andere Registrierung und nur Einzelregistrierungen (siehe URIs, die eine App verwenden darf). Besteht keine Verbindung, wartet die Methode längstens für die Dauer des Neuverbindungsfensters auf eine (standardmäßig 60 Sekunden, siehe Verbindungszuverlässigkeit). Die Registrierung wird nach jeder Neuverbindung wiederhergestellt. Die Methode gibt eine Registration zurück, das Handle der Registrierung; dessen unregister() entfernt sie (siehe unsubscribe / unregister).

def add(a, b): return a + b await ironflock.register_device_function("com.myapp.add", add)

register() ist ein Alias für register_device_function().

Die früheren Namen register_function / registerFunction / RegisterFunction und call_function / callFunction / CallFunction funktionieren weiterhin, sind aber veraltet.

Apps, die ENV überschreiben: SDK-Versionen bis 1.8.6 (Python) und 1.8.4 (JavaScript) übernehmen den ENV-Wert unverändert in das Stage-Segment. Das macht nur dann einen Unterschied, wenn eine App ENV selbst in einer anderen Schreibweise setzt, etwa prod: Geräte mit diesen Versionen und Geräte mit neueren können dann gegenseitig ihre Funktionen nicht aufrufen.

callDeviceFunction / call_device_function

Ruft eine Prozedur auf, die ein anderes Gerät derselben App registriert hat. Das SDK erstellt das vollständige WAMP-Topic automatisch anhand des Schlüssels des Zielgeräts: <SWARM_KEY>.<device_key>.<APP_KEY>.<STAGE>.<topic> — dieselbe URI, die registerDeviceFunction / register_device_function auf dem Zielgerät registriert.

result = await ironflock.call_device_function( 42, # target device key "com.myapp.add", # procedure name args=[3, 5] # arguments ) print(result) # 8

call

Ruft eine Remote-Prozedur über eine vollständige WAMP-URI auf. Eine App kann einen Namen wie some.full.wamp.topic nicht registrieren, daher erreicht ein solcher Name nur Prozeduren, die Plattformdienste auf dem Daten-Realm der App registrieren, etwa die des Daten-Backends und des Dateispeichers. Um eine Funktion aufzurufen, die ein anderes Gerät Ihrer App registriert hat, verwenden Sie callDeviceFunction / call_device_function.

result = await ironflock.call("some.full.wamp.topic", args=[42])

URIs, die eine App verwenden darf

Ihr App-Container tritt seinem eigenen Daten-Realm realm-<SWARM_KEY>-<APP_KEY>-<dev|prod> mit der Router-Rolle app bei. Der Router prüft dort jedes Veröffentlichen, Abonnieren, Aufrufen und Registrieren gegen die Allow-List dieser Rolle. Die eigenen Methoden des SDK bleiben innerhalb dieser Liste; die Regeln sind relevant, wenn Sie eigene URIs an publish, subscribe oder call übergeben oder die connection direkt verwenden (Connection() in Go). Eine abgelehnte Aktion schlägt mit wamp.error.not_authorized fehl. In Python löst die Methode einen RuntimeError aus, und wamp_error_uri(err) liefert die URI; in JavaScript ist der Fehler ein WampError, dessen error die URI ist; in Go liefert ironflock.WampURI(err) sie.

URI auf Ihrem eigenen RealmErlaubtVerwendet von
Eigene Namen, z. B. com.myapp.statusveröffentlichen, abonnieren (auch per Präfix), aufrufen — nie registrierenpublish, subscribe, call
<SWARM_KEY>.<APP_KEY>.<table>, bulk.<SWARM_KEY>.<APP_KEY>.<table>veröffentlichen — nie abonnieren– (die Schreib-Topics der Tabellen; die Tabellenschreibvorgänge des SDK schreiben stattdessen per Append)
transformed.<table>, transformed.bulk.<table>nur abonnierensubscribeToTable / subscribe_to_table
databackend.errors.…, files.events…nur abonnierenDie Fehler- und Datei-Ereignisse des Daten-Backends
<SWARM_KEY>.<DEVICE_KEY>.<APP_KEY>.<STAGE>.<name>registrieren (einzeln), aufrufen, veröffentlichen — nie abonnierenregisterDeviceFunction / register_device_function, callDeviceFunction / call_device_function
append.…, appendBulk.…, history.transformed.…, secret.reveal.…, secret.verify.…, files.read.…, files.write.…aufrufen (das SDK ruft sie auf)Methoden für Tabellen, Verlauf, geheime Spalten und Dateien
sys.appaccess.list, sys.appaccess.resolveaufrufenlistConsumableApps / list_consumable_apps, connectToApp / connect_to_app, connectToAllApps / connect_to_all_apps
wamp.session.getaufrufenDer Heartbeat des JavaScript-SDK

Identitätsgeprüfte Namen. Namen, die mit einer Ziffer beginnen — die Schreib-Topics der Tabellen und die URIs der Gerätefunktionen —, durchlaufen zusätzlich die Identitätsprüfung der Plattform, die sie an den Swarm-Key Ihres Realms bindet. Eine Registrierung wird nur für eine Funktions-URI Ihres eigenen Geräts mit Swarm-Key, App-Key und Stage Ihres Realms akzeptiert; eine Präfix- oder Wildcard-Registrierung nur, wenn sie diese vier Segmente festlegt. Veröffentlichen und Aufrufen benötigen nur den Swarm-Key Ihres Realms: Eine Zeile, die an ein Tabellen-Topic mit einem anderen App-Key veröffentlicht wird, wird angenommen, erreicht aber kein Daten-Backend und geht verloren. Lassen Sie das SDK diese URIs bilden.

Reservierte Namen. Der Router lehnt sys. ab, abgesehen von den beiden app-übergreifenden Prozeduren oben, und wamp., abgesehen von wamp.session.get. Die übrigen Namen in der Tabelle gehören der Plattform: Beginnen Sie eigene Namen nicht mit einer Ziffer, bulk., transformed., databackend.errors., files., append., appendBulk., history. oder secret.. Jedes andere erste Segment ist der eigene Topic-Raum Ihrer App.

Niemand abonniert rohe Tabellen-Topics. Sie transportieren die Zeile genau so, wie sie gesendet wurde, einschließlich des Klartexts geheimer Spalten, die das Daten-Backend erst beim Speichern der Zeile verschlüsselt. subscribeToTable / subscribe_to_table liest stattdessen die gespeicherten Zeilen aus transformed.<table>, typisiert und mit maskierten geheimen Spalten.

Daten anderer Apps sind schreibgeschützt. Auf dem Realm eines Anbieters darf Ihre App (Router-Rolle app_reader) neben dem Heartbeat wamp.session.get nur transformed.… und files.events… abonnieren und history.transformed.… und files.read.… aufrufen — kein Veröffentlichen, kein Registrieren, keine Schreibvorgänge und keine Prozeduren für geheime Spalten.

Gerätemetadaten

setDeviceLocation / set_device_location

Bittet die Plattform, den GPS-Standort des Geräts zu aktualisieren, den IronFlock-Karten in Echtzeit anzeigen.

Derzeit nicht verfügbar: Noch beantwortet kein Plattformdienst diesen Aufruf auf dem Daten-Realm der App, daher schlägt er mit wamp.error.no_such_procedure fehl. Halten Sie Standorte bis dahin in einer eigenen Tabelle.

await ironflock.set_device_location(long=8.6821, lat=50.1109)
ParameterBereich
long-180 bis 180
lat-90 bis 90

Der Standortverlauf wird nicht gespeichert. Um den Standort über die Zeit zu verfolgen, erstellen Sie eine eigene Tabelle und verwenden Sie publish_to_table / publishToTable.

getRemoteAccessUrlForPort

Gibt die URL zurück, unter der ein in der port-template.yml der App deklarierter Port erreichbar ist, sobald sein Tunnel aktiv ist.

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>"
ParameterTypBeschreibung
portint / numberDer Port, wie in port-template.yml deklariert
protocolstr / string, optionalDas Protokoll des Ports: "http" (Standard), "https", "tcp" oder "udp", unabhängig von Groß- und Kleinschreibung; umgebende Leerzeichen werden ignoriert, und ein leeres gilt als "http"
remote_port_environment / remotePortEnvironmentstr / string, optionalNur tcp/udp: die Variable, die den entfernten Port enthält, anstelle von REMOTE_PORT_FOR_<port> — das eigene remote_port_environment des Ports aus dem Template, das Geräte-Agenten vor 0.19.8 benötigen

http- und https-Ports werden unter dem Tunnel-Label <device_key>-<app name, lower case>-<port> (https-Ports mit dem Plattform-Präfix secure-) auf der Tunnel-Domain ausgeliefert, die der Geräte-Agent injiziert (TUNNEL_DOMAIN), immer als https://-URL. Auf einem Gerät, das zu einer On-Premises-Appliance gehört (der Agent injiziert INSTANCE_KEY), wird stattdessen die über die Cloud weitergeleitete Route https://i<INSTANCE_KEY>-<label>.<CLOUD_TUNNEL_DOMAIN> zurückgegeben.

Eine tcp- oder udp-URL enthält den öffentlichen Port, den der Tunnel zugewiesen hat und den der Geräte-Agent als REMOTE_PORT_FOR_<port> bekanntgibt (Agent 0.19.8 und neuer): <protocol>://<TUNNEL_DOMAIN>:<remote port>. Auf dem Gerät einer Appliance wird der aus dem Internet erreichbare Port REMOTE_PORT_FOR_<port>_CLOUD bevorzugt, solange der Agent ihn bekanntgibt; bevor er erscheint, und wieder, sobald der Agent ihn entfernt hat (Cloud-Weiterleitung ausgeschaltet), wird die Appliance-lokale URL zurückgegeben. Ein Port 0 ist noch nicht zugewiesen.

Die Portwerte werden live aus /data/env gelesen; rufen Sie die Methode daher erneut auf, statt das Ergebnis zwischenzuspeichern. Einige Fälle kann die URL nicht abdecken:

  • Der Geräte-Agent tunnelt nur die Ports von Produktionsinstallationen (PROD). In einem Entwicklungscontainer (DEV) ist die http(s)-URL die der PROD-Installation derselben App auf diesem Gerät, und tcp/udp liefern keine; erreichen Sie eine DEV-App stattdessen über die LAN-Adresse des Geräts (DEVICE_LAN_IP, DEVICE_PORT_FOR_<port>).
  • Auf einem Edge-Gerät einer ohne Domain installierten Appliance (Modus „Einfaches HTTP“) setzt der Agent TUNNEL_DOMAIN auf localhost, sodass die dort gebildeten URLs auf localhost zeigen: Eine solche Appliance stellt App-Ports auf ihren Host-Ports bereit, was eine Tunnel-URL nicht ausdrücken kann.
  • Nicht gesetzte oder leere Tunnel-Domains fallen auf app.ironflock.com zurück. Die Plattform setzt CLOUD_TUNNEL_DOMAIN derzeit nicht: Die URLs einer Appliance verwenden app.ironflock.com, sofern die App diese Variable nicht selbst in ihren Umgebungseinstellungen setzt.
  • In einem Container, der unter einem anderen Benutzer als root läuft, sind die Werte die, mit denen der Container gestartet wurde (siehe Umgebungsvariablen).

Die Methode gibt None / null (in Go "" und false) zurück, wenn sich die URL nicht bilden lässt: Geräteschlüssel oder App-Name fehlen, für einen tcp/udp-Port ist noch kein entfernter Port bekanntgegeben (oder er ist 0), das Protokoll ist unbekannt, oder das Label ist kein gültiges DNS-Label (über 63 Zeichen oder ein App-Name mit Punkt). Im Browser gibt es keine Umgebung: Die http(s)-URL wird auf app.ironflock.com aus den Optionen deviceKey und appName gebildet.

Verbindungseigenschaften & Lebenszyklus

EigenschaftTypBeschreibung
is_connectedboolOb die Plattformverbindung aktiv ist
connectionCrossbarConnectionDie zugrunde liegende Verbindungsinstanz (für fortgeschrittene Nutzung)
filesFileStoreDer verwaltete Dateispeicher der App (siehe Verwalteter Dateispeicher)
MethodeBeschreibung
run()Startet die Verbindung, führt mainFunc aus (blockierend) und stoppt dann die Verbindung
await start()Startet die Verbindung asynchron. Operationen, die vorher abgesetzt werden, warten innerhalb ihrer Wartezeit auf diesen Start (siehe Verbindungszuverlässigkeit)
await stop()Stoppt die Verbindung und die Verbindungen zu konsumierten Apps und bricht die Hauptaufgabe (mainFunc) ab. Die Verbindungen verlassen ihre Realms und verbinden sich danach nicht neu; start() verbindet von Neuem. Jede Operation, die noch auf eine Verbindung oder auf start() wartet, schlägt sofort fehl, ebenso ein noch wartendes start() oder connect_to_app; eine Operation, die nach stop() abgesetzt wird, wartet auf das nächste start() (siehe Verbindungszuverlässigkeit)
await run_async()Startet die Verbindung und hält sie asynchron aufrecht

run() und run_async() stoppen die Verbindung, sobald mainFunc zurückkehrt oder eine Exception auslöst; eine solche Exception wird nach dem Stoppen erneut ausgelöst. Eine mainFunc, die nur abonniert oder Gerätefunktionen registriert, muss daher weiterlaufen, zum Beispiel mit await asyncio.Event().wait(). mainFunc muss eine asynchrone Funktion sein (async def). Wird keine mainFunc übergeben, halten run() und run_async() die Verbindung offen, bis der Prozess endet.

Verbindungszuverlässigkeit

Das SDK verbindet sich selbstständig neu. Bricht die Verbindung ab, wird so lange erneut versucht, bis der Router wieder erreichbar ist; danach werden alle Subscriptions und alle registrierten Gerätefunktionen wiederhergestellt — Sie müssen nach einer Neuverbindung nichts erneut abonnieren, und die Handles, die Sie erhalten haben, bleiben gültig; deren active gibt an, ob jedes einzelne gerade beim Router aktiv ist (siehe unsubscribe / unregister). Tabellen-Subscriptions liefern außerdem die Zeilen nach, die gespeichert wurden, während die Verbindung unterbrochen war (siehe subscribeToTable); Nachrichten auf Ihren eigenen Topics werden dagegen nicht gespeichert, sodass verloren geht, was in der Zwischenzeit veröffentlicht wurde. Lässt sich eine davon nicht wiederherstellen, werden die übrigen dennoch wiederhergestellt, und die fehlgeschlagene wird erneut versucht, solange die Session besteht — nach 1 Sekunde, mit Verdopplung bis 30 Sekunden, jede Wartezeit um bis zu 15 % variiert — und erneut nach jeder Neuverbindung: Der Router hat sie zuvor akzeptiert, ihre Ablehnung ist also vorübergehend, etwa weil die Identitätsprüfung der Plattform für eine Gerätefunktion kurzzeitig im Fail-Closed-Modus ablehnt. Der erste Fehlschlag wird als Warnung protokolliert, und die Wiederherstellung wird protokolliert, sobald sie gelingt.

Tabellenoperationen — die Tabellenschreibvorgänge, Lesevorgänge von Verlauf, Serien und geheimen Spalten sowie die Verlaufs- und Serienlesevorgänge einer konsumierten App — überbrücken außerdem einen Plattform-Neustart, etwa ein Router- oder Plattform-Update. Sie warten bis zu reconnect_window (Python, in Sekunden) / reconnectWindowMs (JavaScript) / WithReconnectWindow (Go, eine time.Duration), standardmäßig 60 Sekunden, auf die Rückkehr der Verbindung; eine Operation, die vor start() / Start abgesetzt wird, wartet innerhalb desselben Fensters auf den Start. Außerdem versuchen sie es erneut, solange das Daten-Backend seine Prozeduren noch nicht bereitstellt: Der Router antwortet mit wamp.error.no_such_procedure, oder das Daten-Backend lehnt einen Aufruf, der seiner Registrierung zuvorgekommen ist, mit “client has no handler for registration” ab. Ein solcher Aufruf hat nie einen Handler erreicht, daher schreibt ein erneuter Versuch nie eine Zeile doppelt; jeder andere Fehler wird unverändert ausgelöst. Der Lesevorgang der Nachlieferung einer Tabellen-Subscription ist einer dieser Lesevorgänge.

Zu den Tabellenschreibvorgängen gehören publishToTable / publish_to_table, publishRowsToTable / publish_rows_to_table und reportError / report_error: Sie rufen dieselben Append-Prozeduren auf wie appendToTable und appendRowsToTable, sodass eine Zeile, die geschrieben wird, während das Daten-Backend neu startet, nicht verloren geht.

publish, subscribe, subscribeToTable / subscribe_to_table (auch das einer konsumierten App) und registerDeviceFunction / register_device_function warten ebenfalls innerhalb des Neuverbindungsfensters auf eine Verbindung, sodass sie bei einer kurzen Unterbrechung nicht fehlschlagen. Sie werden nicht wiederholt; eine Subscription oder Registrierung wird nach jeder späteren Neuverbindung wiederhergestellt.

Jede andere Operation — call, callDeviceFunction / call_device_function, setDeviceLocation / set_device_location, die Dateiaufrufe sowie das app-übergreifende Nachschlagen und Öffnen von Verbindungen — wartet bis zu 10 Sekunden auf eine Verbindung und wird nicht wiederholt. Eine Operation, die vor start() / Start abgesetzt wird, wartet innerhalb dieser 10 Sekunden auf den Start. Eine Operation, die innerhalb ihrer Wartezeit keine Verbindung erhält, schlägt mit einem Fehler fehl, der die Wartezeit in Sekunden nennt: In Python lösen die Methoden von IronFlock einen RuntimeError aus, dessen Ursache ein TimeoutError ist; in JavaScript ist es ein einfacher Error; in Go ein Fehler, der wamp.ErrNotConnected umhüllt. unsubscribe und unregister warten überhaupt nicht: Sie wirken sofort, auch während die Verbindung unterbrochen ist (siehe unsubscribe / unregister).

0 schaltet das Fenster ab: Diese Operationen verhalten sich dann wie jede andere Operation. Sie warten 10 Sekunden auf eine Verbindung und werden nicht wiederholt, sodass auch eine Nachlieferung direkt nach einem Plattform-Neustart nicht erneut versucht wird. Ein negatives Fenster und in Python und JavaScript auch NaN oder ein unendlicher Wert werden beim Erzeugen des IronFlock-Objekts abgelehnt (siehe Konstruktor-Optionen).

Eine Anfrage, die bereits gesendet ist, wenn ihre Session geschlossen wird (der Router schließt sie, die Verbindung bricht ab oder stop()), schlägt sofort fehl, statt auf eine Antwort zu warten, und wird nicht wiederholt: Sie hat ihren Handler möglicherweise erreicht. In Python ist die Ursache des Fehlers der ApplicationError von autobahn mit dem Grund für das Schließen (z. B. wamp.close.system_shutdown) oder TransportLost, wenn die Verbindung abgebrochen ist. In JavaScript ist der Fehler ein WampError, dessen error der Grund für das Schließen ist: der des Routers, wamp.close.goodbye_and_out nach stop() oder wamp.close.transport_lost, wenn die Verbindung abgebrochen ist. In Go schlägt eine solche Anfrage mit einem Fehler fehl, der wamp.ErrNotConnected umhüllt (wamp.ErrStopped nach Stop).

Ein Sonderfall ist ein erreichbarer Router, der für die App noch kein Realm hat. Beim gleichzeitigen Installieren startet der App-Container, bevor das Daten-Backend sein Realm bereitgestellt hat, sodass die ersten Joins mit wamp.error.no_such_realm abgewiesen werden; das SDK versucht es alle paar Sekunden erneut, und die App ist da, sobald das Realm da ist. Ein Realm, das nach einer Minute noch immer fehlt, wird höchstwahrscheinlich nie erscheinen — die App wurde gelöscht oder hat für diese Stage kein Daten-Backend —, daher verlangsamt das SDK von da an auf einen Versuch alle zwei Minuten, statt den Router für immer jede Sekunde anzufragen. Der schnelle Takt kehrt zurück, sobald das Realm erscheint oder der Router selbst wegfällt und wiederkommt.

stop() / Stop beendet jedes Warten sofort. Eine Operation, die noch auf eine Verbindung wartet — oder, vor start() / Start abgesetzt, auf den Start —, schlägt mit dem Stopp-Fehler des SDK fehl, ebenso ein Tabellenaufruf, der zwischen zwei Wiederholungen pausiert: in Python ein ConnectionError mit der Meldung, dass die IronFlock-Verbindung gestoppt wurde (die Methoden von IronFlock lösen ihn als Ursache ihres RuntimeError aus), in JavaScript ein ConnectionStoppedError und in Go ein Fehler, der wamp.ErrStopped umhüllt. Ein start(), das noch auf seinen ersten Join wartet — etwa auf ein Realm, das es noch nicht gibt —, schlägt mit Connection to realm <realm> stopped before it opened fehl; in Go gibt Start einen Fehler zurück, der wamp.ErrStopped umhüllt. Ein connect_to_app / connectToApp, das noch die Verbindung zu einer bereitstellenden App öffnet, schlägt mit Connection to app '<app>' (<stage>) aborted by stop() fehl; tritt die Verbindung trotzdem ihrem Realm bei, wird sie geschlossen, und der Aufruf schlägt mit Connection to app '<app>' (<stage>) closed by stop() fehl. In Go schlägt ConnectToApp mit ironflock: connection to app '<app>' (<stage>) aborted by Stop fehl (oder mit closed by Stop). Eine Verweigerung, die gleichzeitig mit stop() eintrifft, behält ihren CrossAppAccessError. In Python und JavaScript baut nichts mehr eine Verbindung auf, wenn stop() zurückkehrt, und ein Router, der auf das Verlassen des Realms nicht antwortet, wird nach 5 Sekunden getrennt; Stop in Go wartet innerhalb von ctx auf das Verlassen des Realms und schließt dann den Socket, und Run gibt ihm dafür 10 Sekunden.

In Python und JavaScript lässt sich die Instanz nach stop() erneut starten: start() verbindet von Neuem, und eine Operation, die nach stop() abgesetzt wird, wartet auf das nächste start(), so wie eine, die vor dem ersten start() abgesetzt wird. Stop in Go ist endgültig: Eine danach abgesetzte Operation schlägt sofort mit wamp.ErrStopped fehl.

Schwieriger ist eine Verbindung, die stillschweigend stirbt: Ein NAT oder Proxy kappt sie im Leerlauf, ohne eine der beiden Seiten zu informieren, oder ein Router startet hinter einem Load Balancer neu. Es trifft nichts ein, was den Verlust signalisiert. Eine App, die nur veröffentlicht, merkt es beim nächsten fehlschlagenden Schreibvorgang — eine App, die nur abonniert, schreibt jedoch nie und merkt es daher gar nicht: Sie bleibt tot, bis der Container neu startet.

Alle drei SDKs erkennen dies. Die Erkennung ist standardmäßig aktiv und benötigt keine Konfiguration.

Das Python-SDK nutzt WebSocket-Keepalive: Es sendet alle 30 Sekunden einen Ping an den Router, solange die Verbindung im Leerlauf ist, und erwartet innerhalb von 10 Sekunden einen Pong. Bleibt dieser aus, wird die Verbindung verworfen und neu aufgebaut. Eingehender Datenverkehr gilt als Lebenszeichen, sodass eine ausgelastete Verbindung nie geprüft wird — der Ping geht nur raus, wenn die Verbindung tatsächlich still geworden ist.

Mit connectToApp / connect_to_app geöffnete app-übergreifende Verbindungen sind typischerweise reine Subscribe-Verbindungen — genau der Fall, den dies schützt. Sie sind automatisch abgedeckt und verbinden sich auch dann neu, wenn der Router sie ablehnt, bis die Ablehnung anhält (siehe connectToApp).

Fehlerbehandlung

Eine SDK-Methode, die fehlschlägt — bei ungültigen Argumenten, einer abgerissenen Verbindung oder einer Ablehnung durch die Plattform —, löst eine Exception aus (Python), liefert ein abgelehntes Promise (JavaScript) bzw. gibt einen Fehler zurück (Go), mit einer Meldung, die die Operation, das Topic und den Grund nennt; verpacken Sie Aufrufe, die überleben sollen, daher in einen try-Block — in Go prüfen Sie den zurückgegebenen Fehler. Manche Fehlschläge erreichen Ihren Code nicht. Eine Nachricht, die auf einem eigenen Topic veröffentlicht wird, erreicht keinen Abonnenten, der in der Zwischenzeit nicht läuft. Ein fehlgeschlagener Lesevorgang der Nachlieferung nach einer Neuverbindung wird nur protokolliert (siehe subscribeToTable).

try: rows = await ironflock.getHistory("sensordata", {"limit": 100}) except ValueError as e: # Invalid parameters — e.g. limit out of range, or a malformed filter print(f"Bad query: {e}") except RuntimeError as e: # Not connected, table not in the data-template, or the platform rejected the call print(f"Query failed: {e}")

In JavaScript ist eine Ablehnung durch den Router oder einen Aufgerufenen in der Regel ein WampError — eine gewöhnliche Unterklasse von Error, die zusätzlich die WAMP-Fehler-URI in error und die Fehlerdaten in args / kwargs transportiert. Drei Arten von Aufrufen schlagen anders fehl: Ein Verlaufs-, Serien-, Reveal- oder Verify-Aufruf, dessen Prozedur nicht registriert ist, schlägt mit einem einfachen Error fehl, der das besagt und die wahrscheinlichen Ursachen nennt, und zwar erst nach dem gesamten Neuverbindungsfenster, weil das SDK den Aufruf bei wamp.error.no_such_procedure wiederholt; ein Dateiaufruf schlägt mit einem FileStoreError fehl (siehe Fehler beim Dateispeicher); und ein app-übergreifender Aufruf, dem der Zugriff verweigert wird, subscribeToTable einer konsumierten App eingeschlossen, schlägt mit einem CrossAppAccessError fehl. Eine Operation, die stop() beendet, während sie auf die Verbindung wartet, schlägt mit einem ConnectionStoppedError fehl, einer exportierten Unterklasse von Error. Alles andere (fehlerhafte Parameter, keine Verbindung) ist ein einfacher Error.

In Go gibt eine fehlgeschlagene Operation einen *ironflock.OperationError zurück, dessen Meldung die Operation und die Ursache nennt, die er umhüllt; errors.Is und errors.As sehen durch ihn hindurch. Eine Ablehnung durch den Router oder einen Aufgerufenen ist ein *ironflock.WampError (*wamp.Error) mit URI, Args und Kwargs; ironflock.WampURI(err) liefert die URI oder "". Weitere Sentinel-Fehler sind ironflock.ErrMissingConfig (eine Identitätsangabe, die die Operation benötigt, fehlt), ironflock.ErrAlreadyStarted und wamp.ErrStopped (eine Operation nach Stop oder eine, die Stop unterbrochen hat).

Migration: Ältere Versionen des Python- und des JavaScript-SDK protokollierten eine Meldung und gaben None / null zurück, wenn ein Aufruf fehlschlug. Sie lösen jetzt stattdessen einen Fehler aus, sodass Code der Form if result is None: Fehlschläge nicht mehr erkennt — verwenden Sie try / except (bzw. try / catch).

Ablehnungen durch das Daten-Backend

Eine Operation, die das Daten-Backend ablehnt, schlägt mit einer der folgenden WAMP-Fehler-URIs fehl, mit dem Grund als erstem Argument. Alle drei SDKs exportieren sie als Konstanten; Go benennt sie im Go-Stil (ironflock.URIRateLimited, ironflock.URIStorageFull, …). In Python lösen die Methoden von IronFlock einen RuntimeError aus, dessen Ursache der ApplicationError von autobahn ist (Lesevorgänge einer konsumierten App lösen den ApplicationError selbst aus), und wamp_error_uri(err) liest die URI aus beiden. In JavaScript ist der Fehler ein WampError, dessen error die URI ist. In Go ist es ein *ironflock.OperationError um einen *ironflock.WampError, und ironflock.WampURI(err) liest die URI. Die SDKs wiederholen keine dieser Ablehnungen. Jeder Tabellenschreibvorgang meldet sie, auch die Publish-Methoden: Sie schreiben per Append.

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
KonstanteAbgelehnt wird
URI_RATE_LIMITEDrevealSecrets / reveal_secrets über 30, verifySecret / verify_secret über 120 Aufrufe pro Minute und App-Zugangsdaten auf einem Gerät
URI_RESULT_TOO_LARGEEin Lesevorgang mit einer einzelnen Zeile über dem Nachrichtenbudget von 8 MiB oder einem Ergebnis über der 1-GiB-Schutzgrenze (andere große Ergebnisse kommen in Chunks an, die das SDK wieder zusammensetzt). Seine Keyword-Argumente nennen die größten Spalten: Senken Sie limit, verengen Sie timeRange, wählen Sie weniger columns aus oder beschneiden Sie mit columnPaths
URI_INVALID_LIMITEin Lesevorgang eines Transforms mit einem limit über 3000
URI_INVALID_TIME_RANGE, URI_INVALID_METRIC, URI_INVALID_GROUP_BY, URI_SERIES_TOO_MANY_GROUPSSerienabfragen, die die Tabelle nicht beantworten kann: kein Start oder ein Ende, das nicht danach liegt; eine Metrik, die die Tabelle nicht aggregieren kann (keine solche Spalte, eine geheime Spalte, eine Methode, die der Typ der Spalte nicht zulässt, tsp mit einer anderen Methode als COUNT); eine groupBy-Spalte, nach der sie nicht gruppieren kann; mehr als 50.000 Zeilen
URI_SECRET_COLUMN, URI_NOT_A_SECRET_COLUMNEin Filter auf eine geheime Spalte; verifySecret / verify_secret auf eine Spalte, die nicht geheim ist. Eine Tabelle ohne geheime Spalten hat überhaupt keine Reveal- oder Verify-Funktion (siehe revealSecrets)
URI_STORAGE_FULLEin Append, während die Festplatte der Appliance fast voll ist (unter etwa 3 GiB frei; ab 4 GiB wieder angenommen) — vorübergehend: Hängen Sie die Zeilen später an
URI_STORAGE_OVERUSAGEEin Append über das Speicherkontingent des Kontos hinaus; ein baldiger neuer Versuch hilft nicht
URI_ENTITY_KEY_CONFLICTEine Zeile, deren Entitätsschlüssel (maintainLatestFlagFor) und tsp bereits eine andere Zeile hat, im Stapel oder gespeichert: Geben Sie jeder Zeile ihren eigenen tsp
URI_SECRET_SENTINEL_UNRESOLVABLE, URI_SECRET_CIPHERTEXT_REJECTEDEin Schreibvorgang, der den bisherigen Wert eines Geheimnisses beibehält (__secret__), ohne einen Entitätsschlüssel, über den er sich finden lässt; ein Schreibvorgang mit einem bereits verschlüsselten Wert

Ein Append, das das Daten-Backend ohne Begründung ablehnt — wamp.error.runtime_error, seine Antwort auf eine Zeile ohne tsp —, ist keine typisierte Ablehnung: Die Fehlermeldung des SDK verweist auf den fehlenden tsp und auf die error-logs-Tabelle der App, in der das Daten-Backend die Ursache protokolliert.

Browser-Nutzung (nur JavaScript)

Das JavaScript SDK funktioniert in modernen Browsern. Da Browser keine Umgebungsvariablen haben, übergeben Sie alle Konfigurationsoptionen über den Konstruktor:

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 }]);

Verwenden Sie IronFlock.fromServer(), um die Konfiguration von Ihrem Backend abzurufen, anstatt Zugangsdaten fest einzuprogrammieren:

const ironflock = await IronFlock.fromServer("/api/ironflock-config"); await ironflock.start();

Ihr Backend-Endpunkt sollte ein JSON-Objekt mit den Verbindungsoptionen (serialNumber, deviceKey, appName, swarmKey, appKey, env) zurückgeben.

AI-Agent-Funktionen registrieren

Das SDK kann Funktionen registrieren, die von AI-Agents aufgerufen werden können. Registrieren Sie eine Prozedur und referenzieren Sie deren Topic in Ihrer ai-template.yml:

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)

Der AI-Agent kann diese Funktion dann aufrufen, wenn ein Benutzer eine Frage stellt, die aktuelle Sensordaten erfordert.

Um das registrierte WAMP-Topic mit einem AI-Agenten zu verbinden, referenzieren Sie es in der .ironflock/ai-template.yml Ihrer App:

sensor_agent: tool_description: | Delegiere an diesen Agenten, wenn der Benutzer nach Sensormesswerten, Live-Gerätedaten oder aktuellen Umgebungsbedingungen fragt. system_prompt: | Du bist ein Sensordaten-Spezialist. Verwende get_current, um den aktuellen Messwert eines beliebigen Sensors abzurufen. Gib in deiner Antwort immer die Einheit an. main: true max_context_tokens: 30000 max_iterations: 5 tools: get_current: description: Gibt den aktuellen Messwert eines Sensors zurück. topic: sensors.get_latest parameters: sensor_id: type: string description: Der abzufragende Sensor-Bezeichner. required: true

Der Wert von topic (sensors.get_latest) muss mit dem Namen übereinstimmen, der bei register_device_function / registerDeviceFunction / RegisterDeviceFunction im Edge-Code angegeben wurde. IronFlock leitet den Aufruf automatisch an das Gerät weiter, auf dem die Funktion registriert ist.

Die vollständige Referenz für ai-template.yml finden Sie unter Agents & Tools definieren.

Umgebungsvariablen

Diese Variablen werden von der IronFlock-Laufzeitumgebung automatisch innerhalb von App-Containern gesetzt:

VariableBeschreibung
DEVICE_NAMEAnzeigename des Geräts
DEVICE_SERIAL_NUMBEREindeutiger, unveränderlicher Gerätebezeichner
DEVICE_KEYDer numerische Schlüssel des Geräts. Das SDK bildet daraus die URIs der Gerätefunktionen und die Tunnel-Labels für den Fernzugriff und sendet ihn als Metadaten mit Veröffentlichungen und Tabellenschreibvorgängen; zur Authentifizierung verwendet es ihn nicht
SWARM_KEYProjektbezeichner
APP_KEYApp-Bezeichner
APP_NAMEApp-Name
ENVUmgebung: DEV oder PROD
APP_AUTH_ID, APP_AUTH_SECRETDie eigenen Zugangsdaten der App, die das SDK dem Router vorlegt. Der app-übergreifende Zugriff setzt sie voraus; ohne sie (bei einem älteren Geräte-Agenten) greift das SDK auf die Seriennummer des Geräts zurück
DEVICE_ENDPOINT_URLDie Router-URL, mit der sich das SDK verbindet, wobei ihr Pfad durch /ws-ua-usr ersetzt wird. Sie hat Vorrang vor RESWARM_URL; eine als Option übergebene Router-URL (ironFlockUrl, WithURL in Go) überschreibt beide
RESWARM_URLDie Studio-URL, aus der das SDK die Router-URL ableitet, wenn DEVICE_ENDPOINT_URL nicht gesetzt ist. Eine Studio-URL, die das SDK nicht kennt, lässt start() / Start fehlschlagen. Ist keine der beiden Variablen gesetzt, verbindet sich das SDK mit wss://cbw.ironflock.com/ws-ua-usr
import os device_name = os.environ.get("DEVICE_NAME") serial = os.environ.get("DEVICE_SERIAL_NUMBER") project_key = os.environ.get("SWARM_KEY")

Die Variablen für den Fernzugriff (TUNNEL_DOMAIN, DEVICE_LAN_IP, DEVICE_PORT_FOR_<port>, REMOTE_PORT_FOR_<port>, …) sind unter Ports konfigurieren aufgeführt.

Der Geräte-Agent spiegelt diese Werte außerdem nach /data/env, je eine Datei <NAME>.txt, und hält die Spiegelung aktuell, solange die App läuft. Zwei Arten von Werten lesen die SDKs vorrangig aus der Spiegelung, damit eine Änderung eine laufende App erreicht: die Zugangsdaten pro App (APP_AUTH_ID, APP_AUTH_SECRET), die bei jedem Verbindungsversuch neu gelesen werden, sodass Zugangsdaten, die der Agent rotiert, während die App läuft, bei der nächsten Neuverbindung vorgelegt werden; und die entfernten Ports (REMOTE_PORT_FOR_<port> und REMOTE_PORT_FOR_<port>_CLOUD), die bei jedem Aufruf von getRemoteAccessUrlForPort gelesen werden. Alle anderen Werte — die Identität (DEVICE_KEY, DEVICE_NAME, …), ENV und die Tunnel-Domains — stammen aus der Umgebung, mit der der Container gestartet wurde (Go liest die Identität und ENV einmalig in New). Ältere Geräte-Agenten machen /data/env nur für root lesbar: In einem Container, der unter einem anderen Benutzer läuft, sieht das SDK dann die Werte, mit denen der Container gestartet wurde, und protokolliert einmalig eine Warnung. In JavaScript setzt das Lesen der Spiegelung Node.js 22.3 oder neuer voraus; ein älteres Node.js sieht nur die Umgebung.

Last updated on