use crate::AppState;
use axum::extract::{Path, State};
use axum::http::{header, StatusCode};
use axum::response::IntoResponse;
use axum::Json;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize)]
pub struct ControlRequest {
pub action: String,
pub value: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct ControlResponse {
pub status: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub output: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
pub async fn control_camera(
State(state): State<AppState>,
Path(id): Path<String>,
Json(request): Json<ControlRequest>,
) -> Result<Json<ControlResponse>, StatusCode> {
let camera = state.get(&id).ok_or(StatusCode::NOT_FOUND)?;
let control = camera.control();
let result = match request.action.as_str() {
"day_night" => control.set_day_night(&request.value).await,
"ir_mode" => control.set_ir_mode(&request.value).await,
"ptz" => control.ptz_move(&request.value).await,
_ => return Err(StatusCode::BAD_REQUEST),
};
Ok(Json(match result {
Ok(output) => ControlResponse { status: "ok", output: Some(output), message: None },
Err(e) => {
tracing::warn!(camera = %id, action = %request.action, error = %e, "control failed");
ControlResponse { status: "error", output: None, message: Some(e.to_string()) }
}
}))
}
pub async fn cgi_snapshot(
State(state): State<AppState>,
Path(id): Path<String>,
) -> axum::response::Response {
let Some(camera) = state.get(&id) else {
return (StatusCode::NOT_FOUND, format!("no camera '{id}'")).into_response();
};
match camera.control().snapshot().await {
Ok(jpeg) => ([(header::CONTENT_TYPE, "image/jpeg")], jpeg).into_response(),
Err(e) => {
tracing::warn!(camera = %id, error = %e, "cgi snapshot failed");
(StatusCode::BAD_GATEWAY, e.to_string()).into_response()
}
}
}