콘텐츠로 이동
무료 평가 시작

OPC UA PubSub over MQTT

설비 쪽 발행자와 서버 쪽 구독자가 MQTT 브로커만 공유하는 구성을 만듭니다.

publisher process Mosquitto subscriber process
(publisher_id 100) ──────▶ 127.0.0.1:1883 ──────▶ (Received messages)
opcua/uadp/data/100/line1 │
└──▶ mosquitto_sub (observer)
  • 발행자는 헤드리스 시뮬레이터의 시나리오 모드로 DataSet 두 개를 보내고 끝납니다.
  • 구독자는 헤드리스 시뮬레이터를 대화형으로 실행해 받은 메시지를 확인합니다.
  • mosquitto_sub는 와일드카드 필터로 모든 PublisherId의 데이터를 관찰합니다.

PubSub의 DataSet, WriterGroup, DataSetWriter 개념은 OPC UA 배경 지식에서 설명합니다.

  1. mqtt-lab 디렉터리에 구독자 입력 파일과 발행자 시나리오 파일을 저장합니다.

    subscriber.jsonl
    {"action":"configure","config":{"pubsub":{"transport":"mqtt","role":"subscriber","broker":"127.0.0.1:1883","topic":"opcua/uadp/data/100/line1"}}}
    {"action":"pubsub_start"}

    구독자 입력은 헤드리스 시뮬레이터에 보낼 JSON 명령을 한 줄에 하나씩 담습니다.

    publisher-scenario.json
    [
    {
    "command": {
    "action": "configure",
    "config": {
    "pubsub": {
    "transport": "mqtt",
    "role": "publisher",
    "broker": "127.0.0.1:1883",
    "topic": "opcua/uadp/data/100/line1",
    "publisher_id": 100,
    "writer_group_id": 1,
    "dataset_writer_id": 1
    }
    }
    }
    },
    { "command": { "action": "pubsub_start" }, "expect": { "running": true, "transport": "mqtt" } },
    {
    "command": {
    "action": "pubsub_publish",
    "fields": [
    { "type": "Double", "value": 21.5 },
    { "type": "Boolean", "value": true },
    { "type": "String", "value": "RUN" }
    ]
    },
    "expect": { "sequence": 1 }
    },
    {
    "command": {
    "action": "pubsub_publish",
    "fields": [
    { "type": "Double", "value": 21.75 },
    { "type": "Boolean", "value": true },
    { "type": "String", "value": "RUN" }
    ]
    },
    "expect": { "sequence": 2 }
    },
    { "command": { "action": "pubsub_stop" }, "expect": { "running": false } }
    ]

    시나리오 파일은 단계 배열입니다. 각 단계의 command를 차례로 실행하고, expect가 있으면 결과의 같은 이름 값과 비교합니다. 발행자는 PublisherId 100, WriterGroupId 1, DataSetWriterId 1을 쓰고, OPC UA PubSub의 토픽 관례에 따라 opcua/uadp/data/100/line1로 발행합니다.

  2. 네 터미널 모두에서 시뮬레이터 패키지 경로를 변수로 정하고 mqtt-lab으로 이동합니다.

    터미널 창
    PDCOMM_APPS=<시뮬레이터 패키지 디렉터리>
    cd mqtt-lab
  3. 첫 번째 터미널에서 브로커를 시작합니다.

    터미널 창
    mosquitto -c local.conf
  4. 두 번째 터미널에서 OPC UA 데이터 토픽 전체를 관찰합니다.

    터미널 창
    mosquitto_sub -V 5 -t 'opcua/uadp/data/+/#' -F '%t qos=%q retain=%r content-type=%C user-properties=%P %l bytes'

    +는 PublisherId 단계, #은 그 아래 모든 단계와 일치합니다.

  5. 세 번째 터미널에서 구독자를 시작합니다.

    터미널 창
    cat subscriber.jsonl - | "$PDCOMM_APPS/bin/pdcomm-opcua-headless"

    cat이 파일의 두 줄을 먼저 보내고, 이어서 키보드 입력을 시뮬레이터로 넘깁니다(-). 두 번째 출력 줄에 "role":"subscriber","running":true가 있으면 구독이 준비된 것입니다.

    {"ok":true,"result":{"acknowledged":0,"broker":"127.0.0.1:1883","destination":null,"dropped":0,"error":null,"listen":null,"queued":0,"received":0,"rejected":0,"role":"subscriber","running":true,"sent":0,"topic":"opcua/uadp/data/100/line1","transport":"mqtt"}}

    Windows PowerShell에서는 bin\pdcomm-opcua-headless.exe를 실행한 뒤 subscriber.jsonl의 두 줄을 붙여 넣습니다.

  6. 네 번째 터미널에서 발행자 시나리오를 실행합니다.

    터미널 창
    "$PDCOMM_APPS/bin/pdcomm-opcua-headless" --scenario publisher-scenario.json

    단계마다 결과가 한 줄씩 나오고 종료 코드 0으로 끝납니다. 첫 줄(구성 결과)은 길어서 생략했습니다.

    {"result":{"acknowledged":0,"broker":"127.0.0.1:1883","destination":null,"dropped":0,"error":null,"listen":null,"queued":0,"received":0,"rejected":0,"role":"publisher","running":true,"sent":0,"topic":"opcua/uadp/data/100/line1","transport":"mqtt"},"step":2}
    {"result":{"bytes":52,"fields":3,"sequence":1},"step":3}
    {"result":{"bytes":52,"fields":3,"sequence":2},"step":4}
    {"result":{"running":false},"step":5}

    두 번째 터미널의 관찰자에는 두 줄이 나옵니다.

    opcua/uadp/data/100/line1 qos=0 retain=0 content-type=application/opcua+uadp user-properties=UAMessageType:ua-data 52 bytes
    opcua/uadp/data/100/line1 qos=0 retain=0 content-type=application/opcua+uadp user-properties=UAMessageType:ua-data 52 bytes
  7. 세 번째 터미널(구독자)에 다음 줄을 입력합니다.

    {"action":"pubsub_received"}
    {"ok":true,"result":{"messages":[{"bytes":52,"datasets":[{"fields":[{"type":"Double","value":21.5},{"type":"Boolean","value":true},{"type":"String","value":"RUN"}],"major":1,"minor":1,"sequence":1}],"publisher_id":100,"source":"mqtt","writer_group":1,"writer_ids":[1]},{"bytes":52,"datasets":[{"fields":[{"type":"Double","value":21.75},{"type":"Boolean","value":true},{"type":"String","value":"RUN"}],"major":1,"minor":1,"sequence":2}],"publisher_id":100,"source":"mqtt","writer_group":1,"writer_ids":[1]}]}}

    publisher_id 100, writer_ids [1], 순번 1과 2가 발행자 설정과 일치합니다. 구독자는 자신의 publisher_id(기본값 77)와 관계없이 토픽으로 메시지를 받습니다.

