Skip to main content

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.json and sent to the LED controller over UDP.
  • Eyes actions are configured through /opt/fftai/fouriermedia/bin/cfg/media_eyes_action.json and forwarded to the head over DDS topic head_eyes_cmd.
  • Sound actions are forwarded to the head over DDS topic head_sound_cmd.
ElementValue
Runtime app namefouriermedia
Domain ID123
Input messagefourier_msgs/msg/MediaSignalCmd
Input topicsmedia_signal_led_cmd, media_signal_eyes_cmd, media_signal_sound_cmd
Head output topicshead_eyes_cmd, head_sound_cmd

2. DDS Interface

2.1 Topics

Subscribed

TopicMessage TypeDescription
media_signal_led_cmdfourier_msgs/msg/MediaSignalCmdLED ID. The ID must exist in media_leds_action.json.
media_signal_eyes_cmdfourier_msgs/msg/MediaSignalCmdEyes ID. The ID must exist in media_eyes_action.json.
media_signal_sound_cmdfourier_msgs/msg/MediaSignalCmdSound ID. Forwarded as SoundControlCmd.sound_id.

Published / Forwarded

Topic / OutputMessage TypeDescription
head_eyes_cmdfourier_msgs/msg/EyesControlGroupCmdPublished after an eyes action is resolved.
head_sound_cmdfourier_msgs/msg/SoundControlCmdPublished after a sound action is received.

2.2 IDL Definitions

MediaSignalCmd

Idl
struct MediaSignalCmd {
std_msgs::msg::TracerHeader header;
int32 action; // action ID
};

EyesControlGroupCmd

Idl
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

Idl
struct SoundControlCmd {
std_msgs::msg::TracerHeader header;
int32 sound_id;
};

3. Action IDs

3.1 LED

Action IDMeaning
0Warning red, constant
1Warning red, breathing
2Warning orange, constant
3Warning orange, breathing
4Normal white, constant
5Normal white, breathing
6Working blue, constant
7Working blue, breathing
8Working purple, constant
9Working purple, breathing
10Touch pink, constant

3.2 Eyes Screen

These IDs come from media_eyes_action.json.

Action IDMeaning
0Center/default
1Up
2Smile
3Down
4Right
5Left
6Upper right
7Upper left
8Lower right
9Lower 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.

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],
)

# 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:

Bash
# 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.

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++ - Send a Media Action

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;
}

Build example:

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

Run example:

Bash
./send_media_signal eyes 2

5. Advanced - Runtime Workflow

5.1 LED Workflow

  1. Publish MediaSignalCmd.action to media_signal_led_cmd.
  2. fouriermedia looks up that ID in cfg/media_leds_action.json.
  3. If the action exists, it converts the configured LED action to JSON.
  4. The JSON is sent over UDP to the execution side.

5.2 Eyes Workflow

  1. Publish MediaSignalCmd.action to media_signal_eyes_cmd.
  2. fouriermedia looks up that ID in cfg/media_eyes_action.json.
  3. The runtime builds two EyesControlCmd entries, one for each eye.
  4. The result is published to head_eyes_cmd.

5.3 Sound Workflow

  1. Publish MediaSignalCmd.action to media_signal_sound_cmd.
  2. fouriermedia copies the action ID to SoundControlCmd.sound_id.
  3. 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.