v0.6.6
This commit is contained in:
+11
-2
@@ -1,8 +1,8 @@
|
||||
# GREE Controller v0.6.5 - build and validation report
|
||||
# GREE Controller v0.6.6 - build and validation report
|
||||
|
||||
## Scope
|
||||
|
||||
Version 0.6.5 includes the 0.6.4 changes described below plus the whole-house thermostat semantics, master power control, Home Assistant house controls and LAN response-time reporting documented in this report.
|
||||
Version 0.6.6 includes the 0.6.5 changes described below plus synchronized whole-house activation: selecting a global operating mode or work profile now re-enables master power and powers on all enabled units. The global power panel is also placed above the house climate controls.
|
||||
|
||||
## Implemented changes
|
||||
|
||||
@@ -130,3 +130,12 @@ Validation performed in the build environment:
|
||||
- Home Assistant now exposes controller-level **Thermostat mode** and **Work profile** select entities plus an **All air conditioners** master-power switch, backed by dedicated restricted HA endpoints.
|
||||
- Device state stores the latest successful GREE communication round-trip time. **LAN network -> Devices** displays it in milliseconds next to the IP address and protocol.
|
||||
- PWA cache key advanced to `gree-controller-v065`.
|
||||
|
||||
|
||||
## 0.6.6 synchronized whole-house activation
|
||||
|
||||
- The global power panel is displayed above **House climate mode**.
|
||||
- Selecting whole-house Cooling or Heating clears a previous global power-off and powers on all enabled units.
|
||||
- Selecting Auto, Comfort, Sleep or Away as a whole-house profile does the same.
|
||||
- House mode `off` / **Do not control** remains non-invasive and does not alter master power.
|
||||
- The behavior is implemented in the shared backend endpoints, so Home Assistant house-mode/profile controls follow the same rules.
|
||||
|
||||
Generated
+1
-1
@@ -633,7 +633,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gree-controller"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "gree-controller"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
edition = "2021"
|
||||
authors = ["GREE Controller contributors"]
|
||||
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
|
||||
|
||||
+14
-14
@@ -1,19 +1,19 @@
|
||||
d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example
|
||||
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
||||
c6ea33a11a287f159c6e4c5f19967d8599146692cd576cfe6b8ccacddadd558a ./BUILD_REPORT.md
|
||||
c9673a32218f054f3d3d3b7f89cba3f0aa75f7078558e43ed3adff064e65d209 ./Cargo.lock
|
||||
735f566fe24949e8a4fae1bebd42d12e91a91cb99a36b114803f0cf0b93212a6 ./Cargo.toml
|
||||
c5c9d9dad288bd9ec4695db7e4c1003ddd39a6cdf70073362dd84c7a87da57f8 ./BUILD_REPORT.md
|
||||
dae36dbeeb9f26dce98eefc232c47b2fcbd5b75c9b896943990fa65b642c3638 ./Cargo.lock
|
||||
2a20c8523878f420c0f3d4df542b60220d7b645da8ed2acdc71d7328d721013e ./Cargo.toml
|
||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||
90fe7b3c18f2ce0d244b8134b73841d3b416d33f7c13d3da107517a124c58d7e ./README.md
|
||||
0e702e55efa6445d959c03c54dff71b1e56317b6fa9de28c52ef34a38c65e9fc ./README.md
|
||||
f2c8eb28fedb26bb8f5636e1805e7a782cc472b87d882f6752d520f16216a248 ./build.rs
|
||||
6e0e5eb103c4acdaeb04c224fdd49e7ec051c667732d131439a165ac1739b713 ./docs/API.md
|
||||
1aeca9e23d3a4fcbe3214acafdc2a33cdffa0ad46efb5ead4477a2ebc3a4286c ./docs/API.md
|
||||
234dd200e380a13ecd3e61b4ea455f6f08d64ce89382077dee80684acadb9703 ./docs/HOME_ASSISTANT_MIGRATION.md
|
||||
7a88d6e76fda21e5d34ab351e26bc10dc1f8f7b3055505aefad1df7c56d65ae4 ./docs/LOCALIZATION.md
|
||||
10a0722e1100fb4a05e3067daeb67dc47b0c0a096b43b1cbf2bf002967ce7d98 ./docs/LXC.md
|
||||
19e6532f21c59b93c77863e8a8ac3a7d9abaf869f18e30401b7d0975b404b06e ./docs/PROJECT_SPEC.md
|
||||
7748b1230029439a405139750b2e0607b8e463bfd56ad86982394105069697c5 ./docs/REVERSE_PROXY.md
|
||||
28f1403d84b01b37bebb72548aa39a75bb14bdbb611a43bfa761319957cd6f39 ./docs/SECURITY.md
|
||||
248570ceff0e020c9d81e8b9e1622ce33f3350f51a04371a9f4fc3f8fa13f62c ./home-assistant/README.md
|
||||
8942e83eabe4edc9728594236b61e3c2be39b8bb46b66c19c4eff7ec7fe80716 ./home-assistant/README.md
|
||||
f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./home-assistant/custom_components/gree_controller/__init__.py
|
||||
001215412d77abd393f34c414ff6d0ebbaf419f140d76037b7f114a7955df6c3 ./home-assistant/custom_components/gree_controller/api.py
|
||||
abcca32b090e5a5d02574a3a340e97321f9879ae6c94ef12eb1f42d967eae2d5 ./home-assistant/custom_components/gree_controller/climate.py
|
||||
@@ -23,7 +23,7 @@ b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./home-assista
|
||||
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./home-assistant/custom_components/gree_controller/entity_map.py
|
||||
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./home-assistant/custom_components/gree_controller/icon.png
|
||||
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./home-assistant/custom_components/gree_controller/logo.png
|
||||
8666eebd03276d6649104dcf538b79073f4f50a51b1440bcbad0a23c09b0f78b ./home-assistant/custom_components/gree_controller/manifest.json
|
||||
52c67fb50b8e9fec5c60af20df6db3385cc40afe08e57b9ea1776ab5fefdaf70 ./home-assistant/custom_components/gree_controller/manifest.json
|
||||
d0206766d76f70ea1c75a618e41bf1e8e8c0429f42ee97f40daac1ea12f16658 ./home-assistant/custom_components/gree_controller/number.py
|
||||
459233ccefa7827fc1c6887146d8a69e01149a497635fd5e1802c220f418eb28 ./home-assistant/custom_components/gree_controller/select.py
|
||||
ec35191e27a018a72cb341914e2abd7fecd25f6b641ea25bebb9e809dd8ecaff ./home-assistant/custom_components/gree_controller/sensor.py
|
||||
@@ -32,8 +32,8 @@ ec35191e27a018a72cb341914e2abd7fecd25f6b641ea25bebb9e809dd8ecaff ./home-assista
|
||||
13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./home-assistant/custom_components/gree_controller/translations/pl.json
|
||||
4513070521d3dda0efb0d974a86ba674494cfb2b66fe9e5cac5b1b0430dede97 ./home-assistant/generated/gree_controller_entities.example.json
|
||||
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
||||
997c7a91017b5d51b3e44016d21252b37d302f95ba0fb5802f3d8fd116687d6f ./lang/en.json
|
||||
33f6935ad1fe72ea0e6893e230ff95565388410fb0fbfe974b34610d789c5d2b ./lang/pl.json
|
||||
3b147813b7ad9d44c99ec4b2c062de77f914ae04538abc6cfaa9e5451fc6a83c ./lang/en.json
|
||||
c301de561c5999031b7a09101a67ab5c7ee49118fe181ad152741c6c2ea01272 ./lang/pl.json
|
||||
028e1f16e9fbaed57cadb88eff04e65b4bd67722c50b4d6b1fb525f5a2f39abf ./make_zip.py
|
||||
bb89bac237e750e9b1bf73761d7df97a6b81853091615878c03f13d7b6399aa7 ./scripts/README.md
|
||||
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
||||
@@ -46,7 +46,7 @@ e00d211e3885e30d7fed1e43b44e6fdad40a67019060156c0641816a93e3365f ./scripts/netw
|
||||
81345b6a0b51736bdbc98fd23199b62e4c721b4e7437e02dab7ea79b97dff29a ./scripts/service.sh
|
||||
b48fc84d79aab381226363ac8473f981bcba5e4911c4cc0011261182debf4250 ./scripts/smoke.sh
|
||||
b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/update.sh
|
||||
f8d1349236819fe0f0253df5269353245f0cacf5b29614ac79c2317c16943636 ./src/api.rs
|
||||
f3d38b84a241cf0dd14ae8f8db7f1ef972734c1f2acaeb8d0bd7997c6e8f05bd ./src/api.rs
|
||||
32b1d0a7ac2060fd18fe7f1ca1d110ee45beaf67e81514d24d6bde3d36301387 ./src/config.rs
|
||||
a89bc49e359715109149130a10ac60abd26bfd2bc18c5ee5142e01696d9f9cdd ./src/db.rs
|
||||
8ec14c01036c73f28cdb330d696f0d3573198e7ea4d03ba6bf9cc1d34ec6c7a8 ./src/engine.rs
|
||||
@@ -62,10 +62,10 @@ a910bd9432a393740c0f6fab52bfcb551f0ea756718d66d290fd2610767cf07c ./src/protocol
|
||||
d1cba5e55c77d44c75a9c9897ae76c327fd96bdf9c01ad27e34322b689348487 ./src/queries.rs
|
||||
f74ac398c6f2e64ebb965900330324f3ec7403e70339638a408229f29fbd07ae ./src/state.rs
|
||||
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
||||
53b94be090889a8f709b16b0f88e9e9c732427151de3cb740c1f16144dec37b4 ./web/app.js
|
||||
8a5471859605115780e1a1cc3d07345698c846c642a9a604977ae2ca73cec0ec ./web/app.js
|
||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||
59e85721942f533243d12491e201dbfedef859dc752b7f0c7c37405705832d2d ./web/index.html
|
||||
3d47ede16e8652f2b36223ca26271ca302d8a1c62030f18a47b461da9c1bf47f ./web/index.html
|
||||
fd26156e9f1d6713d3def564ad000553d9a16a24376059701db2ee762c99ee6c ./web/manifest.webmanifest
|
||||
a9643913bf5ceba3b0940fff583350f5bb853e7d0421296a9661a4e3e256bde1 ./web/styles.css
|
||||
e804540ecc928c3f29a8e71fdae68a6e0a36eb0c0698b1ca22cf8f42a888d61c ./web/sw.js
|
||||
3796167c55146cf549a6df8b7057072ba4731ed667f9691a35a21832139169cd ./web/styles.css
|
||||
aba62a4b8535e59fd78650142e96c7234ef744e02f28178923b40b5188cb0265 ./web/sw.js
|
||||
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
Standalone local GREE air-conditioner controller written in Rust. It runs on a regular Linux host or an LXC container and provides a mobile-first web interface without depending on the vendor cloud.
|
||||
|
||||
Current version: **0.6.5**.
|
||||
Current version: **0.6.6**.
|
||||
|
||||
## Highlights
|
||||
|
||||
@@ -46,7 +46,7 @@ See [`BUILD_REPORT.md`](BUILD_REPORT.md) for package validation details and [`do
|
||||
On Debian, Ubuntu or an LXC container:
|
||||
|
||||
```bash
|
||||
unzip gree-controller-v0.6.5.zip
|
||||
unzip gree-controller-v0.6.6.zip
|
||||
cd gree-controller
|
||||
chmod +x scripts/*.sh
|
||||
./scripts/dev.sh
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ curl -X POST "$BASE/api/house/preset" -H "$AUTH" -H 'Content-Type: application/j
|
||||
-d '{"preset":"sleep"}'
|
||||
```
|
||||
|
||||
House modes: `cool`, `heat`, `off` (`off` = no house-level thermostat control). House presets: `auto`, `comfort`, `sleep`, `away`. A non-`auto` house preset creates temporary per-zone overrides that expire at each zone's next schedule boundary. `POST /api/house/power` controls a separate whole-house master power state. With master power off, zones and controller automations cannot restart units; the selected house thermostat mode is preserved. This is independent from house mode `off`, which only means no house-level thermostat control and allows direct/manual device operation.
|
||||
House modes: `cool`, `heat`, `off` (`off` = no house-level thermostat control). House presets: `auto`, `comfort`, `sleep`, `away`. A non-`auto` house preset creates temporary per-zone overrides that expire at each zone's next schedule boundary. `POST /api/house/power` controls a separate whole-house master power state. With master power off, zones and controller automations cannot restart units; the selected house thermostat mode is preserved. This is independent from house mode `off`, which only means no house-level thermostat control and allows direct/manual device operation. Choosing whole-house `cool`/`heat` or any whole-house preset is treated as an explicit whole-house activation: master power is set back to on and all enabled units are powered on. Choosing house mode `off` does not change master power.
|
||||
|
||||
A zone stores separate profile temperatures for both seasons:
|
||||
|
||||
|
||||
@@ -132,6 +132,7 @@ The integration also exposes three controller-level entities on the **GREE Contr
|
||||
|
||||
- **Thermostat mode** (`select`) — Cooling, Heating or **Do not control**. Do not control pauses house-level thermostat commands but leaves direct device control and explicit per-zone Heat/Cool overrides untouched.
|
||||
- **Work profile** (`select`) — Auto schedule, Comfort, Sleep or Away. If zones have mixed manual profiles, the select has no single current option until a whole-house profile is chosen again.
|
||||
- Choosing **Cooling/Heating** in the thermostat-mode select or any whole-house **Work profile** is treated as explicit whole-house activation: it switches **All air conditioners** back on. **Do not control** does not change master power.
|
||||
- **All air conditioners** (`switch`) — separate master power. Turning it off powers every enabled unit down and prevents zones/controller automations from restarting them. Turning it on powers all enabled units on again without changing the selected thermostat mode or profile.
|
||||
|
||||
These controls use the Home Assistant integration token and the dedicated `/api/integrations/home-assistant/house/*` endpoints.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "gree_controller",
|
||||
"name": "GREE Controller",
|
||||
"version": "0.6.5",
|
||||
"version": "0.6.6",
|
||||
"config_flow": true,
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
|
||||
+1
-1
@@ -534,7 +534,7 @@
|
||||
"house.noControl": "Do not control",
|
||||
"house.powerAllEyebrow": "Global power",
|
||||
"house.powerAllTitle": "All air conditioners",
|
||||
"house.powerAllHint": "Separate master power for all enabled units. “Turn all off” prevents zones and automations from restarting them without changing the selected thermostat mode.",
|
||||
"house.powerAllHint": "Master power for all enabled units. “Turn all off” prevents them from restarting. Choosing whole-house Cooling/Heating or an Auto/Comfort/Sleep/Away profile automatically switches master power back on.",
|
||||
"house.powerOnAll": "Turn all on",
|
||||
"house.powerOffAll": "Turn all off",
|
||||
"house.powerOnDone": "All air conditioners turned on",
|
||||
|
||||
+1
-1
@@ -534,7 +534,7 @@
|
||||
"house.noControl": "Nie steruj",
|
||||
"house.powerAllEyebrow": "Zasilanie globalne",
|
||||
"house.powerAllTitle": "Wszystkie klimatyzatory",
|
||||
"house.powerAllHint": "Osobne nadrzędne zasilanie wszystkich aktywnych jednostek. „Wyłącz wszystkie” blokuje ponowne uruchamianie przez strefy i automatyzacje, ale nie zmienia wybranego trybu termostatu.",
|
||||
"house.powerAllHint": "Nadrzędne zasilanie wszystkich aktywnych jednostek. „Wyłącz wszystkie” blokuje ich ponowne uruchamianie. Globalny wybór Chłodzenia/Grzania albo profilu Auto/Komfort/Sen/Poza domem automatycznie ustawia „Włącz wszystkie”.",
|
||||
"house.powerOnAll": "Włącz wszystkie",
|
||||
"house.powerOffAll": "Wyłącz wszystkie",
|
||||
"house.powerOnDone": "Włączono wszystkie klimatyzatory",
|
||||
|
||||
+71
-24
@@ -594,16 +594,55 @@ async fn update_zone_control(State(state): State<AppState>, Path(id): Path<Strin
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct HouseControlPatch { mode: String }
|
||||
|
||||
async fn command_all_enabled_devices_power(state: &AppState, power: bool, source: &str) -> Result<Vec<Value>, AppError> {
|
||||
let mut failed = Vec::new();
|
||||
for device in state.db.list_devices()? {
|
||||
if !device.enabled || device.power == power { continue; }
|
||||
let command = DeviceCommand { power: Some(power), ..Default::default() };
|
||||
if let Err(err) = engine::send_command(state, &device.id, command).await {
|
||||
state.log("error", "house.power_all_error", &err.to_string(), json!({
|
||||
"device_id": device.id,
|
||||
"device_name": device.name,
|
||||
"power": power,
|
||||
"source": source,
|
||||
}));
|
||||
failed.push(json!({
|
||||
"device_id": device.id,
|
||||
"device_name": device.name,
|
||||
"error": err.to_string(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
Ok(failed)
|
||||
}
|
||||
|
||||
async fn update_house_control(State(state): State<AppState>, Json(input): Json<HouseControlPatch>) -> Result<Json<Value>, AppError> {
|
||||
if !matches!(input.mode.as_str(), "cool" | "heat" | "off") {
|
||||
return Err(AppError::BadRequest("house mode must be cool, heat or off".into()));
|
||||
}
|
||||
let mut settings = state.settings.write().await;
|
||||
settings.house_mode = input.mode;
|
||||
state.db.save_runtime_settings(&settings)?;
|
||||
let payload = public_settings(&settings);
|
||||
let mode = input.mode;
|
||||
let activate_all = mode != "off";
|
||||
let payload = {
|
||||
let mut settings = state.settings.write().await;
|
||||
settings.house_mode = mode.clone();
|
||||
// Choosing a real whole-house operating mode is an explicit request to run the
|
||||
// house climate. It therefore clears a previous global power-off. "off" keeps
|
||||
// its separate meaning: do not control, without changing master power.
|
||||
if activate_all { settings.house_power_enabled = true; }
|
||||
state.db.save_runtime_settings(&settings)?;
|
||||
public_settings(&settings)
|
||||
};
|
||||
state.broadcast("settings.updated", payload.clone());
|
||||
state.log("info", "house.mode", &format!("House mode set to {}", settings.house_mode), json!({"mode": settings.house_mode}));
|
||||
if activate_all {
|
||||
let failed = command_all_enabled_devices_power(&state, true, "house_mode").await?;
|
||||
if !failed.is_empty() {
|
||||
state.log("warn", "house.mode_power_partial", "House mode enabled master power, but some devices could not be powered on", json!({
|
||||
"mode": mode,
|
||||
"failed": failed.len(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
state.log("info", "house.mode", &format!("House mode set to {}", mode), json!({"mode": mode, "master_power_enabled": activate_all}));
|
||||
Ok(Json(payload))
|
||||
}
|
||||
|
||||
@@ -623,23 +662,7 @@ async fn update_house_power(State(state): State<AppState>, Json(input): Json<Hou
|
||||
}
|
||||
}
|
||||
|
||||
let mut failed = Vec::new();
|
||||
for device in state.db.list_devices()? {
|
||||
if !device.enabled || device.power == input.power { continue; }
|
||||
let command = DeviceCommand { power: Some(input.power), ..Default::default() };
|
||||
if let Err(err) = engine::send_command(&state, &device.id, command).await {
|
||||
state.log("error", "house.power_all_error", &err.to_string(), json!({
|
||||
"device_id": device.id,
|
||||
"device_name": device.name,
|
||||
"power": input.power,
|
||||
}));
|
||||
failed.push(json!({
|
||||
"device_id": device.id,
|
||||
"device_name": device.name,
|
||||
"error": err.to_string(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
let failed = command_all_enabled_devices_power(&state, input.power, "house_power").await?;
|
||||
|
||||
let devices = state.db.list_devices()?;
|
||||
let settings = state.settings.read().await;
|
||||
@@ -664,6 +687,17 @@ async fn update_house_preset(State(state): State<AppState>, Json(input): Json<Ho
|
||||
if !matches!(input.preset.as_str(), "auto" | "comfort" | "sleep" | "away") {
|
||||
return Err(AppError::BadRequest("house preset must be auto, comfort, sleep or away".into()));
|
||||
}
|
||||
|
||||
// A whole-house profile is also an explicit whole-house activation. This mirrors
|
||||
// selecting cooling/heating and makes the separate master-power control intuitive.
|
||||
let settings_payload = {
|
||||
let mut settings = state.settings.write().await;
|
||||
settings.house_power_enabled = true;
|
||||
state.db.save_runtime_settings(&settings)?;
|
||||
public_settings(&settings)
|
||||
};
|
||||
state.broadcast("settings.updated", settings_payload.clone());
|
||||
|
||||
let schedules = state.db.list_schedules()?;
|
||||
let mut zones = state.db.list_zones()?;
|
||||
for zone in &mut zones {
|
||||
@@ -680,8 +714,21 @@ async fn update_house_preset(State(state): State<AppState>, Json(input): Json<Ho
|
||||
state.db.save_zone(zone)?;
|
||||
state.broadcast("zone.updated", serde_json::to_value(&*zone)?);
|
||||
}
|
||||
state.log("info", "house.preset", &format!("House preset set to {}", input.preset), json!({"preset": input.preset}));
|
||||
Ok(Json(json!({"preset": input.preset, "zones": zones})))
|
||||
|
||||
let failed = command_all_enabled_devices_power(&state, true, "house_preset").await?;
|
||||
let devices = state.db.list_devices()?;
|
||||
state.log("info", "house.preset", &format!("House preset set to {}", input.preset), json!({
|
||||
"preset": input.preset,
|
||||
"master_power_enabled": true,
|
||||
"failed": failed.len(),
|
||||
}));
|
||||
Ok(Json(json!({
|
||||
"preset": input.preset,
|
||||
"zones": zones,
|
||||
"devices": devices,
|
||||
"settings": settings_payload,
|
||||
"failed": failed,
|
||||
})))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
||||
+7
-1
@@ -1439,7 +1439,13 @@ document.addEventListener('click', async event => {
|
||||
return;
|
||||
}
|
||||
if (action === 'house-preset') {
|
||||
try { const result=await api('/api/house/preset',{method:'POST',body:{preset:button.dataset.value}}); app.zones=result.zones||app.zones; renderHouseClimate(); renderZones(); scheduleControlPlanLoad(); toast(tr('house.presetUpdated')); }
|
||||
try {
|
||||
const result=await api('/api/house/preset',{method:'POST',body:{preset:button.dataset.value}});
|
||||
app.settings=result.settings||app.settings; app.zones=result.zones||app.zones; app.devices=result.devices||app.devices;
|
||||
renderAll(); scheduleControlPlanLoad();
|
||||
const failed=Array.isArray(result.failed)?result.failed.length:0;
|
||||
if(failed) toast(tr('house.powerPartial',{count:failed}),true); else toast(tr('house.presetUpdated'));
|
||||
}
|
||||
catch(error){ toast(error.message,true); } return;
|
||||
}
|
||||
if (action === 'zone-temperature') { const zone=app.zones.find(v=>v.id===button.dataset.id); if(zone) { const base=Number(zone.manual_setpoint ?? zone.effective_setpoint ?? zone.setpoint); queueZoneTemperature(zone, base+Number(button.dataset.delta)); } return; }
|
||||
|
||||
+2
-2
@@ -37,11 +37,11 @@
|
||||
<div class="hero-temp" id="heroTemperature">--<small>°C</small></div>
|
||||
</div>
|
||||
<div class="metrics" id="metrics"></div>
|
||||
<div class="panel house-climate" id="houseClimate"></div>
|
||||
<div class="panel house-power-panel">
|
||||
<div><span class="eyebrow" data-i18n="house.powerAllEyebrow">Global power</span><h3 data-i18n="house.powerAllTitle">All air conditioners</h3><p data-i18n="house.powerAllHint">Direct power control for all enabled units, independent from thermostat presets.</p></div>
|
||||
<div><span class="eyebrow" data-i18n="house.powerAllEyebrow">Global power</span><h3 data-i18n="house.powerAllTitle">All air conditioners</h3><p data-i18n="house.powerAllHint">Master power for all enabled units. Whole-house mode or profile selection automatically switches it back on.</p></div>
|
||||
<div class="house-power-actions"><button id="housePowerOn" type="button" class="secondary" data-action="house-power" data-value="true" data-i18n="house.powerOnAll">Turn all on</button><button id="housePowerOff" type="button" class="secondary" data-action="house-power" data-value="false" data-i18n="house.powerOffAll">Turn all off</button></div>
|
||||
</div>
|
||||
<div class="panel house-climate" id="houseClimate"></div>
|
||||
<div class="section-heading"><div><span class="eyebrow" data-i18n="plan.eyebrow">Control plan</span><h2 data-i18n="plan.title">What happens next</h2></div></div>
|
||||
<div class="automation-plan-grid" id="controlPlan"></div>
|
||||
<div class="section-heading"><div><span class="eyebrow" data-i18n="nav.zones">Zones</span><h2 data-i18n="dashboard.quickThermostats">Quick thermostats</h2></div></div>
|
||||
|
||||
+3
-3
@@ -298,7 +298,7 @@ legend { padding: 0 5px; color: var(--muted); font-size: 11px; }
|
||||
.mode-row, .fan-row, .preset-row { scrollbar-width: none; }
|
||||
.mode-row::-webkit-scrollbar, .fan-row::-webkit-scrollbar, .preset-row::-webkit-scrollbar { display: none; }
|
||||
|
||||
.house-climate { display: grid; gap: 16px; margin: -15px 0 12px; }
|
||||
.house-climate { display: grid; gap: 16px; margin: 0 0 12px; }
|
||||
.house-climate-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
|
||||
.house-climate-head h3 { margin: 2px 0 4px; font-size: 20px; }
|
||||
.house-climate-head p { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.45; }
|
||||
@@ -325,7 +325,7 @@ legend { padding: 0 5px; color: var(--muted); font-size: 11px; }
|
||||
.plan-loading { grid-column: 1/-1; padding: 18px; color: var(--muted); }
|
||||
.house-mode-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
|
||||
.house-mode-row button { min-height: 44px; color: var(--muted); background: var(--surface-muted); }
|
||||
.house-power-panel { display: flex; align-items: center; justify-content: space-between; gap: 18px; margin: 0 0 34px; }
|
||||
.house-power-panel { display: flex; align-items: center; justify-content: space-between; gap: 18px; margin: -15px 0 12px; }
|
||||
.house-power-panel h3 { margin: 2px 0 4px; font-size: 18px; }
|
||||
.house-power-panel p { margin: 0; max-width: 650px; color: var(--muted); font-size: 12px; line-height: 1.45; }
|
||||
.house-power-actions { display: flex; flex: 0 0 auto; gap: 8px; }
|
||||
@@ -360,7 +360,7 @@ legend { padding: 0 5px; color: var(--muted); font-size: 11px; }
|
||||
.warning-note { color: var(--warning); }
|
||||
|
||||
@media (max-width: 620px) {
|
||||
.house-climate { margin-top: -20px; }
|
||||
.house-power-panel { margin-top: -20px; }
|
||||
.house-climate-head { align-items: flex-start; }
|
||||
.house-climate-head p { max-width: 220px; }
|
||||
.house-power-panel { align-items: stretch; flex-direction: column; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'gree-controller-v065';
|
||||
const CACHE = 'gree-controller-v066';
|
||||
const SCOPE = new URL(self.registration.scope).pathname.replace(/\/$/, '');
|
||||
const path = value => `${SCOPE}${value.startsWith('/') ? value : `/${value}`}` || '/';
|
||||
const ASSETS = [path('/'), path('/styles.css'), path('/app.js'), path('/theme-init.js'), path('/favicon.svg'), path('/manifest.webmanifest'), path('/lang/index.json'), path('/lang/en.json')];
|
||||
|
||||
Reference in New Issue
Block a user