Skip to content

3.13 TopicPubSub 구독/발행 클래스

개요

TopicPubSub 은 WebSocket 기반 실시간 구독/발행 기능을 제공합니다. 권장 진입점은 Arm::topicPubSub 입니다.

  1. 먼저 Arm::Connect() 를 호출합니다.
  2. 그다음 arm.topicPubSub.Connect() 를 호출합니다.
  3. 수신을 시작합니다.
  4. 상태 / 레지스터 / IO 구독을 시작합니다.

권장 사용법

cpp
Arm arm;  // 로봇 세션 객체를 생성합니다.
STATUS_CODE ret = arm.Connect("10.27.1.254");  // 먼저 컨트롤러 또는 라우터 주소에 연결합니다.
if (ret != STATUS_CODE::OK) {  // Arm 연결 실패 여부를 판단합니다.
    return;  // 연결 실패 시 현재 업무 흐름을 종료합니다.
}  // Arm 연결 결과 판단을 종료합니다.
ret = arm.topicPubSub.Connect();  // Arm 연결 성공 후 SubPub WebSocket 연결을 설정합니다.
if (ret != STATUS_CODE::OK) {  // SubPub 연결 실패 여부를 판단합니다.
    arm.Disconnect();  // SubPub 연결 실패 시 Arm 세션을 연결 해제합니다.
    return;  // 현재 업무 흐름을 종료합니다.
}  // SubPub 연결 결과 판단을 종료합니다.
arm.topicPubSub.StartReceiving(  // 백그라운드 수신을 시작하고 메시지 콜백을 등록합니다.
    [](const Json::Value& message) {  // JSON 메시지를 받았을 때 실행할 콜백을 정의합니다.
        // 메시지 처리  // 여기서 message를 파싱하고, 시간이 걸리는 로직은 업무 스레드로 넘기십시오.
    }  // 메시지 콜백을 종료합니다.
);  // 수신 시작 호출을 종료합니다.
arm.topicPubSub.SubscribeStatus({RobotTopicType::JOINT_POSITION});  // 로봇 관절 위치 topic을 구독합니다.

시나리오 예제

아래 조각들은 "연결/수신, topic 구독, 능동 송수신, 연결 정리" 흐름으로 이 페이지의 API를 교차해서 다룹니다. 조각들은 이미 Arm::Connect() 에 성공한 arm 객체를 이어받는다고 가정합니다.

연결 설정 및 콜백 수신 시작

cpp
STATUS_CODE connectRet = arm.topicPubSub.Connect("", 10);  // Arm에 이미 바인딩된 주소로 SubPub 연결을 설정하며, handshake timeout은 10초입니다.
if (connectRet != STATUS_CODE::OK || !arm.topicPubSub.IsConnected()) {  // SubPub 연결 성공 여부를 판단합니다.
    return;  // 연결 실패 시 현재 흐름을 종료합니다.
}  // SubPub 연결 판단을 종료합니다.
STATUS_CODE receiveRet = arm.topicPubSub.StartReceiving(  // 백그라운드 수신을 시작하고 JSON 메시지 콜백을 설치합니다.
    [](const Json::Value& message) {  // 메시지를 받았을 때의 처리 함수를 정의합니다.
        const Json::Value copy = message;  // 예제에서는 메시지만 복사합니다. 실제 업무에서는 자체 큐로 넘길 수 있습니다.
        (void)copy;  // 예제 변수 미사용 경고를 피합니다.
    }  // 메시지 콜백을 종료합니다.
);  // 백그라운드 수신 시작 호출을 종료합니다.

상태, 레지스터 및 IO 구독

cpp
STATUS_CODE statusRet = arm.topicPubSub.SubscribeStatus(  // 로봇 상태 topic을 구독합니다.
    {RobotTopicType::JOINT_POSITION},  // 관절 위치 topic을 구독합니다.
    200  // 200Hz 구독 주파수를 사용합니다.
);  // 상태 구독 호출을 종료합니다.
STATUS_CODE regRet = arm.topicPubSub.SubscribeRegister(  // 레지스터 topic을 구독합니다.
    RegTopicType::R,  // R 숫자 레지스터를 구독합니다.
    {1, 2},  // 1번과 2번 레지스터를 구독합니다.
    100  // 100Hz 구독 주파수를 사용합니다.
);  // 레지스터 구독 호출을 종료합니다.
STATUS_CODE ioRet = arm.topicPubSub.SubscribeIo(  // IO topic을 구독합니다.
    {{IOTopicType::DI, 1}},  // 1번 디지털 입력을 구독합니다.
    50  // 50Hz 구독 주파수를 사용합니다.
);  // IO 구독 호출을 종료합니다.

텍스트를 능동 전송하고 큐에서 메시지 가져오기

