config files

This commit is contained in:
AviiNL
2023-12-22 16:24:17 +01:00
parent f79b6b5dfb
commit 8851594537
14 changed files with 302 additions and 36 deletions

39
Cargo.lock generated
View File

@@ -2285,7 +2285,9 @@ dependencies = [
"clap", "clap",
"guardian_commands", "guardian_commands",
"guardian_core", "guardian_core",
"serde",
"tokio", "tokio",
"toml",
] ]
[[package]] [[package]]
@@ -3872,6 +3874,15 @@ dependencies = [
"syn 2.0.41", "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]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@@ -4249,11 +4260,26 @@ dependencies = [
"tracing", "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]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.5" version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
@@ -4277,6 +4303,19 @@ dependencies = [
"winnow", "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]] [[package]]
name = "tonic" name = "tonic"
version = "0.10.2" version = "0.10.2"

View File

@@ -47,5 +47,7 @@ shlex = "1.2"
bevy.workspace = true bevy.workspace = true
clap.workspace = true clap.workspace = true
tokio.workspace = true tokio.workspace = true
serde.workspace = true
guardian_commands.workspace = true guardian_commands.workspace = true
guardian_core.workspace = true guardian_core.workspace = true
toml = "0.8"

13
config.toml Normal file
View File

@@ -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"

View File

@@ -8,10 +8,12 @@ use bevy::{
reflect::{std_traits::ReflectDefault, Reflect}, reflect::{std_traits::ReflectDefault, Reflect},
utils::AHasher, utils::AHasher,
}; };
use serde::{Deserialize, Serialize};
#[derive(Reflect, Resource, Clone)] #[derive(Reflect, Resource, Clone, Deserialize, Serialize)]
#[reflect(Resource, Default, Debug)] #[reflect(Resource, Default, Debug)]
pub struct GrpcBaseUrl { pub struct GrpcBaseUrl {
#[serde(skip)]
hash: u64, hash: u64,
url: Cow<'static, str>, url: Cow<'static, str>,
} }

View File

@@ -8,6 +8,7 @@ mod player;
mod position; mod position;
mod side; mod side;
mod srs_socket_addr; mod srs_socket_addr;
mod stt_base_url;
mod unit_type; mod unit_type;
pub use awacs::*; pub use awacs::*;
@@ -20,4 +21,5 @@ pub use player::*;
pub use position::*; pub use position::*;
pub use side::*; pub use side::*;
pub use srs_socket_addr::*; pub use srs_socket_addr::*;
pub use stt_base_url::*;
pub use unit_type::*; pub use unit_type::*;

View File

@@ -1,8 +1,9 @@
use std::net::SocketAddr; use std::net::SocketAddr;
use bevy::ecs::system::Resource; use bevy::ecs::system::Resource;
use serde::{Deserialize, Serialize};
#[derive(Debug, Resource, Clone)] #[derive(Debug, Resource, Clone, Deserialize, Serialize)]
pub struct SrsSocketAddr(SocketAddr); pub struct SrsSocketAddr(SocketAddr);
impl Default for SrsSocketAddr { impl Default for SrsSocketAddr {

View File

@@ -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<Cow<'static, str>>) -> 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<Cow<'static, str>>) {
*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<F: FnOnce(&mut String)>(&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)
}
}

View File

@@ -6,6 +6,7 @@ mod voice_codec;
mod voice_command; mod voice_command;
use guardian_commands::{call::parse_call, ConsoleCommandEntered, ConsoleConfiguration}; use guardian_commands::{call::parse_call, ConsoleCommandEntered, ConsoleConfiguration};
use serde::{Deserialize, Serialize};
use simsearch::SimSearch; use simsearch::SimSearch;
pub use voice_command::VoiceCommand; pub use voice_command::VoiceCommand;
@@ -104,6 +105,7 @@ fn listen_srs(
>, >,
tokio: Res<TokioResource>, tokio: Res<TokioResource>,
addr: Res<SrsSocketAddr>, addr: Res<SrsSocketAddr>,
stt_url: Res<SttBaseUrl>,
) { ) {
for (ent, id, callsign, position, mut radio, red, blue) in units.iter_mut() { for (ent, id, callsign, position, mut radio, red, blue) in units.iter_mut() {
let addr: SocketAddr = addr.clone().into(); let addr: SocketAddr = addr.clone().into();
@@ -159,6 +161,8 @@ fn listen_srs(
let frequency = radio.frequency; let frequency = radio.frequency;
let id = *id; let id = *id;
let voice = radio.voice;
let stt_url = stt_url.as_str().to_string();
radio.handle = Some(tokio.0.spawn(async move { radio.handle = Some(tokio.0.spawn(async move {
let tcp = TcpStream::connect(addr).await?; let tcp = TcpStream::connect(addr).await?;
@@ -246,7 +250,7 @@ fn listen_srs(
} }
} }
Some(data) = voice_handle.recv() => { 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() => { Some(Ok(data)) = voice_stream.next() => {
// Collect voice packets // 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; 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)] #[derive(Component)]
pub struct ClientHandler(Receiver<Client>); pub struct ClientHandler(Receiver<Client>);
@@ -494,6 +514,7 @@ pub struct ClientHandler(Receiver<Client>);
pub struct Radio { pub struct Radio {
pub frequency: u64, // the way srs wants it pub frequency: u64, // the way srs wants it
pub modulation: Modulation, pub modulation: Modulation,
pub voice: Voice,
sguid: String, sguid: String,
voice_sink: Option<Sender<VoiceMessage>>, voice_sink: Option<Sender<VoiceMessage>>,
message_sink: Option<Sender<MessageRequest>>, message_sink: Option<Sender<MessageRequest>>,
@@ -501,10 +522,12 @@ pub struct Radio {
} }
impl Radio { impl Radio {
pub fn new(frequency: u64, modulation: Modulation) -> Self { pub fn new(frequency: u64, modulation: impl Into<Modulation>, voice: Voice) -> Self {
let modulation = modulation.into();
Self { Self {
frequency, frequency,
modulation, modulation,
voice,
sguid: create_sguid(), sguid: create_sguid(),
voice_sink: None, voice_sink: None,
message_sink: None, message_sink: None,

View File

@@ -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)] #[repr(u8)]
#[derive(Default)]
pub enum Modulation { pub enum Modulation {
Am = 0, Am = 0,
Fm = 1, Fm = 1,

View File

@@ -7,14 +7,15 @@ use windows::core::HSTRING;
use windows::Media::SpeechSynthesis::SpeechSynthesizer; use windows::Media::SpeechSynthesis::SpeechSynthesizer;
use windows::Storage::Streams::DataReader; use windows::Storage::Streams::DataReader;
use crate::srs::Voice;
#[derive(Debug)] #[derive(Debug)]
pub struct WinConfig { pub struct WinConfig {
pub voice: Option<String>, pub voice: Option<String>,
} }
impl WinConfig { impl WinConfig {
pub fn new() -> Self { pub fn new(voice: &str) -> Self {
let voice = "David"; // for now
Self { Self {
voice: Some(voice.to_string()), voice: Some(voice.to_string()),
} }
@@ -23,8 +24,8 @@ impl WinConfig {
static MUTEX: Mutex<()> = Mutex::const_new(()); static MUTEX: Mutex<()> = Mutex::const_new(());
pub async fn synthesize(text: &str) -> Result<Vec<Vec<u8>>, WinError> { pub async fn synthesize(text: &str, voice: &Voice) -> Result<Vec<Vec<u8>>, WinError> {
let config = WinConfig::new(); let config = WinConfig::new(&voice.to_string());
// Note, there does not seem to be a way to explicitly set 16000kHz, 16 audio bits per // Note, there does not seem to be a way to explicitly set 16000kHz, 16 audio bits per
// sample and mono channel. // sample and mono channel.

View File

@@ -22,7 +22,6 @@ pub fn set_tripwire(
callsign: Query<&Callsign>, callsign: Query<&Callsign>,
) { ) {
let Some(pilot) = &cmd.pilot else { let Some(pilot) = &cmd.pilot else {
eprintln!("no pilot");
return; // no pilot return; // no pilot
}; };
let Some(operator) = &cmd.operator else { let Some(operator) = &cmd.operator else {

95
src/config.rs Normal file
View File

@@ -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<String, ChannelConfig>);
#[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<PathBuf> for Config {
type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
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<Self, Box<dyn std::error::Error + Send + Sync + 'static>> {
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<Modulation> 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,
}
}
}

View File

@@ -1,5 +1,6 @@
mod braa; mod braa;
mod commands; mod commands;
mod config;
mod tripwire; mod tripwire;
use bevy::{ use bevy::{
@@ -7,44 +8,62 @@ use bevy::{
ecs::{ ecs::{
entity::Entity, entity::Entity,
query::{With, Without}, query::{With, Without},
system::{Commands, Query}, system::{Commands, Query, Res},
}, },
}; };
use commands::CommandsPlugin; use commands::CommandsPlugin;
use config::{Channels, Config};
use guardian_core::{ use guardian_core::{
components::{Awacs, Callsign}, components::{Awacs, Callsign},
srs::{Modulation, Radio}, srs::Radio,
DefaultPlugins, TokioResource, DefaultPlugins, TokioResource,
}; };
use tokio::runtime::Handle; use tokio::runtime::Handle;
use tripwire::TripwirePlugin; use tripwire::TripwirePlugin;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let config = Config::load("config.toml".into())?;
App::new() App::new()
.insert_resource(config.srs.server)
.insert_resource(config.stt)
.insert_resource(config.grpc)
.insert_resource(config.channels)
.insert_resource(TokioResource(Handle::current())) .insert_resource(TokioResource(Handle::current()))
.add_plugins(DefaultPlugins) .add_plugins(DefaultPlugins)
.add_plugins(TripwirePlugin) .add_plugins(TripwirePlugin)
.add_plugins(CommandsPlugin) .add_plugins(CommandsPlugin)
.add_systems(Update, give_awacs_radio) .add_systems(Update, give_awacs_radio)
.run(); .run();
Ok(())
} }
fn give_awacs_radio( fn give_awacs_radio(
mut commands: Commands, mut commands: Commands,
channels: Res<Channels>,
units: Query<(Entity, &Callsign), (With<Awacs>, Without<Radio>)>, units: Query<(Entity, &Callsign), (With<Awacs>, Without<Radio>)>,
) { ) {
for (ent, callsign) in units.iter() { for (ent, callsign) in units.iter() {
if callsign.as_str() == "Overlord1-1" { let Some(channel) = channels.0.get(callsign.as_str()) else {
commands continue; // not configured for this unit
.entity(ent) };
.insert(Radio::new(251000000, Modulation::Am));
}
if callsign.as_str() == "Magic1-1" { commands.entity(ent).insert(Radio::new(
commands channel.frequency,
.entity(ent) channel.modulation,
.insert(Radio::new(266000000, Modulation::Am)); 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<Entity, Added<Player>>) {
// for ent in players.iter() {
// commands.entity(ent).insert(Tripwire {
// range: 2.0,
// reported: vec![],
// });
// }
// }

View File

@@ -3,7 +3,7 @@ use bevy::{
ecs::{ ecs::{
component::Component, component::Component,
entity::Entity, entity::Entity,
query::{Added, With, Without}, query::{With, Without},
system::{Commands, Query}, system::{Commands, Query},
}, },
}; };
@@ -23,16 +23,6 @@ impl Plugin for TripwirePlugin {
} }
} }
// this will be on a tripwire command event (from srs), instead of Added<Player>, not everyone may want it; opt-in over opt-out
// fn add_tripwire(mut commands: Commands, players: Query<Entity, Added<Player>>) {
// for ent in players.iter() {
// commands.entity(ent).insert(Tripwire {
// range: 2.0,
// reported: vec![],
// });
// }
// }
fn tripwire( fn tripwire(
mut commands: Commands, mut commands: Commands,
awacs: Query<(Entity, &Callsign, &Radio), (With<Awacs>, With<Blue>, With<Radio>)>, awacs: Query<(Entity, &Callsign, &Radio), (With<Awacs>, With<Blue>, With<Radio>)>,