This commit is contained in:
Mateusz Gruszczyński
2026-09-16 09:04:43 +02:00
parent 3d69e74319
commit 0437ef7b6f
28 changed files with 1254 additions and 221 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ async fn export_configuration(
fn validate_configuration_header(export: &ConfigurationExport) -> Result<(), AppError> {
if export.format_version != 3 {
return Err(AppError::BadRequest("unsupported configuration export version; version 3 is required by GREE Controller 0.14.4".into()));
return Err(AppError::BadRequest("unsupported configuration export version; version 3 is required by GREE Controller 0.14.8".into()));
}
if export.settings.control_strategy != "setpoint" {
return Err(AppError::BadRequest(
+44 -5
View File
@@ -1,3 +1,33 @@
fn map_home_assistant_integration_error(error: anyhow::Error) -> AppError {
let message = error.to_string();
if message.contains("Home Assistant URL is not configured")
|| message.contains("Home Assistant token is not configured")
|| message.contains("invalid Home Assistant URL")
|| message.contains("Home Assistant URL must use http or https")
|| message.contains("cannot build Home Assistant API URL")
|| message.contains("cannot build Home Assistant service URL")
{
AppError::BadRequest(message)
} else {
AppError::Dependency(message)
}
}
fn map_notification_test_error(message: String) -> AppError {
if matches!(
message.as_str(),
"Pushover credentials are incomplete"
| "invalid webhook URL"
| "webhook URL must use HTTPS"
| "webhook host is not supported"
| "unsupported notification provider"
) {
AppError::BadRequest(message)
} else {
AppError::Dependency(message)
}
}
async fn home_assistant_snapshot(
State(state): State<AppState>,
) -> Result<Json<Value>, AppError> {
@@ -17,7 +47,7 @@ async fn test_home_assistant(
let settings = state.settings.read().await.clone();
home_assistant::test_connection(&state.http, &settings.home_assistant)
.await
.map_err(|e| AppError::Device(e.to_string()))?;
.map_err(map_home_assistant_integration_error)?;
Ok(Json(json!({"ok": true})))
}
@@ -41,7 +71,7 @@ async fn inspect_home_assistant_entity(
Some(entity_id.as_str()),
)
.await
.map_err(|e| AppError::Device(e.to_string()))?;
.map_err(map_home_assistant_integration_error)?;
let raw_state = payload
.get("state")
.and_then(Value::as_str)
@@ -78,7 +108,7 @@ async fn test_notifications(
}
notifications::test(&state, input)
.await
.map_err(AppError::Device)?;
.map_err(map_notification_test_error)?;
Ok(Json(json!({"ok": true})))
}
@@ -86,9 +116,15 @@ async fn list_home_assistant_energy_sensors(
State(state): State<AppState>,
) -> Result<Json<Value>, AppError> {
let settings = state.settings.read().await.home_assistant.clone();
if settings.url.trim().is_empty() || settings.token.trim().is_empty() {
return Ok(Json(json!({
"configured": false,
"sensors": [],
})));
}
let entities = home_assistant::list_entities(&state.http, &settings)
.await
.map_err(|error| AppError::Device(error.to_string()))?;
.map_err(|error| AppError::Dependency(error.to_string()))?;
let sensors = entities
.into_iter()
.filter_map(|entity| {
@@ -112,5 +148,8 @@ async fn list_home_assistant_energy_sensors(
}))
})
.collect::<Vec<_>>();
Ok(Json(json!({"sensors": sensors})))
Ok(Json(json!({
"configured": true,
"sensors": sensors,
})))
}
+2 -2
View File
@@ -84,10 +84,10 @@ async fn sample_home_assistant_energy_target(state: &AppState, target_id: &str,
let settings = state.settings.read().await.home_assistant.clone();
let payload = home_assistant::read_entity(&state.http, &settings, Some(entity_id))
.await
.map_err(|err| AppError::Device(err.to_string()))?;
.map_err(|err| AppError::Dependency(err.to_string()))?;
let state_value = payload.get("state").and_then(Value::as_str).unwrap_or_default();
if matches!(state_value, "" | "unknown" | "unavailable") { return Ok(()); }
let raw_value: f64 = state_value.parse().map_err(|_| AppError::Device("Home Assistant energy state is not numeric".into()))?;
let raw_value: f64 = state_value.parse().map_err(|_| AppError::Dependency("Home Assistant energy state is not numeric".into()))?;
let attrs = payload.get("attributes").and_then(Value::as_object).cloned().unwrap_or_default();
let device_class = attrs.get("device_class").and_then(Value::as_str).unwrap_or_default();
let state_class = attrs.get("state_class").and_then(Value::as_str).unwrap_or_default();