use axum::{
Json,
extract::{State, rejection::JsonRejection},
http::StatusCode,
};
use std::sync::atomic::Ordering;
use crate::{
models::{
ApiResponse, CollectionModeConfig, CsiConfig, DeviceConfig, LogModeConfig, OutputMode,
OutputModeConfig, TrafficConfig, WifiConfig,
},
state::AppState,
};
pub async fn get_config(State(state): State<AppState>) -> Json<DeviceConfig> {
let config = state.config.lock().await;
Json(config.clone())
}
pub async fn reset_config(State(state): State<AppState>) -> (StatusCode, Json<ApiResponse>) {
let result = send_cmd(&state, "reset-config".to_string()).await;
if result.0 == StatusCode::OK {
*state.config.lock().await = DeviceConfig::default();
}
result
}
pub async fn set_wifi(
State(state): State<AppState>,
Json(body): Json<WifiConfig>,
) -> (StatusCode, Json<ApiResponse>) {
let cmd = body.to_cli_command();
let result = send_cmd(&state, cmd).await;
if result.0 == StatusCode::OK {
let mut cfg = state.config.lock().await;
cfg.wifi_mode = Some(body.mode);
cfg.channel = body.channel;
cfg.sta_ssid = body.sta_ssid;
}
result
}
pub async fn set_traffic(
State(state): State<AppState>,
Json(body): Json<TrafficConfig>,
) -> (StatusCode, Json<ApiResponse>) {
let cmd = body.to_cli_command();
let result = send_cmd(&state, cmd).await;
if result.0 == StatusCode::OK {
state.config.lock().await.traffic_hz = Some(body.frequency_hz);
}
result
}
pub async fn set_csi(
State(state): State<AppState>,
Json(body): Json<CsiConfig>,
) -> (StatusCode, Json<ApiResponse>) {
send_cmd(&state, body.to_cli_command()).await
}
pub async fn set_collection_mode(
State(state): State<AppState>,
Json(body): Json<CollectionModeConfig>,
) -> (StatusCode, Json<ApiResponse>) {
let cmd = body.to_cli_command();
let result = send_cmd(&state, cmd).await;
if result.0 == StatusCode::OK {
state.config.lock().await.collection_mode = Some(body.mode);
}
result
}
pub async fn set_log_mode(
State(state): State<AppState>,
body: Result<Json<LogModeConfig>, JsonRejection>,
) -> (StatusCode, Json<ApiResponse>) {
let body = match body {
Ok(Json(body)) => body,
Err(_) => {
return (
StatusCode::BAD_REQUEST,
Json(ApiResponse {
success: false,
message: "Invalid log mode. Use one of: text, array-list, serialized"
.to_string(),
}),
);
}
};
let cmd = body.to_cli_command();
let result = send_cmd(&state, cmd).await;
if result.0 == StatusCode::OK {
let mut cfg = state.config.lock().await;
cfg.log_mode = Some(body.mode.as_cli_value().to_string());
let _ = state.log_mode_tx.send(body.mode);
}
result
}
pub async fn set_output_mode(
State(state): State<AppState>,
Json(body): Json<OutputModeConfig>,
) -> (StatusCode, Json<ApiResponse>) {
let mode = match body.mode.to_ascii_lowercase().as_str() {
"stream" => OutputMode::Stream,
"dump" => OutputMode::Dump,
"both" => OutputMode::Both,
other => {
return (
StatusCode::BAD_REQUEST,
Json(ApiResponse {
success: false,
message: format!(
"Unknown output mode '{other}'; expected stream, dump, or both"
),
}),
);
}
};
let _ = state.output_mode_tx.send(mode);
(
StatusCode::OK,
Json(ApiResponse {
success: true,
message: format!("Output mode set to {}", body.mode),
}),
)
}
async fn send_cmd(state: &AppState, cmd: String) -> (StatusCode, Json<ApiResponse>) {
if !state.serial_connected.load(Ordering::SeqCst) {
return (
StatusCode::SERVICE_UNAVAILABLE,
Json(ApiResponse {
success: false,
message: "ESP32 disconnected; serial command unavailable".to_string(),
}),
);
}
match state.cmd_tx.send(cmd.clone()).await {
Ok(_) => (
StatusCode::OK,
Json(ApiResponse {
success: true,
message: format!("Sent: {cmd}"),
}),
),
Err(e) => {
let (status, message) = if !state.serial_connected.load(Ordering::SeqCst) {
(
StatusCode::SERVICE_UNAVAILABLE,
"ESP32 disconnected; serial command unavailable".to_string(),
)
} else {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to send command: {e}"),
)
};
(
status,
Json(ApiResponse {
success: false,
message,
}),
)
}
}
}