[
  {
    "id": "tab_opcua_read",
    "type": "tab",
    "label": "Edge 데이터 수집",
    "disabled": false,
    "info": "",
    "env": []
  },
  {
    "id": "inj_taglist",
    "type": "inject",
    "z": "tab_opcua_read",
    "name": "5초 태그 조회",
    "props": [
      {
        "p": "payload"
      }
    ],
    "repeat": "5",
    "once": true,
    "onceDelay": 3,
    "payload": "",
    "payloadType": "date",
    "x": 130,
    "y": 160,
    "wires": [
      [
        "fn_opc_list_prep"
      ]
    ]
  },
  {
    "id": "fn_opc_list_prep",
    "type": "function",
    "z": "tab_opcua_read",
    "name": "OPC 목록 조회 준비",
    "func": "var sid = global.get('jsession');\nif (!sid) {\n    node.warn('세션 없음 - 모니터 탭 로그인 대기중');\n    return null;\n}\nmsg.url = 'http://127.0.0.1/connect/opc/list';\nmsg.method = 'POST';\nmsg.payload = JSON.stringify({});\nmsg.headers = {\n    'Content-Type': 'application/json',\n    'Cookie': 'PP_EDGE_JSESSIONID=' + sid\n};\nreturn msg;",
    "outputs": 1,
    "x": 330,
    "y": 160,
    "wires": [
      [
        "http_opc_list"
      ]
    ]
  },
  {
    "id": "http_opc_list",
    "type": "http request",
    "z": "tab_opcua_read",
    "name": "OPC 목록 조회",
    "method": "use",
    "ret": "obj",
    "paytoqs": "ignore",
    "url": "",
    "x": 540,
    "y": 160,
    "wires": [
      [
        "fn_opc_list_split"
      ]
    ]
  },
  {
    "id": "fn_opc_list_split",
    "type": "function",
    "z": "tab_opcua_read",
    "name": "OPC별 태그 조회 분기",
    "func": "var d = msg.payload;\nif (!d || d.result !== 'OK') {\n    node.warn('OPC 목록 조회 실패: ' + JSON.stringify(d && d.message || d));\n    if (d && d.result === 'ERROR') global.set('jsession', null);\n    return null;\n}\nvar opcs = (d.data && d.data.data) ? d.data.data : [];\nvar sid = global.get('jsession');\n\n// 총 OPC 수를 flow context에 저장 (합산용)\nflow.set('opc_total', opcs.length);\nflow.set('opc_done', 0);\nflow.set('all_tags', []);\n\nvar msgs = opcs.map(function(opc) {\n    return {\n        url: 'http://127.0.0.1/connect/tag/list',\n        method: 'POST',\n        payload: JSON.stringify({ opc_id: opc.opc_id }),\n        headers: {\n            'Content-Type': 'application/json',\n            'Cookie': 'PP_EDGE_JSESSIONID=' + sid\n        },\n        opc_id: opc.opc_id\n    };\n});\nreturn [msgs];",
    "outputs": 1,
    "x": 760,
    "y": 160,
    "wires": [
      [
        "http_taglist"
      ]
    ]
  },
  {
    "id": "http_taglist",
    "type": "http request",
    "z": "tab_opcua_read",
    "name": "태그 목록 조회",
    "method": "use",
    "ret": "obj",
    "paytoqs": "ignore",
    "url": "",
    "x": 980,
    "y": 160,
    "wires": [
      [
        "fn_taglist_collect"
      ]
    ]
  },
  {
    "id": "fn_taglist_collect",
    "type": "function",
    "z": "tab_opcua_read",
    "name": "태그 수집 및 합산",
    "func": "var d = msg.payload;\nif (!d || d.result !== 'OK') {\n    node.warn('태그 조회 실패 [' + msg.opc_id + ']: ' + JSON.stringify(d && d.message || d));\n    if (d && d.result === 'ERROR') global.set('jsession', null);\n    // 실패해도 카운트는 진행\n}\n\nvar rows = (d && d.data && d.data.data) ? d.data.data : [];\nvar tags = rows.map(function(r) {\n    return {\n        id:      r.tag_id,\n        opc_id:  msg.opc_id,\n        value:   r.value,\n        type:    r.data_type,\n        time:    r.value_time,\n        status:  r.value_read_status,\n        address: r.plc_address\n    };\n});\n\n// flow context에 누적\nvar allTags = flow.get('all_tags') || [];\nallTags = allTags.concat(tags);\nflow.set('all_tags', allTags);\n\nvar done = (flow.get('opc_done') || 0) + 1;\nflow.set('opc_done', done);\nvar total = flow.get('opc_total') || 1;\n\nif (done >= total) {\n    // 모든 OPC 완료 - global cache 업데이트\n    global.set('tags_cache', allTags);\n    flow.set('all_tags', []);\n    flow.set('opc_done', 0);\n    msg.payload = allTags;\n    msg.topic = '전체 태그 ' + allTags.length + '개 수집 완료';\n    return msg;\n}\n// 아직 수집 중 - 출력 없음\nreturn null;",
    "outputs": 1,
    "x": 1200,
    "y": 160,
    "wires": [
      [
        "dbg_taglist",
        "n_ws_out_opc"
      ]
    ]
  },
  {
    "id": "dbg_taglist",
    "type": "debug",
    "z": "tab_opcua_read",
    "name": "전체 태그 현재값",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": true,
    "complete": "topic",
    "targetType": "msg",
    "statusVal": "topic",
    "statusType": "auto",
    "x": 1420,
    "y": 160,
    "wires": []
  },
  {
    "id": "n_ws_out_opc",
    "type": "websocket out",
    "z": "tab_opcua_read",
    "name": "WebSocket push /ws/edge",
    "server": "cfg_ws_listener",
    "client": "",
    "x": 1420,
    "y": 220,
    "wires": []
  },
  {
    "id": "cfg_ws_listener",
    "type": "websocket-listener",
    "path": "/ws/edge",
    "wholemsg": "false"
  }
]
