IronFlock SDK
IronFlock SDK는 엣지 애플리케이션이 IronFlock 플랫폼과 상호작용할 수 있게 합니다. 등록된 디바이스에서 실행될 때 자동으로 인증을 처리하고 데이터 발행, 이력 조회, 디바이스 간 원격 프로시저 호출, 디바이스 메타데이터 업데이트 기능을 제공합니다.
| SDK | 패키지 | 요구 사항 |
|---|---|---|
| Python | ironflock on PyPI | Python 3.8+ |
| JavaScript | ironflock on npm | Node.js 18+ 또는 최신 브라우저 |
설치
Python
pip install ironflock또는 앱의 requirements.txt에 ironflock을 추가하십시오.
빠른 시작
Python
import asyncio
from ironflock import IronFlock
async def main():
while True:
await ironflock.publish_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60
})
await asyncio.sleep(5)
ironflock = IronFlock(mainFunc=main)
ironflock.run()IronFlock 앱 컨테이너 내에서 사용할 때 SDK는 환경에서 자동으로 연결 자격 증명을 읽습니다 — 수동 구성이 필요 없습니다.
생성자 옵션
Python
ironflock = IronFlock(
mainFunc=main, # 연결 후 실행할 비동기 함수
serial_number="abc123" # 디바이스 시리얼 재정의 (선택 사항)
)| 파라미터 | 설명 |
|---|---|
mainFunc | 연결이 설정된 후 실행되는 비동기 함수 |
serial_number | 디바이스 시리얼 번호 재정의. 기본값은 DEVICE_SERIAL_NUMBER 환경 변수 |
데이터 발행
publishToTable / publish_to_table
플릿 테이블에 데이터 레코드를 발행합니다. 테이블 이름은 앱의 data-template.yml에 정의된 테이블과 일치해야 합니다. SDK는 자동으로 데이터를 올바른 프로젝트 데이터베이스로 라우팅합니다.
Python
await ironflock.publish_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60,
"device_id": "sensor-001"
})appendToTable / append_to_table
pub/sub 대신 원격 프로시저 호출을 사용하여 플릿 테이블에 데이터를 추가합니다. 데이터가 저장되었음을 확인이 필요한 경우 이 방법을 사용하십시오.
Python
result = await ironflock.append_to_table("sensordata", {
"temperature": 22.5,
"humidity": 60
})publishRowsToTable / publish_rows_to_table
플릿 테이블에 단일 메시지로 여러 행(대량 삽입)을 발행합니다. 플랫폼은 전체 배치를 단일 작업으로 원자적으로(전부 아니면 전무) 삽입합니다. 행마다 한 번의 왕복이 너무 비용이 큰 고빈도 데이터에 이 방법을 사용하십시오. publishToTable과 마찬가지로 이는 fire-and-forget 방식입니다 — 확인 응답은 라우터로의 전달을 보장하는 것이지 데이터베이스 삽입을 보장하는 것은 아닙니다.
Python
await ironflock.publish_rows_to_table("sensordata", [
{"tsp": "2024-01-15T10:30:00.000Z", "temperature": 22.5},
{"tsp": "2024-01-15T10:30:01.000Z", "temperature": 22.7},
])두 번째 인수는 삽입할 행 객체의 비어 있지 않은 목록입니다.
appendRowsToTable / append_rows_to_table
플릿 테이블에 단일 원격 프로시저 호출로 여러 행(대량 삽입)을 추가합니다. 플랫폼은 전체 배치를 원자적으로(전부 아니면 전무) 삽입합니다. 하나의 행이라도 유효하지 않으면 전체 배치가 거부되며 아무것도 저장되지 않습니다. 삽입 결과가 필요한 경우 publishRowsToTable / publish_rows_to_table보다 이 방법을 사용하십시오.
Python
result = await ironflock.append_rows_to_table("sensordata", [
{"tsp": "2024-01-15T10:30:00.000Z", "temperature": 22.5},
{"tsp": "2024-01-15T10:30:01.000Z", "temperature": 22.7},
])
# result -> {"success": True, "count": 2}reportError / report_error
애플리케이션 오류를 플릿의 error-logs 테이블에 기록합니다. 이는 publishToTable / appendToTable을 감싼 편의 래퍼입니다. 행에 source: "app", 심각도 level, 타임스탬프를 찍은 다음 일반 테이블 행과 동일하게 작성합니다. 이 오류는 fleetdb 시스템 오류가 사용하는 것과 동일한 error-logs 테이블(source: "system"으로 태그됨)에 기록되므로, getHistory로 조회할 수 있고, subscribeToTable / subscribe_to_table로 스트리밍할 수 있으며, 보드 템플릿에서 사용할 수 있고, transformed.error-logs로 실시간 전달됩니다. 이때 플랫폼의 시스템 오류 토스트는 발생시키지 않습니다.
Python
# Fire-and-forget (default): publishes to the error-logs table
await ironflock.report_error("Sensor read timed out", level="warn")
# Pass an exception to capture its traceback (falls back to the message)
try:
risky_operation()
except Exception as err:
await ironflock.report_error(err)
# Use the append RPC when you want to await the insert outcome
await ironflock.report_error("Calibration failed", level="error", append=True)파라미터:
| 파라미터 | 유형 | 설명 |
|---|---|---|
error | str / string 또는 예외 / Error | 오류 메시지 또는 트레이스백/스택(또는 메시지)이 기록되는 예외 |
level | str / string, 선택 | 심각도: "error", "warn", "info" 또는 "debug". 기본값은 "error" |
append | bool / boolean, 선택 | true인 경우 append RPC를 사용합니다(삽입 결과를 반환). 기본값은 false(fire-and-forget 발행) |
tsp | str / string, 선택 | ISO-8601 타임스탬프 재정의. 기본값은 현재 시간 |
Python에서는 옵션이 키워드 인자입니다(
report_error(error, level=..., append=..., tsp=...)). JavaScript에서는 옵션 객체를 통해 전달됩니다(reportError(error, { level, append, tsp })).
publish
모든 WAMP 토픽에 메시지를 발행합니다. 데이터베이스 테이블에 매핑되지 않는 커스텀 메시징 또는 이벤트에 이 방법을 사용하십시오.
Python
await ironflock.publish("com.myapp.alerts", {
"level": "warning",
"message": "온도 임계값 초과"
})이력 데이터 조회
getHistory
플릿 테이블에서 이력 데이터를 가져옵니다. 필터링, 시간 범위, 페이지네이션을 지원합니다.
Python
# 단순 조회
data = await ironflock.getHistory("sensordata", {"limit": 100})
# 시간 범위 및 필터를 사용한 조회
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}
]
})
# 현재 값만 조회: "latest" 마커는 엔티티별 최신 행을 반환합니다
current = await ironflock.getHistory("sensordata", {
"limit": 100,
"filterAnd": [{"latest": True}]
})쿼리 파라미터:
| 필드 | 유형 | 설명 |
|---|---|---|
limit | int / number | 반환할 최대 행 수 (1–10,000, 필수) |
offset | int / number | 페이지네이션 오프셋 |
timeRange | dict / object | {"start": "<ISO datetime>", "end": "<ISO datetime>"} |
filterAnd | list / array | AND 필터 조건 및/또는 latest 마커 (아래 참조) |
columns | list / array | 반환할 컬럼 (선택). tsp, device_key, authid는 항상 포함됩니다. 생략하면 모든 컬럼을 반환합니다 |
필터 연산자: =, !=, >, <, >=, <=, LIKE, ILIKE, IN, NOT IN, IS, IS NOT
각 필터는 column, operator, value 키가 있는 객체입니다.
현재 값 조회. filterAnd의 {"latest": true} 항목은 필터 조건이 아니라 모드 스위치입니다. 데이터 백엔드는 테이블이 maintainLatestFlagFor로 선언한 엔티티 키를 기준으로 SQL에서 도출한, 엔티티별 최신 행만 반환합니다. 엔티티 키가 없는 테이블은 가장 최근 행 하나를 반환합니다.
다른 조건은 예상대로 이 마커와 조합됩니다. 엔티티 키 컬럼에 대한 조건은 어떤 엔티티가 반환되는지를 좁히고, 그 외의 모든 조건과 timeRange는 그 결과로 얻은 최신 행에 적용됩니다. 따라서 {"latest": true}를 deleted 필터와 조합하면 삭제된 엔티티의 이전 행이 다시 나타나는 대신 해당 엔티티가 숨겨집니다.
이전 버전의 IronFlock은 물리적 latest_flag 컬럼을 저장했습니다. 이 컬럼은 더 이상 존재하지 않습니다 — 레거시 latest_flag = true 필터는 여전히 허용되며 이 마커와 동일하게 처리되지만, 새 코드에서는 {"latest": true}를 사용해야 합니다. latest 마커는 getSeriesHistory에서는 사용할 수 없습니다.
getSeriesHistory / get_series_history
플릿 테이블에서 다운샘플링된 시계열 데이터를 가져옵니다. 숫자 컬럼을 시간 버킷으로 집계합니다(예: 시간별 평균). 긴 기간에 걸친 차트에 이상적입니다. 테이블에서 사용할 수 있습니다(transform에서는 불가).
Python
series = await ironflock.get_series_history("sensordata", {
"metrics": ["temperature", "humidity"],
"method": "AVG",
"limit": 500,
"timeRange": ["2026-01-01T00:00:00Z", "2026-03-01T00:00:00Z"],
"groupBy": ["device_id"]
})쿼리 파라미터:
| 필드 | 유형 | 설명 |
|---|---|---|
metrics | list / array | 다운샘플링할 숫자 컬럼 |
method | str / string | 버킷별 집계: "AVG", "SUM", "COUNT", "MIN", "MAX", "FIRST" 또는 "LAST" |
limit | int / number | 버킷의 최대 개수(1–10,000) |
timeRange | list / array | [start, end] — ISO 날짜/시간 문자열 또는 epoch-ms 숫자. null = 열린 끝(필수) |
groupBy | list / array | 시리즈를 그룹화할 컬럼(선택) |
filterAnd | list / array | AND 필터 조건(선택). 필터 조건만 가능합니다 — latest 마커는 여기서 지원되지 않으며, 현재 값을 조회하려면 getHistory를 사용하십시오 |
데이터 구독
subscribeToTable / subscribe_to_table
플릿 테이블의 실시간 업데이트를 구독합니다. 테이블에 새 데이터가 발행될 때마다 핸들러가 호출됩니다. 대량 삽입 경로(publishRowsToTable / appendRowsToTable)를 통해 작성된 행은 한 번에 하나씩 핸들러로 전달되므로, 데이터가 어떻게 작성되었는지와 관계없이 핸들러 코드는 동일하게 유지됩니다.
Python
def on_sensor_data(*args, **kwargs):
print("새 판독값:", args, kwargs)
await ironflock.subscribe_to_table("sensordata", on_sensor_data)subscribe
커스텀 실시간 메시징을 위해 모든 WAMP 토픽을 구독합니다.
Python
def on_alert(*args, **kwargs):
print("알람 수신:", args, kwargs)
await ironflock.subscribe("com.myapp.alerts", on_alert)앱 간 데이터 접근
같은 프로젝트 내에서 다른 앱의 플릿 데이터를 자신의 앱에서 읽습니다. 제공자 앱은 자신의 data-template.yml의 consumes: 섹션에 당신의 앱을 선언해야 하며, 프로젝트 사용자가 접근 권한을 부여해야 합니다. 접근은 읽기 전용입니다. 제공자가 공유하는 테이블과 transform의 이력을 조회하고 행을 실시간으로 구독할 수 있지만, 여기에 쓸 수는 없습니다. 소비 대상 앱에 대한 연결은 앱별로 캐시되며 인스턴스가 중지되면 자동으로 닫힙니다.
앱이 와일드카드 권한(consumes: [{ app: "*" }])을 가지고 있으면, listConsumableApps / list_consumable_apps와 connectToAllApps / connect_to_all_apps(아래)로 제공자를 동적으로 발견하고 열 수 있습니다.
connectToApp / connect_to_app
다른 앱의 데이터 백엔드에 대한 읽기 전용 연결을 열고 핸들을 반환합니다. 핸들은 getHistory / get_history, subscribeToTable / subscribe_to_table, getSeriesHistory / get_series_history(테이블 전용)를 제공합니다 — 자신의 테이블에서 사용하는 것과 동일한 조회 및 구독입니다 — 여기에 더해 close와 공유된 tables / transforms 카탈로그도 제공합니다.
Python
# Open a read-only handle on another app's data backend
weather = await ironflock.connect_to_app("weather-app")
# Inspect what the provider shares
print([t["tablename"] for t in weather.tables])
# Query history and subscribe, just like your own tables
rows = await weather.get_history("forecasts", {"limit": 100})
def on_forecast(*args, **kwargs):
print("New forecast:", args)
await weather.subscribe_to_table("forecasts", on_forecast)파라미터:
| 파라미터 | 유형 | 설명 |
|---|---|---|
app_name / appName | str / string | consumes: 섹션에 선언된 제공자 앱 이름 |
stage | str / string, 선택 | 제공자 스테이지: "dev" 또는 "prod". 기본값은 자신의 앱 스테이지 |
on_error / onError | callable, 선택 | 연결이 수립된 후 접근이 거부될 경우(예: 권한이 나중에 취소된 경우) CrossAppAccessError와 함께 호출됩니다 |
접근이 거부되거나 잘못 사용되면 code 필드를 가진 CrossAppAccessError가 발생(Python)/throw(JavaScript)됩니다: NO_GRANT, PROVIDER_NOT_INSTALLED, UNKNOWN_APP, PRIVATE_TABLE 또는 NOT_AUTHORIZED.
Python에서는
stage와on_error가 키워드 인자입니다. JavaScript에서는 옵션 객체를 통해 전달됩니다(connectToApp(appName, { stage, onError })).
listConsumableApps / list_consumable_apps
프로젝트 내의 모든 비공개가 아닌 제공자를 나열합니다 — 와일드카드 소비 권한(data-template.yml의 consumes: [{ app: "*" }], 프로젝트 사용자가 부여)을 가진 앱을 위한 발견 기본 요소입니다. 단일 호출을 수행하며 연결은 전혀 열지 않습니다: 반환된 카탈로그를 선택기에 렌더링한 다음, 원하는 제공자에 대해 connectToApp / connect_to_app을 호출하거나 — 한 번에 모두 열려면 connectToAllApps / connect_to_all_apps를 호출하십시오.
참고: 앱의
data-template.yml에 권한을 선언하고,*를 따옴표로 감싸십시오 — 따옴표 없는*는 YAML 별칭이므로 파싱되지 않습니다:
consumes:
- app: "*"Python
providers = await ironflock.list_consumable_apps()
for p in providers:
print(p["app"], list(p["stages"].keys())) # e.g. "weather-app" ["dev", "prod"]각 항목은 하나의 제공자를 설명합니다:
| 필드 | 유형 | 설명 |
|---|---|---|
app | str / string | 제공자 앱 이름 |
provider_app_key | int / number | 제공자의 앱 키 |
stages | dict / object | 스테이지별 카탈로그 { dev?, prod? }; 스테이지는 제공자가 해당 스테이지에 대한 데이터 백엔드를 가지고 있는 경우에만 존재합니다. 각 카탈로그는 제공자가 공유하는 비공개가 아닌 tables와 transforms를 담고 있습니다 |
앱이 와일드카드 권한을 가지고 있지 않으면 code: NO_GRANT를 가진 CrossAppAccessError가 발생(Python)/throw(JavaScript)됩니다.
connectToAllApps / connect_to_all_apps
프로젝트 내의 모든 비공개가 아닌 제공자에 대한 읽기 전용 핸들을 한 번의 호출로 엽니다(와일드카드 소비자 전용). listConsumableApps / list_consumable_apps를 통해 제공자를 열거하고 각각을 열며, 요청된 스테이지에 대한 데이터 백엔드가 없는 제공자는 건너뜁니다. 각 핸들은 connectToApp / connect_to_app과 동일한 키로 캐시되므로, 이후 connectToApp(name) 호출은 이미 준비된 핸들을 반환합니다. 반환된 핸들은 인스턴스가 중지될 때 함께 닫힙니다.
Python
apps = await ironflock.connect_to_all_apps(
on_error=lambda err: print("Provider skipped:", err)
)
for app in apps:
rows = await app.get_history(app.tables[0]["tablename"], {"limit": 10})
print(app.app, rows)파라미터:
| 파라미터 | 유형 | 설명 |
|---|---|---|
stage | str / string, 선택 | 제공자 스테이지: "dev" 또는 "prod". 기본값은 자신의 앱 스테이지 |
continue_on_error / continueOnError | bool / boolean, 선택 | true(기본값)인 경우, 여는 데 실패한 제공자는 on_error / onError에 보고되고 결과에서 제외됩니다. false인 경우, 첫 번째 실패가 발생/throw됩니다 |
on_error / onError | callable, 선택 | 열 수 없었던 각 제공자와 함께 호출되며(continue_on_error / continueOnError가 true인 동안), 이미 열린 연결이 나중에 거부될 경우(예: 권한이 취소된 경우) CrossAppAccessError와 함께 호출됩니다 |
성공적으로 열린 제공자 핸들을 반환합니다(connectToApp / connect_to_app과 동일한 핸들 유형). 앱이 와일드카드 권한을 가지고 있지 않으면 code: NO_GRANT를 가진 CrossAppAccessError가 발생(Python)/throw(JavaScript)됩니다.
Python에서는
stage,on_error,continue_on_error가 키워드 인자입니다. JavaScript에서는 옵션 객체를 통해 전달됩니다(connectToAllApps({ stage, onError, continueOnError })).
관리형 파일 저장소
모든 앱 데이터 백엔드는 테이블과 함께 비공개 객체 저장소를 제공받으며, files 속성을 통해 여기에 접근합니다. 이미지, PDF, 카메라 프레임, 펌웨어 바이너리 — 테이블 행에 담을 만한 것이 아닌 모든 데이터에 사용하십시오. 별도의 설정은 필요 없습니다: 데이터 템플릿에 files: 섹션이 없는 앱도 default라는 이름의 네임스페이스를 하나 갖습니다.
핵심은 객체를 저장하면 그 즉시 영구 URL을 돌려받는다는 점입니다. 이 URL은 테이블 컬럼에 바로 기록할 수 있으므로, 대시보드 위젯이 추가 작업 없이 이를 렌더링할 수 있습니다:
Python
# Store an object and get a permanent URL back in the same call
info = await ironflock.files.put("part-1.jpg", jpeg_bytes, content_type="image/jpeg")
# The URL is safe to store in a table column — a dashboard widget can then
# render <img src="{{photo_url}}"> without any extra round trip
await ironflock.publish_to_table("inspections", part_id="1", photo_url=info.url)
# Read it back
data = await ironflock.files.get("part-1.jpg")
# Walk every object under a prefix (pages are fetched for you)
async for obj in ironflock.files.iter(prefix="2026/"):
print(obj.key, obj.size)이 URL은 만료되지 않지만 공개 링크는 아닙니다: 이 데이터 백엔드에 대한 READ 접근 권한을 가진 인증된 요청자에게만 읽을 수 있는 상태로 유지되며, 인증 프록시가 매 요청마다 이를 다시 확인합니다. 따라서 데이터베이스에 저장해도 안전합니다.
네임스페이스
네임스페이스는 정책 — 보존 기간, 공유 규칙, 허용되는 콘텐츠 유형 — 을 담고 있는 키 접두사입니다. 별도의 버킷이 아닙니다. 앱의 모든 네임스페이스는 해당 앱의 단일 저장 영역 안에 존재합니다. 어떤 객체 집합에 다른 규칙이 필요할 때만 네임스페이스를 선언하고, 그렇지 않다면 default에 머무르면서 2026/03/part-1.jpg와 같은 키 경로로 객체를 정리하십시오.
추가 네임스페이스는 data-template.yml에 선언합니다:
files:
# Storage budget the app suggests for itself. The project user can change it,
# and their setting is the one that gets enforced.
quotaBytes: 5368709120
namespaces:
- name: frames
description: Raw camera frames, one JPEG per inspected part.
contentTypes: ["image/jpeg"]
maxObjectBytes: 20971520
retention: { deleteAfter: 30 days }이 예산은 네임스페이스별이 아니라 앱 전체에 대해 한 번만 선언된다는 점에 유의하십시오. 네임스페이스는 앱의 단일 저장 영역 안에 있는 키 접두사일 뿐이므로, 접두사별 예산을 강제할 대상 자체가 존재하지 않습니다. 반면 maxObjectBytes는 네임스페이스별로 적용됩니다 — 총량이 아니라 개별 객체 하나의 크기를 제한합니다.
아래의 모든 메서드는 네임스페이스를 선택적 인자로 받으며, 기본값은 default입니다.
객체 저장 및 읽기
Python
# Bytes in, bytes out
info = await ironflock.files.put("reports/march.pdf", pdf_bytes, content_type="application/pdf")
data = await ironflock.files.get("reports/march.pdf")
# Or straight from/to a local file — these stream on the large-object path,
# so a multi-gigabyte file never has to fit in memory
await ironflock.files.put_file("firmware/v2.bin", "/data/build/v2.bin")
await ironflock.files.get_to_file("firmware/v2.bin", "/tmp/v2.bin")| 메서드 | 설명 |
|---|---|
put(key, data, …) | 객체를 저장합니다(Python에서는 bytes, JavaScript에서는 Uint8Array). url을 포함한 객체의 메타데이터를 반환합니다 |
get(key, namespace?) | 객체의 내용을 반환합니다 |
put_file(key, path, …) / get_to_file(key, path, …) | Python 전용. 로컬 파일에서 저장하거나 로컬 파일로 씁니다. 대용량 객체 경로에서는 스트리밍합니다 |
delete(key, namespace?) | 객체를 삭제합니다 |
copy(key, to, …) | 객체를 복사하며, 선택적으로 다른 네임스페이스로 복사할 수 있습니다 |
move(key, to, …) | 복사 후 삭제입니다. 원자적이지 않습니다 — 서비스에 move 동사가 없으므로, 삭제가 실패하면 사본이 양쪽에 모두 남습니다 |
JavaScript에는 파일 경로 헬퍼가 없습니다. 패키지가 Node와 브라우저 양쪽을 위해 단일 빌드를 배포하기 때문입니다 — 로컬 파일은 fs로 직접 읽고 쓰십시오.
put이 받는 인자: content_type / contentType(MIME 유형. 네임스페이스가 어떤 유형을 허용할지 제한할 수 있습니다)과 namespace. Python에서는 키워드 인자이며, JavaScript에서는 옵션 객체에 담아 전달합니다.
목록 조회 및 검사
| 메서드 | 설명 |
|---|---|
list(…) | 객체 한 페이지를 반환합니다. objects, prefixes, is_truncated / isTruncated, 그리고 다음 페이지를 위해 다시 전달할 cursor를 반환합니다 |
iter(…) / iterate(…) | 접두사 아래의 모든 객체를 순회하는 비동기 이터레이터이며, 페이지네이션은 자동으로 처리됩니다. Python에서는 iter, JavaScript에서는 iterate라는 이름입니다 |
stat(key, namespace?) | 내용을 전송하지 않고 객체 하나의 메타데이터를 조회합니다 |
exists(key, namespace?) | 객체의 존재 여부 |
namespaces() | 이 앱이 사용할 수 있는 네임스페이스 |
usage(…) | 앱이 사용 중인 저장 용량 — 아래 참조 |
catalog() | 네임스페이스와 서버가 발급한 제한값 및 할당량. 첫 호출 이후 캐시됩니다 |
객체는 두 SDK에서 동일한 필드로 기술되며, 각 언어의 명명 방식을 따릅니다: namespace, key, size, etag, content_type / contentType, last_modified / lastModified, checksum_sha256 / checksumSha256, url.
저장소 사용량 및 할당량
usage는 객체 저장소에서 단 한 번의 호출로 답을 받아오므로, 합계는 SDK가 더해서 만든 값이 아니라 정확한 값입니다:
Python
u = await ironflock.files.usage()
print(u.size_bytes, u.object_count, u.quota_bytes, u.free_bytes)
# Break the total down per namespace (costs one listing per namespace)
detailed = await ironflock.files.usage(detail=True)
print(detailed.per_namespace) # {"default": 1048576, "frames": 73400320}| 필드 | 의미 |
|---|---|
size_bytes / sizeBytes | 현재 저장되어 있는 바이트 수 |
object_count / objectCount | 저장된 객체의 개수 |
quota_bytes / quotaBytes | 강제되는 예산. 0은 무제한을 의미합니다 |
free_bytes / freeBytes | 남은 바이트 수. -1은 무제한을 의미합니다 — 여기에 0을 보고하면 “가득 참”으로 읽히기 때문입니다 |
per_namespace / perNamespace | 네임스페이스별 바이트 수. 상세 분석을 요청한 경우에만 존재합니다 |
네임스페이스별 분석은 기본적으로 꺼져 있습니다. 저장소가 이를 직접 답해줄 수 없기 때문입니다: 저장소는 저장 영역 단위로 집계하고 네임스페이스는 접두사일 뿐이므로, SDK가 각 네임스페이스를 나열해 크기를 더해야 합니다. 필요할 때만 요청하고, 자주 실행되는 경로에서는 사용하지 마십시오.
서로 다른 두 가지 할당량이 등장하며, 이 둘을 구분해 두는 것이 좋습니다. catalog()는 두 값을 모두 보고합니다:
| 필드 | 의미 |
|---|---|
quota_bytes / quotaBytes | 객체 저장소에서 읽어온, 실제로 강제되는 값 — 프로젝트 사용자의 설정 |
suggested_quota_bytes / suggestedQuotaBytes | 앱의 데이터 템플릿이 요청한 값. 아무것도 요청하지 않았다면 0 |
이 두 값은 사용자가 앱의 예산을 올리거나 내린 경우 언제나 달라집니다. 강제되는 값을 템플릿이 아니라 저장소에서 읽어오는 이유가 바로 이것입니다 — 앱을 재배포한다고 해서 사용자의 선택이 조용히 초기화되어서는 안 되기 때문입니다. UI는 두 값을 모두 보여줄 수 있습니다(“앱은 X를 제안하며, 사용자는 Y로 설정했습니다”). 강제 적용에는 언제나 첫 번째 값이 사용됩니다.
객체 공유
링크에는 두 종류가 있으며, 그 차이가 중요합니다:
| 메서드 | 수명 | 읽을 수 있는 대상 |
|---|---|---|
url(key, …) | 영구 | 이 데이터 백엔드에 대한 READ 권한을 가진 인증된 요청자만 — 매 요청마다 다시 확인됩니다. 테이블 컬럼에 저장해도 안전합니다 |
share_url / shareUrl | 만료됨(기본 15분, 서버가 상한을 강제) | 링크를 가진 사람은 누구나. 링크가 사용될 때 권한을 다시 확인하는 것은 아무것도 없습니다 |
share_url / shareUrl은 소지자 권한(bearer capability)입니다: 임시 접근이 필요한 사람에게 건네주되, 데이터베이스에는 저장하지 마십시오. 대시보드가 렌더링하는 대상에는 url을 사용하십시오.
배포 환경에 HTTP 엣지가 없는 경우(예: 순수 HTTP 어플라이언스) url은 None / undefined를 반환합니다 — 이는 get으로 대체하라는 신호입니다. 객체의 etag를 version 인자로 전달하면 브라우저가 응답을 불변으로 캐시할 수 있습니다.
upload_url / uploadUrl은 직접 업로드를 받아주는 만료형 URL을 발급하며, url, method, headers, expires_in / expiresIn을 반환합니다. 반환된 헤더를 정확히 그대로 보내십시오. 그렇지 않으면 서명이 검증되지 않습니다.
대용량 객체
SDK는 크기에 따라 전송 방식을 자동으로 선택합니다 — 설정할 것은 없습니다:
| 객체 크기 | 전송 방식 |
|---|---|
| 인라인 제한까지(현재 6 MiB) | 메시지 라우터를 통한 단일 호출 |
| 그보다 큰 경우 | 라우터를 우회하여 HTTPS로 객체 저장소에 직접 전송 |
정확한 제한값은 런타임에 서버가 catalog()의 inline_max_bytes / inlineMaxBytes로 알려주므로, SDK 릴리스 없이도 상향할 수 있습니다.
두 가지 상한은 그대로 남아 있으며, 둘 다 어느 쪽에 걸렸는지를 명시한 reason과 함께 TOO_LARGE를 보고합니다:
- 5 GiB — 객체 저장소의 단일 업로드 제한입니다. 멀티파트 업로드는 아직 구현되지 않았습니다.
- 직접 엔드포인트가 없는 환경에서의 인라인 제한 — 망 분리된 어플라이언스는 대용량 객체를 아예 전송할 수 없습니다. 재시도하거나 청크를 더 작게 해도 해결되지 않으며, 메시지가 그 점을 알려줍니다.
직접 경로에서는 디바이스가 라우터뿐 아니라 객체 저장소 호스트에도 도달할 수 있어야 합니다. 현장에서 흔한 두 가지 실패는 권한 문제처럼 보이지 않도록 각자의 코드를 갖습니다: PRESIGN_UNREACHABLE(라우터만 허용하는 프록시)와 CLOCK_SKEW(객체 저장소는 15분 넘게 어긋난 요청을 거부합니다 — 디바이스의 NTP를 확인하십시오).
파일 저장소 오류
모든 파일 작업은 안정적인 code와 사람이 읽을 수 있는 reason을 담은 FileStoreError를 발생(Python)/throw(JavaScript)시킵니다. 분기는 항상 code로 하고, 절대 reason으로 하지 마십시오.
Python
from ironflock.filestore import FileStoreError
try:
await ironflock.files.put("huge.bin", payload)
except FileStoreError as e:
if e.code == "QUOTA_EXCEEDED":
print("Filestore is full:", e.reason)
else:
raise| 코드 | 의미 |
|---|---|
NOT_AUTHORIZED | 호출자가 이 작업을 수행할 수 없습니다 |
NO_SUCH_NAMESPACE | 네임스페이스가 데이터 템플릿에 선언되어 있지 않습니다 |
NO_SUCH_OBJECT | 키가 존재하지 않습니다 |
TOO_LARGE | 단일 호출 전송 제한을 초과했습니다 |
OBJECT_TOO_LARGE | 네임스페이스 자체의 maxObjectBytes를 초과했습니다 |
QUOTA_EXCEEDED | 파일 저장소가 가득 찼습니다 |
CONTENT_TYPE_NOT_ALLOWED | 네임스페이스가 contentTypes를 제한합니다 |
NOT_SUPPORTED | 백엔드가 이를 수행할 수 없습니다 |
NOT_AVAILABLE | 이 배포 환경에는 파일 서비스가 없습니다 |
PRESIGN_UNREACHABLE | 객체 저장소에 직접 도달할 수 없습니다(프록시 때문일 수 있습니다) |
CLOCK_SKEW | 디바이스 시계가 너무 많이 어긋나 있습니다 |
INTERNAL | 그 외 모든 경우 |
더 새로운 서버는 이 SDK 릴리스가 알지 못하는 코드를 도입할 수 있습니다. 이런 코드는 하나로 뭉뚱그려지지 않고 code에 그대로 전달되므로, 인식할 수 없는 값은 일반적인 실패로 처리하십시오.
Python에서는
FileStoreError를ironflock.filestore에서 임포트합니다. JavaScript에서는 패키지 루트에서 익스포트됩니다(import { FileStoreError } from "ironflock").
디바이스 간 통신
registerDeviceFunction / register_device_function
같은 프로젝트의 다른 디바이스가 호출할 수 있는 프로시저를 등록합니다. SDK가 자동으로 현재 디바이스에 프로시저의 네임스페이스를 지정합니다.
Python
def add(a, b):
return a + b
await ironflock.register_device_function("com.myapp.add", add)
register()는register_device_function()의 별칭입니다.
callDeviceFunction / call_device_function
다른 디바이스가 등록한 프로시저를 호출합니다. SDK가 타겟 디바이스의 키를 사용하여 전체 WAMP 토픽을 자동으로 조합합니다.
Python
result = await ironflock.call_device_function(
42, # 타겟 디바이스 키
"com.myapp.add", # 프로시저 이름
args=[3, 5] # 인수
)
print(result) # 8call
전체 WAMP URI를 사용하여 원격 프로시저를 호출합니다. 정확한 토픽을 알고 있을 때 직접 호출에 사용하십시오.
Python
result = await ironflock.call("some.full.wamp.topic", args=[42])디바이스 메타데이터
setDeviceLocation / set_device_location
플랫폼에서 디바이스의 GPS 위치를 업데이트합니다. 변경 사항이 IronFlock 맵에 실시간으로 반영됩니다.
Python
await ironflock.set_device_location(long=8.6821, lat=50.1109)| 파라미터 | 범위 |
|---|---|
long | -180 ~ 180 |
lat | -90 ~ 90 |
위치 이력은 저장되지 않습니다. 시간에 따른 위치를 추적하려면 전용 테이블을 생성하고
publish_to_table/publishToTable을 사용하십시오.
getRemoteAccessUrlForPort
디바이스의 특정 포트에 대한 공개 원격 접근 URL을 반환합니다.
Python
url = ironflock.getRemoteAccessUrlForPort(8080)
# "https://<device_key>-<app_name>-8080.app.ironflock.com"연결 속성 및 생명주기
Python
| 속성 | 유형 | 설명 |
|---|---|---|
is_connected | bool | 플랫폼 연결이 활성화되어 있는지 여부 |
connection | CrossbarConnection | 내부 연결 인스턴스 (고급 사용) |
| 메서드 | 설명 |
|---|---|
run() | 연결을 시작하고 mainFunc 실행 (블로킹) |
await start() | 비동기적으로 연결 시작 |
await stop() | 연결 중지 및 실행 중인 작업 취소 |
await run_async() | 비동기적으로 연결을 시작하고 유지 |
오류 처리
모든 SDK 메서드는 실패를 명확하게 알립니다. 잘못된 인자, 연결 끊김, 플랫폼의 거부가 발생하면 작업, 토픽, 원인을 명시한 메시지와 함께 예외를 발생시키거나(Python) reject합니다(JavaScript). 조용히 무시되는 오류는 없으므로, 실패해도 계속 진행되어야 하는 호출은 try 블록으로 감싸십시오.
Python
try:
rows = await ironflock.getHistory("sensordata", {"limit": 100})
except ValueError as e:
# Invalid parameters — e.g. limit out of range, or a malformed filter
print(f"Bad query: {e}")
except RuntimeError as e:
# Not connected, table not in the data-template, or the platform rejected the call
print(f"Query failed: {e}")JavaScript에서 플랫폼으로부터 발생한 실패는 WampError 인스턴스입니다 — 일반적인 Error 하위 클래스이며, 추가로 WAMP 오류 URI를 error에, 오류 페이로드를 args / kwargs에 담고 있습니다. 그 외의 모든 경우(잘못된 파라미터, 연결 없음)는 일반 Error입니다.
업그레이드 안내: 이전 SDK 버전은 호출이 실패하면 메시지를 로그에 남기고
None/null을 반환했습니다. 이제는 대신 예외를 발생시키므로if result is None:형태의 코드로는 더 이상 실패를 감지할 수 없습니다 —try/except(또는try/catch)를 사용하십시오.
브라우저 사용 (JavaScript 전용)
JavaScript SDK는 최신 브라우저에서 작동합니다. 브라우저에는 환경 변수가 없으므로 생성자를 통해 모든 구성을 전달하십시오:
import { IronFlock } from "ironflock";
const ironflock = new IronFlock({
serialNumber: "device-serial-from-server",
deviceKey: "my-device-key",
appName: "MyWebApp",
swarmKey: 10,
appKey: 20,
env: "PROD",
});
await ironflock.start();
await ironflock.publishToTable("sensordata", [{ temperature: 22 }]);자격 증명을 하드코딩하는 대신 IronFlock.fromServer()를 사용하여 백엔드에서 구성을 가져오십시오:
const ironflock = await IronFlock.fromServer("/api/ironflock-config");
await ironflock.start();백엔드 엔드포인트는 연결 옵션(serialNumber, deviceKey, appName, swarmKey, appKey, env)이 포함된 JSON 객체를 반환해야 합니다.
AI 에이전트 함수 등록
SDK는 AI 에이전트가 호출할 수 있는 함수를 등록할 수 있습니다. 프로시저를 등록하고 ai-template.yml에서 해당 토픽을 참조하십시오:
Python
def get_sensor_reading(sensor_id):
"""센서에서 최신 판독값을 반환합니다."""
reading = read_from_hardware(sensor_id)
return {
"sensor_id": sensor_id,
"temperature": reading.temp,
"humidity": reading.hum,
"timestamp": reading.ts
}
await ironflock.register_device_function("sensors.get_latest", get_sensor_reading)그런 다음 AI 에이전트는 사용자가 라이브 센서 데이터가 필요한 질문을 할 때 이 함수를 호출할 수 있습니다.
등록된 WAMP 토픽을 AI 에이전트에 연결하려면 앱의 .ironflock/ai-template.yml에서 참조하십시오:
sensor_agent:
tool_description: |
사용자가 센서 판독값, 라이브 디바이스 데이터, 또는 현재
환경 조건에 대해 질문할 때 이 에이전트에 위임하세요.
system_prompt: |
당신은 센서 데이터 전문가입니다. get_current를 사용하여
모든 센서에서 최신 판독값을 가져오세요. 응답에 항상 단위를
포함하세요.
main: true
max_context_tokens: 30000
max_iterations: 5
tools:
get_current:
description: 센서에서 최신 판독값을 반환합니다.
topic: sensors.get_latest
parameters:
sensor_id:
type: string
description: 조회할 센서 식별자.
required: truetopic 값(sensors.get_latest)은 엣지 코드에서 register_device_function / registerDeviceFunction에 전달된 이름과 일치해야 합니다. IronFlock이 자동으로 함수가 등록된 디바이스로 호출을 라우팅합니다.
전체 ai-template.yml 참조는 에이전트 및 도구 정의를 참조하십시오.
환경 변수
이 변수들은 앱 컨테이너 내에서 IronFlock 런타임이 자동으로 설정합니다:
| 변수 | 설명 |
|---|---|
DEVICE_NAME | 디바이스 표시 이름 |
DEVICE_SERIAL_NUMBER | 고유하고 변경 불가능한 디바이스 식별자 |
DEVICE_KEY | 인증용 디바이스 키 |
SWARM_KEY | 프로젝트 식별자 |
APP_KEY | 앱 식별자 |
APP_NAME | 앱 이름 |
ENV | 환경: DEV 또는 PROD |
Python
import os
device_name = os.environ.get("DEVICE_NAME")
serial = os.environ.get("DEVICE_SERIAL_NUMBER")
project_key = os.environ.get("SWARM_KEY")