cpp
STATUS_CODE sendRet = arm.topicPubSub.SendText("{\"cmd\":\"ping\"}");  // 원시 텍스트를 전송합니다. 보통 디버깅 또는 사용자 정의 프로토콜에서만 사용합니다.
auto [message, messageRet] = arm.topicPubSub.Receive(1000);  // SDK 수신 큐에서 메시지 하나를 가져오며 최대 1000ms 대기합니다.
(void)sendRet;  // 예제에서는 전송 결과를 보관합니다. 실제 코드는 오류 코드를 확인해야 합니다.
(void)message;  // 예제에서는 수신 메시지를 보관합니다. 실제 코드는 JSON 내용을 파싱해야 합니다.
(void)messageRet;  // 예제에서는 수신 결과를 보관합니다. 실제 코드는 timeout과 연결 끊김을 구분해야 합니다.

콜백 제거 및 연결 해제

cpp
STATUS_CODE removeRet = arm.topicPubSub.RemoveMessageHandler();  // 현재 메시지 콜백을 제거하고 수신 큐 기능은 유지합니다.
STATUS_CODE disconnectRet = arm.topicPubSub.Disconnect();  // SubPub WebSocket 연결을 해제합니다.
(void)removeRet;  // 예제에서는 콜백 제거 결과를 보관합니다. 실제 코드는 오류 코드를 확인해야 합니다.
(void)disconnectRet;  // 예제에서는 연결 해제 결과를 보관합니다. 실제 코드는 오류 코드를 확인해야 합니다.

예제 코드

cpp17/sub_pub_basic/src/main.cpp
cpp
#include "multi_instance_isolation/run.h"
#include "subscribe_topics/run.h"
#include "send_receive_text/run.h"

int main(void)
{
    // [ZH] 默认只调用一个门面方法;如需体验其他接口,请把下一行替换成下面任意一行。
    // [EN] The main function calls only one facade by default. Replace the next line with any line below to try other APIs.
    return RunSubPubBasicSubscribeTopics();
    // return RunSubPubBasicSendReceiveText();
    // return RunSubPubBasicMultiInstanceIsolation();
}

인터페이스 개요

cpp
Connect(const std::string& teachPanelIp = "", int32_t timeoutSecs = 10) -> STATUS_CODE
IsConnected() const -> bool
StartReceiving(const MessageHandler& handler) -> STATUS_CODE
SubscribeStatus(const std::vector<ROBOT_TOPIC_TYPE>& topics, int32_t frequency = 200) -> STATUS_CODE
SubscribeRegister(const REG_TOPIC_TYPE& regType, const std::vector<int32_t>& regIds, int32_t frequency = 200) -> STATUS_CODE
SubscribeIo(const std::vector<std::pair<IO_TOPIC_TYPE, int32_t>>& ioList, int32_t frequency = 200) -> STATUS_CODE
SendText(const std::string& text) -> STATUS_CODE
RemoveMessageHandler() -> STATUS_CODE
Receive(int32_t timeoutMs = 5000) -> std::pair<Json::Value, STATUS_CODE>
Disconnect() -> STATUS_CODE
메서드입력출력핵심 동작
Connect선택 주소, handshake timeout 초STATUS_CODE주소가 비어 있으면 Arm::Connect() 중 바인딩된 TP/IP를 우선 사용
IsConnected없음boolsub_pub WebSocket 연결 상태만 반영
StartReceiving단일 JSON 콜백STATUS_CODE백그라운드 수신 시작. 이후 콜백이 이전 콜백을 덮어씀
SubscribeStatustopic 목록, 주파수STATUS_CODEaddTopic 명령 전송
SubscribeRegister레지스터 타입, ID 목록, 주파수STATUS_CODEaddRegTopic 명령 전송
SubscribeIoIO 타입+번호 목록, 주파수STATUS_CODEaddIoTopic 명령 전송
SendText원시 텍스트STATUS_CODE원시 텍스트 전송
RemoveMessageHandler없음STATUS_CODE현재 활성 콜백 제거. SDK 수신 큐에는 영향 없음
Receivetimeout 밀리초std::pair<Json::Value, STATUS_CODE>SDK 수신 큐에서 다음 메시지 가져오기
Disconnect없음STATUS_CODE현재 WebSocket 연결 닫기

상세 의미

Connect

시그니처

cpp
STATUS_CODE Connect(const std::string& teachPanelIp = "", int32_t timeoutSecs = 10);
항목설명
teachPanelIp명시적으로 전달하면 직접 사용합니다. 빈 문자열이면 Arm::Connect() 가 이미 바인딩한 대상 주소를 우선 사용합니다.
timeoutSecsWebSocket handshake timeout. 기본값은 10초입니다.
반환OK / INVALID_IP_ADDRESS / 연결 단계의 기타 오류 코드