구독자를 GUI로 바꿔도 결과는 같습니다.

  1. OPC UA 시뮬레이터 GUI를 열고 PubSub 탭으로 이동합니다.
  2. Transport를 UADP over MQTT, Role을 Subscriber로 고릅니다.
  3. MQTT broker에 127.0.0.1:1883, MQTT topic에 opcua/uadp/data/100/line1을 입력하고 Start PubSub를 누릅니다.
  4. 6단계의 발행자 시나리오를 다시 실행한 뒤 Show received를 누르면 표에 두 행이 나타납니다.

화면 구성은 시뮬레이터 PubSub 화면에서 설명합니다.

  1. 세 번째 터미널에서 {"action":"shutdown"}을 입력해 구독자를 끝냅니다.
  2. 두 번째 터미널의 mosquitto_sub와 첫 번째 터미널의 브로커를 Ctrl+C로 끝냅니다.
증상 원인 해결
pubsub_received의 messages가 비어 있음 구독자를 발행자보다 늦게 시작했습니다. 시뮬레이터는 보존 메시지를 쓰지 않고 매번 새 세션으로 연결합니다. 구독자를 먼저 시작하고 발행자 시나리오를 다시 실행합니다.
관찰자에는 보이는데 구독자는 받지 못함 구독자의 토픽이 발행 토픽과 다릅니다. 두 파일의 topic이 같은지 확인합니다.
시나리오가 Scenario step 2 failed: MQTT broker connection failed: …로 끝남 브로커에 연결하지 못했습니다. 3단계의 브로커와 broker 주소를 확인합니다. 오류 뒷부분은 연결 문제에서 찾습니다.