SDK IronFlock
SDK IronFlock pozwala aplikacjom brzegowym wchodzić w interakcję z platformą IronFlock. Automatycznie obsługuje uwierzytelnianie podczas działania na zarejestrowanym urządzeniu i zapewnia funkcje do publikowania danych, zapytywania historii, wywoływania zdalnych procedur na urządzeniach oraz aktualizowania metadanych urządzenia.
| SDK | Pakiet | Wymaga |
|---|---|---|
| Python | ironflock na PyPI | Python 3.8+ |
| JavaScript | ironflock na npm | Node.js 18+ lub nowoczesna przeglądarka |
Instalacja
Python
pip install ironflockLub dodaj ironflock do requirements.txt swojej aplikacji.
Szybki start
Python
import asyncio
from ironflock import IronFlock
async def main():
while True:
await ironflock.publish_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60
})
await asyncio.sleep(5)
ironflock = IronFlock(mainFunc=main)
ironflock.run()Podczas używania wewnątrz kontenera aplikacji IronFlock, SDK automatycznie odczytuje dane uwierzytelniające połączenia ze środowiska — nie jest wymagana ręczna konfiguracja.
Opcje konstruktora
Python
ironflock = IronFlock(
mainFunc=main, # async function to run after connecting
serial_number="abc123" # override device serial (optional)
)| Parametr | Opis |
|---|---|
mainFunc | Funkcja asynchroniczna, która uruchamia się po nawiązaniu połączenia |
serial_number | Nadpisuje numer seryjny urządzenia. Domyślnie zmienna środowiskowa DEVICE_SERIAL_NUMBER |
Publikowanie danych
publishToTable / publish_to_table
Publikuje rekord danych do tabeli floty. Nazwa tabeli musi pasować do tabeli zdefiniowanej w data-template.yml aplikacji. SDK automatycznie kieruje dane do właściwej bazy danych projektu.
Python
await ironflock.publish_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60,
"device_id": "sensor-001"
})appendToTable / append_to_table
Dołącza dane do tabeli floty za pomocą zdalnego wywołania procedury zamiast pub/sub. Użyj tego, gdy potrzebujesz potwierdzenia, że dane zostały zapisane.
Python
result = await ironflock.append_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60
})publishRowsToTable / publish_rows_to_table
Publikuje wiele wierszy w jednej wiadomości (wstawianie zbiorcze) do tabeli floty. Platforma wstawia całą partię atomowo (wszystko albo nic) w jednej operacji. Użyj tego dla danych o wysokiej częstotliwości, gdzie jedno połączenie na wiersz byłoby zbyt kosztowne. Podobnie jak publishToTable, działa to w trybie „wyślij i zapomnij” — potwierdzenie potwierdza dostarczenie do routera, a nie wstawienie do bazy danych.
Python
await ironflock.publish_rows_to_table("sensordata", [
{"tsp": "2024-01-15T10:30:00.000Z", "temperature": 22.5},
{"tsp": "2024-01-15T10:30:01.000Z", "temperature": 22.7},
])Drugim argumentem jest niepusta lista obiektów wierszy do wstawienia.
appendRowsToTable / append_rows_to_table
Dołącza wiele wierszy w jednym zdalnym wywołaniu procedury (wstawianie zbiorcze) do tabeli floty. Platforma wstawia całą partię atomowo (wszystko albo nic): jeśli którykolwiek wiersz jest nieprawidłowy, cała partia zostaje odrzucona i nic nie jest zapisywane. Preferuj to zamiast publishRowsToTable / publish_rows_to_table, gdy potrzebujesz wyniku wstawienia.
Python
result = await ironflock.append_rows_to_table("sensordata", [
{"tsp": "2024-01-15T10:30:00.000Z", "temperature": 22.5},
{"tsp": "2024-01-15T10:30:01.000Z", "temperature": 22.7},
])
# result -> {"success": True, "count": 2}reportError / report_error
Zgłasza błąd aplikacji do tabeli error-logs twojej floty. Jest to wygodne opakowanie wokół publishToTable / appendToTable: oznacza wiersz wartością source: "app", poziomem ważności level oraz znacznikiem czasu, a następnie zapisuje go jak każdy normalny wiersz tabeli. Błąd trafia do tej samej tabeli error-logs, której używają błędy systemowe fleetdb (oznaczone source: "system"), więc można go odpytywać za pomocą getHistory, strumieniować za pomocą subscribeToTable / subscribe_to_table, używać w szablonach tablic oraz dostarczać w czasie rzeczywistym na transformed.error-logs — bez wyzwalania systemowego powiadomienia o błędzie platformy.
Python
# Fire-and-forget (default): publishes to the error-logs table
await ironflock.report_error("Sensor read timed out", level="warn")
# Pass an exception to capture its traceback (falls back to the message)
try:
risky_operation()
except Exception as err:
await ironflock.report_error(err)
# Use the append RPC when you want to await the insert outcome
await ironflock.report_error("Calibration failed", level="error", append=True)Parametry:
| Parametr | Typ | Opis |
|---|---|---|
error | str / string lub wyjątek / Error | Komunikat błędu lub wyjątek, którego ślad stosu (traceback/stack) lub komunikat zostaje zapisany |
level | str / string, opcjonalny | Poziom ważności: "error", "warn", "info" lub "debug". Domyślnie "error" |
append | bool / boolean, opcjonalny | Gdy true, używa zdalnego wywołania append (zwraca wynik wstawienia). Domyślnie false (publikacja typu fire-and-forget) |
tsp | str / string, opcjonalny | Nadpisanie znacznika czasu w formacie ISO-8601. Domyślnie bieżący czas |
W Pythonie opcje są argumentami nazwanymi (
report_error(error, level=..., append=..., tsp=...)); w JavaScript są przekazywane przez obiekt opcji (reportError(error, { level, append, tsp })).
publish
Publikuje wiadomość do dowolnego tematu (topic) WAMP. Użyj tego do niestandardowych wiadomości lub zdarzeń, które nie odpowiadają tabeli bazy danych.
Python
await ironflock.publish("com.myapp.alerts", {
"level": "warning",
"message": "Temperature threshold exceeded"
})Zapytywanie historii
getHistory
Pobiera historyczne dane z tabeli floty. Obsługuje filtrowanie, zakresy czasu oraz stronicowanie.
Python
# Simple query
data = await ironflock.getHistory("sensordata", {"limit": 100})
# Query with time range and filters
data = await ironflock.getHistory("sensordata", {
"limit": 500,
"offset": 0,
"timeRange": {
"start": "2026-01-01T00:00:00Z",
"end": "2026-03-01T00:00:00Z"
},
"filterAnd": [
{"column": "temperature", "operator": ">", "value": 20},
{"column": "humidity", "operator": "<=", "value": 80}
]
})
# Current value(s) only: the "latest" marker returns the newest row per entity
current = await ironflock.getHistory("sensordata", {
"limit": 100,
"filterAnd": [{"latest": True}]
})Parametry zapytania:
| Pole | Typ | Opis |
|---|---|---|
limit | int / number | Maksymalna liczba zwracanych wierszy (1–10 000, wymagane) |
offset | int / number | Przesunięcie dla stronicowania |
timeRange | dict / object | {"start": "<ISO datetime>", "end": "<ISO datetime>"} |
filterAnd | list / array | Warunki filtra AND i/lub znacznik latest (patrz niżej) |
columns | list / array | Kolumny do zwrócenia (opcjonalne). tsp, device_key oraz authid są zawsze dołączane; pomiń, aby otrzymać wszystkie kolumny |
Operatory filtra: =, !=, >, <, >=, <=, LIKE, ILIKE, IN, NOT IN, IS, IS NOT
Każdy filtr to obiekt z kluczami column, operator i value.
Odczytywanie aktualnych wartości. Wpis {"latest": true} w filterAnd nie jest warunkiem filtra, lecz przełącznikiem trybu: zaplecze danych zwraca tylko najnowszy wiersz dla każdej encji, wyznaczony w SQL na podstawie klucza encji, który tabela deklaruje za pomocą maintainLatestFlagFor. Tabela bez klucza encji zwraca swój pojedynczy, najnowszy wiersz.
Pozostałe warunki łączą się ze znacznikiem zgodnie z oczekiwaniami: warunki na kolumnach klucza encji zawężają to, które encje są zwracane, natomiast wszystkie inne warunki oraz timeRange są stosowane do wynikowych najnowszych wierszy. Dlatego połączenie {"latest": true} z filtrem na kolumnie deleted ukrywa usunięte encje, zamiast przywracać ich poprzedni wiersz.
Wcześniejsze wersje IronFlock przechowywały fizyczną kolumnę latest_flag. Kolumna ta już nie istnieje — starszy filtr latest_flag = true jest nadal akceptowany i traktowany jak ten znacznik, ale nowy kod powinien używać {"latest": true}. Znacznik latest nie jest dostępny w getSeriesHistory.
getSeriesHistory / get_series_history
Pobiera zredukowane (down-sampled) dane szeregów czasowych z tabeli floty: kolumny liczbowe agregowane w przedziały czasu (np. średnie godzinowe). Idealne do wykresów obejmujących długie zakresy czasu. Dostępne dla tabel (nie dla transformacji).
Python
series = await ironflock.get_series_history("sensordata", {
"metrics": ["temperature", "humidity"],
"method": "AVG",
"limit": 500,
"timeRange": ["2026-01-01T00:00:00Z", "2026-03-01T00:00:00Z"],
"groupBy": ["device_id"]
})Parametry zapytania:
| Pole | Typ | Opis |
|---|---|---|
metrics | list / array | Kolumny liczbowe do zredukowania |
method | str / string | Agregacja na przedział: "AVG", "SUM", "COUNT", "MIN", "MAX", "FIRST" lub "LAST" |
limit | int / number | Maksymalna liczba przedziałów (1–10 000) |
timeRange | list / array | [start, end] — łańcuchy ISO datetime lub liczby epoch-ms; null = otwarty koniec (wymagane) |
groupBy | list / array | Kolumny, według których grupować serię (opcjonalne) |
filterAnd | list / array | Warunki filtra AND (opcjonalne). Wyłącznie warunki filtra — znacznik latest nie jest tutaj obsługiwany; aby odczytać aktualne wartości, użyj getHistory |
Subskrybowanie danych
subscribeToTable / subscribe_to_table
Subskrybuje aktualizacje tabeli floty w czasie rzeczywistym. Handler jest wywoływany za każdym razem, gdy nowe dane zostają opublikowane do tabeli. Wiersze zapisane przez ścieżkę wstawiania zbiorczego (publishRowsToTable / appendRowsToTable) są dostarczane do twojego handlera pojedynczo, więc kod handlera pozostaje taki sam niezależnie od tego, jak dane zostały zapisane.
Python
def on_sensor_data(*args, **kwargs):
print("New reading:", args, kwargs)
await ironflock.subscribe_to_table("sensordata", on_sensor_data)subscribe
Subskrybuje dowolny temat (topic) WAMP do niestandardowej komunikacji w czasie rzeczywistym.
Python
def on_alert(*args, **kwargs):
print("Alert received:", args, kwargs)
await ironflock.subscribe("com.myapp.alerts", on_alert)Dostęp do danych między aplikacjami
Odczytuj dane floty innej aplikacji z poziomu własnej aplikacji, w obrębie tego samego projektu. Aplikacja dostawcy musi zadeklarować twoją aplikację w sekcji consumes: swojego pliku data-template.yml, a użytkownik projektu musi przyznać dostęp. Dostęp jest tylko do odczytu: możesz odpytywać historię oraz subskrybować w czasie rzeczywistym wiersze tabel i transformacji (transforms) udostępnianych przez dostawcę, ale nie możesz do nich zapisywać. Połączenia z konsumowanymi aplikacjami są buforowane per aplikacja i zamykane automatycznie, gdy twoja instancja zostaje zatrzymana.
Jeśli twoja aplikacja posiada wieloznaczne (wildcard) uprawnienie (consumes: [{ app: "*" }]), możesz dynamicznie wykrywać i otwierać dostawców za pomocą listConsumableApps / list_consumable_apps oraz connectToAllApps / connect_to_all_apps (poniżej).
connectToApp / connect_to_app
Otwiera połączenie tylko do odczytu z zapleczem danych innej aplikacji i zwraca uchwyt (handle). Uchwyt udostępnia getHistory / get_history, subscribeToTable / subscribe_to_table oraz getSeriesHistory / get_series_history (tylko tabele) — te same zapytania i subskrypcje, których używasz we własnych tabelach — a także close oraz udostępnione katalogi tables / transforms.
Python
# Open a read-only handle on another app's data backend
weather = await ironflock.connect_to_app("weather-app")
# Inspect what the provider shares
print([t["tablename"] for t in weather.tables])
# Query history and subscribe, just like your own tables
rows = await weather.get_history("forecasts", {"limit": 100})
def on_forecast(*args, **kwargs):
print("New forecast:", args)
await weather.subscribe_to_table("forecasts", on_forecast)Parametry:
| Parametr | Typ | Opis |
|---|---|---|
app_name / appName | str / string | Nazwa aplikacji dostawcy, zadeklarowana w twojej sekcji consumes: |
stage | str / string, opcjonalny | Etap (stage) dostawcy: "dev" lub "prod". Domyślnie etap twojej własnej aplikacji |
on_error / onError | callable, opcjonalny | Wywoływany z obiektem CrossAppAccessError, gdy dostęp zostanie odmówiony po nawiązaniu połączenia (np. gdy uprawnienie zostanie później cofnięte) |
Jeśli dostęp zostanie odmówiony lub użyty nieprawidłowo, zgłaszany (Python) / rzucany (JavaScript) jest CrossAppAccessError z polem code: NO_GRANT, PROVIDER_NOT_INSTALLED, UNKNOWN_APP, PRIVATE_TABLE lub NOT_AUTHORIZED.
W Pythonie
stageion_errorsą argumentami nazwanymi; w JavaScript są przekazywane przez obiekt opcji (connectToApp(appName, { stage, onError })).
listConsumableApps / list_consumable_apps
Wyświetla listę wszystkich niepublicznych dostawców w projekcie — prymityw wykrywania dla aplikacji posiadających wieloznaczne uprawnienie do konsumpcji (consumes: [{ app: "*" }] w twoim pliku data-template.yml, przyznane przez użytkownika projektu). Wykonuje pojedyncze wywołanie i nie otwiera żadnych połączeń: wyrenderuj zwrócone katalogi w selektorze, a następnie wywołaj connectToApp / connect_to_app dla tych, które chcesz otworzyć — albo connectToAllApps / connect_to_all_apps, aby otworzyć je wszystkie naraz.
Uwaga: Zadeklaruj uprawnienie w pliku
data-template.ymlswojej aplikacji i ujmij*w cudzysłów — samo*jest aliasem YAML i nie zostanie sparsowane:
consumes:
- app: "*"Python
providers = await ironflock.list_consumable_apps()
for p in providers:
print(p["app"], list(p["stages"].keys())) # e.g. "weather-app" ["dev", "prod"]Każdy wpis opisuje jednego dostawcę:
| Pole | Typ | Opis |
|---|---|---|
app | str / string | Nazwa aplikacji dostawcy |
provider_app_key | int / number | Klucz aplikacji dostawcy (app key) |
stages | dict / object | Katalog per etap { dev?, prod? }; etap jest obecny tylko wtedy, gdy dostawca ma dla niego zaplecze danych. Każdy katalog zawiera niepubliczne tables oraz transforms, które udostępnia |
Zgłaszany (Python) / rzucany (JavaScript) jest CrossAppAccessError z code: NO_GRANT, jeśli twoja aplikacja nie posiada uprawnienia wieloznacznego.
connectToAllApps / connect_to_all_apps
Otwiera uchwyty tylko do odczytu dla wszystkich niepublicznych dostawców w projekcie w jednym wywołaniu (tylko dla konsumentów wieloznacznych). Wylicza dostawców za pomocą listConsumableApps / list_consumable_apps i otwiera każdego z nich, pomijając tych, którzy nie mają zaplecza danych dla żądanego etapu. Każdy uchwyt jest buforowany pod tym samym kluczem co connectToApp / connect_to_app, więc późniejsze wywołanie connectToApp(name) zwraca już otwarty uchwyt. Zwrócone uchwyty są zamykane razem, gdy twoja instancja zostaje zatrzymana.
Python
apps = await ironflock.connect_to_all_apps(
on_error=lambda err: print("Provider skipped:", err)
)
for app in apps:
rows = await app.get_history(app.tables[0]["tablename"], {"limit": 10})
print(app.app, rows)Parametry:
| Parametr | Typ | Opis |
|---|---|---|
stage | str / string, opcjonalny | Etap (stage) dostawcy: "dev" lub "prod". Domyślnie etap twojej własnej aplikacji |
continue_on_error / continueOnError | bool / boolean, opcjonalny | Gdy true (wartość domyślna), dostawca, którego nie udało się otworzyć, jest zgłaszany do on_error / onError i pomijany w wyniku. Gdy false, pierwszy błąd jest zgłaszany/rzucany |
on_error / onError | callable, opcjonalny | Wywoływany z każdym dostawcą, którego nie udało się otworzyć (gdy continue_on_error / continueOnError ma wartość true), oraz z obiektem CrossAppAccessError, jeśli dostęp do już otwartego połączenia zostanie później odmówiony (np. gdy uprawnienie zostanie cofnięte) |
Zwraca pomyślnie otwarte uchwyty dostawców (ten sam typ uchwytu co connectToApp / connect_to_app). Zgłaszany (Python) / rzucany (JavaScript) jest CrossAppAccessError z code: NO_GRANT, jeśli twoja aplikacja nie posiada uprawnienia wieloznacznego.
W Pythonie
stage,on_erroricontinue_on_errorsą argumentami nazwanymi; w JavaScript są przekazywane przez obiekt opcji (connectToAllApps({ stage, onError, continueOnError })).
Zarządzane przechowywanie plików
Każde zaplecze danych aplikacji otrzymuje obok swoich tabel prywatny magazyn obiektów, dostępny przez właściwość files. Używaj go do obrazów, plików PDF, klatek z kamery, obrazów firmware — wszystkiego, co nie należy do wiersza tabeli. Nie jest wymagana żadna konfiguracja: aplikacja bez sekcji files: w swoim szablonie danych i tak otrzymuje jedną przestrzeń nazw o nazwie default.
Kluczowa idea jest taka, że zapisanie obiektu zwraca ci od razu trwały adres URL, który możesz wpisać wprost do kolumny tabeli, dzięki czemu widget na panelu może go wyrenderować bez żadnej dodatkowej pracy:
Python
# Store an object and get a permanent URL back in the same call
info = await ironflock.files.put("part-1.jpg", jpeg_bytes, content_type="image/jpeg")
# The URL is safe to store in a table column — a dashboard widget can then
# render <img src="{{photo_url}}"> without any extra round trip
await ironflock.publish_to_table("inspections", part_id="1", photo_url=info.url)
# Read it back
data = await ironflock.files.get("part-1.jpg")
# Walk every object under a prefix (pages are fetched for you)
async for obj in ironflock.files.iter(prefix="2026/"):
print(obj.key, obj.size)Ten adres URL nigdy nie wygasa, ale nie jest linkiem publicznym: pozostaje czytelny wyłącznie dla uwierzytelnionego żądającego, który posiada uprawnienie READ do tego zaplecza danych, a proxy uwierzytelniające sprawdza to ponownie przy każdym żądaniu. Dlatego można go bezpiecznie przechowywać w bazie danych.
Przestrzenie nazw
Przestrzeń nazw to prefiks klucza, z którym powiązane są zasady — retencja, reguły udostępniania, dozwolone typy zawartości. Nie jest to osobny bucket; wszystkie przestrzenie nazw aplikacji znajdują się w jej jednym obszarze przechowywania. Deklaruj nową tylko wtedy, gdy zbiór obiektów wymaga innych reguł; w przeciwnym razie pozostań w default i porządkuj obiekty ścieżkami kluczy w rodzaju 2026/03/part-1.jpg.
Dodatkowe przestrzenie nazw deklarujesz w data-template.yml:
files:
# Storage budget the app suggests for itself. The project user can change it,
# and their setting is the one that gets enforced.
quotaBytes: 5368709120
namespaces:
- name: frames
description: Raw camera frames, one JPEG per inspected part.
contentTypes: ["image/jpeg"]
maxObjectBytes: 20971520
retention: { deleteAfter: 30 days }Zwróć uwagę, że budżet deklaruje się raz dla całej aplikacji, a nie osobno dla każdej przestrzeni nazw. Przestrzeń nazw to tylko prefiks klucza wewnątrz jednego obszaru przechowywania aplikacji, więc budżet per prefiks nie miałby czego egzekwować. maxObjectBytes jest ustawiany per przestrzeń nazw — ogranicza pojedynczy obiekt, a nie sumę.
Każda z poniższych metod przyjmuje przestrzeń nazw jako argument opcjonalny, a domyślnie używa default.
Zapisywanie i odczytywanie obiektów
Python
# Bytes in, bytes out
info = await ironflock.files.put("reports/march.pdf", pdf_bytes, content_type="application/pdf")
data = await ironflock.files.get("reports/march.pdf")
# Or straight from/to a local file — these stream on the large-object path,
# so a multi-gigabyte file never has to fit in memory
await ironflock.files.put_file("firmware/v2.bin", "/data/build/v2.bin")
await ironflock.files.get_to_file("firmware/v2.bin", "/tmp/v2.bin")| Metoda | Opis |
|---|---|
put(key, data, …) | Zapisuje obiekt (bytes w Pythonie, Uint8Array w JavaScript). Zwraca metadane obiektu wraz z jego url |
get(key, namespace?) | Zwraca zawartość obiektu |
put_file(key, path, …) / get_to_file(key, path, …) | Tylko Python. Zapis z pliku lokalnego lub do pliku lokalnego. Strumieniuje na ścieżce dużych obiektów |
delete(key, namespace?) | Usuwa obiekt |
copy(key, to, …) | Kopiuje obiekt, opcjonalnie do innej przestrzeni nazw |
move(key, to, …) | Kopiowanie, a następnie usunięcie. Nie jest atomowe — usługa nie ma operacji przeniesienia, więc nieudane usunięcie pozostawia obie kopie |
JavaScript nie ma pomocników operujących na ścieżkach plików, ponieważ pakiet dostarcza jedną kompilację zarówno dla Node, jak i dla przeglądarki — pliki lokalne odczytuj i zapisuj samodzielnie za pomocą fs.
put przyjmuje: content_type / contentType (typ MIME; przestrzeń nazw może ograniczać dozwolone wartości) oraz namespace. W Pythonie są to argumenty nazwane; w JavaScript trafiają do obiektu opcji.
Wyświetlanie listy i inspekcja
| Metoda | Opis |
|---|---|
list(…) | Jedna strona obiektów. Zwraca objects, prefixes, is_truncated / isTruncated oraz cursor, który przekazujesz z powrotem po następną stronę |
iter(…) / iterate(…) | Asynchroniczny iterator po każdym obiekcie pod danym prefiksem, stronicujący automatycznie. W Pythonie nazywa się iter, w JavaScript iterate |
stat(key, namespace?) | Metadane jednego obiektu bez przesyłania jego zawartości |
exists(key, namespace?) | Czy obiekt istnieje |
namespaces() | Przestrzenie nazw, których ta aplikacja może używać |
usage(…) | Ile magazynu zajmuje aplikacja — patrz niżej |
catalog() | Przestrzenie nazw wraz z limitami i przydziałami wystawionymi przez serwer. Buforowane po pierwszym wywołaniu |
Obiekty są opisywane tymi samymi polami w obu SDK, w stylu nazewnictwa właściwym dla danego języka: namespace, key, size, etag, content_type / contentType, last_modified / lastModified, checksum_sha256 / checksumSha256 oraz url.
Wykorzystanie magazynu i przydział
usage odpowiada prosto z magazynu obiektów w jednym wywołaniu, więc sumy są dokładne, a nie doliczane przez SDK:
Python
u = await ironflock.files.usage()
print(u.size_bytes, u.object_count, u.quota_bytes, u.free_bytes)
# Break the total down per namespace (costs one listing per namespace)
detailed = await ironflock.files.usage(detail=True)
print(detailed.per_namespace) # {"default": 1048576, "frames": 73400320}| Pole | Znaczenie |
|---|---|
size_bytes / sizeBytes | Aktualnie przechowywane bajty |
object_count / objectCount | Liczba przechowywanych obiektów |
quota_bytes / quotaBytes | Egzekwowany budżet. 0 oznacza brak limitu |
free_bytes / freeBytes | Pozostałe bajty. -1 oznacza brak limitu — zgłoszenie tu 0 czytałoby się jako „pełny” |
per_namespace / perNamespace | Bajty w rozbiciu na przestrzenie nazw. Obecne tylko wtedy, gdy poprosisz o szczegółowe rozbicie |
Rozbicie na przestrzenie nazw jest domyślnie wyłączone, ponieważ magazyn nie potrafi na nie odpowiedzieć wprost: rozlicza się per obszar przechowywania, a przestrzeń nazw to tylko prefiks, więc SDK musi wylistować każdą przestrzeń nazw i zsumować rozmiary. Proś o nie wtedy, gdy naprawdę go potrzebujesz, a nie na gorącej ścieżce.
Pojawiają się dwa różne przydziały i warto je od siebie odróżniać. catalog() raportuje oba:
| Pole | Znaczenie |
|---|---|
quota_bytes / quotaBytes | To, co jest faktycznie egzekwowane, odczytane z magazynu obiektów — ustawienie użytkownika projektu |
suggested_quota_bytes / suggestedQuotaBytes | To, o co poprosił szablon danych aplikacji. 0, jeśli o nic nie prosił |
Różnią się zawsze wtedy, gdy użytkownik podniósł lub obniżył budżet aplikacji — i właśnie dlatego wartość egzekwowana jest odczytywana z magazynu, a nie z szablonu: ponowne wdrożenie aplikacji nie może po cichu zresetować wyboru użytkownika. Interfejs może pokazywać obie („aplikacja sugeruje X, Ty ustawiłeś Y”). Egzekwowanie zawsze korzysta z tej pierwszej.
Udostępnianie obiektów
Istnieją dwa rodzaje linków, a różnica między nimi ma znaczenie:
| Metoda | Czas życia | Kto może go odczytać |
|---|---|---|
url(key, …) | Trwały | Wyłącznie uwierzytelniony żądający z uprawnieniem READ do tego zaplecza danych — sprawdzanym ponownie przy każdym żądaniu. Można go bezpiecznie przechowywać w kolumnie tabeli |
share_url / shareUrl | Wygasający (domyślnie 15 min, ograniczany przez serwer) | Każdy, kto ma ten link. Nic nie sprawdza ponownie uprawnień w chwili jego użycia |
share_url / shareUrl to poświadczenie na okaziciela: przekaż je osobie, która potrzebuje tymczasowego dostępu, i nie przechowuj go w bazie danych. Do wszystkiego, co renderuje panel, używaj url.
url zwraca None / undefined tam, gdzie wdrożenie nie ma krawędzi HTTP (na przykład Appliance działający po zwykłym HTTP) — to sygnał, aby sięgnąć po get. Przekazanie etag obiektu jako argumentu version pozwala przeglądarkom buforować odpowiedź jako niezmienną.
upload_url / uploadUrl tworzy wygasający adres URL, który przyjmuje bezpośrednie przesłanie danych, i zwraca url, method, headers oraz expires_in / expiresIn. Wyślij dokładnie te nagłówki, które zwróci, w przeciwnym razie podpis nie zostanie zweryfikowany.
Duże obiekty
SDK wybiera sposób transferu automatycznie, na podstawie rozmiaru — nie ma tu nic do konfigurowania:
| Rozmiar obiektu | Sposób transferu |
|---|---|
| Do limitu inline (obecnie 6 MiB) | Pojedyncze wywołanie przez router wiadomości |
| Powyżej | Bezpośrednio do magazynu obiektów przez HTTPS, z pominięciem routera |
Dokładny limit serwer podaje w czasie działania jako inline_max_bytes / inlineMaxBytes w catalog(), dzięki czemu można go podnieść bez wydawania nowej wersji SDK.
Pozostają dwa górne pułapy i oba zgłaszają TOO_LARGE z przyczyną wskazującą, na który z nich trafiłeś:
- 5 GiB — limit pojedynczego przesłania w magazynie obiektów. Przesyłanie wieloczęściowe (multipart) nie jest jeszcze zaimplementowane.
- Limit inline tam, gdzie nie ma bezpośredniego punktu końcowego — Appliance odcięty od sieci (air-gapped) w ogóle nie może przesłać dużego obiektu. Nie pomoże tu ani ponowienie próby, ani mniejszy fragment, i komunikat wprost o tym informuje.
Ścieżka bezpośrednia wymaga, aby urządzenie miało dostęp do hosta magazynu obiektów, a nie tylko do routera. Dwa niepowodzenia częste w terenie mają własne kody, zamiast wyglądać jak problemy z autoryzacją: PRESIGN_UNREACHABLE (proxy przepuszczające wyłącznie ruch do routera) oraz CLOCK_SKEW (magazyn obiektów odrzuca żądania rozbieżne w czasie o więcej niż 15 minut — sprawdź NTP na urządzeniu).
Błędy przechowywania plików
Każda operacja na plikach zgłasza (Python) / rzuca (JavaScript) FileStoreError, który niesie stabilny code oraz czytelny dla człowieka reason. Rozgałęziaj logikę na podstawie code, nigdy na podstawie reason.
Python
from ironflock.filestore import FileStoreError
try:
await ironflock.files.put("huge.bin", payload)
except FileStoreError as e:
if e.code == "QUOTA_EXCEEDED":
print("Filestore is full:", e.reason)
else:
raise| Kod | Znaczenie |
|---|---|
NOT_AUTHORIZED | Wywołujący nie może wykonać tej operacji |
NO_SUCH_NAMESPACE | Przestrzeń nazw nie jest zadeklarowana w szablonie danych |
NO_SUCH_OBJECT | Klucz nie istnieje |
TOO_LARGE | Przekracza limit transferu w pojedynczym wywołaniu |
OBJECT_TOO_LARGE | Przekracza własny maxObjectBytes przestrzeni nazw |
QUOTA_EXCEEDED | Magazyn plików jest pełny |
CONTENT_TYPE_NOT_ALLOWED | Przestrzeń nazw ogranicza contentTypes |
NOT_SUPPORTED | Zaplecze nie potrafi tego wykonać |
NOT_AVAILABLE | To wdrożenie nie ma usługi plików |
PRESIGN_UNREACHABLE | Magazyn obiektów nie jest osiągalny bezpośrednio (proxy?) |
CLOCK_SKEW | Zegar urządzenia jest zbyt rozbieżny |
INTERNAL | Wszystko pozostałe |
Nowszy serwer może wprowadzić kody, których to wydanie SDK nie zna. Są one przekazywane dalej jako code, zamiast być sprowadzane do jednej wartości, więc traktuj nierozpoznaną wartość jako ogólne niepowodzenie.
W Pythonie
FileStoreErrorjest importowany zironflock.filestore; w JavaScript jest eksportowany z katalogu głównego pakietu (import { FileStoreError } from "ironflock").
Komunikacja między urządzeniami
registerDeviceFunction / register_device_function
Rejestruje procedurę, którą mogą wywoływać inne urządzenia w tym samym projekcie. SDK automatycznie przypisuje procedurę do przestrzeni nazw bieżącego urządzenia.
Python
def add(a, b):
return a + b
await ironflock.register_device_function("com.myapp.add", add)
register()jest aliasem dlaregister_device_function().
callDeviceFunction / call_device_function
Wywołuje procedurę zarejestrowaną przez inne urządzenie. SDK automatycznie składa pełny temat (topic) WAMP, używając klucza docelowego urządzenia.
Python
result = await ironflock.call_device_function(
42, # target device key
"com.myapp.add", # procedure name
args=[3, 5] # arguments
)
print(result) # 8call
Wywołuje zdalną procedurę, używając pełnego identyfikatora URI WAMP. Użyj tego do bezpośrednich wywołań, gdy znasz dokładny temat (topic).
Python
result = await ironflock.call("some.full.wamp.topic", args=[42])Metadane urządzenia
setDeviceLocation / set_device_location
Aktualizuje lokalizację GPS urządzenia w platformie. Zmiany są odzwierciedlane w czasie rzeczywistym na mapach IronFlock.
Python
await ironflock.set_device_location(long=8.6821, lat=50.1109)| Parametr | Zakres |
|---|---|
long | -180 do 180 |
lat | -90 do 90 |
Historia lokalizacji nie jest przechowywana. Aby śledzić lokalizację w czasie, utwórz dedykowaną tabelę i użyj
publish_to_table/publishToTable.
getRemoteAccessUrlForPort
Zwraca publiczny adres URL zdalnego dostępu dla danego portu na urządzeniu.
Python
url = ironflock.getRemoteAccessUrlForPort(8080)
# "https://<device_key>-<app_name>-8080.app.ironflock.com"Właściwości i cykl życia połączenia
Python
| Właściwość | Typ | Opis |
|---|---|---|
is_connected | bool | Czy połączenie z platformą jest aktywne |
connection | CrossbarConnection | Bazowa instancja połączenia (zastosowania zaawansowane) |
| Metoda | Opis |
|---|---|
run() | Uruchamia połączenie i wykonuje mainFunc (blokujące) |
await start() | Uruchamia połączenie asynchronicznie |
await stop() | Zatrzymuje połączenie i anuluje uruchomione zadania |
await run_async() | Uruchamia i utrzymuje połączenie działające asynchronicznie |
Obsługa błędów
Każda metoda SDK zgłasza niepowodzenie głośno: przy nieprawidłowych argumentach, utraconym połączeniu lub odrzuceniu przez platformę zgłasza wyjątek (Python) lub odrzuca obietnicę (JavaScript) z komunikatem wskazującym operację, temat (topic) oraz przyczynę. Nic nie jest po cichu pomijane, więc opakuj w blok try te wywołania, które mają przetrwać błąd.
Python
try:
rows = await ironflock.getHistory("sensordata", {"limit": 100})
except ValueError as e:
# Invalid parameters — e.g. limit out of range, or a malformed filter
print(f"Bad query: {e}")
except RuntimeError as e:
# Not connected, table not in the data-template, or the platform rejected the call
print(f"Query failed: {e}")W JavaScript niepowodzenia pochodzące z platformy są instancjami WampError — zwykłej podklasy Error, która dodatkowo przenosi identyfikator URI błędu WAMP w polu error oraz ładunek błędu w polach args / kwargs. Wszystko pozostałe (nieprawidłowe parametry, brak połączenia) to zwykły Error.
Migracja: starsze wersje SDK zapisywały komunikat w logu i zwracały
None/null, gdy wywołanie się nie powiodło. Teraz zamiast tego zgłaszają błąd, więc kod w rodzajuif result is None:nie wykrywa już niepowodzeń — użyjtry/except(lubtry/catch).
Użycie w przeglądarce (tylko JavaScript)
SDK JavaScript działa w nowoczesnych przeglądarkach. Ponieważ przeglądarki nie mają zmiennych środowiskowych, przekaż całą konfigurację przez konstruktor:
import { IronFlock } from "ironflock";
const ironflock = new IronFlock({
serialNumber: "device-serial-from-server",
deviceKey: "my-device-key",
appName: "MyWebApp",
swarmKey: 10,
appKey: 20,
env: "PROD",
});
await ironflock.start();
await ironflock.publishToTable("sensordata", [{ temperature: 22 }]);Użyj IronFlock.fromServer(), aby pobrać konfigurację z twojego zaplecza (backend) zamiast wpisywać dane uwierzytelniające na stałe:
const ironflock = await IronFlock.fromServer("/api/ironflock-config");
await ironflock.start();Twój punkt końcowy zaplecza powinien zwrócić obiekt JSON z opcjami połączenia (serialNumber, deviceKey, appName, swarmKey, appKey, env).
Rejestrowanie funkcji agentów AI
SDK może rejestrować funkcje, które mogą być wywoływane przez agentów AI. Zarejestruj procedurę i odwołaj się do jej tematu (topic) w pliku ai-template.yml:
Python
def get_sensor_reading(sensor_id):
"""Returns the latest reading from a sensor."""
reading = read_from_hardware(sensor_id)
return {
"sensor_id": sensor_id,
"temperature": reading.temp,
"humidity": reading.hum,
"timestamp": reading.ts
}
await ironflock.register_device_function("sensors.get_latest", get_sensor_reading)Agent AI może następnie wywołać tę funkcję, gdy użytkownik zada pytanie wymagające bieżących danych z czujnika.
Aby połączyć zarejestrowany temat (topic) WAMP z agentem AI, odwołaj się do niego w pliku .ironflock/ai-template.yml swojej aplikacji:
sensor_agent:
tool_description: |
Delegate to this agent when the user asks about sensor readings,
live device data, or current environmental conditions.
system_prompt: |
You are a sensor data specialist. Use get_current to retrieve
the latest reading from any sensor. Always include the unit in
your response.
main: true
max_context_tokens: 30000
max_iterations: 5
tools:
get_current:
description: Returns the latest reading from a sensor.
topic: sensors.get_latest
parameters:
sensor_id:
type: string
description: The sensor identifier to query.
required: trueWartość topic (sensors.get_latest) musi pasować do nazwy przekazanej do register_device_function / registerDeviceFunction w twoim kodzie brzegowym. IronFlock automatycznie kieruje wywołanie do urządzenia, na którym zarejestrowana jest funkcja.
Pełną dokumentację pliku ai-template.yml znajdziesz w Definiowanie agentów i narzędzi.
Zmienne środowiskowe
Te zmienne są ustawiane automatycznie przez środowisko uruchomieniowe IronFlock wewnątrz kontenerów aplikacji:
| Zmienna | Opis |
|---|---|
DEVICE_NAME | Wyświetlana nazwa urządzenia |
DEVICE_SERIAL_NUMBER | Unikalny, niezmienny identyfikator urządzenia |
DEVICE_KEY | Klucz urządzenia do uwierzytelniania |
SWARM_KEY | Identyfikator projektu |
APP_KEY | Identyfikator aplikacji |
APP_NAME | Nazwa aplikacji |
ENV | Środowisko: DEV lub PROD |
Python
import os
device_name = os.environ.get("DEVICE_NAME")
serial = os.environ.get("DEVICE_SERIAL_NUMBER")
project_key = os.environ.get("SWARM_KEY")