Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions rust/contracts/prm/audio_mic.v1.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Microphone audio stream (raw PCM or encoded).
# ROS: `robonix_msg/msg/AudioChunk` published on a topic.
[contract]
id = "robonix/prm/audio/mic"
version = "1"
kind = "primitive"

[io.msg]
msg = "robonix_msg/msg/AudioChunk"

[mode]
type = "topic_out"

[semantics]
realtime = true
payload = "audio"
16 changes: 16 additions & 0 deletions rust/contracts/prm/audio_speaker.v1.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Speaker audio stream input (raw PCM or encoded).
# ROS: `robonix_msg/msg/AudioChunk` consumed from a topic.
[contract]
id = "robonix/prm/audio/speaker"
version = "1"
kind = "primitive"

[io.msg]
msg = "robonix_msg/msg/AudioChunk"

[mode]
type = "topic_in"

[semantics]
realtime = true
payload = "audio"
16 changes: 16 additions & 0 deletions rust/contracts/srv/speech_asr.v1.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# ASR one-shot recognition.
# Caller sends raw audio; returns transcript.
# ROS: `lib/asr/srv/Recognize.srv`.
[contract]
id = "robonix/srv/speech/asr"
version = "1"
kind = "service"

[io.srv]
srv = "asr/srv/Recognize"

[mode]
type = "rpc"

[semantics]
stateless = true
19 changes: 19 additions & 0 deletions rust/contracts/srv/speech_asr_stream.v1.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# ASR streaming recognition.
# Client streams AudioChunk; server streams RecognizeStreamEvent.
# ROS: `lib/asr/srv/RecognizeStream.srv`. Stream element types in TOML only.
[contract]
id = "robonix/srv/speech/asr_stream"
version = "1"
kind = "service"

[io.srv]
srv = "asr/srv/RecognizeStream"
stream_request = "asr/msg/AsrAudioChunk"
stream_response = "asr/msg/RecognizeStreamEvent"

[mode]
type = "rpc_bidirectional_stream"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

robonix codegen (input: toml+ROS IDL, output: proto, mcp types, ...) currently does not support "rpc_bidirectional_stream" communication model


[semantics]
stateless = true
realtime = true
19 changes: 19 additions & 0 deletions rust/contracts/srv/speech_dialog.v1.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Voice dialog session manager.
# Orchestrates mic → ASR → intent → TTS → speaker pipeline.
# Manages VAD, dialog state machine, and language switching.
# ROS: `lib/speech/srv/StartDialog.srv`. Stream element: `robonix_msg/msg/DialogEvent`.
[contract]
id = "robonix/srv/speech/dialog"
version = "1"
kind = "service"

[io.srv]
srv = "speech/srv/StartDialog"
stream_response = "robonix_msg/msg/DialogEvent"

[mode]
type = "rpc_server_stream"

[semantics]
stateful = true
interruptible = true
16 changes: 16 additions & 0 deletions rust/contracts/srv/speech_tts.v1.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# TTS one-shot synthesis.
# Caller sends text; returns synthesized audio.
# ROS: `lib/tts/srv/Synthesize.srv`.
[contract]
id = "robonix/srv/speech/tts"
version = "1"
kind = "service"

[io.srv]
srv = "tts/srv/Synthesize"

[mode]
type = "rpc"

[semantics]
stateless = true
18 changes: 18 additions & 0 deletions rust/contracts/srv/speech_tts_stream.v1.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# TTS streaming synthesis.
# Client sends one SynthesizeStreamRequest; server streams back SynthesizeAudioChunk.
# ROS: `lib/tts/srv/SynthesizeStream.srv`. Stream element type in TOML only.
[contract]
id = "robonix/srv/speech/tts_stream"
version = "1"
kind = "service"

[io.srv]
srv = "tts/srv/SynthesizeStream"
stream_response = "tts/msg/SynthesizeAudioChunk"

[mode]
type = "rpc_server_stream"

