GR-Media
1. Overview
GR-Media is the routing runtime for robot media operations. The runtime service fouriermedia receives a simple media operation ID over DDS, then routes it to one of three media channels:
- LED actions are configured through
/opt/fftai/fouriermedia/bin/cfg/media_leds_action.jsonand sent to the LED controller over UDP. - Eyes actions are configured through
/opt/fftai/fouriermedia/bin/cfg/media_eyes_action.jsonand forwarded to the head over DDS topichead_eyes_cmd. - Sound actions are forwarded to the head over DDS topic
head_sound_cmd.
| Element | Value |
|---|---|
| Runtime app name | fouriermedia |
| Domain ID | 123 |
| Input message | fourier_msgs/msg/MediaSignalCmd |
| Input topics | media_signal_led_cmd, media_signal_eyes_cmd, media_signal_sound_cmd |
| Head output topics | head_eyes_cmd, head_sound_cmd |
2. DDS Interface
2.1 Topics
Subscribed
| Topic | Message Type | Description |
|---|---|---|
media_signal_led_cmd | fourier_msgs/msg/MediaSignalCmd | LED ID. The ID must exist in media_leds_action.json. |
media_signal_eyes_cmd | fourier_msgs/msg/MediaSignalCmd | Eyes ID. The ID must exist in media_eyes_action.json. |
media_signal_sound_cmd | fourier_msgs/msg/MediaSignalCmd | Sound ID. Forwarded as SoundControlCmd.sound_id. |
Published / Forwarded
| Topic / Output | Message Type | Description |
|---|---|---|
head_eyes_cmd | fourier_msgs/msg/EyesControlGroupCmd | Published after an eyes action is resolved. |
head_sound_cmd | fourier_msgs/msg/SoundControlCmd | Published after a sound action is received. |
2.2 IDL Definitions
MediaSignalCmd
struct MediaSignalCmd {
std_msgs::msg::TracerHeader header;
int32 action; // action ID
};
EyesControlGroupCmd
struct EyesControlCmd {
int32 eyes_index; // 0 = left eye, 1 = right eye
int32 emoji_id;
int32 x; // -100..100
int32 y; // -100..100
};
struct EyesControlGroupCmd {
std_msgs::msg::TracerHeader header;
sequence<EyesControlCmd> eyes_cmd;
};
SoundControlCmd
struct SoundControlCmd {
std_msgs::msg::TracerHeader header;
int32 sound_id;
};
3. Action IDs
3.1 LED
| Action ID | Meaning |
|---|---|
0 | Warning red, constant |
1 | Warning red, breathing |
2 | Warning orange, constant |
3 | Warning orange, breathing |
4 | Normal white, constant |
5 | Normal white, breathing |
6 | Working blue, constant |
7 | Working blue, breathing |
8 | Working purple, constant |
9 | Working purple, breathing |
10 | Touch pink, constant |
3.2 Eyes Screen
These IDs come from media_eyes_action.json.
| Action ID | Meaning |
|---|---|
0 | Center/default |
1 | Up |
2 | Smile |
3 | Down |
4 | Right |
5 | Left |
6 | Upper right |
7 | Upper left |
8 | Lower right |
9 | Lower left |
3.3 Sound Actions
Sound action IDs are passed through as SoundControlCmd.sound_id. The actual audio mapping is handled by the head sound service.
4. Basic Examples
4.1 Python - Send a Media Action
The script below publishes one MediaSignalCmd to the selected channel. Confirm that fouriermedia is running before executing it.
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],
)
# Wait for DDS discovery before publishing the first sample.
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()
Run examples:
# LED: working blue, constant
python3 send_media_signal.py led 6
# Eyes: smile
python3 send_media_signal.py eyes 2
# Sound: forward sound_id=1 to the head
python3 send_media_signal.py sound 1
4.2 Python - Observe Head Output
For eyes and sound, GR-Media forwards the resolved command to head-domain DDS topics. This subscriber is useful for integration testing.
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++ - Send a Media Action
#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;
}
Build example:
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
Run example:
./send_media_signal eyes 2
5. Advanced - Runtime Workflow
5.1 LED Workflow
- Publish
MediaSignalCmd.actiontomedia_signal_led_cmd. fouriermedialooks up that ID incfg/media_leds_action.json.- If the action exists, it converts the configured LED action to JSON.
- The JSON is sent over UDP to the execution side.
5.2 Eyes Workflow
- Publish
MediaSignalCmd.actiontomedia_signal_eyes_cmd. fouriermedialooks up that ID incfg/media_eyes_action.json.- The runtime builds two
EyesControlCmdentries, one for each eye. - The result is published to
head_eyes_cmd.
5.3 Sound Workflow
- Publish
MediaSignalCmd.actiontomedia_signal_sound_cmd. fouriermediacopies the action ID toSoundControlCmd.sound_id.- The result is published to
head_sound_cmd.
6. Troubleshooting & Reminders
No effect after publishing
Confirm fouriermedia is running, the DDS domain is 123, and the channel topic matches the intended output. For example, LED commands must be sent to media_signal_led_cmd, not to the eyes or sound topic.
Invalid LED or eyes action ID
LED and eyes actions are config-backed. If the ID does not exist in media_leds_action.json or media_eyes_action.json, the runtime logs a warning and ignores the command.