From 8851594537f96e3f25d9a07371a308242e5fcf70 Mon Sep 17 00:00:00 2001 From: AviiNL Date: Fri, 22 Dec 2023 16:24:17 +0100 Subject: [PATCH] config files --- Cargo.lock | 39 ++++++++ Cargo.toml | 2 + config.toml | 13 +++ .../src/components/grpc_base_url.rs | 4 +- crates/guardian_core/src/components/mod.rs | 2 + .../src/components/srs_socket_addr.rs | 3 +- .../src/components/stt_base_url.rs | 81 ++++++++++++++++ crates/guardian_core/src/srs.rs | 29 +++++- crates/guardian_core/src/srs/message.rs | 3 +- crates/guardian_core/src/tts.rs | 9 +- src/commands/tripwire.rs | 1 - src/config.rs | 95 +++++++++++++++++++ src/main.rs | 45 ++++++--- src/tripwire.rs | 12 +-- 14 files changed, 302 insertions(+), 36 deletions(-) create mode 100644 config.toml create mode 100644 crates/guardian_core/src/components/stt_base_url.rs create mode 100644 src/config.rs diff --git a/Cargo.lock b/Cargo.lock index 948e104..c790265 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2285,7 +2285,9 @@ dependencies = [ "clap", "guardian_commands", "guardian_core", + "serde", "tokio", + "toml", ] [[package]] @@ -3872,6 +3874,15 @@ dependencies = [ "syn 2.0.41", ] +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -4249,11 +4260,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.21.0", +] + [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -4277,6 +4303,19 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap 2.1.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" version = "0.10.2" diff --git a/Cargo.toml b/Cargo.toml index d75f30b..5a1a625 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,5 +47,7 @@ shlex = "1.2" bevy.workspace = true clap.workspace = true tokio.workspace = true +serde.workspace = true guardian_commands.workspace = true guardian_core.workspace = true +toml = "0.8" diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..51b2b87 --- /dev/null +++ b/config.toml @@ -0,0 +1,13 @@ +[srs] +server = "127.0.0.1:5002" + +[grpc] +url = "http://127.0.0.1:50051/" + +[stt] +url = "http://192.168.0.2:3000/" + +[channels.Overlord1-1] +frequency = 251000000 +modulation = "Am" +voice = "David" diff --git a/crates/guardian_core/src/components/grpc_base_url.rs b/crates/guardian_core/src/components/grpc_base_url.rs index 921ece4..b306c21 100644 --- a/crates/guardian_core/src/components/grpc_base_url.rs +++ b/crates/guardian_core/src/components/grpc_base_url.rs @@ -8,10 +8,12 @@ use bevy::{ reflect::{std_traits::ReflectDefault, Reflect}, utils::AHasher, }; +use serde::{Deserialize, Serialize}; -#[derive(Reflect, Resource, Clone)] +#[derive(Reflect, Resource, Clone, Deserialize, Serialize)] #[reflect(Resource, Default, Debug)] pub struct GrpcBaseUrl { + #[serde(skip)] hash: u64, url: Cow<'static, str>, } diff --git a/crates/guardian_core/src/components/mod.rs b/crates/guardian_core/src/components/mod.rs index c260c75..724dba7 100644 --- a/crates/guardian_core/src/components/mod.rs +++ b/crates/guardian_core/src/components/mod.rs @@ -8,6 +8,7 @@ mod player; mod position; mod side; mod srs_socket_addr; +mod stt_base_url; mod unit_type; pub use awacs::*; @@ -20,4 +21,5 @@ pub use player::*; pub use position::*; pub use side::*; pub use srs_socket_addr::*; +pub use stt_base_url::*; pub use unit_type::*; diff --git a/crates/guardian_core/src/components/srs_socket_addr.rs b/crates/guardian_core/src/components/srs_socket_addr.rs index 31d6beb..096649a 100644 --- a/crates/guardian_core/src/components/srs_socket_addr.rs +++ b/crates/guardian_core/src/components/srs_socket_addr.rs @@ -1,8 +1,9 @@ use std::net::SocketAddr; use bevy::ecs::system::Resource; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Resource, Clone)] +#[derive(Debug, Resource, Clone, Deserialize, Serialize)] pub struct SrsSocketAddr(SocketAddr); impl Default for SrsSocketAddr { diff --git a/crates/guardian_core/src/components/stt_base_url.rs b/crates/guardian_core/src/components/stt_base_url.rs new file mode 100644 index 0000000..0bb3cf0 --- /dev/null +++ b/crates/guardian_core/src/components/stt_base_url.rs @@ -0,0 +1,81 @@ +use std::{ + borrow::Cow, + hash::{Hash, Hasher}, +}; + +use bevy::{ + ecs::{reflect::ReflectResource, system::Resource}, + reflect::{std_traits::ReflectDefault, Reflect}, + utils::AHasher, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Reflect, Resource, Clone, Deserialize, Serialize)] +#[reflect(Resource, Default, Debug)] +pub struct SttBaseUrl { + #[serde(skip)] + hash: u64, + url: Cow<'static, str>, +} + +impl Default for SttBaseUrl { + fn default() -> Self { + SttBaseUrl::new("http://127.0.0.1:3000/") + } +} + +impl SttBaseUrl { + /// Creates a new [`SttBaseUrl`] from any string-like type. + /// + /// The internal hash will be computed immediately. + pub fn new(url: impl Into>) -> Self { + let url = url.into(); + let mut url = SttBaseUrl { url, hash: 0 }; + url.update_hash(); + url + } + + /// Sets the entity's url. + /// + /// The internal hash will be re-computed. + #[inline(always)] + pub fn set(&mut self, url: impl Into>) { + *self = SttBaseUrl::new(url); + } + + /// Updates the url of the entity in place. + /// + /// This will allocate a new string if the url was previously + /// created from a borrow. + #[inline(always)] + pub fn mutate(&mut self, f: F) { + f(self.url.to_mut()); + self.update_hash(); + } + + /// Gets the url of the entity as a `&str`. + #[inline(always)] + pub fn as_str(&self) -> &str { + &self.url + } + + fn update_hash(&mut self) { + let mut hasher = AHasher::default(); + self.url.hash(&mut hasher); + self.hash = hasher.finish(); + } +} + +impl std::fmt::Display for SttBaseUrl { + #[inline(always)] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.url, f) + } +} + +impl std::fmt::Debug for SttBaseUrl { + #[inline(always)] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Debug::fmt(&self.url, f) + } +} diff --git a/crates/guardian_core/src/srs.rs b/crates/guardian_core/src/srs.rs index 072682e..2a30931 100644 --- a/crates/guardian_core/src/srs.rs +++ b/crates/guardian_core/src/srs.rs @@ -6,6 +6,7 @@ mod voice_codec; mod voice_command; use guardian_commands::{call::parse_call, ConsoleCommandEntered, ConsoleConfiguration}; +use serde::{Deserialize, Serialize}; use simsearch::SimSearch; pub use voice_command::VoiceCommand; @@ -104,6 +105,7 @@ fn listen_srs( >, tokio: Res, addr: Res, + stt_url: Res, ) { for (ent, id, callsign, position, mut radio, red, blue) in units.iter_mut() { let addr: SocketAddr = addr.clone().into(); @@ -159,6 +161,8 @@ fn listen_srs( let frequency = radio.frequency; let id = *id; + let voice = radio.voice; + let stt_url = stt_url.as_str().to_string(); radio.handle = Some(tokio.0.spawn(async move { let tcp = TcpStream::connect(addr).await?; @@ -246,7 +250,7 @@ fn listen_srs( } } Some(data) = voice_handle.recv() => { - frames.push(synthesize(data.as_str()).await?).await; + frames.push(synthesize(data.as_str(), &voice).await?).await; } Some(Ok(data)) = voice_stream.next() => { // Collect voice packets @@ -280,7 +284,7 @@ fn listen_srs( } } - let Some(message) = whisper("http://192.168.0.2:3000/", &wav_data).await else { + let Some(message) = whisper(&stt_url, &wav_data).await else { continue; }; @@ -487,6 +491,22 @@ fn handle_voice_command( } } +#[derive(Default, Clone, Copy, Debug, Deserialize, Serialize)] +pub enum Voice { + #[default] + David, + Zira, +} + +impl std::fmt::Display for Voice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::David => write!(f, "David"), + Self::Zira => write!(f, "Zira"), + } + } +} + #[derive(Component)] pub struct ClientHandler(Receiver); @@ -494,6 +514,7 @@ pub struct ClientHandler(Receiver); pub struct Radio { pub frequency: u64, // the way srs wants it pub modulation: Modulation, + pub voice: Voice, sguid: String, voice_sink: Option>, message_sink: Option>, @@ -501,10 +522,12 @@ pub struct Radio { } impl Radio { - pub fn new(frequency: u64, modulation: Modulation) -> Self { + pub fn new(frequency: u64, modulation: impl Into, voice: Voice) -> Self { + let modulation = modulation.into(); Self { frequency, modulation, + voice, sguid: create_sguid(), voice_sink: None, message_sink: None, diff --git a/crates/guardian_core/src/srs/message.rs b/crates/guardian_core/src/srs/message.rs index 426c2f2..a423b21 100644 --- a/crates/guardian_core/src/srs/message.rs +++ b/crates/guardian_core/src/srs/message.rs @@ -129,9 +129,8 @@ impl Default for Radio { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)] +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)] #[repr(u8)] -#[derive(Default)] pub enum Modulation { Am = 0, Fm = 1, diff --git a/crates/guardian_core/src/tts.rs b/crates/guardian_core/src/tts.rs index 0864d8b..1dae2a5 100644 --- a/crates/guardian_core/src/tts.rs +++ b/crates/guardian_core/src/tts.rs @@ -7,14 +7,15 @@ use windows::core::HSTRING; use windows::Media::SpeechSynthesis::SpeechSynthesizer; use windows::Storage::Streams::DataReader; +use crate::srs::Voice; + #[derive(Debug)] pub struct WinConfig { pub voice: Option, } impl WinConfig { - pub fn new() -> Self { - let voice = "David"; // for now + pub fn new(voice: &str) -> Self { Self { voice: Some(voice.to_string()), } @@ -23,8 +24,8 @@ impl WinConfig { static MUTEX: Mutex<()> = Mutex::const_new(()); -pub async fn synthesize(text: &str) -> Result>, WinError> { - let config = WinConfig::new(); +pub async fn synthesize(text: &str, voice: &Voice) -> Result>, WinError> { + let config = WinConfig::new(&voice.to_string()); // Note, there does not seem to be a way to explicitly set 16000kHz, 16 audio bits per // sample and mono channel. diff --git a/src/commands/tripwire.rs b/src/commands/tripwire.rs index 93d49fc..7d1a33b 100644 --- a/src/commands/tripwire.rs +++ b/src/commands/tripwire.rs @@ -22,7 +22,6 @@ pub fn set_tripwire( callsign: Query<&Callsign>, ) { let Some(pilot) = &cmd.pilot else { - eprintln!("no pilot"); return; // no pilot }; let Some(operator) = &cmd.operator else { diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..0ad67f0 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,95 @@ +use std::{collections::HashMap, path::PathBuf}; + +use bevy::ecs::system::Resource; +use guardian_core::{ + components::{GrpcBaseUrl, SrsSocketAddr, SttBaseUrl}, + srs::{Modulation as SrsModulation, Voice}, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Resource, Clone, Debug, Deserialize, Serialize, Default)] +pub struct Channels(pub HashMap); + +#[derive(Clone, Debug, Deserialize, Serialize, Default)] +pub struct Config { + pub srs: SrsConfig, + pub grpc: GrpcBaseUrl, + pub stt: SttBaseUrl, + pub channels: Channels, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Default)] +pub struct SrsConfig { + pub server: SrsSocketAddr, +} + +#[derive(Clone, Debug, Deserialize, Serialize, Default)] +pub struct ChannelConfig { + pub frequency: u64, + pub modulation: Modulation, // 0=AM, 1=FM? + pub voice: Voice, +} + +impl TryFrom for Config { + type Error = Box; + + fn try_from(value: PathBuf) -> Result { + use std::io::Read; + let mut config = std::fs::File::open(value)?; + let mut config_str = String::new(); + config.read_to_string(&mut config_str)?; + Ok(toml::from_str(&config_str)?) + } +} + +impl Config { + pub fn load(file: PathBuf) -> Result> { + if file.exists() { + return file.try_into(); + } + + use std::io::Write; + + let mut defaults = Config::default(); + + defaults.channels.0.insert( + "Overlord1-1".to_string(), + ChannelConfig { + frequency: 251000000, + modulation: Modulation::Am, + voice: Voice::David, + }, + ); + + // write the file + std::fs::File::create(file)?.write_all(toml::to_string_pretty(&defaults)?.as_bytes())?; + + Ok(defaults) + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize, Default)] +pub enum Modulation { + #[default] + Am, + Fm, + Intercom, + Disabled, + HaveQuick, + Satcom, + Mids, +} + +impl From for SrsModulation { + fn from(value: Modulation) -> Self { + match value { + Modulation::Am => SrsModulation::Am, + Modulation::Fm => SrsModulation::Fm, + Modulation::Intercom => SrsModulation::Intercom, + Modulation::Disabled => SrsModulation::Disabled, + Modulation::HaveQuick => SrsModulation::HaveQuick, + Modulation::Satcom => SrsModulation::Satcom, + Modulation::Mids => SrsModulation::Mids, + } + } +} diff --git a/src/main.rs b/src/main.rs index 71051c4..2f77391 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod braa; mod commands; +mod config; mod tripwire; use bevy::{ @@ -7,44 +8,62 @@ use bevy::{ ecs::{ entity::Entity, query::{With, Without}, - system::{Commands, Query}, + system::{Commands, Query, Res}, }, }; use commands::CommandsPlugin; +use config::{Channels, Config}; use guardian_core::{ components::{Awacs, Callsign}, - srs::{Modulation, Radio}, + srs::Radio, DefaultPlugins, TokioResource, }; use tokio::runtime::Handle; use tripwire::TripwirePlugin; #[tokio::main] -async fn main() { +async fn main() -> Result<(), Box> { + let config = Config::load("config.toml".into())?; + App::new() + .insert_resource(config.srs.server) + .insert_resource(config.stt) + .insert_resource(config.grpc) + .insert_resource(config.channels) .insert_resource(TokioResource(Handle::current())) .add_plugins(DefaultPlugins) .add_plugins(TripwirePlugin) .add_plugins(CommandsPlugin) .add_systems(Update, give_awacs_radio) .run(); + + Ok(()) } fn give_awacs_radio( mut commands: Commands, + channels: Res, units: Query<(Entity, &Callsign), (With, Without)>, ) { for (ent, callsign) in units.iter() { - if callsign.as_str() == "Overlord1-1" { - commands - .entity(ent) - .insert(Radio::new(251000000, Modulation::Am)); - } + let Some(channel) = channels.0.get(callsign.as_str()) else { + continue; // not configured for this unit + }; - if callsign.as_str() == "Magic1-1" { - commands - .entity(ent) - .insert(Radio::new(266000000, Modulation::Am)); - } + commands.entity(ent).insert(Radio::new( + channel.frequency, + channel.modulation, + channel.voice, + )); } } + +// // Initial player tripwire? get from some db so player doesnt need to redo it everytime +// fn add_tripwire(mut commands: Commands, players: Query>) { +// for ent in players.iter() { +// commands.entity(ent).insert(Tripwire { +// range: 2.0, +// reported: vec![], +// }); +// } +// } diff --git a/src/tripwire.rs b/src/tripwire.rs index dcc5468..87be1a0 100644 --- a/src/tripwire.rs +++ b/src/tripwire.rs @@ -3,7 +3,7 @@ use bevy::{ ecs::{ component::Component, entity::Entity, - query::{Added, With, Without}, + query::{With, Without}, system::{Commands, Query}, }, }; @@ -23,16 +23,6 @@ impl Plugin for TripwirePlugin { } } -// this will be on a tripwire command event (from srs), instead of Added, not everyone may want it; opt-in over opt-out -// fn add_tripwire(mut commands: Commands, players: Query>) { -// for ent in players.iter() { -// commands.entity(ent).insert(Tripwire { -// range: 2.0, -// reported: vec![], -// }); -// } -// } - fn tripwire( mut commands: Commands, awacs: Query<(Entity, &Callsign, &Radio), (With, With, With)>,