127 lines
4.8 KiB
Rust
127 lines
4.8 KiB
Rust
mod api;
|
|
mod config;
|
|
mod db;
|
|
mod engine;
|
|
mod error;
|
|
mod home_assistant;
|
|
mod influxdb;
|
|
mod models;
|
|
mod notifications;
|
|
mod protocol;
|
|
mod queries;
|
|
mod state;
|
|
|
|
use std::{sync::{Arc, atomic::AtomicBool}, time::{Duration, Instant}};
|
|
use anyhow::{Context, Result};
|
|
use config::Config;
|
|
use db::Db;
|
|
use models::Device;
|
|
use protocol::GreeClient;
|
|
use state::AppState;
|
|
use tokio::{net::TcpListener, signal, sync::{broadcast, Notify, RwLock}};
|
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
let config = Config::load()?;
|
|
init_tracing();
|
|
|
|
let db = Db::open(&config.database)?;
|
|
let mut runtime_settings = db.load_runtime_settings()?.unwrap_or_else(|| config.runtime_defaults());
|
|
// Network deployment settings explicitly provided by the service environment are authoritative.
|
|
// This makes /etc/gree-controller.env useful even after runtime settings were persisted in SQLite.
|
|
if std::env::var_os("GREE_CONTROLLER_DISCOVERY_BROADCAST").is_some() {
|
|
runtime_settings.discovery_broadcast = config.discovery_broadcast.clone();
|
|
}
|
|
config.apply_runtime_env_overrides(&mut runtime_settings);
|
|
db.save_runtime_settings(&runtime_settings)?;
|
|
|
|
if config.simulate && config.auto_seed && db.count_devices()? == 0 {
|
|
db.save_device(&Device::simulated_default())?;
|
|
db.log_event(
|
|
"info",
|
|
"simulator.seeded",
|
|
"Created the default simulator device",
|
|
&serde_json::json!({"device_id":"sim-salon"}),
|
|
)?;
|
|
}
|
|
|
|
let (events, _) = broadcast::channel(512);
|
|
let debug_gree_frames = Arc::new(AtomicBool::new(runtime_settings.debug.gree_frames));
|
|
let http = reqwest::Client::builder()
|
|
.timeout(Duration::from_secs(10))
|
|
.user_agent(concat!("gree-controller/", env!("CARGO_PKG_VERSION")))
|
|
.build()?;
|
|
let state = AppState {
|
|
db,
|
|
settings: Arc::new(RwLock::new(runtime_settings.clone())),
|
|
config: Arc::new(config.clone()),
|
|
gree: GreeClient::new(
|
|
runtime_settings.controller_id.clone(),
|
|
(!config.gree_interface.trim().is_empty()).then(|| config.gree_interface.trim().to_string()),
|
|
Some(events.clone()),
|
|
debug_gree_frames.clone(),
|
|
),
|
|
events,
|
|
http,
|
|
outdoor_temperature: Arc::new(RwLock::new(None)),
|
|
debug_gree_frames,
|
|
initial_device_sync_complete: Arc::new(AtomicBool::new(false)),
|
|
zone_control_wakeup: Arc::new(Notify::new()),
|
|
device_operation_locks: Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())),
|
|
zone_operation_locks: Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())),
|
|
group_operation_locks: Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())),
|
|
schedule_operation_lock: Arc::new(tokio::sync::Mutex::new(())),
|
|
automation_operation_lock: Arc::new(tokio::sync::Mutex::new(())),
|
|
house_operation_lock: Arc::new(tokio::sync::Mutex::new(())),
|
|
configuration_operation_lock: Arc::new(tokio::sync::Mutex::new(())),
|
|
zone_control_cycle_lock: Arc::new(tokio::sync::Mutex::new(())),
|
|
pending_controller_commands: Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())),
|
|
started: Instant::now(),
|
|
};
|
|
|
|
engine::start(state.clone());
|
|
let app = api::router(state.clone());
|
|
let listener = TcpListener::bind(config.bind).await
|
|
.with_context(|| format!("cannot bind HTTP server to {}", config.bind))?;
|
|
|
|
let gree_interface_log = if config.gree_interface.trim().is_empty() { "auto" } else { config.gree_interface.trim() };
|
|
tracing::info!(
|
|
address = %config.bind,
|
|
database = %config.database.display(),
|
|
simulator = config.simulate,
|
|
auth = !config.app_token.trim().is_empty(),
|
|
gree_interface = %gree_interface_log,
|
|
discovery_broadcast = %runtime_settings.discovery_broadcast,
|
|
"GREE Controller started"
|
|
);
|
|
|
|
axum::serve(listener, app)
|
|
.with_graceful_shutdown(shutdown_signal())
|
|
.await?;
|
|
tracing::info!("GREE Controller stopped");
|
|
Ok(())
|
|
}
|
|
|
|
fn init_tracing() {
|
|
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| "info,tower_http=info".into());
|
|
tracing_subscriber::registry()
|
|
.with(filter)
|
|
.with(tracing_subscriber::fmt::layer().compact())
|
|
.init();
|
|
}
|
|
|
|
async fn shutdown_signal() {
|
|
let ctrl_c = async { signal::ctrl_c().await.expect("cannot install Ctrl+C handler"); };
|
|
#[cfg(unix)]
|
|
let terminate = async {
|
|
signal::unix::signal(signal::unix::SignalKind::terminate())
|
|
.expect("cannot install SIGTERM handler")
|
|
.recv().await;
|
|
};
|
|
#[cfg(not(unix))]
|
|
let terminate = std::future::pending::<()>();
|
|
tokio::select! { _ = ctrl_c => {}, _ = terminate => {} }
|
|
}
|