Update to Rust programming language.
This commit is contained in:
parent
943efbfbdb
commit
f88248643b
201 changed files with 12954 additions and 1690 deletions
19
src-tauri/src/tauri_commands/db.rs
Normal file
19
src-tauri/src/tauri_commands/db.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use crate::DB;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn db_read(key: &str) -> String {
|
||||
if let Some(value) = DB.lock().unwrap().get(key) {
|
||||
value
|
||||
} else {
|
||||
String::from("")
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn db_write(key: &str, val: &str) -> bool {
|
||||
if let Ok(_) = DB.lock().unwrap().set(key, &val) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
32
src-tauri/src/tauri_commands/etc.rs
Normal file
32
src-tauri/src/tauri_commands/etc.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use crate::config::APP_VERSION;
|
||||
use crate::config::AUTHOR_NAME;
|
||||
use crate::config::REPOSITORY_LINK;
|
||||
|
||||
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_app_version() -> String {
|
||||
if let Some(ver) = APP_VERSION {
|
||||
ver.to_string()
|
||||
} else {
|
||||
String::from("error")
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_author_name() -> String {
|
||||
if let Some(ver) = AUTHOR_NAME {
|
||||
ver.to_string()
|
||||
} else {
|
||||
String::from("error")
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_repository_link() -> String {
|
||||
if let Some(ver) = REPOSITORY_LINK {
|
||||
ver.to_string()
|
||||
} else {
|
||||
String::from("error")
|
||||
}
|
||||
}
|
||||
193
src-tauri/src/tauri_commands/listener.rs
Normal file
193
src-tauri/src/tauri_commands/listener.rs
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
use porcupine::{BuiltinKeywords, Porcupine, PorcupineBuilder};
|
||||
use pv_recorder::RecorderBuilder;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::events::Payload;
|
||||
use tauri::Manager;
|
||||
|
||||
use rand::seq::SliceRandom;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::assistant_commands;
|
||||
use crate::events;
|
||||
|
||||
use crate::config;
|
||||
use crate::vosk;
|
||||
|
||||
use crate::COMMANDS;
|
||||
use crate::DB;
|
||||
|
||||
// track listening state
|
||||
static LISTENING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
// stop listening with Atomic flag (to make it work between different threads)
|
||||
static STOP_LISTENING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[tauri::command]
|
||||
pub fn is_listening() -> bool {
|
||||
LISTENING.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn stop_listening() {
|
||||
if is_listening() {
|
||||
STOP_LISTENING.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
// wait until listening stops
|
||||
while is_listening() {}
|
||||
}
|
||||
|
||||
#[tauri::command(async)]
|
||||
pub fn start_listening(app_handle: tauri::AppHandle) -> Result<bool, String> {
|
||||
// only one listener thread is allowed
|
||||
if is_listening() {
|
||||
return Err("Already listening.".into());
|
||||
}
|
||||
|
||||
// vars
|
||||
let porcupine: Porcupine;
|
||||
let picovoice_api_key: String;
|
||||
let selected_microphone: i32;
|
||||
|
||||
let mut start = SystemTime::now();
|
||||
|
||||
// Retrieve API key from DB
|
||||
if let Some(pkey) = DB.lock().unwrap().get::<String>("api_key__picovoice") {
|
||||
picovoice_api_key = pkey;
|
||||
} else {
|
||||
return Err("Picovoice API key is not set!".into());
|
||||
}
|
||||
|
||||
// Create instance of Porcupine with the given API key
|
||||
if let Ok(pinstance) =
|
||||
PorcupineBuilder::new_with_keywords(picovoice_api_key, &[BuiltinKeywords::Jarvis])
|
||||
.sensitivities(&[1.0f32]) // max sensitivity possible
|
||||
.init()
|
||||
{
|
||||
// porcupine successfully initialized with the valid API key
|
||||
porcupine = pinstance;
|
||||
} else {
|
||||
// something went wrong
|
||||
return Err(
|
||||
"Porcupine error: either API key is not valid or there is no internet connection"
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
// Retrieve microphone index
|
||||
if let Some(smic) = DB.lock().unwrap().get::<String>("selected_microphone") {
|
||||
selected_microphone = smic.parse().unwrap_or(-1);
|
||||
} else {
|
||||
selected_microphone = -1; // use default, if not selected
|
||||
}
|
||||
|
||||
// Create recorder instance
|
||||
let recorder = RecorderBuilder::new()
|
||||
.device_index(selected_microphone)
|
||||
.frame_length(porcupine.frame_length() as i32)
|
||||
.init()
|
||||
.expect("Failed to initialize pvrecorder");
|
||||
|
||||
// Start recording
|
||||
println!("Listening (microphone idx = {selected_microphone}) ...");
|
||||
recorder.start().expect("Failed to start audio recording");
|
||||
LISTENING.store(true, Ordering::SeqCst);
|
||||
|
||||
// Greet user
|
||||
events::play("run", &app_handle);
|
||||
|
||||
// Listen until stop flag will be true
|
||||
let mut frame_buffer = vec![0; porcupine.frame_length() as usize];
|
||||
while !STOP_LISTENING.load(Ordering::SeqCst) {
|
||||
recorder
|
||||
.read(&mut frame_buffer)
|
||||
.expect("Failed to read audio frame");
|
||||
|
||||
if let Ok(keyword_index) = porcupine.process(&frame_buffer) {
|
||||
if keyword_index >= 0 {
|
||||
println!("Yes, sir! {}", keyword_index);
|
||||
events::play(
|
||||
config::ASSISTANT_GREET_PHRASES
|
||||
.choose(&mut rand::thread_rng())
|
||||
.unwrap(),
|
||||
&app_handle,
|
||||
);
|
||||
start = SystemTime::now();
|
||||
|
||||
app_handle
|
||||
.emit_all(events::EventTypes::AssistantGreet.get(), ())
|
||||
.unwrap();
|
||||
|
||||
loop {
|
||||
recorder
|
||||
.read(&mut frame_buffer)
|
||||
.expect("Failed to read audio frame");
|
||||
|
||||
// vosk part (partials included)
|
||||
if let Some(mut test) = vosk::recognize(&frame_buffer) {
|
||||
if !test.is_empty() {
|
||||
println!("Recognized: {}", test);
|
||||
|
||||
// some filtration
|
||||
test = test.to_lowercase();
|
||||
for tbr in config::ASSISTANT_PHRASES_TBR {
|
||||
test = test.replace(tbr, "");
|
||||
}
|
||||
|
||||
// infer command
|
||||
if let Some((cmd_path, cmd_config)) =
|
||||
assistant_commands::fetch_command(&test, &COMMANDS)
|
||||
{
|
||||
println!("Recognized (filtered): {}", test);
|
||||
println!("Command found: {:?}", cmd_path);
|
||||
println!("Executing ...");
|
||||
|
||||
let cmd_result = assistant_commands::execute_command(
|
||||
&cmd_path,
|
||||
&cmd_config,
|
||||
&app_handle,
|
||||
);
|
||||
|
||||
match cmd_result {
|
||||
Ok(_) => {
|
||||
println!("Command executed successfully!");
|
||||
start = SystemTime::now(); // listen for more commands
|
||||
continue;
|
||||
}
|
||||
Err(error_message) => {
|
||||
println!("Error executing command: {}", error_message);
|
||||
}
|
||||
}
|
||||
|
||||
app_handle
|
||||
.emit_all(events::EventTypes::AssistantWaiting.get(), ())
|
||||
.unwrap();
|
||||
break; // return to picovoice after command execution (no matter successfull or not)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match start.elapsed() {
|
||||
Ok(elapsed) if elapsed > config::CMS_WAIT_DELAY => {
|
||||
// return to picovoice after N seconds
|
||||
app_handle
|
||||
.emit_all(events::EventTypes::AssistantWaiting.get(), ())
|
||||
.unwrap();
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop listening
|
||||
println!("Stop listening ...");
|
||||
recorder.stop().expect("Failed to stop audio recording");
|
||||
LISTENING.store(false, Ordering::SeqCst);
|
||||
STOP_LISTENING.store(false, Ordering::SeqCst);
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
33
src-tauri/src/tauri_commands/recorder.rs
Normal file
33
src-tauri/src/tauri_commands/recorder.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use pv_recorder::RecorderBuilder;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn pv_get_audio_devices() -> Vec<String> {
|
||||
let audio_devices = RecorderBuilder::default().get_audio_devices();
|
||||
match audio_devices {
|
||||
Ok(audio_devices) => audio_devices,
|
||||
Err(err) => panic!("Failed to get audio devices: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn pv_get_audio_device_name(idx: i32) -> String {
|
||||
let audio_devices = RecorderBuilder::default().get_audio_devices();
|
||||
let mut first_device: String = String::new();
|
||||
match audio_devices {
|
||||
Ok(audio_devices) => {
|
||||
for (_idx, device) in audio_devices.iter().enumerate() {
|
||||
if idx as usize == _idx {
|
||||
return device.to_string();
|
||||
}
|
||||
|
||||
if _idx == 0 {
|
||||
first_device = device.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => panic!("Failed to get audio devices: {}", err),
|
||||
};
|
||||
|
||||
// return first device as default, if none were matched
|
||||
first_device
|
||||
}
|
||||
48
src-tauri/src/tauri_commands/sys.rs
Normal file
48
src-tauri/src/tauri_commands/sys.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
use peak_alloc::PeakAlloc;
|
||||
|
||||
#[global_allocator]
|
||||
static PEAK_ALLOC: PeakAlloc = PeakAlloc;
|
||||
|
||||
extern crate systemstat;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use systemstat::{saturating_sub_bytes, Platform, System};
|
||||
|
||||
lazy_static! {
|
||||
static ref SYS: System = System::new();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_current_ram_usage() -> String {
|
||||
let result = String::from(format!("{}", PEAK_ALLOC.current_usage_as_mb()));
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_peak_ram_usage() -> String {
|
||||
let result = String::from(format!("{}", PEAK_ALLOC.peak_usage_as_gb()));
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_cpu_temp() -> String {
|
||||
if let Ok(cpu_temp) = SYS.cpu_temp() {
|
||||
String::from(format!("{}", cpu_temp))
|
||||
} else {
|
||||
String::from("error")
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/valpackett/systemstat/blob/trunk/examples/info.rs
|
||||
#[tauri::command(async)]
|
||||
pub async fn get_cpu_usage() -> String {
|
||||
if let Ok(cpu) = SYS.cpu_load_aggregate() {
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
let cpu = cpu.done().unwrap();
|
||||
String::from(format!("{}", cpu.user * 100.0))
|
||||
} else {
|
||||
String::from("error")
|
||||
}
|
||||
}
|
||||
29
src-tauri/src/tauri_commands/voice.rs
Normal file
29
src-tauri/src/tauri_commands/voice.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use rodio::{Decoder, OutputStream, Sink};
|
||||
|
||||
#[tauri::command(async)]
|
||||
pub fn play_sound(filename: &str, sleep: bool) {
|
||||
// Get a output stream handle to the default physical sound device
|
||||
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||
let sink = Sink::try_new(&stream_handle).unwrap();
|
||||
|
||||
// Load a sound from a file, using a path relative to Cargo.toml
|
||||
// let filepath = format!("{PUBLIC_PATH}/sound/{filename}.wav");
|
||||
let filepath = filename;
|
||||
let file = BufReader::new(File::open(&filepath).unwrap());
|
||||
|
||||
// Decode that sound file into a source
|
||||
let source = Decoder::new(file).unwrap();
|
||||
|
||||
// Play the sound directly on the device
|
||||
println!("Playing {} ...", filepath);
|
||||
// stream_handle.play_raw(source.convert_samples());
|
||||
sink.append(source);
|
||||
|
||||
if sleep {
|
||||
// The sound plays in a separate thread. This call will block the current thread until the sink
|
||||
// has finished playing all its queued sounds.
|
||||
sink.sleep_until_end();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue