跳转到内容

底层通信示例

文档目的: 指导用户搭建状态监控程序获取X30通过UDP协议上报运行状态信息。

第一步:修改X30运动主机网络配置

Section titled “第一步:修改X30运动主机网络配置”
  1. 登录X30运动主机

    连接X30 WiFi,通过ssh进入运动主机的后台(192.168.1.103),登录账号ysc的密码是 '(英文单引号)

    Terminal window
    ssh ysc@192.168.1.103
  2. 进入配置文件目录

    Terminal window
    cd /home/ysc/jy_exe/conf
  3. 编辑network.toml配置文件

    Terminal window
    vim network.toml
  4. 在文件末尾添加以下配置

    # UDP状态上报配置
    [udp_status]
    ip = '192.168.1.105'
    target_port = 43897
    local_port = 43893
    ips = ["192.168.1.105", "192.168.1.106","xxxxx"] # 接收状态消息的IP地址列表
    ports = [30001, 30001, xxxxx] # 对应的端口号列表

    配置说明:

    • ips: 接收UDP状态消息的客户端IP地址数组
    • ports: 对应的端口号数组,顺序与IP地址一一对应
    • 请根据实际网络环境修改IP地址
Terminal window
cd /home/ysc/jy_exe/scripts
./restart.sh

第三步:编译并运行状态监控程序

Section titled “第三步:编译并运行状态监控程序”
Terminal window
g++ UDPtest.cpp -o UDPtest
./UDPtest

程序正常运行后将显示:

开始监听机器人状态上报,端口: 30001

当收到机器人状态数据时,显示类似以下信息:

========================================
收到状态数据 [来源:192.168.1.103]
----------------------------------------
机器人名称: X30_Robot
当前里程: 1500 cm
总里程: 12500 cm
当前运行时间: 3600 秒
总运行时间: 86400 秒
当前运动时间: 1800 秒
总运动时间: 43200 秒
摇杆数据: [0, 0.5, 0, 0]
运行模式: 自动
急停来源: 0
错误状态: 0x0
========================================

UDP状态监控运行效果

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdint.h>
#define PORT 30001
struct RcsData {
char robot_name[15];
int32_t current_mileage;
int32_t total_mileage;
int32_t current_run_time;
int32_t total_run_time;
int32_t current_motion_time;
int32_t total_motion_time;
float joystick[4];
union {
struct {
uint8_t is_nav_mode;
uint8_t emergency_source;
uint8_t mode_reserved[8];
} rcs_state_list;
uint8_t rcs_state[10];
};
union {
struct {
uint32_t imu_error : 1;
uint32_t wifi_error : 1;
uint32_t driver_heat_warn : 1;
uint32_t driver_error : 1;
uint32_t motor_heat_warn : 1;
uint32_t battery_low_warn : 1;
uint32_t battery_heat_warn : 1;
uint32_t gpio_error : 1;
uint32_t cpu_heat_warn : 1;
uint32_t cpu_freq_warn : 1;
uint32_t reserved : 22;
} error_state_bit;
uint32_t error_state;
};
};
int main() {
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
std::cout << "创建Socket失败" << std::endl;
return -1;
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
std::cout << "绑定端口失败" << std::endl;
close(sockfd);
return -1;
}
std::cout << "开始监听机器人状态上报,端口: " << PORT << std::endl;
while (true) {
RcsData robot_data;
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
ssize_t recv_len = recvfrom(sockfd, &robot_data, sizeof(robot_data), 0,
(struct sockaddr*)&client_addr, &client_len);
if (recv_len == sizeof(robot_data)) {
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, INET_ADDRSTRLEN);
std::cout << "========================================" << std::endl;
std::cout << "收到状态数据 [来源:" << client_ip << "]" << std::endl;
std::cout << "----------------------------------------" << std::endl;
std::cout << "机器人名称: " << robot_data.robot_name << std::endl;
std::cout << "当前里程: " << robot_data.current_mileage << " cm" << std::endl;
std::cout << "总里程: " << robot_data.total_mileage << " cm" << std::endl;
std::cout << "当前运行时间: " << robot_data.current_run_time << " 秒" << std::endl;
std::cout << "总运行时间: " << robot_data.total_run_time << " 秒" << std::endl;
std::cout << "当前运动时间: " << robot_data.current_motion_time << " 秒" << std::endl;
std::cout << "总运动时间: " << robot_data.total_motion_time << " 秒" << std::endl;
std::cout << "摇杆数据: [" << robot_data.joystick[0] << ", "
<< robot_data.joystick[1] << ", "
<< robot_data.joystick[2] << ", "
<< robot_data.joystick[3] << "]" << std::endl;
std::cout << "运行模式: " << (robot_data.rcs_state_list.is_nav_mode ? "自动" : "手动") << std::endl;
std::cout << "急停来源: " << static_cast<int>(robot_data.rcs_state_list.emergency_source) << std::endl;
std::cout << "错误状态: 0x" << std::hex << robot_data.error_state << std::dec << std::endl;
if (robot_data.error_state_bit.imu_error) std::cout << "- IMU错误" << std::endl;
if (robot_data.error_state_bit.wifi_error) std::cout << "- WiFi错误" << std::endl;
if (robot_data.error_state_bit.driver_heat_warn) std::cout << "- 驱动器过热警告" << std::endl;
if (robot_data.error_state_bit.driver_error) std::cout << "- 驱动器错误" << std::endl;
if (robot_data.error_state_bit.motor_heat_warn) std::cout << "- 电机过热警告" << std::endl;
if (robot_data.error_state_bit.battery_low_warn) std::cout << "- 电池电量低警告" << std::endl;
if (robot_data.error_state_bit.battery_heat_warn) std::cout << "- 电池过热警告" << std::endl;
if (robot_data.error_state_bit.gpio_error) std::cout << "- GPIO错误" << std::endl;
if (robot_data.error_state_bit.cpu_heat_warn) std::cout << "- CPU过热警告" << std::endl;
if (robot_data.error_state_bit.cpu_freq_warn) std::cout << "- CPU频率警告" << std::endl;
std::cout << "========================================" << std::endl;
}
}
close(sockfd);
return 0;
}

