Files
gree-controller/src/api/integrations.rs
T
2026-09-14 16:32:28 +02:00

131 lines
4.8 KiB
Rust

async fn home_assistant_snapshot(
State(state): State<AppState>,
) -> Result<Json<Value>, AppError> {
let control_plan = engine::get_control_plan_snapshot(&state).await?;
let groups = list_home_assistant_groups(State(state.clone())).await?.0;
Ok(Json(json!({
"devices": state.db.list_devices()?,
"control_plan": control_plan.plan.as_ref(),
"control_plan_revision": control_plan.revision,
"groups": groups,
})))
}
#[derive(Debug, Deserialize)]
struct HaTestRequest {
entity_id: Option<String>,
}
async fn test_home_assistant(
State(state): State<AppState>,
Json(input): Json<HaTestRequest>,
) -> Result<Json<Value>, AppError> {
let settings = state.settings.read().await.clone();
let resolved_entity_id =
home_assistant::resolve_entity_id(&settings.home_assistant, input.entity_id.as_deref());
let temperature = home_assistant::read_temperature(
&state.http,
&settings.home_assistant,
resolved_entity_id.as_deref(),
Some(settings.home_assistant.sensor_stale_after_seconds),
)
.await
.map_err(|e| AppError::Device(e.to_string()))?;
Ok(Json(
json!({"ok": true, "temperature_c": temperature, "entity_id": resolved_entity_id}),
))
}
#[derive(Debug, Deserialize)]
struct HaEntityRequest {
entity_id: String,
}
async fn inspect_home_assistant_entity(
State(state): State<AppState>,
Json(input): Json<HaEntityRequest>,
) -> Result<Json<Value>, AppError> {
let settings = state.settings.read().await.clone();
let entity_id =
home_assistant::resolve_entity_id(&settings.home_assistant, Some(input.entity_id.as_str()))
.filter(|value| !value.trim().is_empty())
.ok_or_else(|| AppError::BadRequest("Home Assistant entity_id is required".into()))?;
let payload = home_assistant::read_entity(
&state.http,
&settings.home_assistant,
Some(entity_id.as_str()),
)
.await
.map_err(|e| AppError::Device(e.to_string()))?;
let raw_state = payload
.get("state")
.and_then(Value::as_str)
.unwrap_or_default()
.to_string();
let available = !matches!(raw_state.as_str(), "unknown" | "unavailable" | "");
Ok(Json(json!({
"ok": true,
"entity_id": entity_id,
"state": raw_state,
"available": available,
"attributes": payload.get("attributes").cloned().unwrap_or_else(|| json!({})),
"last_changed": payload.get("last_changed").cloned().unwrap_or(Value::Null),
"last_updated": payload.get("last_updated").cloned().unwrap_or(Value::Null),
})))
}
async fn test_notifications(
State(state): State<AppState>,
Json(mut input): Json<NotificationSettings>,
) -> Result<Json<Value>, AppError> {
let old = state.settings.read().await.notifications.clone();
if input.pushover_app_token.trim().is_empty() {
input.pushover_app_token = old.pushover_app_token;
}
if input.pushover_user_key.trim().is_empty() {
input.pushover_user_key = old.pushover_user_key;
}
if input.slack_webhook_url.trim().is_empty() {
input.slack_webhook_url = old.slack_webhook_url;
}
if input.discord_webhook_url.trim().is_empty() {
input.discord_webhook_url = old.discord_webhook_url;
}
notifications::test(&state, input)
.await
.map_err(AppError::Device)?;
Ok(Json(json!({"ok": true})))
}
async fn list_home_assistant_energy_sensors(
State(state): State<AppState>,
) -> Result<Json<Value>, AppError> {
let settings = state.settings.read().await.home_assistant.clone();
let entities = home_assistant::list_entities(&state.http, &settings)
.await
.map_err(|error| AppError::Device(error.to_string()))?;
let sensors = entities
.into_iter()
.filter_map(|entity| {
let attributes = entity.get("attributes")?.as_object()?;
let device_class = attributes.get("device_class")?.as_str()?;
let state_class = attributes.get("state_class")?.as_str()?;
let unit = attributes.get("unit_of_measurement")?.as_str()?;
if device_class != "energy"
|| !matches!(state_class, "total" | "total_increasing")
|| !matches!(unit.to_ascii_lowercase().as_str(), "wh" | "kwh")
{
return None;
}
Some(json!({
"entity_id": entity.get("entity_id").and_then(Value::as_str).unwrap_or_default(),
"name": attributes.get("friendly_name").and_then(Value::as_str).unwrap_or_default(),
"state": entity.get("state").and_then(Value::as_str).unwrap_or_default(),
"unit": unit,
"device_class": device_class,
"state_class": state_class,
}))
})
.collect::<Vec<_>>();
Ok(Json(json!({"sensors": sensors})))
}