3.13 TopicPubSub 구독/발행 클래스
개요
TopicPubSub 은 WebSocket 기반 실시간 구독/발행 기능을 제공합니다. 권장 진입점은 Arm::topicPubSub 입니다.
- 먼저
Arm::Connect()를 호출합니다. - 그다음
arm.topicPubSub.Connect()를 호출합니다. - 수신을 시작합니다.
- 상태 / 레지스터 / 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; // 예제에서는 연결 해제 결과를 보관합니다. 실제 코드는 오류 코드를 확인해야 합니다.예제 코드
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 | 없음 | bool | sub_pub WebSocket 연결 상태만 반영 |
StartReceiving | 단일 JSON 콜백 | STATUS_CODE | 백그라운드 수신 시작. 이후 콜백이 이전 콜백을 덮어씀 |
SubscribeStatus | topic 목록, 주파수 | STATUS_CODE | addTopic 명령 전송 |
SubscribeRegister | 레지스터 타입, ID 목록, 주파수 | STATUS_CODE | addRegTopic 명령 전송 |
SubscribeIo | IO 타입+번호 목록, 주파수 | STATUS_CODE | addIoTopic 명령 전송 |
SendText | 원시 텍스트 | STATUS_CODE | 원시 텍스트 전송 |
RemoveMessageHandler | 없음 | STATUS_CODE | 현재 활성 콜백 제거. SDK 수신 큐에는 영향 없음 |
Receive | timeout 밀리초 | 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() 가 이미 바인딩한 대상 주소를 우선 사용합니다. |
timeoutSecs | WebSocket handshake timeout. 기본값은 10초입니다. |
| 반환 | OK / INVALID_IP_ADDRESS / 연결 단계의 기타 오류 코드 |
제약 및 동작
arm.topicPubSub을 통해 사용하는 경우 보통 파라미터 없는Connect()를 바로 호출하면 됩니다.TopicPubSub를 별도로 인스턴스화한 경우 주소를 명시적으로 전달해야 합니다.- 프록시 WebSocket 포트
5609를 고정으로 사용합니다. - 현재 같은
TopicPubSub인스턴스에 이미 연결되어 있으면 다시 호출해도 현재 연결을 유지하고 바로OK를 반환합니다. - 여러
Arm/TopicPubSub인스턴스는 같은 프로세스 안에서 동시에 공존할 수 있습니다. 그중 하나를 연결 해제해도 다른 실행 중인 인스턴스는 각자의 WebSocket 네트워크 상태를 계속 사용합니다.
다중 인스턴스 격리 예제:
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);입력
| 파라미터 | 타입 | 설명 |
|---|---|---|
handler | std::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);| 항목 | 설명 |
|---|---|
timeoutMs | timeout 시간. 기본값은 5000ms |
| 성공 반환 | Json::Value + OK |
| timeout 반환 | 빈 Json::Value + SUB_PUB_RECEIVE_TIMEOUT |
| 연결 끊김 반환 | 빈 Json::Value + NOT_CONNECTED |
Disconnect
시그니처
cpp
STATUS_CODE Disconnect();현재 WebSocket 연결을 해제합니다. 현재 아직 연결되지 않았으면 OK 를 반환합니다.