제약 및 동작

  • arm.topicPubSub 을 통해 사용하는 경우 보통 파라미터 없는 Connect() 를 바로 호출하면 됩니다.
  • TopicPubSub 를 별도로 인스턴스화한 경우 주소를 명시적으로 전달해야 합니다.
  • 프록시 WebSocket 포트 5609 를 고정으로 사용합니다.
  • 현재 같은 TopicPubSub 인스턴스에 이미 연결되어 있으면 다시 호출해도 현재 연결을 유지하고 바로 OK 를 반환합니다.
  • 여러 Arm / TopicPubSub 인스턴스는 같은 프로세스 안에서 동시에 공존할 수 있습니다. 그중 하나를 연결 해제해도 다른 실행 중인 인스턴스는 각자의 WebSocket 네트워크 상태를 계속 사용합니다.

다중 인스턴스 격리 예제:

cpp17/sub_pub_basic/src/multi_instance_isolation/run.cpp
cpp
#include <iostream>
#include <json/json.h>
#include <string>
#include <utility>

#include "arm_api.h"
#include "status_code.h"

#include "multi_instance_isolation/run.h"

namespace {

std::pair<Json::Value, STATUS_CODE> SendPingAndReceive(
    Arm& arm,
    const std::string& source
)
{
    Json::Value request;
    request["cmd"] = "ping";
    request["param"]["source"] = source;

    Json::StreamWriterBuilder builder;
    builder["indentation"] = "";
    STATUS_CODE sendRet = arm.topicPubSub.SendText(Json::writeString(builder, request));
    if (sendRet != STATUS_CODE::OK) {
        return std::make_pair(Json::Value(), sendRet);
    }

    return arm.topicPubSub.Receive(5000);
}

} // namespace

/**
 * 多实例 TopicPubSub 隔离门面。
 * @return 0 表示成功,否则返回 1。
 */
int RunSubPubBasicMultiInstanceIsolation(void)
{
    // [ZH] 请把下面地址替换成当前机器人控制器 IP 和示教器 IP。
    // [EN] Replace the following addresses with the current controller IP and teach panel IP.
    const std::string controllerIp = "10.27.1.2";
    const std::string teachPanelIp = "10.27.1.102";

    Arm armA;
    Arm armB;
    STATUS_CODE armConnectRetA = armA.Connect(controllerIp, teachPanelIp);
    STATUS_CODE armConnectRetB = armB.Connect(controllerIp, teachPanelIp);
    if (armConnectRetA != STATUS_CODE::OK || armConnectRetB != STATUS_CODE::OK) {
        std::cerr << "[cpp17_sub_pub] 多实例连接失败 / Multi-instance connect failed, A="
                  << static_cast<int>(armConnectRetA) << ", B="
                  << static_cast<int>(armConnectRetB) << "\n";
        armA.Disconnect();
        armB.Disconnect();
        return 1;
    }

    STATUS_CODE subPubConnectRetA = armA.topicPubSub.Connect(teachPanelIp, 10);
    STATUS_CODE subPubConnectRetB = armB.topicPubSub.Connect(teachPanelIp, 10);
    auto [ackA, ackRetA] = SendPingAndReceive(armA, "cpp17_multi_instance_A");
    STATUS_CODE disconnectRetA = armA.topicPubSub.Disconnect();
    auto [ackB, ackRetB] = SendPingAndReceive(
        armB,
        "cpp17_multi_instance_B_after_A_disconnect"
    );
    STATUS_CODE disconnectRetB = armB.topicPubSub.Disconnect();

    Json::StreamWriterBuilder builder;
    builder["indentation"] = "";
    std::cout << "[cpp17_sub_pub] ArmA Connect 状态码 / ArmA connect status code: "
              << static_cast<int>(subPubConnectRetA) << "\n";
    std::cout << "[cpp17_sub_pub] ArmB Connect 状态码 / ArmB connect status code: "
              << static_cast<int>(subPubConnectRetB) << "\n";
    std::cout << "[cpp17_sub_pub] ArmA Receive 状态码 / ArmA receive status code: "
              << static_cast<int>(ackRetA) << ", 消息 / Message: "
              << Json::writeString(builder, ackA) << "\n";
    std::cout << "[cpp17_sub_pub] ArmA Disconnect 状态码 / ArmA disconnect status code: "
              << static_cast<int>(disconnectRetA) << "\n";
    std::cout << "[cpp17_sub_pub] ArmB Receive 状态码 / ArmB receive status code: "
              << static_cast<int>(ackRetB) << ", 消息 / Message: "
              << Json::writeString(builder, ackB) << "\n";
    std::cout << "[cpp17_sub_pub] ArmB Disconnect 状态码 / ArmB disconnect status code: "
              << static_cast<int>(disconnectRetB) << "\n";

    armA.Disconnect();
    armB.Disconnect();
    std::cout << "[cpp17_sub_pub] 多实例隔离示例结束 / Multi-instance isolation example finished\n";

    const bool success =
        subPubConnectRetA == STATUS_CODE::OK &&
        subPubConnectRetB == STATUS_CODE::OK &&
        ackRetA == STATUS_CODE::OK &&
        disconnectRetA == STATUS_CODE::OK &&
        ackRetB == STATUS_CODE::OK &&
        disconnectRetB == STATUS_CODE::OK;
    return success ? 0 : 1;
}