文档目的: 指导用户通过TCP协议获取X30机器狗的实时基础状态信息。

确保监控主机与X30导航主机在同一网络段。X30导航主机默认IP:192.168.1.106,TCP端口30000。

验证连通性:

Terminal window
nc -zv 192.168.1.106 30000

第二步:编译并运行状态查询程序

Section titled “第二步:编译并运行状态查询程序”
Terminal window
g++ TCPtest.cpp -o TCPtest
./TCPtest

程序正常运行后将显示:

=== X30机器狗实时状态获取程序 ===
连接到机器狗 192.168.1.106:30000...
连接成功
发送状态查询请求...
发送成功: 175 字

收到响应时显示:

=== 收到机器狗状态响应 ===
响应长度: 689 字节
响应内容:
<?xml version="1.0" encoding="UTF-8"?>
<PatrolDevice>
<Type>1002</Type>
<Command>1</Command>
<Time>2025-11-12 14:30:25</Time>
<Items>
<MotionState>2</MotionState>
<PosX>1.5</PosX><PosY>2.3</PosY><PosZ>0.0</PosZ>
<Roll>0.05</Roll><Pitch>-0.02</Pitch><Yaw>1.57</Yaw>
<Speed>0.5</Speed>
<CurOdom>0.15</CurOdom><SumOdom>12.5</SumOdom>
<Electricity>85</Electricity>
<Location>0</Location>
<GaitState>0</GaitState>
<MotorState>0</MotorState>
<ChargeState>0</ChargeState>
<ControlMode>1</ControlMode>
<OnDockState>0</OnDockState>
</Items>
</PatrolDevice>