[semantics]
stateless = true
realtime = true
50 changes: 50 additions & 0 deletions rust/crates/robonix-codegen/src/codegen/contract_gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ struct IoMsgIo {
#[derive(Debug, Deserialize)]
struct IoSrvIo {
srv: String,
#[serde(default)]
stream_request: Option<String>,
#[serde(default)]
stream_response: Option<String>,
}

#[derive(Debug, Deserialize)]
Expand Down Expand Up @@ -158,6 +162,7 @@ pub fn generate(
"rpc" => format_unary(in_t, out_t),
"rpc_server_stream" | "topic_out" => format_stream_out(in_t, out_t),
"rpc_client_stream" | "topic_in" => format_stream_in(in_t, out_t),
"rpc_bidirectional_stream" => format_bidi_stream(in_t, out_t),
other => bail!(
"unknown [mode].type '{other}' in contract {} (expected rpc | rpc_server_stream | rpc_client_stream | topic_out | topic_in)",
c.contract.id
Expand Down Expand Up @@ -205,6 +210,14 @@ fn format_stream_in(input: &ResolvedType, output: &ResolvedType) -> String {
)
}

fn format_bidi_stream(input: &ResolvedType, output: &ResolvedType) -> String {
format!(
"rpc Stream(stream {}) returns (stream {});",
stream_element(input),
stream_element(output)
)
}

fn format_unary(input: &ResolvedType, output: &ResolvedType) -> String {
format!(
"rpc Call({}) returns ({});",
Expand Down Expand Up @@ -301,6 +314,15 @@ fn resolve_contract_io(
})?;
resolve_srv_client_stream(s, &c.contract.id, resolver, imports, needs_string_wire)
}
"rpc_bidirectional_stream" => {
let s = s.ok_or_else(|| {
anyhow::anyhow!(
"contract {}: [mode].type = \"rpc_bidirectional_stream\" requires [io.srv]",
c.contract.id
)
})?;
resolve_srv_bidi_stream(s, &c.contract.id, resolver, imports, needs_string_wire)
}
"topic_out" => {
let m = m.ok_or_else(|| {
anyhow::anyhow!(
Expand Down Expand Up @@ -407,6 +429,34 @@ fn resolve_srv_client_stream(
Ok((in_t, out_t))
}

/// Bidirectional stream: uses `stream_request` and `stream_response` from the
/// contract TOML as stream element types. Falls back to deriving from the .srv
/// fields if those hints are absent.
fn resolve_srv_bidi_stream(
io: &IoSrvIo,
contract_id: &str,
_resolver: &mut MsgResolver,
imports: &mut BTreeSet<String>,
_needs_string_wire: &mut bool,
) -> Result<(ResolvedType, ResolvedType)> {
let in_ref = io
.stream_request
.as_deref()
.ok_or_else(|| anyhow::anyhow!(
"contract {contract_id}: [mode] rpc_bidirectional_stream requires [io.srv].stream_request"
))?;
let out_ref = io
.stream_response
.as_deref()
.ok_or_else(|| anyhow::anyhow!(
"contract {contract_id}: [mode] rpc_bidirectional_stream requires [io.srv].stream_response"
))?;

let in_t = resolve_io(in_ref, _resolver, imports, _needs_string_wire)?;
let out_t = resolve_io(out_ref, _resolver, imports, _needs_string_wire)?;
Ok((in_t, out_t))
}

fn srv_request_to_contract_input(
srv: &SrvSpec,
resolver: &mut MsgResolver,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Audio chunk sent by client during streaming ASR recognition.
robonix_msg/msg/AudioChunk chunk
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Event streamed back by the ASR service during streaming recognition.

uint8 PARTIAL = 0
uint8 FINAL = 1
uint8 ERROR = 2
uint8 event_type

string text # recognised text so far (partial or final)
float32 confidence # 0.0 – 1.0
string language # detected language (BCP-47)
bool is_final # true when this is the final recognition result
string error # non-empty on error
5 changes: 2 additions & 3 deletions rust/crates/robonix-interfaces/lib/asr/srv/Recognize.srv
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
# ASR one-shot recognition
# abstract_interface_id: robonix/sys/speech/asr/recognize
# Caller sends raw audio bytes; service returns a transcript string.
---
bytes audio_data
uint8[] audio_data
string encoding # e.g. "pcm_s16le", "opus", "mp3"
uint32 sample_rate_hz # e.g. 16000
string language # BCP-47, e.g. "zh-CN", "en-US", "" = auto-detect
===
---
string text # recognised transcript (empty if silent / error)
float32 confidence # 0.0 – 1.0; 0.0 if unavailable
string error # non-empty on failure
15 changes: 6 additions & 9 deletions rust/crates/robonix-interfaces/lib/asr/srv/RecognizeStream.srv
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
# ASR streaming recognition
# @robonix.grpc stream_client stream_server
# abstract_interface_id: robonix/sys/speech/asr/recognize_stream
#
# Client streams AudioChunk messages; server streams back RecognizeStreamEvent.
# Use for real-time microphone input.
# ASR streaming recognition (bidirectional stream).
# Client streams AsrAudioChunk; server streams back RecognizeStreamEvent.
# Stream element types are referenced in the contract TOML.
robonix_msg/AudioConfig audio_config
string language
---
# (empty — streaming RPC, no single request body)
===
# (empty — streaming RPC, no single response body)
asr/RecognizeStreamEvent event
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# A chunk of raw or encoded audio data.
# Used as the stream element for mic topic_out, speaker topic_in,
# ASR streaming, and TTS streaming.

uint64 timestamp_ns # nanoseconds since epoch (replaces std_msgs/Header for portability)
uint8[] data # raw or encoded audio bytes
uint32 sequence # monotonically increasing within a session
float32 duration_s # duration of this chunk in seconds (0 if unknown)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Audio configuration descriptor for speech services.
# Shared across ASR, TTS, mic, and speaker primitives.

string encoding # e.g. "pcm_s16le", "pcm_f32le", "opus", "mp3"
uint32 sample_rate_hz # e.g. 16000, 22050, 44100
uint32 channels # 1 = mono, 2 = stereo
uint32 bits_per_sample # e.g. 16 for pcm_s16le
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Event emitted by the speech dialog service during a voice interaction session.
# Streamed back to the caller (e.g. Liaison) over the dialog RPC stream.

uint64 timestamp_ns # nanoseconds since epoch
string session_id

uint8 VAD_EVENT = 0
uint8 PARTIAL_TRANSCRIPT = 1
uint8 FINAL_TRANSCRIPT = 2
uint8 BOT_RESPONSE_TEXT = 3
uint8 BOT_AUDIO_CHUNK = 4
uint8 STATE_CHANGE = 5
uint8 ERROR = 6
uint8 event_type

# VAD event data
robonix_msg/msg/VadEvent vad_event

# Transcript data (for PARTIAL_TRANSCRIPT and FINAL_TRANSCRIPT)
string text
float32 confidence
string language # detected or specified language

# Bot response text (for BOT_RESPONSE_TEXT)
string response_text

# Audio data (for BOT_AUDIO_CHUNK)
robonix_msg/msg/AudioChunk audio_chunk

# State change (for STATE_CHANGE)
robonix_msg/msg/DialogState dialog_state

# Error info (for ERROR)
string error
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# State of a voice dialog session.
# Used to track the lifecycle of a voice interaction.

string session_id
uint8 IDLE = 0
uint8 LISTENING = 1 # mic is active, waiting for speech
uint8 PROCESSING = 2 # ASR/NLP processing in progress
uint8 SPEAKING = 3 # TTS is playing audio to speaker
uint8 ERROR = 4
uint8 state
string language # current BCP-47 language code
string error # non-empty if state == ERROR
10 changes: 10 additions & 0 deletions rust/crates/robonix-interfaces/lib/robonix_msg/msg/VadEvent.msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Voice Activity Detection event.
# Emitted by the dialog manager or ASR service to signal speech state changes.

uint64 timestamp_ns # nanoseconds since epoch
uint8 SPEECH_BEGIN = 0
uint8 SPEECH_END = 1
uint8 SILENCE = 2
uint8 event_type
float32 confidence # 0.0 – 1.0 confidence of the VAD decision
float32 energy_level # audio energy level (optional, 0.0 if unavailable)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Dialog request sent by client to start a voice dialog session.
string language # BCP-47, e.g. "zh-CN", "" = auto-detect
string voice_id # preferred voice, "" = default
bool enable_vad # enable voice activity detection
float32 vad_silence_threshold_s # seconds of silence before ending speech (default 0.8)
float32 vad_energy_threshold # energy threshold for VAD (0.0 = auto)
robonix_msg/msg/AudioConfig audio_config # desired audio format
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Query current dialog session status.
---
string session_id
---
robonix_msg/msg/DialogState state
string error
9 changes: 9 additions & 0 deletions rust/crates/robonix-interfaces/lib/speech/srv/SendAudio.srv
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Inject audio into an active dialog session (for non-streaming input).
---
string session_id
uint8[] audio_data
string encoding
uint32 sample_rate_hz
---
bool accepted
string error
8 changes: 8 additions & 0 deletions rust/crates/robonix-interfaces/lib/speech/srv/SendText.srv
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Send a text-based message into the dialog (bypass ASR).
---
string session_id
string text
string language
---
bool accepted
string error
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Change language mid-session.
---
string session_id
string language # BCP-47 code
---
bool success
string error
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Start a voice dialog session.
# Returns initial dialog state; events are streamed via the Dialog contract.
string language
bool enable_vad
robonix_msg/msg/AudioConfig audio_config
---
robonix_msg/msg/DialogEvent event
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Stop an active voice dialog session.
---
string session_id
---
bool success
string error
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Audio chunk streamed back by TTS during streaming synthesis.
robonix_msg/msg/AudioChunk chunk
string encoding # e.g. "pcm_s16le", "mp3"
uint32 sample_rate_hz
bool is_final # true when this is the last chunk
Loading
Loading