IsConnected

시그니처

cpp
bool IsConnected() const;

제약 및 동작

  • 여기서 확인하는 것은 sub_pub WebSocket이며, Arm 의 HTTP 세션이 아닙니다.
  • Arm::Connect() 가 성공한 뒤에도 arm.topicPubSub.IsConnected() 는 여전히 false 입니다. arm.topicPubSub.Connect() 가 성공한 뒤에만 true 가 됩니다.

StartReceiving

시그니처

cpp
STATUS_CODE StartReceiving(const MessageHandler& handler);

입력

파라미터타입설명
handlerstd::function<void(const Json::Value&)>메시지 콜백

출력

  • 성공: OK 반환
  • 미연결: NOT_CONNECTED 반환

제약 및 동작

  • 같은 TopicPubSub 인스턴스는 하나의 활성 콜백만 유지하며, 이후 호출은 이전 콜백을 덮어씁니다.
  • 콜백은 이미 파싱된 Json::Value 를 받습니다.
  • 콜백은 WebSocket 메시지가 도착할 때 트리거됩니다.
  • SDK 수신 큐의 상한은 100 개이며, 상한을 초과하면 가장 오래된 메시지를 버립니다.
  • 콜백은 가능한 한 가볍게 유지하고, 시간이 걸리는 로직은 업무 스레드로 넘기십시오.
  • 콜백 안에서 Arm::Connect() , Arm::Disconnect() , TopicPubSub::Connect() , TopicPubSub::Disconnect() , StartReceiving() , RemoveMessageHandler() 를 직접 호출하지 마십시오. 이러한 재진입 작업은 OTHER_ERR 를 반환하거나 적용되지 않을 수 있습니다.

SubscribeStatus

시그니처

cpp
STATUS_CODE SubscribeStatus(
    const std::vector<ROBOT_TOPIC_TYPE>& topics,
    int32_t frequency = 200
);
항목설명
topics구독할 topic 목록. 상수는 ROBOT_TOPIC_TYPE 참고
frequency구독 주파수. 단위는 Hz, 기본값은 200
구독 동작로봇 상태 topic 추가

SubscribeRegister

시그니처

cpp
STATUS_CODE SubscribeRegister(
    const REG_TOPIC_TYPE& regType,
    const std::vector<int32_t>& regIds,
    int32_t frequency = 200
);
항목설명
regType레지스터 타입. REG_TOPIC_TYPE 참고
regIds레지스터 번호 목록
frequency구독 주파수. 단위는 Hz
구독 동작레지스터 topic 추가

SubscribeIo

시그니처

cpp
STATUS_CODE SubscribeIo(
    const std::vector<std::pair<IO_TOPIC_TYPE, int32_t>>& ioList,
    int32_t frequency = 200
);
항목설명
ioList각 항목은 (ioType, ioId)
frequency구독 주파수. 단위는 Hz
구독 동작IO topic 추가

SendText

시그니처

cpp
STATUS_CODE SendText(const std::string& text);

원시 텍스트를 직접 전송하는 데 사용됩니다. 상태, 레지스터 또는 IO 구독만 하는 경우 보통 위의 구독 인터페이스를 우선 사용합니다.

RemoveMessageHandler

시그니처

cpp
STATUS_CODE RemoveMessageHandler();

제약 및 동작

  • 같은 TopicPubSub 인스턴스에는 활성 콜백이 하나만 있으므로 여기서 제거하는 것은 "현재 콜백"입니다.
  • 콜백을 제거한 뒤에도 Receive() 는 SDK 수신 큐에서 계속 메시지를 가져올 수 있습니다.
  • 현재 연결되어 있지 않아도 OK 를 반환합니다.

Receive

시그니처

cpp
std::pair<Json::Value, STATUS_CODE> Receive(int32_t timeoutMs = 5000);
항목설명
timeoutMstimeout 시간. 기본값은 5000ms
성공 반환Json::Value + OK
timeout 반환Json::Value + SUB_PUB_RECEIVE_TIMEOUT
연결 끊김 반환Json::Value + NOT_CONNECTED

Disconnect

시그니처

cpp
STATUS_CODE Disconnect();

현재 WebSocket 연결을 해제합니다. 현재 아직 연결되지 않았으면 OK 를 반환합니다.