This commit is contained in:
Mateusz Gruszczyński
2026-09-07 10:01:52 +02:00
parent 11da46c4d6
commit 7772e1e339
18 changed files with 347 additions and 170 deletions
+3 -2
View File
@@ -4,12 +4,13 @@ use crate::{
home_assistant, influxdb,
models::{
ApiTokenInfo, ApplicationSettings, Automation, ClimateGroup, ConfigurationExport,
DebugSettings, Device, DeviceCommand, DevicePatch, DiscoveryRequest, GreeSettings,
DebugSettings, Device, DeviceCommand, DevicePatch, DiscoveryRequest, Flow, GreeSettings,
GroupControlPatch, HaReading, HistorySettings, HomeAssistantSettings,
HomeAssistantSettingsUpdate, HomeAssistantSettingsView, InfluxDbSettings,
InfluxDbSettingsUpdate, InfluxDbSettingsView, ManualDeviceRequest, NightModeSettings,
NotificationSettings, NotificationSettingsUpdate, NotificationSettingsView, Reading,
RuntimeSettings, Schedule, TemporaryQuickThermostat, TemporaryQuickThermostatRequest, Zone,
RuntimeSettings, Schedule, SettingsSnapshot, TemporaryQuickThermostat,
TemporaryQuickThermostatRequest, Zone,
ZoneControlPatch, ZoneReading,
},
notifications,
+13
View File
@@ -70,6 +70,19 @@ fn home_assistant_settings(settings: &RuntimeSettings) -> HomeAssistantSettingsV
}
}
fn settings_snapshot(settings: &RuntimeSettings) -> SettingsSnapshot {
SettingsSnapshot {
application: application_settings(settings),
gree: gree_settings(settings),
history: history_settings(settings),
influxdb: influxdb_settings(settings),
notifications: notification_settings(settings),
night: settings.night_mode.clone(),
home_assistant: home_assistant_settings(settings),
debug: settings.debug.clone(),
}
}
async fn get_application_settings(State(state): State<AppState>) -> Json<ApplicationSettings> {
Json(application_settings(&*state.settings.read().await))
}
+93 -52
View File
@@ -1,3 +1,42 @@
#[derive(Debug, serde::Serialize)]
struct HouseSnapshot {
mode: String,
}
#[derive(Debug, serde::Serialize)]
struct SystemInfoResponse {
version: &'static str,
uptime_seconds: u64,
auth_required: bool,
control_ready: bool,
database: String,
device_count: usize,
online_count: usize,
simulator_count: usize,
bind: String,
base_path: String,
gree_interface: String,
gree_received_frames: u64,
gree_received_frames_by_device: std::collections::HashMap<String, u64>,
}
#[derive(Debug, serde::Serialize)]
struct BootstrapResponse {
devices: Vec<Device>,
zones: Vec<Zone>,
groups: Vec<ClimateGroup>,
schedules: Vec<Schedule>,
automations: Vec<Automation>,
flows: Vec<Flow>,
access_tokens: Vec<ApiTokenInfo>,
settings: SettingsSnapshot,
house: HouseSnapshot,
outdoor_temperature: Option<f64>,
control_plan: crate::models::ControlPlan,
control_plan_revision: u64,
system: SystemInfoResponse,
}
async fn health(State(state): State<AppState>) -> Json<Value> {
Json(json!({
"status": "ok",
@@ -9,64 +48,66 @@ async fn health(State(state): State<AppState>) -> Json<Value> {
}))
}
async fn bootstrap(State(state): State<AppState>) -> Result<Json<Value>, AppError> {
async fn bootstrap(State(state): State<AppState>) -> Result<Json<BootstrapResponse>, AppError> {
Ok(Json(build_bootstrap(&state).await?))
}
async fn build_bootstrap(state: &AppState) -> Result<Value, AppError> {
let settings = state.settings.read().await.clone();
async fn build_bootstrap(state: &AppState) -> Result<BootstrapResponse, AppError> {
let (settings, house_mode) = {
let settings = state.settings.read().await;
(settings_snapshot(&settings), settings.house_mode.clone())
};
let devices = state.db.list_devices()?;
let device_count = devices.len();
let online_count = devices.iter().filter(|value| value.online).count();
let simulator_count = devices.iter().filter(|value| value.simulated).count();
let (received_frames_total, received_frames_by_device) = state.gree.received_frame_stats();
let system = build_system_info(state, &devices);
let control_plan = engine::get_control_plan_snapshot(state).await?;
Ok(json!({
"devices": devices,
"zones": state.db.list_zones()?,
"groups": state.db.list_groups()?,
"schedules": state.db.list_schedules()?,
"automations": state.db.list_automations()?,
"flows": state.db.list_flows()?,
"access_tokens": state.db.list_api_tokens()?,
"house": {"mode": settings.house_mode},
"outdoor_temperature": *state.outdoor_temperature.read().await,
"control_plan": control_plan.plan.as_ref(),
"control_plan_revision": control_plan.revision,
"system": {
"version": env!("CARGO_PKG_VERSION"),
"uptime_seconds": state.started.elapsed().as_secs(),
"auth_required": !state.config.app_token.trim().is_empty(),
"control_ready": state.initial_device_sync_complete.load(Ordering::Acquire),
"database": state.config.database.display().to_string(),
"device_count": device_count,
"online_count": online_count,
"simulator_count": simulator_count,
"bind": state.config.bind.to_string(),
"base_path": if state.config.base_path.is_empty() { "/" } else { state.config.base_path.as_str() },
"gree_interface": if state.config.gree_interface.trim().is_empty() { "auto" } else { state.config.gree_interface.trim() },
"gree_received_frames": received_frames_total,
"gree_received_frames_by_device": received_frames_by_device,
}
}))
Ok(BootstrapResponse {
devices,
zones: state.db.list_zones()?,
groups: state.db.list_groups()?,
schedules: state.db.list_schedules()?,
automations: state.db.list_automations()?,
flows: state.db.list_flows()?,
access_tokens: state.db.list_api_tokens()?,
settings,
house: HouseSnapshot { mode: house_mode },
outdoor_temperature: *state.outdoor_temperature.read().await,
control_plan: control_plan.plan.as_ref().clone(),
control_plan_revision: control_plan.revision,
system,
})
}
async fn system_info(State(state): State<AppState>) -> Result<Json<Value>, AppError> {
let devices = state.db.list_devices()?;
fn build_system_info(state: &AppState, devices: &[Device]) -> SystemInfoResponse {
let (received_frames_total, received_frames_by_device) = state.gree.received_frame_stats();
Ok(Json(json!({
"version": env!("CARGO_PKG_VERSION"),
"uptime_seconds": state.started.elapsed().as_secs(),
"database": state.config.database.display().to_string(),
"device_count": devices.len(),
"online_count": devices.iter().filter(|v| v.online).count(),
"simulator_count": devices.iter().filter(|v| v.simulated).count(),
"control_ready": state.initial_device_sync_complete.load(Ordering::Acquire),
"auth_required": !state.config.app_token.trim().is_empty(),
"bind": state.config.bind.to_string(),
"base_path": if state.config.base_path.is_empty() { "/" } else { state.config.base_path.as_str() },
"gree_interface": if state.config.gree_interface.trim().is_empty() { "auto" } else { state.config.gree_interface.trim() },
"gree_received_frames": received_frames_total,
"gree_received_frames_by_device": received_frames_by_device,
})))
SystemInfoResponse {
version: env!("CARGO_PKG_VERSION"),
uptime_seconds: state.started.elapsed().as_secs(),
auth_required: !state.config.app_token.trim().is_empty(),
control_ready: state.initial_device_sync_complete.load(Ordering::Acquire),
database: state.config.database.display().to_string(),
device_count: devices.len(),
online_count: devices.iter().filter(|device| device.online).count(),
simulator_count: devices.iter().filter(|device| device.simulated).count(),
bind: state.config.bind.to_string(),
base_path: if state.config.base_path.is_empty() {
"/".to_string()
} else {
state.config.base_path.clone()
},
gree_interface: if state.config.gree_interface.trim().is_empty() {
"auto".to_string()
} else {
state.config.gree_interface.trim().to_string()
},
gree_received_frames: received_frames_total,
gree_received_frames_by_device: received_frames_by_device,
}
}
async fn system_info(
State(state): State<AppState>,
) -> Result<Json<SystemInfoResponse>, AppError> {
let devices = state.db.list_devices()?;
Ok(Json(build_system_info(&state, &devices)))
}
+1 -1
View File
@@ -28,7 +28,7 @@ fn control_plan_ws_message(snapshot: &crate::state::ControlPlanSnapshot) -> Valu
async fn send_ws_bootstrap(state: &AppState, socket: &mut WebSocket) -> Result<Option<u64>, ()> {
let (message, revision) = match build_bootstrap(state).await {
Ok(data) => {
let revision = data.get("control_plan_revision").and_then(Value::as_u64);
let revision = Some(data.control_plan_revision);
(
json!({"event":"bootstrap","timestamp":Utc::now(),"data":data}),
revision,
+13
View File
@@ -111,3 +111,16 @@ pub struct HomeAssistantSettingsView {
pub flow_inputs: Vec<FlowSharedInput>,
pub outdoor_assist_enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SettingsSnapshot {
pub application: ApplicationSettings,
pub gree: GreeSettings,
pub history: HistorySettings,
pub influxdb: InfluxDbSettingsView,
pub notifications: NotificationSettingsView,
pub night: NightModeSettings,
pub home_assistant: HomeAssistantSettingsView,
pub debug: DebugSettings,
}