REST API (v1)
PlantPulse Edge の v1 REST API マニュアル。画面および外部システムが OPC、タグ、監視、外部送信ヘルスチェックを扱うための標準インターフェースです。
| 項目 | 値 |
|---|---|
| Base URL | http(s)://<edge-host>/api/v1 |
| Content-Type | application/json; charset=utf-8 |
| 認証 | API Key 強制 (edge.rest.api.auth=true) — X-API-Key / Authorization: Bearer、またはログインセッションで通過。?api_key= は 401 で拒否 |
| Audit ログ | ApiAccessLogFilter — すべての /api/* 呼び出しを一行で記録 (メソッド/パス/ステータス/持続時間/IP/ユーザー/auth モード) |
| メインコントローラー | plantpulse.app.edge.api.v1.* (OpcAPI, TagAPI, AppAPI, SystemAPI, MonitoringAPI) |
| エラーエンベロープ | {data, meta, errors} — filter/advice パスも同一 |
| HTTPS | edge.rest.api.https_only=true 基本。plain HTTP client の呼び出しは拒否、または Tomcat HTTPS リダイレクトポリシーに従う |
認証 (ApiAuthFilter)
app.properties から edge.rest.api.auth=true で有効化。キーは edge.rest.api.key であり、運用環境では EDGE_REST_API_KEY_FILE または EDGE_REST_API_KEY で注入します。
次の 3 つつのうち いずれか を満たせば通過します:
A. ログインセッション通過 (ブラウザ)
すでに /login/form でログインしたユーザー — セッションに _USER_LOGIN 属性 (JSONObject) があれば ApiAuthFilter がそのまま chain.doFilter() します。画面(JSP) から ajax で呼び出すすべての /api/* が追加キーなしで動作します。
B. X-API-Key ヘッダー (推奨)
EDGE_REST_API_KEY="$(tr -d '\r\n' < /run/secrets/edge-rest-api-key)"
cfg="$(mktemp)"
trap 'rm -f "$cfg"' EXIT
printf 'header = "X-API-Key: %s"\n' "$EDGE_REST_API_KEY" > "$cfg"
curl --config "$cfg" https://edge.example.com/api/v1/tag/TAG_LS_XBC_0001/value
C. Authorization: Bearer <key> ヘッダー
EDGE_REST_API_KEY="$(tr -d '\r\n' < /run/secrets/edge-rest-api-key)"
cfg="$(mktemp)"
trap 'rm -f "$cfg"' EXIT
printf 'header = "Authorization: Bearer %s"\n' "$EDGE_REST_API_KEY" > "$cfg"
curl --config "$cfg" https://edge.example.com/api/v1/tag/TAG_LS_XBC_0001/value
D. ?api_key=<key> クエリパラメータ — 拒否
URL にキーが平文で入り、アクセスログ/プロキシキャッシュ/ブラウザ履歴に残る可能性があるため、v1 では 401 で拒否します。ヘッダー方式(B/C)を使用してください。
失敗レスポンス
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
{
"data": null,
"meta": {
"timestamp": 1778069486934,
"status": "ERROR",
"http_status": 401,
"request_id": "3b89b8f1-0fc1-4ab4-b4eb-d12dc314d500"
},
"errors": [
{
"code": "UNAUTHORIZED",
"message": "Invalid or missing X-API-Key."
}
]
}
キーの比較は constantTimeEquals() (timing-attack 防御)。
edge.rest.api.auth=false (開発 / 一時的)
認証をオフにすると ApiAuthFilter が即座に chain.doFilter() し、外部からキーなしで呼び出し可能です。運用網では常に true を推奨します。
Audit ログ (ApiAccessLogFilter)
すべての /api/* 呼び出しを一行ログで記録 — 認証失敗(401) も含まれ、監査トレイルを残します (フィルターチェーン順序: ApiAccessLogFilter → ApiAuthFilter)。
形式
[API_ACCESS] method=GET path=/api/v1/tag/TAG_MS_1000/value query= status=200 duration=12ms
ip=100.127.147.98 user=admin auth=session ua=Mozilla/5.0...
| フィールド | 意味 |
|---|---|
method | HTTP メソッド |
path | request URI (クエリ含まず) |
query | クエリ文字列 — 拒否される ?api_key=... 部分も *** でマスキング |
status | HTTP レスポンスコード |
duration | 処理時間 (ms) |
ip | 基本は RemoteAddr。edge.rest.api.trust_x_forwarded_for=true である reverse proxy 配置でのみ X-Forwarded-For の最初の IP |
user | session ユーザーなら user_id / apikey なら XXXX*** (len=16) (先頭4文字 + 長さ) / 匿名なら - |
auth | session (ブラウザログイン) / apikey (X-API-Key/Bearer) / deprecated_query (拒否された ?api_key= 試行) / none (キー欠落または匿名) |
ua | User-Agent (80文字超過時に … で切り捨て) |
error | 例外発生時に error=ClassName 追加 |
ログレベル分岐
| ステータス | レベル | 意図 |
|---|---|---|
| 5xx または throwable | ERROR | サーバーエラー — 即時アラーム |
| 401, 403 | WARN | 認証/権限拒否 — 監査優先 |
| その他 (2xx, 3xx, 4xx の 4xx 一般) | INFO | 通常の呼び出し |
活用
- 誰がキーを誤って送信しているか —
grep '\[API_ACCESS\].*status=401'で 401 トレイルを確認 - 5xx 回帰追跡 —
grep '\[API_ACCESS\].*status=5'+error=フィールド - 呼び出し頻度 / ユーザー別集計 — auth=apikey/session で grouping、IP 別分析
- 応答遅延スロークエリ —
duration=の長いラインを抽出
キーの平文は どこにも ログに残りません — クエリマスキング + ユーザーフィールドの先頭4文字のみで識別。侵害事故分析 に十分な識別性と平文露出防止のバランスを維持しています。
web.xml のマッピング順序:ApiAccessLogFilter が先で、次に ApiAuthFilter です。AccessLog が finally ブロックでステータスを記録するため、認証に失敗した 401 呼び出しもそのまま監査されます。順序が逆になると、401 呼び出しがアクセスログから漏れます。
レスポンスエンベロープ
v1 REST API レスポンスは共通エンベロープで包みます (RestApiSupport, ApiEnvelopeWriter)。
正常レスポンス:
{
"data": { },
"meta": {
"timestamp": 1778069486934,
"status": "OK",
"request_id": "f818abd0-eb0f-4497-9b07-7ccc94b2e2d7"
}
}
エラーレスポンス:
{
"data": null,
"meta": {
"timestamp": 1778069486934,
"status": "ERROR",
"http_status": 400,
"request_id": "3b89b8f1-0fc1-4ab4-b4eb-d12dc314d500"
},
"errors": [
{
"code": "VALIDATION_FAILED",
"message": "Validation failed"
}
]
}
旧 RPC スタイルのエンベロープ (result, _session_id, _user_id) は REST v1 では使用しません。
1. 共通規約
エンドポイントを確認する前に、すべての /api/v1/* が共に遵守することです。これを理解していないと、個別のエンドポイントドキュメントを読んでもクライアントの実装に誤りが生じます。
1.1 運用ヘッダー
| ヘッダー | 方向 | 用途 |
|---|---|---|
X-API-Key | リクエスト | 認証。?api_key= クエリは 破棄(401) |
Authorization: Bearer <key> | リクエスト | API キーの代替 |
X-Request-Id | リクエスト | 追跡用。レスポンス meta.request_id で echo されます。指定がない場合はサーバーが生成します |
Idempotency-Key | リクエスト | POST 重複防止. TTL 10分。同じキーで再試行するとキャッシュされたレスポンスと共に Idempotency-Replay: true が返されます |
If-Match | リクエスト | 楽観的同時実行制御。GET レスポンスの ETag と異なる場合は 412 |
ETag | レスポンス | weak validator(W/"<hex>")。現在は GET /api/v1/opc/{opcId} のみ返します |
Location | レスポンス | POST 作成(201) 時の新リソース URI |
X-RateLimit-Limit · -Remaining · -Reset | レスポンス | レートリミット情報 |
Retry-After | レスポンス | 429 の時に待機すべき秒数 |
レートリミットは edge.rest.api.rate_limit.enabled と edge.rest.api.rate_limit.per_second (デフォルト 1000) で調整します。
Idempotency-Key を使用してください現場の回線は切断されることがあります。応答を受け取らなかった POST は「失敗した」のではなく「不明」です — 単に再送すると OPC が二重に作成される可能性があります。同じキーで送信すれば、ボックスが最初の結果をそのまま返します。
1.2 ページネーション
リストエンドポイントの共通クエリです。
| クエリ | デフォルト値 | 説明 |
|---|---|---|
page | 0 | 0から始まるページ番号 |
size | 0 | 0 の場合は全件を返します。 1..1000 の場合はその分だけ切り出して返し、meta.pagination を埋めます |
size が 1000 を超えるか負数、または page が負数の場合は 400 INVALID_RANGE となります。
1.3 ID ルール
| 対象 | 正規表現 |
|---|---|
site_id | ^SITE_[A-Z0-9_]{1,60}$ |
opc_id | ^OPC_[A-Z0-9_]{1,60}$ |
tag_id | ^TAG_[A-Z0-9_]{1,60}$ |
data_type は Boolean · Integer · Long · Float · Double · String の 6 種類のみです (Cassandra ENUM と一致)。opc_type はドライバが持つプロトコル定数のホワイトリストであり (LS · OPCUA · MODBUS · MODBUS_RTU · HTTP · MELSEC · S7 · EIP …)、リストの正本は GET /v3/api-docs です。ホワイトリスト外の値は field_errors[].reason = "not_in_whitelist" で拒否されます。
2. エンドポイント一覧
全 52 件です。OpenAPI 正本はボックスの GET /v3/api-docs であり、画面で確認するには /public/swagger-ui/index.html を参照してください。
2.1 OPC — 8
| Method | URL | 説明 | レスポンス |
|---|---|---|---|
| GET | /api/v1/opc | OPC リスト + 実行時状態 + 60分推移 | 200 · 400 |
| POST | /api/v1/opc | OPC 登録。tag_list でタグを同時に登録可能です | 201 · 400 · 409 |
| GET | /api/v1/opc/{opcId} | 詳細 + ETag。?include=tags でタグ配列を同時に取得 | 200 · 404 |
| PUT | /api/v1/opc/{opcId} | 修正(If-Match 対応) | 200 · 404 · 412 |
| PATCH | /api/v1/opc/{opcId} | 部分修正 — 送信したフィールドのみ変更 | 200 · 400 · 404 · 412 |
| DELETE | /api/v1/opc/{opcId} | OPC + 下位タグまで削除 | 200 · 404 · 412 |
| POST | /api/v1/opc/{opcId}/start | 収集開始 | 200 · 404 |
| POST | /api/v1/opc/{opcId}/stop | 収集停止 | 200 · 404 |
POST 必須 7 フィールド — site_id · opc_id · opc_name · opc_type · opc_agent_ip · opc_agent_port (1~65535) · timecycle (100ms 以上)。違反は まとめて 返されます (field_errors[])。
tag_list でタグを同時に登録します。 接続一つにタグを数十個入れるのが一般的であるため、登録を二回に分けると OPC は存在するがタグがない中間状態 が残ります。一度に送信してください — OPC のみを送信し、後で POST /api/v1/opc/{opcId}/tag を使って一つずつ追加することも可能です。
2.2 Tag — 10
| Method | URL | 説明 | レスポンス |
|---|---|---|---|
| GET | /api/v1/tag | 全タグリスト | 200 · 400 |
| GET | /api/v1/opc/{opcId}/tag | 特定 OPC のタグリスト | 200 · 400 |
| POST | /api/v1/opc/{opcId}/tag | タグ単一登録 | 201 · 400 · 409 |
| GET | /api/v1/tag/{tagId} | タグ詳細 | 200 · 404 |
| PUT | /api/v1/tag/{tagId} | タグ修正 | 200 · 404 |
| DELETE | /api/v1/tag/{tagId} | タグ削除 | 200 · 404 |
| GET | /api/v1/tag/{tagId}/value | 最新値。?fresh=true の場合はデバイスから再読み込み | 200 · 404 · 503 |
| POST | /api/v1/tag/{tagId}/value | 値書き込み + 書き込み後再読み込み(actual_read) | 200 · 400 · 404 · 503 |
| POST | /api/v1/tag-values:read | 複数タグのキャッシュ値を一括取得(最大 100 件) | 200 · 400 |
| GET | /api/v1/tag/{tagId}/points | 時系列照会 + ダウンサンプル | 200 · 400 · 500 |
POST 必須 6 フィールド — site_id · opc_id (パスから自動設定) · tag_id · tag_name · data_type · plc_address。
points のクエリ:from·to (epoch ms, from < to) · minutes (デフォルト 10, 11440) · 5000 — 超過時は 等間隔に間引いて 提供)。limit (デフォルト 600, 1
書き込み失敗のコードマッピング — タグなし 404 TAG_NOT_FOUND · 接続断 503 DISCONNECTED · 収集停止 503 OPC_STOPPED · 読み取り専用タグ 400 RW_VIOLATION · タイプ不一致 400 TYPE_MISMATCH · その他 500 INTERNAL_ERROR。
2.3 アプリ(Docker コンテナ) — 9
| Method | URL | 説明 | レスポンス |
|---|---|---|---|
| GET | /api/v1/app | コンテナリスト | 200 · 400 |
| POST | /api/v1/app | イメージ pull 後 run | 201 · 400 · 409 · 502 |
| GET | /api/v1/app/{name} | docker inspect 結果 | 200 · 404 |
| DELETE | /api/v1/app/{name} | 削除(?image=true の場合はイメージまで削除) | 200 · 404 |
| POST | /api/v1/app/{name}/start | 開始 | 200 · 404 |
| POST | /api/v1/app/{name}/stop | 中止 | 200 · 404 |
| POST | /api/v1/app/{name}/restart | 再起動 | 200 · 404 |
| GET | /api/v1/app/{name}/logs?lines=N | 最後の N 行(1~5000, デフォルト 200) | 200 · 404 |
| GET | /api/v1/app/{name}/stats | CPU · メモリ · ネットワーク · ディスク スナップショット | 200 · 404 |
name および image は正規表現 + シェルメタ文字遮断 を通過する必要があります (; & | \ $ ( ) < > \ " ' 空白 ). memory_limit_mb 는 16 이상, cpu_limit 은 0.1 이상, network 는 bridge · host · none` です。
配備失敗は段階的に通知されます — pull 失敗 502 DOCKER_PULL_FAILED, run 失敗 502 DOCKER_RUN_FAILED, 同名コンテナが存在すれば 409 CONTAINER_EXISTS。
2.4 システム — 12
| Method | URL | 説明 | 認証 |
|---|---|---|---|
| GET | /api/v1/system/health | liveness + コンポーネント 8 種類 確認 | 不要 |
| GET | /api/v1/system/ready | readiness(監視 + V5 クライアント) | 不要 |
| GET | /api/v1/system/version | バージョン · ビルド日 · イメージタグ · コンテナ有無 | 不要 |
| GET | /api/v1/system/time | ボックスの現在時刻(epoch ms) | 必要 |
| POST | /api/v1/system/time/sync | プラットフォーム時刻に合わせます ({"platform_time":…}) | 必要 |
| GET | /api/v1/system/logs | 最新ログを JSON で取得 | 必要 |
| GET | /api/v1/system/logs/download | ログディレクトリを tar.gz で取得 | 必要 |
| GET | /api/v1/system/events | SSE ストリーム (text/event-stream) | 必要 |
| GET | /api/v1/system/upgrade/check | ダウンロードサーバーの最新バージョンと比較 | 必要 |
| POST | /api/v1/system/upgrade | アップグレードトリガー | 必要 |
| GET | /api/v1/system/upgrade/status | アップグレード進行状態(polling) | 必要 |
| POST | /api/v1/system/firmware | ファームウェアアップデートトリガー | 必要 |
| POST | /api/v1/system/reboot | 5秒後ホスト再起動 | 強制 |
| POST | /api/v1/system/restart | 3秒後エッジエージェント再起動 | 強制 |
reboot · restart は認証を«オフにできません»edge.rest.api.auth=false で認証をオフにしていても、この 2 つだけは セッションログインまたは API キーを要求します。 誰でも呼び出せてしまうと、それ自体がサービス拒否攻撃となるためです。
2.5 モニタリング · 엣지 — 4
| Method | URL | 説明 |
|---|---|---|
| GET | /api/v1/monitoring | 稼働時間 · OPC 数 · 登録タグ · スキャン速度 |
| GET | /api/v1/edge | edge_id · site_id · バージョン · ホスト名 · モード |
| GET | /api/v1/edge/status | エッジ状態サマリー |
| GET | /api/v1/transfer | 送信先(MQTT · SparkplugB · プラットフォーム API)別ステータス |
monitoring はブート直後にモニタが存在しない場合、503 MONITOR_NOT_READY を返します — これは故障ではなく「まだ準備中」であることを意味します。
2.6 二重化(HA) — 4
| Method | URL | 説明 |
|---|---|---|
| GET | /api/v1/ha/status | このエッジの書き込み権限 · モード(manual/auto) · 開始時刻 · 相対ボックスの状態 |
| PUT | /api/v1/ha/write-authority | このエッジの 書き込み権限を上げまたは下げます ({"enabled":true}) |
| PUT | /api/v1/ha/failover/override | 自動フェイルオーバーへの手動介入 — auto モードでのみ可能(それ以外は 409 NOT_AUTO_MODE) |
| POST | /api/v1/ha/definitions | タグ定義を相対ボックスに複製 |
書き込み権限 — 読み取り前に知っておくべきこと
何を制限しますか。 権限のないエッジは 実 PLC への書き込みを拒否します (WRITE_NOT_AUTHORIZED)。設定キーは edge.plc.write_authority であり、ない場合は true となります — 設置直後のボックスは 権限を持った状態で開始します。
HTTP 収集はこのゲートを通過しません — ただし «常に» ではありませんopc_type が HTTP である接続はデバイスへ送り出す書き込みではなく 入ってくる収集 です。そのため、権限を下げたエッジでも HTTP 収集は継続されます — 二重書き込みの危険がないためです。
⚠ ブート直後はその HTTP 書き込みも一時的に拒否される場合があります。 ゲートは opc_type をキャッシュから読み込みますが、キャッシュがまだ空であるか照会に失敗した場合、「実 PLC」と見なして遮断します (安全側に倒す意図的な動作です)。また、その照会失敗はログに残りません。
したがって、「HTTP は継続されるはずなのに拒否された」場合は 起動直後かどうかをまず確認してください。 キャッシュが埋まれば自然に解消されます — 設定を修正する問題ではありません。
この呼び出しは 自ボックスのみ を変更します。相手に問い合わせず、ロックもせず、譲受もしません。調停するコードは存在しません。
そのため、二つのボックスが同時に権限を持つ状態が実際に起こり得ます。 次の順序を守ってください —
- A を下げる —
PUT /api/v1/ha/write-authority {"enabled":false} - A が
falseであることを確認する —GET /api/v1/ha/status - B を上げる
逆に行うと、その間に 両方が true となる期間 が生じ、その期間に二つのボックスが同じ PLC に書き込みます。
GET /api/v1/ha/status の peer.write_authority は相手の値を 表示 はします。しかし、それは 観測であり強制ではありません — 相手が権限を持っていても、この呼び出しは遮断されません。
persisted:false は 「失敗」ではなく「再起動すれば消える」ですトグルは二段階です。① ライブ反映 — ゲートが即座に新しい値を確認します (再起動不要)。② app.properties 永続化 — これが失敗しても ① は 維持され persisted:false と報告されます。
つまり、今は適用されたが、次回の再起動時に古い値に戻ります。 権限を下げたと信じて再起動したが、再び上がっている状態が現場で最も陥りやすいミスです。再起動前に原因を解決してください — 多くの場合 /etc/kopens/conf の書き込み権限です。診断はレスポンスではなく サーバーログ にあります:
[HA] write-authority persist to app.properties failed — applied live only
since は 「最後に制御権が移動した時刻」です。 値が 実際に変更された場合にのみ 更新されるため、同じ値を再度送信しても動きません。
監査は常に残ります — 変更があれば WARN、変更がなくても INFO に [no-op] を付与して記録し、両方とも呼び出し者の IP とアカウントを含めます。
[HA][AUDIT] write-authority false -> true by 10.0.0.5 (admin)
⚠ 推移履歴とプラットフォーム診断プッシュは 変更時のみ であり、その二つが失敗してもトグルは壊れません (ログにのみ残ります)。「履歴にない」ことが「変更されていない」ことを意味しません — 確認は status で行ってください。
エッジが自ら知らせる二つの状態。 ペアを監視する側が不一致の状態をログに残します。設定を自動で修正することはありません — 運用者が行います。
[HA] DUAL WRITE AUTHORITY detected — both edges have edge.plc.write_authority=true.
[HA] NO WRITE AUTHORITY — both edges have edge.plc.write_authority=false.
後半は どこでも PLC 書き込みができない 状態です — 「両方下げた後、上げるのを忘れた」場合に相当します。
フェイルオーバー手動介入 — PUT /api/v1/ha/failover/override
自動フェイルオーバーが このエッジに対して判断することを停止し、役割を固定します。解除後に自動判断が再開されます。
role | 意味 |
|---|---|
"WRITER" | ハートビートを継続的に送信し 権限を保持 |
"STANDBY" | 書き込みを停止し 権限を放棄 |
null | 介入を解除 — 自動判断に戻す |
{ "data": { "override": "WRITER", "previous": null }, "meta": { "status": "OK" } }
autoモード専用 です。自動フェイルオーバーが動作していない場合は 409NOT_AUTO_MODEとなります — 前述のwrite-authorityは両モードで利用可能なため、手動運用中の場合はそちらを使用してください。WRITER·STANDBYは 大文字小文字を区別しません。- 値が三つのいずれでもない場合は 400
VALIDATION_FAILEDとなりfield_errors:[{field:"role", reason:"invalid_value"}]が返ります。
時刻同期 — POST /api/v1/system/time/sync
{ "platform_time": 1789780169674 }
epoch ミリ秒 (正数) であり、数値または数値形式の文字列 のいずれでも受け付けます。platform_time を省略すると ボックスに設定された NTP 原本から 同期します — 値を送信することと省略することでは動作が異なります。
レスポンスは {"applied":true,"method":"system","before":…,"after":…} の形式です。before/after を比較すると、実際にどの程度ずれていたか が分かります。
システムを変更する呼び出しであるため、本文を見る前に認証から確認します。キーやセッションがなければ 401 であり、認証後に本文が正しくない場合は 400 VALIDATION_FAILED となります。順序が逆の API と混同しないでください — 誤った本文に対して 401 が返ってきた場合、それは本文の問題ではありません。
2.7 バックアップ · 復旧 — 2
| Method | URL | 説明 |
|---|---|---|
| POST | /api/v1/connect/backup | すべての OPC + すべてのタグを 単一の JSON で出力 |
| POST | /api/v1/connect/restore | その JSON をそのまま適用(upsert) |
対称的です — backup レスポンスをそのまま restore 本文として送信してください。restore レスポンスの skipped[] を必ず確認してください。個別行が失敗しても 全体が失敗せず、スキップされた後にカウントのみが減少します。
2.8 計算式 — 1
| Method | URL | 説明 |
|---|---|---|
| POST | /api/v1/tags/formula/preview | 計算式を保存する前に結果を事前に確認 — 計算式 |
2.9 認証なしで開かれるネット
ApiAuthFilter を通過するのは このネットのみ です。いかなる OPC・タグ情報も出力しません。
GET /api/v1/system/health · GET /api/v1/system/ready · GET /api/v1/system/version · GET /api/health (バージョンなし別名)
このネットは X-API-Key がなくても、あるいは 間違っていても 200 を返します。そのため、キーが正しく変更されたか確認する際にこのパスを使用してはいけません — GET /api/v1/opc のように キーが必要なパス で確認してください。
/health は 8 マス を確認します。8 マスすべてが UP であれば status=UP + 200、一つでも異なれば status=DEGRADED + 503 となります。
{
"data": {
"status": "UP",
"uptime_ms": 1720008,
"components": {
"cassandra": "UP", "redis": "UP", "mqtt": "UP", "node_red": "UP",
"opc_ua": "UP", "edge_core": "UP", "tse": "UP", "grafana": "UP"
}
},
"meta": { "timestamp": 1778925572946, "request_id": "..." }
}
🔴 503 の場合でも components がそのまま返されます。 それが診断のすべてです — ステータスコードだけを見て本文を捨てると、8 つのうち何が停止しているか知る術がありません。
三つのマスは名称から誤解しやすいため別途記載します。
| マス | 実際に確認すること |
|---|---|
edge_core | readiness です — 収集機・モニタが起動しているか。ブートレース中であれば DOWN となり、そのため health が 503 になります。プラットフォーム接続とは無関係 なため、STANDALONE ボックスでも意味を持ちます |
opc_ua | ポートは 固定ではありません。 app.properties の opc.ua.server.tcp.port に従います (開発ボックスは 4840, デフォルトは 12000)。以前に 12000 でハードコーディングした結果、開発ボックスが永久に DOWN と報告された事例があります |
grafana | 2026-07 イメージから HTTPS となります。平文 TCP でアクセスすると Grafana が 10秒ごとに TLS handshake error … EOF をログに残します — そのためボックスは ハンドシェイクを完遂するプローブ を先に使い、旧スタック向けに TCP 確認をフォールバックとして保持しています |
cassandra(9042) · redis(6379) · mqtt(1883) · node_red(1880) · tse(7800) は固定ポートの TCP 確認です。
/version 例:
{
"data": {
"product_name": "PlantPulse Edge",
"version": "2026",
"build_date": "20260523",
"image_tag": "2026-20260523",
"container_mode": true
},
"meta": { "timestamp": 1778925572946, "request_id": "..." }
}
OTA upgrade.sh が /api/health を 300秒間呼び出し 自動ロールバック を判断し、Docker HEALTHCHECK もこのパスを使用します。
2.10 API ではないもの — /ui/opcua/* · /config/*
ボックスには画面が使用するパスも存在します。新しいクライアントは使用しないでください。
/ui/opcua/info·/tree·/history— 内蔵 OPC UA サーバー画面専用です。製品の v1 API 正本ではこれらを 廃止(Deprecated) と宣言しました。コードはまだ応答しますが、正式な契約ではありません。/config/load·/save·/restart·/reboot·/upgrade·/firmware·/backup·/temp-clean·/platform/test— 管理画面が使用するパスです。同様の機能を持つ/api/v1/system/*があるため、そちらを使用してください — エンベロープもエラーコードも v1 規約に従います。/api/http(APIController) — 廃止。POST /api/v1/tag/{tagId}/valueを使用してください。
3. リクエスト / レスポンス例
以下はすべて 現行エンベロープ です。旧ドキュメントにあった {"result":"OK", ...} 形式の例は 製品と一致しないため削除しました — その形式で設計されたクライアントは現在動作しません。
3.1 OPC 登録
curl -sk -X POST "https://<엣지>/api/v1/opc" \
-H "X-API-Key: <key>" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"site_id": "SITE_00001",
"opc_id": "OPC_LS_XBM_0001",
"opc_name": "1라인 LS XBM",
"opc_type": "LS",
"opc_agent_ip": "192.168.0.10",
"opc_agent_port": 2004,
"timecycle": 1000,
"tag_list": [
{ "site_id": "SITE_00001", "opc_id": "OPC_LS_XBM_0001",
"tag_id": "TAG_LS_XBM_0001", "tag_name": "1호기 토출압력",
"data_type": "Float", "plc_address": "%MW100" }
]
}'
tag_list は 省略可能 です — その場合、OPC のみが作成されます。
201 + Location: /api/v1/opc/OPC_LS_XBM_0001:
{
"data": { "info": { "opc_id": "OPC_LS_XBM_0001", "opc_name": "1라인 LS XBM" } },
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}
opc_agent_port と timecycle は数値で送信可能ですストレージの該当カラムが文字列であるため、サーバーが受け取って変換します。数値で送信し文字列で返されても異常ではありません。
3.2 削除
{
"data": { "deleted": true, "opc_id": "OPC_X" },
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}
DELETE も 200 + 本文 です。Swagger 注釈に 204 と記載されていても、実際の動作は 200 です — v1 ではすべてのメソッドがエンベロープを返します。
3.3 失敗のレスポンス形式
POST /api/v1/opc/OPC_NONE/start — 404:
{
"meta": { "timestamp": 1778925572946, "status": "ERROR", "request_id": "..." },
"errors": [ { "code": "OPC_NOT_FOUND", "message": "OPC OPC_NONE 가 존재하지 않습니다." } ]
}
data が全く存在しません (null フィールドはシリアライズ時に省略されます)。data の有無で成否を判断せず、HTTP ステータスまたは meta.status で判断してください。
3.4 複数タグ値の一括取得
curl -sk -X POST "https://<엣지>/api/v1/tag-values:read" \
-H "X-API-Key: <key>" -H "Content-Type: application/json" \
-d '{ "tag_ids": ["TAG_LS_XBM_0001", "TAG_LS_XBM_0002"] }'
{
"data": [
{ "tag_id": "TAG_LS_XBM_0001", "value": "1234", "timestamp": "2026-05-15T11:32:45.000Z", "quality": "SUCCESS" },
{ "tag_id": "TAG_LS_XBM_0002", "value": null, "timestamp": null, "quality": "PENDING" }
],
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}
🔴 quality を確認してください。 PENDING は「値が 0」ではなく 「まだ一度も読み取られていない」 状態です。これを 0 と読み取ると、誰も確認していない設備が「正常」として報告されます。
配列が空であるか 100 件を超える場合は 400(MISSING_FIELD / TOO_MANY) となります。
3.5 再起動
{
"data": { "message": "reboot scheduled in 5 seconds" },
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}
202 です — 「完了した」のではなく 「することにした」 という意味です。レスポンスを受け取った時点ではまだ生存しています。
3.6 準備未完了
GET /api/v1/system/ready — 503:
{
"meta": { "timestamp": 1778925572946, "status": "ERROR", "request_id": "..." },
"errors": [ { "code": "NOT_READY", "message": "엣지의 일부 구성요소가 준비되지 않았습니다." } ]
}
3.7 バックアップと復旧の対称性
{
"data": {
"opcs": [ { "opc_id": "OPC_LS_XBM_0001", "opc_name": "..." } ],
"tags": [ { "tag_id": "TAG_LS_XBM_0001", "opc_id": "OPC_LS_XBM_0001" } ],
"count": { "opc": 1, "tag": 1 },
"exported_at": 1778925572946
},
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}
data をそのまま POST /api/v1/connect/restore の本文として送信してください。復旧レスポンスの skipped[] を必ず確認してください — 個別行が失敗しても全体は 200 となり、スキップされた分だけがそこに記載されます。count のみを見ると、静かに不完全な復旧となります。
4. エラーコード
🔴 v1 では失敗を HTTP ステータスで伝えます。 旧 RPC 方式 ({result:"ERROR"} を HTTP 200 で包んでいたもの) は 廃止されました。 ステータスコードを見ずに本文の result を探すクライアントは、すべての失敗を成功として読み取ります。
判定は次の二つのいずれかで行ってください — HTTP ステータスコード、または meta.status ("OK" / "ERROR")。errors[] を探索する必要はなく、一つのキーで判別可能です。
| code | HTTP | 意味 |
|---|---|---|
MISSING_BODY | 400 | リクエスト本文が空です |
MISSING_FIELD | 400 | 必須フィールド欠落 |
INVALID_JSON | 400 | JSON パース失敗 |
INVALID_RANGE | 400 | page·size·from~to 範囲違反 |
INVALID_ARGUMENT | 400 | 一般引数エラー(イメージ・ボリュームホワイトリスト等) |
INVALID_NAME | 400 | コンテナ名にインジェクション文字 |
VALIDATION_FAILED | 400 | 複数フィールドが同時に 間違っています — field_errors[] を参照 |
TOO_MANY | 400 | 一括リクエスト上限(100件)超過 |
RW_VIOLATION · TYPE_MISMATCH | 400 | タグ書き込み失敗 — 読み取り専用タグ / タイプ不一致 |
UNAUTHORIZED | 401 | 認証欠落または無効 |
HTTPS_REQUIRED | 403 | 平文 HTTP で送信されました — キーの問題ではありません |
OPC_NOT_FOUND · TAG_NOT_FOUND · APP_NOT_FOUND | 404 | 対象が存在しません |
CONTAINER_EXISTS | 409 | 同名のコンテナが既に存在します |
PRECONDITION_FAILED | 412 | If-Match の ETag が異なります — その間に誰かが修正しました |
START_FAILED · STOP_FAILED | 422 | OPC 収集制御失敗 |
DOCKER_PULL_FAILED · DOCKER_RUN_FAILED | 502 | Docker コマンド失敗 |
MONITOR_NOT_READY · NOT_READY · DISCONNECTED · OPC_STOPPED | 503 | 未準備/切断 — リクエストが間違っているわけではありません |
MISCONFIGURED | 500 | edge.rest.api.key が空です |
INTERNAL_ERROR · *_FAILED | 500 | その他内部エラー |
DISCONNECTED · OPC_STOPPED · MONITOR_NOT_READY は リクエストが間違っているのではなく、今は実行できない という意味です。同じリクエストを 後で再送してください。 400 のように「修正して再送」として処理すると、修正すべき点がないのに修正を試みることになります。
5. 動作メモ
- タグ単一照会:
APP_TAG.xmlのR03(WHERE TAG_ID = :tag_id ALLOW FILTERING)。 - OPC 登録 · タグ登録 後は
ConnectService.restartComponents()が動作し、キャッシュを再読み込みして OPC-UA サーバーを再起動します — 登録直後に一時的に収集が途切れるのはこのためです。 - 値書き込み (
tagWriteByTagId): HTTP はHTTPDriver.bind()、それ以外はdriver.write(ProtocolAddress)。失敗時 (ok=false) に親切なヒント (EIP ファームウェア互換性等) を含みます。write 後に即座に read した結果をactual_readで共に返します (HTTP を除く)。 - トランスファーヘルス (
/api/v1/transfer):TransferRegistryに登録されたすべての outbound コンポーネントのgetStatus()を収集。Sparkplug B カード (/ui/main) が 5秒周期で呼び出します。 - OPC UA サーバー (
/ui/opcua/*) は内部網ポーリング (2秒) —OPCAndTagsCache.getInstance()+LastValueMap.getInstance()のみを使用するため負荷が低いです。 - レガシー deprecated:
/api/http(APIController) —/api/v1/tag/{tagId}/valueの使用を推奨。
6. コード位置
- コントローラー:
src/plantpulse/app/edge/api/v1/—OpcAPI·TagAPI·AppAPI·SystemAPI·SystemTimeAPI·MonitoringAPI·EdgeStatusAPI·HaAPI·ConnectIoAPI·FormulaAPI - MCP サーバー:
src/plantpulse/app/edge/module/mcp/— MCP サーバー - エンベロープ · 共通レスポンス:
api/v1/RestApiSupport.java·api/v1/dto/Api*.java - OPC UA サーバー:
src/plantpulse/app/edge/module/opcua/OPCUAViewerController.java - アップグレードプロキシ:
src/plantpulse/app/edge/module/system/config/ConfigController.java - ビジネスロジック:
src/plantpulse/app/edge/module/connect/ConnectService.java - レスポンスエンベロープヘルパー:
plantpulse.app.edge.core.web.ControllerSupport(ok()/error())