如需实现其他功能(例如下发导航任务 Type=1003),只需修改代码中的 XML 数据构造部分和响应解析部分。具体参考 通信架构 中的各消息类型定义。

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string>
#include <ctime>
#define SERVER_IP "192.168.1.106" // 机器狗IP地址
#define PORT 30000 // 机器狗TCP服务端口
// X30协议头部结构(固定16字节)
struct x30ProtocolHeader {
unsigned char sync1; // 同步字符1: 0xEB
unsigned char sync2; // 同步字符2: 0x90
unsigned char sync3; // 同步字符3: 0xEB
unsigned char sync4; // 同步字符4: 0x90
unsigned short dataLength; // XML数据长度(小端字节序)
unsigned short messageID; // 报文ID(小端字节序)
unsigned char reserved[8]; // 预留字段
};
// 获取当前时间字符串
std::string getCurrentTime() {
time_t now = time(0);
struct tm tstruct;
char buf[80];
tstruct = *localtime(&now);
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tstruct);
return buf;
}
int main() {
std::cout << "=== X30机器狗实时状态获取程序 ===" << std::endl;
// 创建TCP套接字
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd < 0) {
perror("创建Socket失败");
return -1;
}
// 配置服务器地址
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);
// 连接到机器狗
std::cout << "连接到机器狗 " << SERVER_IP << ":" << PORT << "..." << std::endl;
if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("连接失败");
close(client_fd);
return -1;
}
std::cout << "连接成功" << std::endl;
// 构造获取实时状态XML数据
// Type=1002: 获取实时基础状态
std::string xmlData = R"(<?xml version="1.0" encoding="UTF-8"?>
<PatrolDevice>
<Type>1002</Type>
<Command>1</Command>
<Time>)" + getCurrentTime() + R"(</Time>
<Items/>
</PatrolDevice>)";
// 构造完整协议报文:16字节头部 + XML数据
unsigned short xmlLength = xmlData.length();
char message[16 + xmlLength];
x30ProtocolHeader* header = (x30ProtocolHeader*)message;
header->sync1 = 0xEB;
header->sync2 = 0x90;
header->sync3 = 0xEB;
header->sync4 = 0x90;
header->dataLength = xmlLength;
header->messageID = 1;
memset(header->reserved, 0x00, 8);
memcpy(message + 16, xmlData.c_str(), xmlLength);
// 发送状态查询指令
std::cout << "发送状态查询请求..." << std::endl;
ssize_t send_len = send(client_fd, message, 16 + xmlLength, 0);
if (send_len < 0) {
perror("发送失败");
close(client_fd);
return -1;
}
std::cout << "发送成功: " << send_len << " 字节" << std::endl;
// 接收机器狗响应
char responseBuffer[4096];
ssize_t recv_len = recv(client_fd, responseBuffer, sizeof(responseBuffer) - 1, 0);
if (recv_len > 0) {
responseBuffer[recv_len] = '\0';
std::cout << "\n=== 收到机器狗状态响应 ===" << std::endl;
std::cout << "响应长度: " << recv_len << " 字节" << std::endl;
if(recv_len > 16) {
std::string responseXml(responseBuffer + 16, recv_len - 16);
std::cout << "响应内容:" << std::endl;
std::cout << responseXml << std::endl;
auto parseField = [&](const std::string& field) -> std::string {
size_t start = responseXml.find("<" + field + ">");
if (start == std::string::npos) return "未知";
start += field.length() + 2;
size_t end = responseXml.find("</" + field + ">", start);
if (end == std::string::npos) return "未知";
return responseXml.substr(start, end - start);
};
std::cout << "\n=== 关键状态信息 ===" << std::endl;
std::cout << "位置: (" << parseField("PosX") << ", " << parseField("PosY") << ", " << parseField("PosZ") << ")" << std::endl;
std::cout << "姿态: (" << parseField("Roll") << ", " << parseField("Pitch") << ", " << parseField("Yaw") << ")" << std::endl;
std::cout << "速度: " << parseField("Speed") << " m/s" << std::endl;
std::cout << "电量: " << parseField("Electricity") << "%" << std::endl;
std::string motionState = parseField("MotionState");
std::cout << "运动状态: " << motionState;
if (motionState == "0") std::cout << " (趴下)";
else if (motionState == "1") std::cout << " (正在起立)";
else if (motionState == "2") std::cout << " (初始站立)";
else if (motionState == "3") std::cout << " (力控站立)";
else if (motionState == "4") std::cout << " (踏步)";
else if (motionState == "5") std::cout << " (正在趴下)";
else if (motionState == "6") std::cout << " (软急停)";
else if (motionState == "7") std::cout << " (摔倒)";
std::cout << std::endl;
std::string controlMode = parseField("ControlMode");
std::cout << "控制模式: " << (controlMode == "0" ? "手动模式" : "自动模式") << std::endl;
}
} else {
std::cout << "未收到响应" << std::endl;
}
close(client_fd);
std::cout << "\n连接已关闭" << std::endl;
return 0;
}
  • 网络连通性:确保X30与监控主机在同一网络段,确保TCP端口30000畅通
  • 配置一致性:network.toml中的端口号必须与监控程序监听端口一致,IP地址必须为监控主机的实际IP地址