This commit is contained in:
Mateusz Gruszczyński
2026-09-16 12:51:06 +02:00
parent 0bd33958da
commit d0ae3d0f6a
22 changed files with 208 additions and 128 deletions
+74 -56
View File
@@ -1,9 +1,72 @@
use crate::models::HomeAssistantSettings;
use anyhow::{anyhow, bail, Context, Result};
use serde_json::Value;
use std::time::Duration;
use std::{env, time::Duration};
use url::Url;
const SUPERVISOR_AUTH_ENV: &str = "GREE_CONTROLLER_HA_AUTH";
const SUPERVISOR_AUTH_MODE: &str = "supervisor";
const SUPERVISOR_TOKEN_ENV: &str = "SUPERVISOR_TOKEN";
const SUPERVISOR_BASE_URL: &str = "http://supervisor/core/";
pub fn uses_supervisor_auth() -> bool {
env::var(SUPERVISOR_AUTH_ENV)
.map(|value| value.trim().eq_ignore_ascii_case(SUPERVISOR_AUTH_MODE))
.unwrap_or(false)
}
pub fn auth_mode() -> &'static str {
if uses_supervisor_auth() {
SUPERVISOR_AUTH_MODE
} else {
"manual"
}
}
pub fn effective_url(settings: &HomeAssistantSettings) -> String {
if uses_supervisor_auth() {
SUPERVISOR_BASE_URL.to_string()
} else {
settings.url.clone()
}
}
pub fn token_configured(settings: &HomeAssistantSettings) -> bool {
if uses_supervisor_auth() {
env::var(SUPERVISOR_TOKEN_ENV)
.map(|token| !token.trim().is_empty())
.unwrap_or(false)
} else {
!settings.token.trim().is_empty()
}
}
fn connection(settings: &HomeAssistantSettings) -> Result<(Url, String)> {
let (raw_url, token) = if uses_supervisor_auth() {
let token = env::var(SUPERVISOR_TOKEN_ENV)
.context("Home Assistant Supervisor token is not available")?;
(SUPERVISOR_BASE_URL.to_string(), token)
} else {
(settings.url.clone(), settings.token.clone())
};
if raw_url.trim().is_empty() {
bail!("Home Assistant URL is not configured")
}
if token.trim().is_empty() {
if uses_supervisor_auth() {
bail!("Home Assistant Supervisor token is not available")
}
bail!("Home Assistant token is not configured")
}
let base = Url::parse(raw_url.trim()).context("invalid Home Assistant URL")?;
if !matches!(base.scheme(), "http" | "https") {
bail!("Home Assistant URL must use http or https")
}
Ok((base, token))
}
fn request_client(
default_client: &reqwest::Client,
settings: &HomeAssistantSettings,
@@ -48,20 +111,11 @@ pub async fn test_connection(
default_client: &reqwest::Client,
settings: &HomeAssistantSettings,
) -> Result<()> {
if settings.url.trim().is_empty() {
bail!("Home Assistant URL is not configured")
}
if settings.token.trim().is_empty() {
bail!("Home Assistant token is not configured")
}
let mut base = Url::parse(settings.url.trim()).context("invalid Home Assistant URL")?;
if !matches!(base.scheme(), "http" | "https") {
bail!("Home Assistant URL must use http or https")
}
let (mut base, token) = connection(settings)?;
base = base.join("api/").context("cannot build Home Assistant API URL")?;
let response = request_client(default_client, settings)?
.get(base)
.bearer_auth(settings.token.trim())
.bearer_auth(token.trim())
.header("Accept", "application/json")
.send()
.await
@@ -83,19 +137,10 @@ pub async fn read_temperature(
entity_override: Option<&str>,
stale_after_seconds: Option<u64>,
) -> Result<f64> {
if settings.url.trim().is_empty() {
bail!("Home Assistant URL is not configured")
}
if settings.token.trim().is_empty() {
bail!("Home Assistant token is not configured")
}
let (mut base, token) = connection(settings)?;
let entity = resolve_entity_id(settings, entity_override)
.ok_or_else(|| anyhow!("Home Assistant entity_id is not configured"))?;
let mut base = Url::parse(settings.url.trim()).context("invalid Home Assistant URL")?;
if !matches!(base.scheme(), "http" | "https") {
bail!("Home Assistant URL must use http or https")
}
let path = format!("api/states/{}", entity.trim_start_matches('/'));
base = base
.join(&path)
@@ -104,7 +149,7 @@ pub async fn read_temperature(
let client = request_client(default_client, settings)?;
let response = client
.get(base)
.bearer_auth(settings.token.trim())
.bearer_auth(token.trim())
.header("Accept", "application/json")
.send()
.await
@@ -195,25 +240,16 @@ pub async fn read_entity(
settings: &HomeAssistantSettings,
entity_override: Option<&str>,
) -> Result<Value> {
if settings.url.trim().is_empty() {
bail!("Home Assistant URL is not configured")
}
if settings.token.trim().is_empty() {
bail!("Home Assistant token is not configured")
}
let (mut base, token) = connection(settings)?;
let entity = resolve_entity_id(settings, entity_override)
.ok_or_else(|| anyhow!("Home Assistant entity_id is not configured"))?;
let mut base = Url::parse(settings.url.trim()).context("invalid Home Assistant URL")?;
if !matches!(base.scheme(), "http" | "https") {
bail!("Home Assistant URL must use http or https")
}
base = base
.join(&format!("api/states/{}", entity.trim_start_matches('/')))
.context("cannot build Home Assistant API URL")?;
let client = request_client(default_client, settings)?;
let response = client
.get(base)
.bearer_auth(settings.token.trim())
.bearer_auth(token.trim())
.header("Accept", "application/json")
.send()
.await
@@ -234,20 +270,11 @@ pub async fn list_entities(
default_client: &reqwest::Client,
settings: &HomeAssistantSettings,
) -> Result<Vec<Value>> {
if settings.url.trim().is_empty() {
bail!("Home Assistant URL is not configured")
}
if settings.token.trim().is_empty() {
bail!("Home Assistant token is not configured")
}
let mut base = Url::parse(settings.url.trim()).context("invalid Home Assistant URL")?;
if !matches!(base.scheme(), "http" | "https") {
bail!("Home Assistant URL must use http or https")
}
let (mut base, token) = connection(settings)?;
base = base.join("api/states").context("cannot build Home Assistant API URL")?;
let response = request_client(default_client, settings)?
.get(base)
.bearer_auth(settings.token.trim())
.bearer_auth(token.trim())
.header("Accept", "application/json")
.send()
.await
@@ -292,19 +319,10 @@ pub async fn call_service(
entity_id: Option<&str>,
data: &Value,
) -> Result<Value> {
if settings.url.trim().is_empty() {
bail!("Home Assistant URL is not configured")
}
if settings.token.trim().is_empty() {
bail!("Home Assistant token is not configured")
}
let (mut base, token) = connection(settings)?;
if domain.trim().is_empty() || service.trim().is_empty() {
bail!("Home Assistant domain/service is required")
}
let mut base = Url::parse(settings.url.trim()).context("invalid Home Assistant URL")?;
if !matches!(base.scheme(), "http" | "https") {
bail!("Home Assistant URL must use http or https")
}
base = base
.join(&format!(
"api/services/{}/{}",
@@ -319,7 +337,7 @@ pub async fn call_service(
let client = request_client(default_client, settings)?;
let response = client
.post(base)
.bearer_auth(settings.token.trim())
.bearer_auth(token.trim())
.header("Accept", "application/json")
.json(&Value::Object(payload))
.send()