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
65 changes: 42 additions & 23 deletions alas/alas/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,33 @@ async fn main() {
let state = Arc::new(RwLock::new(AlasState::new()));
let (event_bus, _) = broadcast::channel::<AlasMessage>(256);

// Initialize redundancy manager and WireGuard interface
let redundancy_manager = redundancy::RedundancyManager::new();
if let Err(e) = redundancy_manager.initialize(&state).await {
eprintln!("Failed to initialize redundancy manager 2: {}", e);
}

// Initialize WireGuard interface on startup
if let Err(e) = redundancy_manager.start_wireguard_interface().await {
eprintln!("Failed to initialize WireGuard interface: {}", e);
}
// Conditionally initialize redundancy manager and WireGuard interface
let _redundancy_manager = if state.read().await.config.redundancy.is_some() {
let manager = redundancy::RedundancyManager::new();
if let Err(e) = manager.initialize(&state).await {
eprintln!("Failed to initialize redundancy manager: {}", e);
}
if let Err(e) = manager.start_wireguard_interface().await {
eprintln!("Failed to start WireGuard interface: {}", e);
}
Some(manager)
} else {
println!("ℹ️ Redundancy not configured");
None
};

let lcd_thread = lcd_display::start(event_bus.clone(), &state).await;

let cell_observer = Arc::new(CellObserver::new(event_bus.clone(), &state));
let cell_changes = cell_observer.listen().await;
// Conditionally start cellular observer
let cell_changes = if state.read().await.config.cellular.is_some() {
let cell_observer = Arc::new(CellObserver::new(event_bus.clone(), &state));
Some(cell_observer.listen().await)
} else {
println!("ℹ️ Cellular not configured");
None
};

// WiFi observer always runs (needed for config hotspot mode)
let wifi_observer = Arc::new(WiFiObserver::new(event_bus.clone(), &state));
let wifi_changes = wifi_observer.listen();

Expand All @@ -54,25 +65,33 @@ async fn main() {

// Await all of our "threads" here to clean up...
println!("Waiting for Wi-Fi to unwrap...");
wifi_changes.await.expect("Oh well 1");
wifi_changes.await.expect("WiFi thread failed");

println!("Waiting for cellular to unwrap...");
let _ = cell_changes.await;
if let Some(cell_changes) = cell_changes {
println!("Waiting for cellular to unwrap...");
let _ = cell_changes.await;
}

// LCD should always be last to exit so that we can display all messages
println!("Waiting for web server to await...");
web_server.await.expect("Oh well 3");
println!("Waiting for lcd to unwrap...");
lcd_thread.await.expect("Oh well 4");
println!("Waiting for audio to unwrap...");
let (config_thread, icecast, recording) = audio.await.expect("Oh well 6");
let audio_threads = audio.await.expect("Audio thread failed");
println!("Waiting for config thread to unwrap...");
let result_one = config_thread.await.unwrap();
let result_one = audio_threads.config_thread.await.unwrap();
println!("Results: {:?}", result_one);
println!("Waiting for Icecast to unwrap...");
let result_two = icecast.await.unwrap();
println!("Icecast unwrapped: {:?}", result_two);
println!("Waiting for recording to unwrap...");
let recording_result = recording.await.unwrap();
println!("Recording result: {:?}", recording_result);

if let Some(icecast) = audio_threads.icecast {
println!("Waiting for Icecast to unwrap...");
let result_two = icecast.await.unwrap();
println!("Icecast unwrapped: {:?}", result_two);
}

if let Some(recording) = audio_threads.recording {
println!("Waiting for recording to unwrap...");
let recording_result = recording.await.unwrap();
println!("Recording result: {:?}", recording_result);
}
}
131 changes: 110 additions & 21 deletions alas/alas/src/web_server/config.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use rocket::{get, post, routes, Route, State};
use rocket::{delete, get, post, routes, Route, State};
use rocket::http::Status;
use rocket::serde::json::Json;
use rocket::serde::{Deserialize, Serialize};
use tokio::sync::broadcast::Sender;
use alas_lib::cellular::connect_to_cellular;
use alas_lib::config::{load_config_async, AlasAudioConfig, AlasDropboxConfig, AlasIcecastConfig, AlasWebhookConfig};
use alas_lib::config::{load_config_async, AlasAudioConfig, AlasDropboxConfig, AlasIcecastConfig, AlasRecordingConfig, AlasWebhookConfig, AlasCellularConfig, AlasWiFiConfig};
use alas_lib::state::{AlasMessage, SafeState};
use alas_lib::wifi::WiFiNetwork;
use alas_lib::redundancy::{RedundancyManager, RedundancyWebRequest, RedundancyWebResponse};
Expand Down Expand Up @@ -52,11 +52,14 @@ struct CellularConfig {
}

#[get("/cellular")]
async fn get_cellular_config() -> Json<CellularConfig> {
async fn get_cellular_config() -> Result<Json<CellularConfig>, Status> {
let config = load_config_async().await;
Json(CellularConfig {
apn: config.cellular.apn,
})
match config.cellular {
Some(cellular) => Ok(Json(CellularConfig {
apn: cellular.apn,
})),
None => Err(Status::NotFound)
}
}

#[post("/cellular", data = "<request>")]
Expand All @@ -67,20 +70,22 @@ async fn set_cellular_config(
) -> Json<CellularConfig> {
let mut state = state.write().await;
let mut new_config = (*state).config.clone();
new_config.cellular.apn = request.apn.clone();
new_config.cellular = Some(AlasCellularConfig {
apn: request.apn.clone(),
});
state.update_config(new_config);

connect_to_cellular(request.apn.clone()).await;

Json(CellularConfig {
apn: state.config.cellular.apn.clone(),
apn: state.config.cellular.as_ref().unwrap().apn.clone(),
})
}

#[get("/icecast")]
async fn get_icecast_config() -> Json<AlasIcecastConfig> {
async fn get_icecast_config() -> Result<Json<AlasIcecastConfig>, Status> {
let config = load_config_async().await;
Json(config.icecast)
config.icecast.map(Json).ok_or(Status::NotFound)
}

#[post("/icecast", format = "json", data = "<request>")]
Expand All @@ -91,10 +96,68 @@ async fn set_icecast_config(
) -> Json<AlasIcecastConfig> {
let mut state = state.write().await;
let mut new_config = (*state).config.clone();
new_config.icecast = request.into_inner();
new_config.icecast = Some(request.into_inner());
state.update_config(new_config);
let _ = bus.send(AlasMessage::StreamingConfigUpdated);
Json(state.config.icecast.clone())
Json(state.config.icecast.clone().unwrap())
}

#[delete("/icecast")]
async fn delete_icecast_config(
bus: &State<Sender<AlasMessage>>,
state: &State<SafeState>
) -> Status {
let mut state = state.write().await;
let mut new_config = (*state).config.clone();
new_config.icecast = None;
state.update_config(new_config);
let _ = bus.send(AlasMessage::StreamingConfigUpdated);
Status::NoContent
}

#[get("/recording")]
async fn get_recording_config() -> Result<Json<AlasRecordingConfig>, Status> {
let config = load_config_async().await;
config.recording.map(Json).ok_or(Status::NotFound)
}

#[post("/recording", format = "json", data = "<request>")]
async fn set_recording_config(
request: Json<AlasRecordingConfig>,
state: &State<SafeState>
) -> Json<AlasRecordingConfig> {
let mut state = state.write().await;
let mut new_config = (*state).config.clone();
new_config.recording = Some(request.into_inner());
state.update_config(new_config);
Json(state.config.recording.clone().unwrap())
}

#[delete("/recording")]
async fn delete_recording_config(state: &State<SafeState>) -> Status {
let mut state = state.write().await;
let mut new_config = (*state).config.clone();
new_config.recording = None;
state.update_config(new_config);
Status::NoContent
}

#[delete("/cellular")]
async fn delete_cellular_config(state: &State<SafeState>) -> Status {
let mut state = state.write().await;
let mut new_config = (*state).config.clone();
new_config.cellular = None;
state.update_config(new_config);
Status::NoContent
}

#[delete("/wifi")]
async fn delete_wifi_config(state: &State<SafeState>) -> Status {
let mut state = state.write().await;
let mut new_config = (*state).config.clone();
new_config.wifi = None;
state.update_config(new_config);
Status::NoContent
}

#[derive(Serialize)]
Expand Down Expand Up @@ -124,7 +187,16 @@ async fn connect_to_wifi(data: Json<WiFiConnectRequest>) -> Status {
}

#[get("/redundancy")]
async fn get_redundancy_config(redundancy_manager: &State<Arc<Mutex<RedundancyManager>>>) -> Result<Json<RedundancyWebResponse>, Status> {
async fn get_redundancy_config(
redundancy_manager: &State<Arc<Mutex<RedundancyManager>>>,
state: &State<SafeState>
) -> Result<Json<RedundancyWebResponse>, Status> {
// Check if redundancy is configured in state first
let config_exists = state.read().await.config.redundancy.is_some();
if !config_exists {
return Err(Status::NotFound);
}

let manager = redundancy_manager.lock().await;
match manager.get_web_config().await {
Ok(config) => Ok(Json(config)),
Expand Down Expand Up @@ -156,6 +228,15 @@ async fn set_redundancy_config(
}
}

#[delete("/redundancy")]
async fn delete_redundancy_config(state: &State<SafeState>) -> Status {
let mut state = state.write().await;
let mut new_config = (*state).config.clone();
new_config.redundancy = None;
state.update_config(new_config);
Status::NoContent
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DropboxConfig {
pub code: String,
Expand Down Expand Up @@ -288,12 +369,19 @@ pub fn routes() -> Vec<Route> {
connect_to_wifi,
get_cellular_config,
set_cellular_config,
delete_cellular_config,
get_icecast_config,
set_icecast_config,
delete_icecast_config,
get_recording_config,
set_recording_config,
delete_recording_config,
delete_wifi_config,
get_audio_config,
set_audio_config,
get_redundancy_config,
set_redundancy_config,
delete_redundancy_config,
post_dropbox_link,
get_dropbox_link,
get_dropbox_status,
Expand Down Expand Up @@ -328,19 +416,20 @@ mod tests {
silence_duration_before_deactivation: 15,
silence_threshold: -55.0,
},
icecast: AlasIcecastConfig {
icecast: Some(AlasIcecastConfig {
hostname: "localhost".to_string(),
port: 8000,
mount: "/test.mp3".to_string(),
password: "password".to_string(),
},
cellular: AlasCellularConfig {
}),
cellular: Some(AlasCellularConfig {
apn: "test".to_string(),
},
wifi: AlasWiFiConfig {
}),
wifi: Some(AlasWiFiConfig {
name: "TestWiFi".to_string(),
password: "password".to_string(),
},
}),
recording: None,
auth: None,
dropbox: None,
redundancy: None,
Expand Down
3 changes: 2 additions & 1 deletion alas/alas/src/web_server/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ async fn volume_meter(broadcast: &State<Sender<AlasMessage>>, mut end: Shutdown)
let now = Instant::now();
if now.duration_since(last_message_sent) >= Duration::from_millis(THROTTLE_MS) {
last_message_sent = now;
yield Event::data(left.to_string());
let average = (left + right) / 2.0;
yield Event::data(average.to_string());
}
},
AlasMessage::Exit => {
Expand Down
Loading
Loading