parent
eb4fac99ba
commit
ec1b1aca51
@ -0,0 +1,103 @@
|
||||
#[cfg(feature = "async-mio")]
|
||||
use mio::{unix::SourceFd, Events, Interest, Poll, Token};
|
||||
#[cfg(feature = "async-mio")]
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
io::{self, Write},
|
||||
os::unix::io::AsRawFd,
|
||||
};
|
||||
|
||||
#[cfg(feature = "async-mio")]
|
||||
use ssip_client::{fifo, AsyncClient, ClientError, ClientName, ClientResult, Request, Response};
|
||||
|
||||
#[cfg(feature = "async-mio")]
|
||||
fn main() -> ClientResult<()> {
|
||||
let stdin = io::stdin();
|
||||
|
||||
// Poll instance
|
||||
let mut poll = Poll::new()?;
|
||||
|
||||
// Register stdin
|
||||
let stdin_fd = stdin.as_raw_fd();
|
||||
let mut source_fd = SourceFd(&stdin_fd);
|
||||
let stdin_token = Token(0);
|
||||
poll.registry()
|
||||
.register(&mut source_fd, stdin_token, Interest::READABLE)?;
|
||||
|
||||
// Register the SSIP client
|
||||
let mut ssip_client = AsyncClient::new(fifo::Builder::new().build()?);
|
||||
let speech_input_token = Token(1);
|
||||
let speech_output_token = Token(2);
|
||||
ssip_client.register(&poll, speech_input_token, speech_output_token)?;
|
||||
|
||||
// Loop for events
|
||||
let mut events = Events::with_capacity(16);
|
||||
let mut speech_writable = false;
|
||||
let mut send_requests = VecDeque::with_capacity(4);
|
||||
ssip_client.push(Request::SetName(ClientName::new("joe", "async")));
|
||||
|
||||
fn prompt() -> io::Result<()> {
|
||||
let mut stdout = io::stdout();
|
||||
write!(stdout, "> ")?;
|
||||
stdout.flush()
|
||||
}
|
||||
|
||||
println!("Enter an empty line to quit.");
|
||||
prompt()?;
|
||||
loop {
|
||||
if !speech_writable || !ssip_client.has_next() {
|
||||
poll.poll(&mut events, None)?;
|
||||
}
|
||||
for event in &events {
|
||||
let token = event.token();
|
||||
if token == stdin_token {
|
||||
let mut text = String::new();
|
||||
stdin.read_line(&mut text)?;
|
||||
text = text.trim_end().to_string();
|
||||
match text.len() {
|
||||
0 => return Ok(()),
|
||||
1 => {
|
||||
if let Some(ch) = text.chars().next() {
|
||||
println!("sending char: {}", ch);
|
||||
ssip_client.push(Request::SendChar(ch))
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
println!("sending line: {}", text);
|
||||
send_requests.push_back(Request::SendLine(text.to_owned()));
|
||||
ssip_client.push(Request::Speak);
|
||||
}
|
||||
}
|
||||
prompt()?;
|
||||
} else if token == speech_input_token {
|
||||
match ssip_client.receive_next() {
|
||||
Err(ClientError::Io(err)) => return Err(ClientError::from(err)),
|
||||
Err(ClientError::Ssip(err)) => eprintln!("SSIP error: {:?}", err),
|
||||
Err(_) => panic!("internal error"),
|
||||
Ok(result) => match result {
|
||||
Response::MessageQueued | Response::ClientNameSet => (),
|
||||
Response::ReceivingData => {
|
||||
ssip_client.push(send_requests.pop_front().unwrap())
|
||||
}
|
||||
_ => panic!("Unexpected response: {:?}", result),
|
||||
},
|
||||
}
|
||||
} else if token == speech_output_token {
|
||||
speech_writable = true;
|
||||
}
|
||||
}
|
||||
if speech_writable {
|
||||
match ssip_client.send_next() {
|
||||
Err(ClientError::NotReady) => speech_writable = false,
|
||||
Err(ClientError::Io(err)) => return Err(ClientError::from(err)),
|
||||
Err(_) => panic!("internal error"),
|
||||
Ok(()) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "async-mio"))]
|
||||
fn main() {
|
||||
println!("see hello for an example of synchronous client.");
|
||||
}
|
@ -0,0 +1,253 @@
|
||||
// ssip-client -- Speech Dispatcher client in Rust
|
||||
// Copyright (c) 2022 Laurent Pelecq
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
|
||||
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. All files in the project carrying such notice may not be copied,
|
||||
// modified, or distributed except according to those terms.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use crate::{
|
||||
client::{Client, ClientError, ClientName, ClientResult},
|
||||
constants::*,
|
||||
types::{EventId, Source, SynthesisVoice},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Request for SSIP server.
|
||||
pub enum Request {
|
||||
SetName(ClientName),
|
||||
Speak,
|
||||
SendLine(String),
|
||||
SendLines(Vec<String>),
|
||||
SendChar(char),
|
||||
Quit,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Response from SSIP server.
|
||||
pub enum Response {
|
||||
LanguageSet, // 201
|
||||
PrioritySet, // 202
|
||||
RateSet, // 203
|
||||
PitchSet, // 204
|
||||
PunctuationSet, // 205
|
||||
CapLetRecognSet, // 206
|
||||
SpellingSet, // 207
|
||||
ClientNameSet, // 208
|
||||
VoiceSet, // 209
|
||||
Stopped, // 210
|
||||
Paused, // 211
|
||||
Resumed, // 212
|
||||
Canceled, // 213
|
||||
TableSet, // 215
|
||||
OutputModuleSet, // 216
|
||||
PauseContextSet, // 217
|
||||
VolumeSet, // 218
|
||||
SsmlModeSet, // 219
|
||||
NotificationSet, // 220
|
||||
PitchRangeSet, // 263
|
||||
DebugSet, // 262
|
||||
HistoryCurSetFirst, // 220
|
||||
HistoryCurSetLast, // 221
|
||||
HistoryCurSetPos, // 222
|
||||
HistoryCurMoveFor, // 223
|
||||
HistoryCurMoveBack, // 224
|
||||
MessageQueued, // 225,
|
||||
SoundIconQueued, // 226
|
||||
MessageCanceled, // 227
|
||||
ReceivingData, // 230
|
||||
Bye, // 231
|
||||
HistoryClientListSent(Vec<String>), // 240
|
||||
HistoryMsgsListSent(Vec<String>), // 241
|
||||
HistoryLastMsg(String), // 242
|
||||
HistoryCurPosRet(String), // 243
|
||||
TableListSent(Vec<String>), // 244
|
||||
HistoryClientIdSent(String), // 245
|
||||
MessageTextSent, // 246
|
||||
HelpSent(Vec<String>), // 248
|
||||
VoicesListSent(Vec<SynthesisVoice>), // 249
|
||||
OutputModulesListSent(Vec<String>), // 250
|
||||
GetString(String), // 251
|
||||
GetInteger(u8), // 251
|
||||
InsideBlock, // 260
|
||||
OutsideBlock, // 261
|
||||
NotImplemented, // 299
|
||||
EventIndexMark(EventId, String), // 700
|
||||
EventBegin(EventId), // 701
|
||||
EventEnd(EventId), // 702
|
||||
EventCanceled(EventId), // 703
|
||||
EventPaused(EventId), // 704
|
||||
EventResumed(EventId), // 705
|
||||
}
|
||||
|
||||
const INITIAL_REQUEST_QUEUE_CAPACITY: usize = 4;
|
||||
|
||||
/// Asynchronous client based on `mio`.
|
||||
///
|
||||
///
|
||||
pub struct AsyncClient<S: Read + Write + Source> {
|
||||
client: Client<S>,
|
||||
requests: VecDeque<Request>,
|
||||
}
|
||||
|
||||
impl<S: Read + Write + Source> AsyncClient<S> {
|
||||
/// New asynchronous client build on top of a synchronous client.
|
||||
pub fn new(client: Client<S>) -> Self {
|
||||
Self {
|
||||
client,
|
||||
requests: VecDeque::with_capacity(INITIAL_REQUEST_QUEUE_CAPACITY),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert two lines of the response in an event id
|
||||
fn parse_event_id(lines: &[String]) -> ClientResult<EventId> {
|
||||
match lines.len() {
|
||||
0 | 1 => Err(ClientError::TooFewLines),
|
||||
2 => Ok(EventId::new(&lines[0], &lines[1])),
|
||||
_ => Err(ClientError::TooManyLines),
|
||||
}
|
||||
}
|
||||
|
||||
/// Register client
|
||||
pub fn register(
|
||||
&mut self,
|
||||
poll: &mio::Poll,
|
||||
input_token: mio::Token,
|
||||
output_token: mio::Token,
|
||||
) -> io::Result<()> {
|
||||
self.client.register(poll, input_token, output_token)
|
||||
}
|
||||
|
||||
/// Push a new request in the queue.
|
||||
pub fn push(&mut self, request: Request) {
|
||||
self.requests.push_back(request);
|
||||
}
|
||||
|
||||
/// Return true if there is a pending request.
|
||||
pub fn has_next(&self) -> bool {
|
||||
!self.requests.is_empty()
|
||||
}
|
||||
|
||||
/// Write one pending request if any.
|
||||
///
|
||||
/// Instance of `mio::Poll` generates a writable event only once until the socket returns `WouldBlock`.
|
||||
/// This error is mapped to `ClientError::NotReady`.
|
||||
pub fn send_next(&mut self) -> ClientResult<()> {
|
||||
match self.requests.pop_front() {
|
||||
Some(request) => match request {
|
||||
Request::SetName(client_name) => self.client.set_client_name(client_name),
|
||||
Request::Speak => self.client.speak(),
|
||||
Request::SendLine(line) => self.client.send_line(&line),
|
||||
Request::SendLines(lines) => self.client.send_lines(
|
||||
lines
|
||||
.iter()
|
||||
.map(|s| s.as_str())
|
||||
.collect::<Vec<&str>>()
|
||||
.as_slice(),
|
||||
),
|
||||
Request::SendChar(ch) => self.client.send_char(ch),
|
||||
Request::Quit => self.client.quit(),
|
||||
}
|
||||
.map(|_| ()),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Receive one response.
|
||||
///
|
||||
/// Must be called each time a readable event is returned by `mio::Poll`.
|
||||
pub fn receive_next(&mut self) -> ClientResult<Response> {
|
||||
const MSG_CURSOR_SET_FIRST: &str = "OK CURSOR SET FIRST";
|
||||
let mut lines = Vec::new();
|
||||
let status = self.client.receive(&mut lines)?;
|
||||
match status.code {
|
||||
OK_LANGUAGE_SET => Ok(Response::LanguageSet),
|
||||
OK_PRIORITY_SET => Ok(Response::PrioritySet),
|
||||
OK_RATE_SET => Ok(Response::RateSet),
|
||||
OK_PITCH_SET => Ok(Response::PitchSet),
|
||||
OK_PUNCTUATION_SET => Ok(Response::PunctuationSet),
|
||||
OK_CAP_LET_RECOGN_SET => Ok(Response::CapLetRecognSet),
|
||||
OK_SPELLING_SET => Ok(Response::SpellingSet),
|
||||
OK_CLIENT_NAME_SET => Ok(Response::ClientNameSet),
|
||||
OK_VOICE_SET => Ok(Response::VoiceSet),
|
||||
OK_STOPPED => Ok(Response::Stopped),
|
||||
OK_PAUSED => Ok(Response::Paused),
|
||||
OK_RESUMED => Ok(Response::Resumed),
|
||||
OK_CANCELED => Ok(Response::Canceled),
|
||||
OK_TABLE_SET => Ok(Response::TableSet),
|
||||
OK_OUTPUT_MODULE_SET => Ok(Response::OutputModuleSet),
|
||||
OK_PAUSE_CONTEXT_SET => Ok(Response::PauseContextSet),
|
||||
OK_VOLUME_SET => Ok(Response::VolumeSet),
|
||||
OK_SSML_MODE_SET => Ok(Response::SsmlModeSet),
|
||||
// Warning OK_CUR_SET_FIRST == OK_NOTIFICATION_SET == 220. Matching message to make the difference
|
||||
OK_NOTIFICATION_SET => {
|
||||
if status.message == MSG_CURSOR_SET_FIRST {
|
||||
//OK_CUR_SET_FIRST => Ok(Response::HistoryCurSetFirst)
|
||||
Ok(Response::HistoryCurSetFirst)
|
||||
} else {
|
||||
Ok(Response::NotificationSet)
|
||||
}
|
||||
}
|
||||
|
||||
OK_CUR_SET_LAST => Ok(Response::HistoryCurSetLast),
|
||||
OK_CUR_SET_POS => Ok(Response::HistoryCurSetPos),
|
||||
OK_PITCH_RANGE_SET => Ok(Response::PitchRangeSet),
|
||||
OK_DEBUG_SET => Ok(Response::DebugSet),
|
||||
OK_CUR_MOV_FOR => Ok(Response::HistoryCurMoveFor),
|
||||
OK_CUR_MOV_BACK => Ok(Response::HistoryCurMoveBack),
|
||||
OK_MESSAGE_QUEUED => Ok(Response::MessageQueued),
|
||||
OK_SND_ICON_QUEUED => Ok(Response::SoundIconQueued),
|
||||
OK_MSG_CANCELED => Ok(Response::MessageCanceled),
|
||||
OK_RECEIVING_DATA => Ok(Response::ReceivingData),
|
||||
OK_BYE => Ok(Response::Bye),
|
||||
OK_CLIENT_LIST_SENT => Ok(Response::HistoryClientListSent(lines)),
|
||||
OK_MSGS_LIST_SENT => Ok(Response::HistoryMsgsListSent(lines)),
|
||||
OK_LAST_MSG => Ok(Response::HistoryLastMsg(Client::<S>::parse_single_value(
|
||||
&lines,
|
||||
)?)),
|
||||
OK_CUR_POS_RET => Ok(Response::HistoryCurPosRet(Client::<S>::parse_single_value(
|
||||
&lines,
|
||||
)?)),
|
||||
OK_TABLE_LIST_SENT => Ok(Response::TableListSent(lines)),
|
||||
OK_CLIENT_ID_SENT => Ok(Response::HistoryClientIdSent(
|
||||
Client::<S>::parse_single_value(&lines)?,
|
||||
)),
|
||||
OK_MSG_TEXT_SENT => Ok(Response::MessageTextSent),
|
||||
OK_HELP_SENT => Ok(Response::HelpSent(lines)),
|
||||
OK_VOICES_LIST_SENT => Ok(Response::VoicesListSent(
|
||||
Client::<S>::parse_synthesis_voices(&lines)?,
|
||||
)),
|
||||
OK_OUTPUT_MODULES_LIST_SENT => Ok(Response::OutputModulesListSent(lines)),
|
||||
OK_GET => {
|
||||
let sval = Client::<S>::parse_single_value(&lines)?;
|
||||
Ok(match sval.parse::<u8>() {
|
||||
Ok(uval) => Response::GetInteger(uval),
|
||||
Err(_) => Response::GetString(sval),
|
||||
})
|
||||
}
|
||||
OK_INSIDE_BLOCK => Ok(Response::InsideBlock),
|
||||
OK_OUTSIDE_BLOCK => Ok(Response::OutsideBlock),
|
||||
OK_NOT_IMPLEMENTED => Ok(Response::NotImplemented),
|
||||
EVENT_INDEX_MARK => {
|
||||
if lines.len() == 3 {
|
||||
Ok(Response::EventIndexMark(
|
||||
Self::parse_event_id(&lines)?,
|
||||
lines[2].to_owned(),
|
||||
))
|
||||
} else {
|
||||
Err(ClientError::TooFewLines)
|
||||
}
|
||||
}
|
||||
EVENT_BEGIN => Ok(Response::EventBegin(Self::parse_event_id(&lines)?)),
|
||||
EVENT_END => Ok(Response::EventEnd(Self::parse_event_id(&lines)?)),
|
||||
EVENT_CANCELED => Ok(Response::EventCanceled(Self::parse_event_id(&lines)?)),
|
||||
EVENT_PAUSED => Ok(Response::EventPaused(Self::parse_event_id(&lines)?)),
|
||||
EVENT_RESUMED => Ok(Response::EventResumed(Self::parse_event_id(&lines)?)),
|
||||
_ => panic!("error should have been caught earlier"),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue