This commit is contained in:
Mateusz Gruszczyński
2026-09-17 08:52:02 +02:00
parent ff4f2b60e7
commit b7846c3b9f
87 changed files with 3783 additions and 1418 deletions
+102 -65
View File
@@ -19,7 +19,9 @@ fn cloud_error_kind(error: &anyhow::Error) -> &'static str {
}
}
async fn cloud_api_from_settings(state: &AppState) -> Result<crate::protocol::gree_cloud::GreeCloudApi, AppError> {
async fn cloud_api_from_settings(
state: &AppState,
) -> Result<crate::protocol::gree_cloud::GreeCloudApi, AppError> {
let settings = state.settings.read().await.gree_cloud.clone();
crate::protocol::gree_cloud::GreeCloudApi::for_region(
state.http.clone(),
@@ -33,7 +35,10 @@ async fn cloud_api_from_settings(state: &AppState) -> Result<crate::protocol::gr
async fn test_gree_cloud(State(state): State<AppState>) -> Result<Json<Value>, AppError> {
let mut api = cloud_api_from_settings(&state).await?;
let started = Instant::now();
cloud_debug_event(&state, json!({"operation":"test_connection","phase":"sent"}));
cloud_debug_event(
&state,
json!({"operation":"test_connection","phase":"sent"}),
);
let result = async {
api.login().await?;
let devices = api.get_all_devices().await?;
@@ -44,12 +49,15 @@ async fn test_gree_cloud(State(state): State<AppState>) -> Result<Json<Value>, A
match result {
Ok(device_count) => {
let now = Utc::now();
cloud_debug_event(&state, json!({
"operation":"test_connection",
"phase":"response",
"duration_ms": started.elapsed().as_millis() as u64,
"device_count": device_count,
}));
cloud_debug_event(
&state,
json!({
"operation":"test_connection",
"phase":"response",
"duration_ms": started.elapsed().as_millis() as u64,
"device_count": device_count,
}),
);
{
let _configuration_guard = state.lock_configuration_operation().await;
let mut settings = state.settings.write().await;
@@ -73,12 +81,15 @@ async fn test_gree_cloud(State(state): State<AppState>) -> Result<Json<Value>, A
}
Err(error) => {
let kind = cloud_error_kind(&error);
cloud_debug_event(&state, json!({
"operation":"test_connection",
"phase":"error",
"duration_ms": started.elapsed().as_millis() as u64,
"kind": kind,
}));
cloud_debug_event(
&state,
json!({
"operation":"test_connection",
"phase":"error",
"duration_ms": started.elapsed().as_millis() as u64,
"kind": kind,
}),
);
tracing::warn!(kind, "GREE Cloud connection test failed");
state.log(
"warn",
@@ -107,38 +118,42 @@ async fn discover_gree_cloud_devices(
let mut api = cloud_api_from_settings(&state).await?;
let started = Instant::now();
cloud_debug_event(&state, json!({"operation":"discovery","phase":"sent"}));
api.login()
.await
.map_err(|err| {
cloud_debug_event(&state, json!({
api.login().await.map_err(|err| {
cloud_debug_event(
&state,
json!({
"operation":"discovery",
"phase":"error",
"stage":"login",
"duration_ms": started.elapsed().as_millis() as u64,
"kind": cloud_error_kind(&err),
}));
AppError::Dependency(format!("GREE Cloud login failed: {err}"))
})?;
let devices = api
.get_all_devices()
.await
.map_err(|err| {
cloud_debug_event(&state, json!({
}),
);
AppError::Dependency(format!("GREE Cloud login failed: {err}"))
})?;
let devices = api.get_all_devices().await.map_err(|err| {
cloud_debug_event(
&state,
json!({
"operation":"discovery",
"phase":"error",
"stage":"devices",
"duration_ms": started.elapsed().as_millis() as u64,
"kind": cloud_error_kind(&err),
}));
AppError::Dependency(format!("GREE Cloud discovery failed: {err}"))
})?;
}),
);
AppError::Dependency(format!("GREE Cloud discovery failed: {err}"))
})?;
let rest_duration_ms = started.elapsed().as_millis().min(u64::MAX as u128) as u64;
cloud_debug_event(&state, json!({
"operation":"discovery",
"phase":"response",
"duration_ms": rest_duration_ms,
"device_count": devices.len(),
}));
cloud_debug_event(
&state,
json!({
"operation":"discovery",
"phase":"response",
"duration_ms": rest_duration_ms,
"device_count": devices.len(),
}),
);
{
let _configuration_guard = state.lock_configuration_operation().await;
let mut settings = state.settings.write().await;
@@ -153,7 +168,10 @@ async fn discover_gree_cloud_devices(
let id = device.mac.replace([':', '-'], "").to_ascii_uppercase();
let already_added = existing.iter().any(|saved| {
saved.connection_type == ConnectionType::GreeCloud
&& saved.cloud_device_id.as_deref().is_some_and(|value| value.eq_ignore_ascii_case(&id))
&& saved
.cloud_device_id
.as_deref()
.is_some_and(|value| value.eq_ignore_ascii_case(&id))
});
crate::protocol::gree_cloud::CloudDeviceView {
parent_mac: crate::protocol::gree_cloud::parent_mac(&id),
@@ -196,50 +214,63 @@ async fn add_gree_cloud_device(
// Re-discover server-side so the frontend never needs to submit/store the device cipher key.
let mut api = cloud_api_from_settings(&state).await?;
let started = Instant::now();
cloud_debug_event(&state, json!({
"operation":"add_device_lookup",
"phase":"sent",
"cloud_device_id": cloud_id.clone(),
}));
api.login()
.await
.map_err(|err| {
cloud_debug_event(&state, json!({
cloud_debug_event(
&state,
json!({
"operation":"add_device_lookup",
"phase":"sent",
"cloud_device_id": cloud_id.clone(),
}),
);
api.login().await.map_err(|err| {
cloud_debug_event(
&state,
json!({
"operation":"add_device_lookup",
"phase":"error",
"stage":"login",
"duration_ms": started.elapsed().as_millis() as u64,
"kind": cloud_error_kind(&err),
}));
AppError::Dependency(format!("GREE Cloud login failed: {err}"))
})?;
}),
);
AppError::Dependency(format!("GREE Cloud login failed: {err}"))
})?;
let cloud_device = api
.get_all_devices()
.await
.map_err(|err| {
cloud_debug_event(&state, json!({
"operation":"add_device_lookup",
"phase":"error",
"stage":"devices",
"duration_ms": started.elapsed().as_millis() as u64,
"kind": cloud_error_kind(&err),
}));
cloud_debug_event(
&state,
json!({
"operation":"add_device_lookup",
"phase":"error",
"stage":"devices",
"duration_ms": started.elapsed().as_millis() as u64,
"kind": cloud_error_kind(&err),
}),
);
AppError::Dependency(format!("GREE Cloud discovery failed: {err}"))
})?
.into_iter()
.find(|device| device.mac.eq_ignore_ascii_case(&cloud_id))
.ok_or_else(|| AppError::NotFound(format!("GREE Cloud device {cloud_id}")))?;
cloud_debug_event(&state, json!({
"operation":"add_device_lookup",
"phase":"response",
"duration_ms": started.elapsed().as_millis() as u64,
"cloud_device_id": cloud_id.clone(),
}));
cloud_debug_event(
&state,
json!({
"operation":"add_device_lookup",
"phase":"response",
"duration_ms": started.elapsed().as_millis() as u64,
"cloud_device_id": cloud_id.clone(),
}),
);
let _configuration_guard = state.lock_configuration_operation().await;
let now = Utc::now();
let account_id = state.settings.read().await.gree_cloud.account_id.clone();
let normalized_cloud_mac = cloud_device.mac.replace([':', '-'], "").to_ascii_uppercase();
let normalized_cloud_mac = cloud_device
.mac
.replace([':', '-'], "")
.to_ascii_uppercase();
let device = Device {
id: format!("gree-cloud-{}", normalized_cloud_mac.to_ascii_lowercase()),
mac: normalized_cloud_mac.clone(),
@@ -251,7 +282,9 @@ async fn add_gree_cloud_device(
connection_type: ConnectionType::GreeCloud,
connection_status: ConnectionStatus::CloudDisconnected,
cloud_device_id: Some(normalized_cloud_mac.clone()),
cloud_parent_mac: Some(crate::protocol::gree_cloud::parent_mac(&normalized_cloud_mac)),
cloud_parent_mac: Some(crate::protocol::gree_cloud::parent_mac(
&normalized_cloud_mac,
)),
cloud_account_id: Some(account_id),
ip: String::new(),
port: 0,
@@ -383,7 +416,9 @@ async fn reconnect_gree_cloud(State(state): State<AppState>) -> Result<Json<Valu
"GREE Cloud MQTT reconnected",
json!({"device_count": devices.iter().filter(|device| device.connection_type == ConnectionType::GreeCloud).count()}),
);
Ok(Json(json!({"ok": true, "mqtt_status": "connected", "last_successful_contact": now})))
Ok(Json(
json!({"ok": true, "mqtt_status": "connected", "last_successful_contact": now}),
))
}
async fn cloud_device_diagnostics(
@@ -395,7 +430,9 @@ async fn cloud_device_diagnostics(
.get_device(&id)?
.ok_or_else(|| AppError::NotFound(format!("device {id}")))?;
if device.connection_type != ConnectionType::GreeCloud {
return Err(AppError::BadRequest("Cloud diagnostics are available only for GREE Cloud devices".into()));
return Err(AppError::BadRequest(
"Cloud diagnostics are available only for GREE Cloud devices".into(),
));
}
let diagnostics = state.providers.cloud().diagnostics(&device.id).await;
Ok(Json(json!({