GR-Media
1. 概述
GR-Media 是机器人媒体操作的路由运行时。运行时服务 fouriermedia 通过 DDS 接收一个简单的媒体操作 ID,然后按通道转发:
- LED 动作通过
/opt/fftai/fouriermedia/bin/cfg/media_leds_action.json配置,并通过 UDP 发给灯控。 - 眼睛动作通过
/opt/fftai/fouriermedia/bin/cfg/media_eyes_action.json配置,并通过 DDS topichead_eyes_cmd转发给头部。 - 声音动作通过 DDS topic
head_sound_cmd转发给头部。
| 元素 | 数值 |
|---|---|
| 运行时应用名 | fouriermedia |
| Domain ID | 123 |
| 输入消息 | fourier_msgs/msg/MediaSignalCmd |
| 输入 topic | media_signal_led_cmd、media_signal_eyes_cmd、media_signal_sound_cmd |
| 头部输出 topic | head_eyes_cmd、head_sound_cmd |
2. DDS 接口
2.1 Topics
订阅
| Topic | 消息类型 | 说明 |
|---|---|---|
media_signal_led_cmd | fourier_msgs/msg/MediaSignalCmd | LED ID。ID 必须存在于 media_leds_action.json。 |
media_signal_eyes_cmd | fourier_msgs/msg/MediaSignalCmd | 眼睛 ID。ID 必须存在于 media_eyes_action.json。 |
media_signal_sound_cmd | fourier_msgs/msg/MediaSignalCmd | 声音 ID,会原样转发为 SoundControlCmd.sound_id。 |
发布 / 转发
| Topic / 输出 | 消息类型 | 说明 |
|---|---|---|
head_eyes_cmd | fourier_msgs/msg/EyesControlGroupCmd | 眼睛动作解析完成后发布。 |
head_sound_cmd | fourier_msgs/msg/SoundControlCmd | 收到声音动作后发布。 |
2.2 IDL 定义
MediaSignalCmd
Idl
struct MediaSignalCmd {
std_msgs::msg::TracerHeader header;
int32 action; // action ID
};
EyesControlGroupCmd
Idl
struct EyesControlCmd {
int32 eyes_index; // 0 = 左眼,1 = 右眼
int32 emoji_id;
int32 x; // -100..100
int32 y; // -100..100
};
struct EyesControlGroupCmd {
std_msgs::msg::TracerHeader header;
sequence<EyesControlCmd> eyes_cmd;
};
SoundControlCmd
Idl
struct SoundControlCmd {
std_msgs::msg::TracerHeader header;
int32 sound_id;
};
3. 动作 ID
3.1 LED
| Action ID | 含义 |
|---|---|
0 | 警告红,常亮 |
1 | 警告红,呼吸 |
2 | 警告橙,常亮 |
3 | 警告橙,呼吸 |
4 | 正常白,常亮 |
5 | 正常白,呼吸 |
6 | 工作蓝,常亮 |
7 | 工作蓝,呼吸 |
8 | 工作紫,常亮 |
9 | 工作紫,呼吸 |
10 | 触摸粉,常亮 |
3.2 眼睛屏幕
以下 ID 来自 media_eyes_action.json。
| Action ID | 含义 |
|---|---|
0 | 居中 / 默认 |
1 | 向上 |
2 | 微笑 |
3 | 向下 |
4 | 向右 |
5 | 向左 |
6 | 右上 |
7 | 左上 |
8 | 右下 |
9 | 左下 |
3.3 声音动作
声音动作 ID 会直接作为 SoundControlCmd.sound_id 转发,实际音频映射由头部声音服务处理。
4. 基础示例
4.1 Python —— 发送媒体动作
下面脚本会向指定通道发布一个 MediaSignalCmd。运行前确认 fouriermedia 已启动。
Python
import argparse
import time
from fourierdds_py import DDSInterface
import fourier_msgs.msg.MediaSignalCmd as MediaSignalCmd
TOPICS = {
"led": "media_signal_led_cmd",
"eyes": "media_signal_eyes_cmd",
"sound": "media_signal_sound_cmd",
}
parser = argparse.ArgumentParser()
parser.add_argument("channel", choices=sorted(TOPICS))
parser.add_argument("action", type=int)
parser.add_argument("--domain", type=int, default=123)
args = parser.parse_args()
dds = DDSInterface(domain_id=args.domain)
pub = dds.create_publisher(
MediaSignalCmd.MediaSignalCmdPubSubType,
TOPICS[args.channel],
)
# 等待 DDS discovery,避免第一帧过早发送。
time.sleep(1.0)
msg = MediaSignalCmd.MediaSignalCmd()
msg.action(args.action)
print(f"[publish] channel={args.channel} topic={TOPICS[args.channel]} action={args.action}")
pub.publish(msg)
time.sleep(0.2)
dds.close()
运行示例:
Bash
# LED:工作蓝,常亮
python3 send_media_signal.py led 6
# 眼睛:微 笑
python3 send_media_signal.py eyes 2
# 声音:向头部转发 sound_id=1
python3 send_media_signal.py sound 1
4.2 Python —— 观察头部输出
眼睛和声音通道会被 GR-Media 转发到头部 DDS topic。联调时可以用下面脚本验证转发结果。
Python
import time
from fourierdds_py import DDSInterface, SubscriberQosProfile
import fourier_msgs.msg.HeadMediaCmd as HeadMediaCmd
dds = DDSInterface(domain_id=123)
def seq_items(seq):
size = seq.size() if hasattr(seq, "size") else len(seq)
return [seq[i] for i in range(size)]
def on_eyes(msg):
for eye in seq_items(msg.eyes_cmd()):
print(
"[eyes]",
"index:", eye.eyes_index(),
"emoji:", eye.emoji_id(),
"x:", eye.x(),
"y:", eye.y(),
)
def on_sound(msg):
print("[sound] sound_id:", msg.sound_id())
dds.create_subscription(
HeadMediaCmd.EyesControlGroupCmdPubSubType,
"head_eyes_cmd",
on_eyes,
qos_profile=SubscriberQosProfile.default(),
)
dds.create_subscription(
HeadMediaCmd.SoundControlCmdPubSubType,
"head_sound_cmd",
on_sound,
qos_profile=SubscriberQosProfile.default(),
)
while True:
time.sleep(0.1)
4.3 C++ —— 发送媒体动作
C++
#include <MediaSignalCmd/MediaSignalCmdPubSubTypes.hpp>
#include "fourier_dds/dds_node.hpp"
#include "fourier_dds/dds_publisher.hpp"
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <unordered_map>
int main(int argc, char** argv) {
if (argc != 3) {
std::cerr << "usage: " << argv[0] << " <led|eyes|sound> <action_id>\n";
return 1;
}
const std::unordered_map<std::string, std::string> topics = {
{"led", "media_signal_led_cmd"},
{"eyes", "media_signal_eyes_cmd"},
{"sound", "media_signal_sound_cmd"},
};
const std::string channel = argv[1];
const auto it = topics.find(channel);
if (it == topics.end()) {
std::cerr << "unknown channel: " << channel << "\n";
return 1;
}
const int action = std::atoi(argv[2]);
auto node = std::make_shared<fourier_dds::DdsNode>(123);
auto pub = std::make_shared<
fourier_dds::DdsPublisher<fourier_msgs::msg::MediaSignalCmdPubSubType>>(
node,
it->second);
std::this_thread::sleep_for(std::chrono::seconds(1));
fourier_msgs::msg::MediaSignalCmd msg;
msg.action(action);
std::cout << "[publish] channel=" << channel
<< " topic=" << it->second
<< " action=" << action << std::endl;
pub->publish(msg);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
return 0;
}
编译示例:
Bash
g++ -std=c++17 send_media_signal.cpp -o send_media_signal \
-I/opt/fftai/include/fourier_dds \
-I/opt/fftai/fourier_dds_msgs/include/fourier_dds_msgs \
-L/opt/fftai/fourier_dds_msgs/lib \
-Wl,-rpath,/opt/fftai/fourier_dds_msgs/lib \
-lMediaSignalCmd \
-lfastdds \
-lfastcdr \
-pthread
运行示例:
Bash
./send_media_signal eyes 2
5. 进阶 —— 运行时流程
5.1 LED 流程
- 向
media_signal_led_cmd发布MediaSignalCmd.action。 fouriermedia在cfg/media_leds_action.json中查找该 ID。- 如果动作存在,运行时将 LED 配置转换成 JSON。
- JSON 通过 UDP 发送到 执行侧。
5.2 眼睛流程
- 向
media_signal_eyes_cmd发布MediaSignalCmd.action。 fouriermedia在cfg/media_eyes_action.json中查找该 ID。- 运行时生成左右眼各一条
EyesControlCmd。 - 结果发布到
head_eyes_cmd。
5.3 声音流程
- 向
media_signal_sound_cmd发布MediaSignalCmd.action。 fouriermedia将 action ID 写入SoundControlCmd.sound_id。- 结果发布到
head_sound_cmd。
6. 故障排查与注意事项
发布后无效果
确认 fouriermedia 已启动、DDS domain 为 123,并且发送到了正确通道。例如 LED 命令必须发到 media_signal_led_cmd,不要发到眼睛或声音 topic。
LED 或眼睛 action ID 无效
LED 和眼睛动作都依赖配置文件。若 ID 不存在于 media_leds_action.json 或 media_eyes_action.json,运行时会打印 warning 并忽略该命令。