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/* 呼び出しを1行で記録 (メソッド/パス/ステータス/所要時間/IP/ユーザー/auth モード) |
| メインコントローラ | plantpulse.app.edge.api.v1.* (OpcAPI, TagAPI, AppAPI, SystemAPI, MonitoringAPI) |
| エラー envelope | {data, meta, errors} — filter/advice 経路も同一 |
| HTTPS | edge.rest.api.https_only=true が既定。plain HTTP クライアント呼び出しは拒否、または Tomcat の HTTPS リダイレクトポリシーに従う |
認証 (ApiAuthFilter)
app.properties にて edge.rest.api.auth=true で有効化します。キーは edge.rest.api.key であり、運用環境では EDGE_REST_API_KEY_FILE または EDGE_REST_API_KEY で注入します。
次の3つのうち いずれか1つでも 満たせば通過します。
A. ログインセッションによる通過 (ブラウザ)
すでに /login/form でログインしているユーザー — セッションに _USER_LOGIN attribute (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() (タイミング攻撃対策)。
edge.rest.api.auth=false (開発 / 一時利用)
認証を無効にすると ApiAuthFilter は直ちに chain.doFilter() — 外部からキーなしで呼び出せます。運用ネットワークでは常に true を推奨します。
Audit ログ (ApiAccessLogFilter)
すべての /api/* 呼び出しを1行のログとして記録します — 認証失敗 (401) も含め、audit トレイルを残します (フィルタチェーン順: 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 のリバースプロキシ構成でのみ 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 | 認証/権限拒否 — audit 優先 |
| その他 (2xx, 3xx, 一般的な 4xx) | INFO | 通常の呼び出し |
活用
- 誰がキーを誤って送信しているか —
grep '\[API_ACCESS\].*status=401'で 401 トレイルを確認 - 5xx リグレッションの追跡 —
grep '\[API_ACCESS\].*status=5'+error=フィールド - 呼び出し頻度 / ユーザー別集計 — auth=apikey/session でグルーピングし、ip 別に分析
- レスポンス遅延・スロークエリ —
duration=が大きい行を抽出
キーの平文は どこにも ログに残りません — query のマスキング + user フィールドの先頭4文字のみで識別します。侵害インシデント分析 に十分な識別性と平文露出防止のバランスです。
web.xml のマッピング順序: ApiAccessLogFilter が先、その次に ApiAuthFilter。AccessLog が finally ブロックで status を記録するため、認証に失敗した 401 呼び出しもそのまま audit されます。順序が逆になると、401 呼び出しが access log から欠落します。
レスポンス envelope
v1 REST API のレスポンスは共通 envelope でラップされます (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-style envelope (result, _session_id, _user_id) は REST v1 では使用しません。
1. エンドポイント一覧
1.1 OPC
| スラグ | Method | URL | 説明 |
|---|---|---|---|
flow_edge_opc_list | GET | /api/v1/opc | OPC 一覧 (tag_count / connection_status / scan_status を含む) |
flow_edge_opc_create | POST | /api/v1/opc | OPC + タグの一括登録 |
flow_edge_opc_update | PUT | /api/v1/opc/{opcId} | OPC + タグの一括更新 (path の opcId が body の opc_id を上書き) |
flow_edge_opc_delete | DELETE | /api/v1/opc/{opcId} | OPC および配下タグの一括削除 |
flow_edge_opc_start | POST | /api/v1/opc/{opcId}/start | 収集開始 |
flow_edge_opc_stop | POST | /api/v1/opc/{opcId}/stop | 収集停止 |
1.2 Tag
| スラグ | Method | URL | 説明 |
|---|---|---|---|
flow_edge_tag_list | GET | /api/v1/opc/{opcId}/tag | 特定 OPC のタグ一覧 (最終値/状態を含む) |
flow_edge_tag_create | POST | /api/v1/opc/{opcId}/tag | タグ単体の登録 |
flow_edge_tag_update | PUT | /api/v1/tag/{tagId} | タグ単体の部分更新 (既存 row をマージ後 upsert) |
flow_edge_tag_delete | DELETE | /api/v1/tag/{tagId} | タグ単体の削除 |
flow_edge_tag_read | GET | /api/v1/tag/{tagId}/value[?fresh=true] | キャッシュ値 (default)、fresh=true の場合は PLC から直接 read |
flow_edge_tag_write | POST | /api/v1/tag/{tagId}/value | 値の書き込み — 全プロトコル対応 (HTTP / OPC-UA / Modbus / MELSEC / S7 / LS / EIP) |
1.3 Monitoring / Edge / Transfer
| スラグ | Method | URL | 説明 |
|---|---|---|---|
flow_edge_monitoring | GET | /api/v1/monitoring | CPU/メモリ/ネットワーク/ディスク/スレッドなど MonitorBean の全フィールド |
flow_edge_info | GET | /api/v1/edge | エッジの識別/バージョン/サイト/OS/稼働時間メタ |
flow_edge_transfer | GET | /api/v1/transfer | outbound transfer (API/MQTT/Sparkplug) ごとのヘルス状態 |
1.3a System (認証不要 — LB / k8s probe / OTA 用)
/api/v1/system/* のうち以下の3つは ApiAuthFilter ホワイトリストを通過します (OPC/Tag 情報は一切公開しません)。
| Method | URL | 説明 | HTTP |
|---|---|---|---|
| GET | /api/v1/system/health | 8コンポーネント (cassandra / redis / mqtt / node_red / opc_ua / edge_core / tse / grafana) の TCP 200ms probe + 総合 status | 200 (all UP) / 503 (DEGRADED) |
| GET | /api/v1/system/ready | monitor + V5 api_client の準備状態 | 200 / 503 |
| GET | /api/v1/system/version | Metadata.VERSION + BUILD_DATE + /etc/kopens/version.env image_tag + /.dockerenv container_mode | 200 |
/health の例:
{
"data": {
"status": "UP",
"uptime_ms": 1720008,
"components": {
"cassandra": "UP", "redis": "UP", "mqtt": "UP",
"node_red": "UP", "opc_ua": "UP"
}
}
}
/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秒) の probe で auto-rollback を判断します。Docker HEALTHCHECK も /health を使用します。
1.4 OPC-UA ビューア (OPCUAViewerAPI)
内蔵 OPC-UA サーバーの endpoint / ノードツリー / 時系列 — /ui/opcua 画面が使用します。
| Method | URL | 説明 |
|---|---|---|
| GET | /ui/opcua/info[?reveal=true] | 内蔵 OPC-UA サーバーの endpoint URL (TCP/TLS) / Application 名 / 認証情報 (reveal=true + 認証済みセッション時は password 平文) |
| GET | /ui/opcua/tree | Site → OPC → Tag ツリー (NodeId 含む)。EDGE_* のシステム OPC は常に connection_status=CONNECTED |
| GET | /ui/opcua/history?tagId=...&minutes=N&limit=M | Cassandra tm_tag_point の時系列照会。入力検証後、ASC ソートされた points 配列を返す |
1.5 アップグレード / システム (ConfigAPI)
/config/* は v1 REST API とは別 (管理者画面用) ですが、本マニュアルで併せて整理します。
| Method | URL | 説明 |
|---|---|---|
| GET | /config/upgrade/check | server-to-server で product.kopens.io の VERSION.JSON を取得し {result, latest_version, latest_build_date} を返す。失敗時は result=ERROR |
| POST | /config/upgrade | bin/upgrade.sh を実行 (長時間処理) |
| POST | /config/restart | bin/restart.sh を実行 (Tomcat 再起動) |
| POST | /config/reboot | bin/reboot.sh を実行 (OS 再起動) |
| POST | /config/temp-clean | bin/clean.sh を実行 (ログ/一時ファイル整理) |
| POST | /config/backup | bin/backup.sh を実行 |
| POST | /config/firmware | bin/firmware.sh を実行 (dnf update -y) |
| GET | /config/load | app.properties のテキストを返す |
| POST | /config/save | app.properties を保存 |
2. リクエスト / レスポンス例
2.1 GET /api/v1/opc — OPC 一覧
curl -s http://<edge-host>/api/v1/opc | jq
{
"result": "OK",
"data": {
"data": [
{
"opc_id": "OPC_UA_Kepware",
"opc_type": "OPCUA",
"opc_name": "OPC_UA_Kepware",
"opc_agent_ip": "192.168.0.40",
"opc_agent_port": "49320",
"auto_collect": "true",
"timecycle": "1000",
"options": {"username": "kopens", "password": "***", "discovery": "false"},
"tag_count": 11,
"point_count": 5819,
"connection_status": "CONNECTED",
"scan_status": "START"
}
]
}
}
⚠
data.data[]の形式 (二重ラップ) — 上記 envelope セクションを参照。
2.2 POST /api/v1/opc — OPC + タグの一括登録
curl -X POST http://<edge-host>/api/v1/opc \
-H "Content-Type: application/json" \
-d '{
"opc_id": "OPC_NEW",
"opc_type": "OPCUA",
"opc_name": "신규 연결",
"opc_agent_ip": "10.0.0.10",
"opc_agent_port": "49320",
"site_id": "SITE_00001",
"auto_collect": true,
"timecycle": 1000,
"tag_list": [
{"tag_id": "TAG_001", "tag_name": "Sine",
"plc_address": "ns=2;s=Sine1", "data_type": "Float"}
]
}'
2.3 POST /api/v1/opc/{opcId}/tag — タグ単体の登録
{
"tag_id": "TAG_NEW",
"tag_name": "신규 태그",
"plc_address": "ns=2;s=NewTag",
"data_type": "Float",
"description": "..."
}
レスポンス:
{ "result": "OK", "data": { "result": "SUCCESS", "tag_id": "TAG_NEW" } }
site_id は OPC から自動的に補完されます。
2.4 PUT /api/v1/tag/{tagId} — タグの部分更新
リクエスト body には変更するフィールドのみを送ります。サーバー側で既存 row とマージ後 upsert します。
{ "description": "변경된 설명" }
2.5 GET /api/v1/tag/{tagId}/value — 最終値の照会
既定 (キャッシュ):
curl -s http://<edge-host>/api/v1/tag/TAG_UA_0004/value | jq
{
"result": "OK",
"data": {
"tag_id": "TAG_UA_0004",
"value": "28.9688",
"value_time": "2026-05-06 20:59:39.000",
"value_read_status": "SUCCESS",
"value_read_error_message": ""
}
}
fresh=true (PLC から直接 read):
curl -s 'http://<edge-host>/api/v1/tag/TAG_UA_0004/value?fresh=true' | jq
PLC read 失敗時は外部 result=ERROR + message:
{ "result": "ERROR", "message": "fresh read failed: ..." }
⚠ 存在しないタグを照会しても envelope は
result=OKで応答し、valueが"-"で埋められた placeholder ペイロードを返します (現在の動作)。
2.6 POST /api/v1/tag/{tagId}/value — 値の書き込み
curl -X POST http://<edge-host>/api/v1/tag/TAG_HTTP_001/value \
-H "Content-Type: application/json" \
-d '{ "value": "42" }'
成功:
{
"result": "OK",
"data": {
"result": "SUCCESS",
"opc_id": "OPC_LS_TEST",
"plc_address": "D1000",
"value": "42",
"actual_read": "42"
}
}
失敗 (ドライバが false を返す / OPC DISCONNECTED / スケジューラ未稼働) — 外部 envelope result=ERROR:
{ "result": "ERROR", "message": "쓰기 실패 (opc_type=EIP) — EIP 펌웨어/패치에 따라 ..." }
対応プロトコル: HTTP / OPC-UA / Modbus / MELSEC / S7 / LS XGT / EIP — すべて可能。EIP は PLC のファームウェア/パッチにより制約が生じる場合があります。
2.7 POST /api/v1/opc/{opcId}/start — 収集開始
curl -X POST http://<edge-host>/api/v1/opc/OPC_NEW/start
内蔵 OPCUA/MODBUS シミュレータの自動起動は未対応です。テスト/デモデータには外部 plantpulse-simulator またはテストタグを使用してください。
2.8 GET /api/v1/edge — エッジ識別 / 稼働情報
EdgeAPI.edgeInfo() が直接埋めるフィールド:
{
"result": "OK",
"data": {
"id": "EDGE_00303",
"site_id": "SITE_00001",
"site_name": "S1_LOTTE_CS_DJ_SITE",
"hostname": "EDGE-303",
"product_name": "PlantPulse Edge",
"version": "2026",
"build_date": "2026-05-08",
"os_name": "Linux",
"os_version": "6.14.5-100.fc40.x86_64",
"os_arch": "amd64",
"started": true,
"started_date": 1778066700000,
"uptime_ms": 124500,
"always_on": false,
"ttl": 30,
"sended_count": 8063
}
}
2.9 GET /api/v1/monitoring — システムメトリクス
MonitorBean の全フィールドをそのままシリアライズします。1秒周期で更新。
curl -s http://<edge-host>/api/v1/monitoring | jq '.data | keys'
主なフィールド: cpu_used_percent, memory_used_percent, disk_used_percent, temperature, ping, api, opc_count, tag_count, mps, mps_history, queue_size, plc_value_read_success_count, plc_value_read_error_count, plc_value_write_success_count, plc_value_write_error_count, plc_con_connected_count, plc_con_disconnected_count, plc_scan_start_count, plc_scan_not_collect_count, plc_scan_stop_count, sended_point_count, sended_point_bytes, system_total_db_size, system_error_count, docker_on, docker_container_up_count, docker_container_total_count。
2.10 GET /api/v1/transfer — 外部転送ヘルス
curl -s http://<edge-host>/api/v1/transfer | jq
{
"result": "OK",
"data": {
"transfers": [
{ "type": "API", "enabled": true, "connected": true, "sent_count": 8063 },
{ "type": "MQTT", "enabled": true, "connected": true, "sent_count": 12345 },
{ "type": "Sparkplug", "enabled": true, "connected": true,
"group_id": "Plant1", "edge_node_id": "EDGE_00303",
"bdSeq": 7, "seq": 211 }
]
}
}
各 transfer の getStatus() 結果をそのまま配列で返します。呼び出し中に例外が発生した場合は {type, status_error} にフォールバックします。
2.11 GET /ui/opcua/info — 内蔵 OPC-UA サーバー情報
curl -s http://<edge-host>/ui/opcua/info | jq
{
"result": "OK",
"data": {
"application_name": "PlantPulse Edge OPC-UA Server",
"product_uri": "urn:plantpulse:opcua:server",
"domain": "127.0.0.1",
"tcp_endpoint": "opc.tcp://127.0.0.1:12000",
"tls_endpoint": "opc.tcp://127.0.0.1:12443",
"namespace_index": 2,
"auth_username": "edge",
"auth_password_set": true,
"auth_anonymous": false
}
}
?reveal=true かつ認証済みセッションの場合は auth_password の平文を追加します。
2.12 GET /ui/opcua/tree — ノードツリー
Site → OPC → Tag の3階層ツリー。各タグに NodeId、最新値、データ型、description を含みます。EDGE システム OPC は connection_status=CONNECTED を強制します。
NodeId の規約: ns=2;s=<SITE>.<OPC>.<TAG>。
2.13 GET /ui/opcua/history — 時系列
| パラメータ | 既定 | 制限 |
|---|---|---|
tagId | 必須 | 最大200文字 |
minutes | 10 | 1 – 1440 (24h) |
limit | 600 | 1 – 5000 |
curl -s 'http://<edge-host>/ui/opcua/history?tagId=TAG_S7_1000&minutes=10&limit=100' | jq
{
"result": "OK",
"data": {
"tag_id": "TAG_S7_1000",
"minutes": 10,
"count": 100,
"points": [
{ "ts": 1778176880010, "value": "211", "type": "integer", "quality": 192 }
]
}
}
tagId の欠落/空白/200文字超過時は外部 result=ERROR (message)。Cassandra の timestamp が wrap オブジェクトで来た場合も extractTimestampMs で epoch ms に変換します。
3. エラーレスポンスのパターン
| ケース | レスポンス形式 |
|---|---|
| 外部 envelope ERROR (write 失敗 / fresh read 失敗 / opcua/history の入力検証失敗 / upgrade/check のダウンロード失敗) | {result:"ERROR", message:"..."} (HTTP 200) |
| 内部業務 ERROR (CRUD 時の tag/opc not found / DB 整合性 / スケジューラ未稼働) | {result:"OK", data:{result:"ERROR", msg:"..."}} (HTTP 200) |
| Spring のマッピング不一致 (必須パラメータ欠落、メソッド誤りなど) | HTTP 500 + スタックトレース — 推奨: コントローラ層で @RequestParam(required=false) を処理し envelope に変換 |
認証 — edge.rest.api.auth はインストール既定値が true であり、ApiAuthFilter が X-API-Key / Bearer を要求します (上記の認証節を参照)。ヘルスエンドポイントのみ例外 |
4. 動作メモ
- タグ単体の照会:
APP_TAG.xmlのR03(WHERE TAG_ID = :tag_id ALLOW FILTERING)。 - OPC + タグの一括登録 / タグ単体の登録 後に
ConnectService.restartComponents()を呼び出し →OPCAndTagsCacheの reload および 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の使用を推奨。
5. コードの場所
- コントローラ:
src/plantpulse/app/edge/api/v1/OpcAPI.java · api/v1/TagAPI.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 - レスポンス envelope helper:
plantpulse.app.edge.core.web.ControllerSupport(ok()/error())