v0.9.4
This commit is contained in:
Generated
+1
-1
@@ -633,7 +633,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gree-controller"
|
||||
version = "0.9.3"
|
||||
version = "0.9.4"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "gree-controller"
|
||||
version = "0.9.3"
|
||||
version = "0.9.4"
|
||||
edition = "2021"
|
||||
authors = ["GREE Controller contributors"]
|
||||
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
|
||||
|
||||
+21
-21
@@ -1,12 +1,12 @@
|
||||
b38e05f8a00969270280b0062ee129fccee39ae9c11cb614a6ad7b2b7ae2dfc7 ./.env.example
|
||||
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
||||
3ce3d5d41bf609d9f3e3edc20c51e470c105a44a0050fd08beef4dd61bbdec2d ./Cargo.lock
|
||||
761439549d8f923adc732c10dbedd81a7e2811dbe3faea9af61dc94bed3151c1 ./Cargo.toml
|
||||
37ba4d5222b221d9a3d560246d968092fa4402288410eaba7e6bab87ebcc5aea ./Cargo.lock
|
||||
a0976abed3b84a5ba0c726410c87d31017d42098fb7e053af986869ea0c9db74 ./Cargo.toml
|
||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||
18040a0fcc08f95609850727fd825cde75e37d44145fb05eb54e1c0f6ee238ee ./README.md
|
||||
4011536d5181ce7d8132c3b2ad50699a3ffa057f33b361aaa96fe71b6ae3efe3 ./README.md
|
||||
ec0f495ee6a618bdec10b260ee7838f7a872d8c5b0aa91c546226c1ad818f601 ./build.rs
|
||||
11873ce428fffabb85a2f1152c3694256c8cbdced6b8bb1afc439e66e62600c9 ./docs/API.md
|
||||
3b3041f6e3e8c1017fa7bca3b6b4b474f5279869952000a5549a5b2749f8396e ./home-assistant/README.md
|
||||
e183861962799504fe7dfc7f43ba5d75dc7f7f68c898689dd2c178f566af73e2 ./docs/API.md
|
||||
a0893b2a56eb1523f1a72871842e9be2139a5fafba1f51ae942fc407a6e4ca34 ./home-assistant/README.md
|
||||
f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./home-assistant/custom_components/gree_controller/__init__.py
|
||||
6910589f27960a28d4de9735884a7e5376e455cd885f19ce2b55947fcd135114 ./home-assistant/custom_components/gree_controller/api.py
|
||||
71c8058f85fd9ce9f063b7a84dcf2956f23cdbbd02bd4bd99d2fbb9d177c72ad ./home-assistant/custom_components/gree_controller/climate.py
|
||||
@@ -16,7 +16,7 @@ ab08fbe40e9bb48ebcbfff98760aaa0c9434b61b705aaaadea0c09c255d99b0e ./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
|
||||
c403e68570a6406c1a333b2f788a5182cc502c17e6c85837e57e6f11a26ee7ae ./home-assistant/custom_components/gree_controller/manifest.json
|
||||
f30f751b4e561e51a3ff9bf9c8384bc2d160dd157b954dd7061d5844ee410b77 ./home-assistant/custom_components/gree_controller/manifest.json
|
||||
38d659d0273c0d1428679e44574a2666c605d1b3796d323b626baa40560788cf ./home-assistant/custom_components/gree_controller/number.py
|
||||
39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./home-assistant/custom_components/gree_controller/select.py
|
||||
cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./home-assistant/custom_components/gree_controller/sensor.py
|
||||
@@ -25,8 +25,8 @@ cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./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
|
||||
f15fbad03393a5f5e971fb3842c1c312da4cdb776ac08731d91e8e35257683f3 ./lang/en.json
|
||||
2272debce08cf971375a748844d79f83548c8696e63fcb0b25d6c212651242ff ./lang/pl.json
|
||||
669da4cb511c9d7f72017edf3f16718eb3b32fc832d8431a597a330f4bc8f5c0 ./lang/en.json
|
||||
9bf8bccb7eaf99aba65f4b4b8776f2834f335fc0b007999d7e5c8d9d287067c8 ./lang/pl.json
|
||||
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
||||
bb89bac237e750e9b1bf73761d7df97a6b81853091615878c03f13d7b6399aa7 ./scripts/README.md
|
||||
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
||||
@@ -48,12 +48,12 @@ b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/upda
|
||||
417940b467c6f00bbf01cc47eb4479880dee9b2f284a80e3662a6d433dc11b92 ./src/api/events.rs
|
||||
d096000e94997cf9aaf9d3cc567214a78b8b2da7da57a76e2b3519247af6e592 ./src/api/groups.rs
|
||||
a13d4e217fe3ddaa73873ba6e0d1bc93750cf61764d867ff7372274ed21d9599 ./src/api/history.rs
|
||||
34129cf37c5e544b506024c82cc917d31c705f42bfbb1dd5c46c27687bae63f8 ./src/api/house.rs
|
||||
b2a730970a767c67727d5bc8f7090edb26d8332a47bc72a28b5a1195a4dbafe7 ./src/api/house.rs
|
||||
0d7d598f7ed24315ed0547a60a72a06f20ebcf926489e1c454986f42d22cc305 ./src/api/integrations.rs
|
||||
bb2a746ecdcc2da5fe54e18b455c7bd19453486dd5c008e71951a2c81d0e7d64 ./src/api/middleware.rs
|
||||
2b3feefbafc1f813c00dfe20db147aa1f5067ec5b8455f041ac3793802911be3 ./src/api/public_settings.rs
|
||||
8f7f0b8c157e9c97d7dc305b79923646ef47d76497abfcf09e9c67bb12af5dd1 ./src/api/schedules.rs
|
||||
918164d24faf02861986b78774ceae4add72b6b2de53b3c15fd1a94dff3de38a ./src/api/settings.rs
|
||||
4ffab50d931ca46dc5708914b6b54256d639cc11ad1b4281f649c310db5d41a0 ./src/api/settings.rs
|
||||
74c156717b37e83d329f7c52ac622e02f690621544c4eb07c4502194db55e6e9 ./src/api/system.rs
|
||||
681cccb8d09f4ad9c2e6467a4dbe2f9d991125775a5d2bfc9043daf940fecd29 ./src/api/websocket.rs
|
||||
923e7434e8f5789150eb34ffc4d18d25fa701f4a9040bf856d7926016f1936aa ./src/api/zones.rs
|
||||
@@ -70,37 +70,37 @@ f3bd91d0bdb7699f319b9341d928d3cc3b25f759653fcd2095a5c5f695d08fac ./src/db/event
|
||||
a384ea4042d1d11692b5f8693df1f54850dcc4e80726780b44b2b096bf667b78 ./src/db/zone_history.rs
|
||||
952ea5906d39ef917ad71edc345f76b29c07c63079644fd5699b12aa254cb35f ./src/engine.rs
|
||||
384f7027ca48bd4c4048320bc6d4cacd631bee40e8630ea22e0d6e5f22ddc3a6 ./src/engine/automations.rs
|
||||
910248f30ac9d697816458cd393c5f7d4bbdd3a12e48cb41cbb3ddb68a1ce395 ./src/engine/commands.rs
|
||||
77931af524e9402fc826050b5fcec422d01b02b625c8ce7f2004f1f6520c9b4d ./src/engine/commands.rs
|
||||
ced02b351ba86c43bd3683480a33ff7bd95c8a8109216c85979bc1cb14db5f41 ./src/engine/control_plan.rs
|
||||
38f42c86c31048780ca414f2c328f363ef803a0edbc1e1ed85503909a10b69ae ./src/engine/deadlines.rs
|
||||
b353d78e8a6d0fa1a76a731a1ebe8e49fab33869d2834ffc5a7ee46d78135896 ./src/engine/groups.rs
|
||||
511caa52f9d1a6c13c15731e6a394ca2790ba642845367da1f0b767c12b5be98 ./src/engine/groups.rs
|
||||
18a7669318949e533f206d25cf7b63771d7f0f2c0b375a72ff78a2d921baaa61 ./src/engine/history.rs
|
||||
e199628c4580e6a225e77d17acaaf8edc2a1ff0e3f91595234bba1f53e6450e1 ./src/engine/local_thermostat.rs
|
||||
20964553f2691c6eb79932271ceb0627f9665a7f7cfb9d61ca3fe5a6f093f6e7 ./src/engine/ownership.rs
|
||||
f7f2570c5b4a0f655e7c68e44d3dcca478b546e31586e98227b0e8c21e73e8c4 ./src/engine/local_thermostat.rs
|
||||
ea61ee04f7cda0c4835a87b1e28f1ff8134a8b977741ef6e8ccb8f284d1c9470 ./src/engine/ownership.rs
|
||||
9c37adf514c96840fd6a5c715c579350fa86d6dcc0655a2aa97d1fd7c7e04299 ./src/engine/polling.rs
|
||||
3d48608a065aa52a1fd8b9c59356a74d8216ecee3d47f8ff8ef7d3d47170c617 ./src/engine/runtime.rs
|
||||
ffa72b93502eece187a96adb4ec94aec6048d4a6dc7af66e7c7af0931318787f ./src/engine/schedules.rs
|
||||
2e000ede494bafb5309af3bbcdf1f10024bb01012ab3f9555ff0c8f1898bc9d7 ./src/engine/targets.rs
|
||||
a9188b588b2617ce1547a45d46141f7e5f1bb0027a48675dd86b274f807d0875 ./src/engine/temperature.rs
|
||||
c2fe371f9245ce299015f0b4e43f6625a207016abf73d2419acb6bd7437f045a ./src/engine/temporary_thermostat.rs
|
||||
9dd08b509057445f14d88881a3c76eb74bf333fd722db10d3e655b825f94c637 ./src/engine/tests.rs
|
||||
9e86ea344e0bf6a2232905a98e5addce5da6f845cfed5b04a5d2d20787490b15 ./src/engine/tests.rs
|
||||
ebf8b9ec5c5a84850d8cad4c4faea090e52625b5a0be56625dc84060ecb35186 ./src/engine/zone_actions.rs
|
||||
15de7195e91336a9e865a6a9c5c0e6768c3deea183941fc7cf3bca387e1b2724 ./src/engine/zone_control.rs
|
||||
51e0d547709da755622c88bea5e69912b519040d5111ba5c2d74d071b73b52a9 ./src/engine/zone_control.rs
|
||||
4b271b6fc365b1078c01d6178eb563841b2ecaed5d8639196f58e1312d2236fe ./src/error.rs
|
||||
c6ff66da9ad08506f839ec56a869ead3abc824b5311e0ea9244517d11f7f4207 ./src/home_assistant.rs
|
||||
6f9ef85cd53ba030b477bc3c186f0f7e762674124210eabcb0686f2806eaada1 ./src/influxdb.rs
|
||||
feb50ecd60b9a9fe9de1f7d8e1e26caab5435d59347468f6266a318f431bc3d3 ./src/influxdb/codec.rs
|
||||
553b44ec0321e62480059bc2451fd34ec7aabe9fc87fc717daba0c4b55679d53 ./src/influxdb/query.rs
|
||||
5e4d94f8a010df05bae9acab72c518080d2dba94d054df95b87e492305357222 ./src/influxdb/write.rs
|
||||
f34ab82de49536119b1abe32c2726f6fa91bf365f3b00e65d2b1371344e5b4ab ./src/main.rs
|
||||
ef8ad176eb2ee6ceb02bbf3d63b65460013475908fced05388b29727af67ef7e ./src/main.rs
|
||||
e935e1fcf0d379b627df5d5055dbdad3dd55786fde7191376918b05afdde1727 ./src/models.rs
|
||||
fca124ec30b5c9286f0f44cc18e8953bb2809f5009d4d521deaebfdeb00c1bd1 ./src/models/automation.rs
|
||||
c88fa0555d8549b4cadf7fa56469898b911054c39e774c9e9122941d65f760b6 ./src/models/control_plan.rs
|
||||
da672b06295370a36c69136e15684ab10ccb8fbdc814d76527a05e01213c2d2b ./src/models/control_plan.rs
|
||||
8228b9b6d9e8b1d13de29ce88500beacd88c8877893b7a3e41a4bd1e237ca36c ./src/models/defaults.rs
|
||||
7cdc99a6f0920baf4ac88fbe7e6f1859e8598a00f00b8c3d84f20bc3a4de35f9 ./src/models/device.rs
|
||||
cfbaa853a5185c4be572588577b313a2d618837f7c07a3860d0e8d2cca04ee60 ./src/models/history.rs
|
||||
668c9cf157679f8c9fe72846f743629c79e263b14827110834b4addbdf9d13ec ./src/models/integrations.rs
|
||||
29448846a4835ea511ef54326866ecc3eaac37bc27bbec38c4048be23fc81029 ./src/models/runtime.rs
|
||||
268b4211df93f08423e39374ab9ffe93a7eba647c173168821736af299b36549 ./src/models/runtime.rs
|
||||
e77f8cdcb5fc0e51cdbf2527a69585a2f6f9089e159d1e45ec4a48735ab0e304 ./src/models/temporary_thermostat.rs
|
||||
5b5c80e25ae2aad1b511bd3f0a82b8fbae2440abf8c656ecb90814587d0be137 ./src/models/zone.rs
|
||||
9c735d7c475f21f2f388c21be784376f7184f0bf18af1886544ef51585a3b290 ./src/notifications.rs
|
||||
@@ -127,12 +127,12 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/
|
||||
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
||||
de66cab9b650cb448814bfeda6d216e168dd3e6f07a1a438e4a9de9acbe6b798 ./web/404.html
|
||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||
0ca4021a39142112632ef5c2f2407eedbb53a4d5519f97d682aa0864dbe01979 ./web/index.html
|
||||
44ce3d17b83c1ab505247ba050a923d5ae109a3a06209aa3d362e9d6daf1ecbf ./web/index.html
|
||||
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md
|
||||
b3d5cb473bf9771de78e14c7de730cb86c73f8cdbb6344da67ba7d6abac879ea ./web/js/bootstrap.js
|
||||
64a43e2142a69ebddf4160c0d0d3b573fa379c650431090fd15c9ba3b62bc4e7 ./web/js/charts.js
|
||||
cf8d97770985b8cf920382725fa33d5f61497a639544bfc528af72b276de0b6a ./web/js/core.js
|
||||
dcba9512d6376bf06ce5e521597a4e849fbaabc258407a536f6ca81d9ba7e6c0 ./web/js/dashboard.js
|
||||
9767d5f2bd2844245245c0bd86dddd528975c1d1dd1d05b1449feda7a95214c4 ./web/js/dashboard.js
|
||||
a41a5d768452872e9e88abeab8d97c0869a3285448165ec306f59341265b4b59 ./web/js/entities.js
|
||||
69a052771dfafbd4b778cbc9d1b7a7695427621ed66047282088a3a07e6e4c2e ./web/js/events.js
|
||||
2fe6c804f86b0613a50a56c54f7a0cbee77273759edf3e6fec36ac77d35edbd7 ./web/js/forms.js
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Self-hosted controller for GREE-compatible air conditioners with a local Web UI, thermostat zones, schedules, Home Assistant integration, history, notifications and a documented HTTP/WebSocket API.
|
||||
|
||||
**Current release: 0.9.3**
|
||||
**Current release: 0.9.4**
|
||||
|
||||
> [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples.
|
||||
|
||||
@@ -189,7 +189,7 @@ This distinction is important: direct device control intentionally behaves diffe
|
||||
|
||||
The zone state exposes who currently owns control (`control_owner`, `control_source`, timestamps and reason). Physical remote/direct device actions can create a manual-device takeover so normal schedules do not immediately overwrite the user. Explicit zone thermostat control hands ownership back to the thermostat engine when appropriate.
|
||||
|
||||
Global **Turn all on/off** is a one-shot physical action. It does not erase or suspend thermostat, group, house-rule or manual ownership; those controllers may act again later. Explicit local/group/manual control has priority over house rules. Global ON respects compressor protection and is shown in the shared queue when a start must wait; global OFF remains immediate.
|
||||
Global **Turn all off** is an immediate bulk thermostat action: it powers every enabled unit off and leaves every thermostat locally OFF with no hand-back timer, but it does not create a persistent global gate. A later local thermostat or manual group action can therefore re-enable only its own scope. **Turn all on** releases those local OFF markers and sends ON to all enabled units; compressor protection may queue protected starts. Explicit local/group/manual control has priority over house rules.
|
||||
|
||||
### Profiles and schedules
|
||||
|
||||
|
||||
+9
-9
@@ -1,6 +1,6 @@
|
||||
# GREE Controller API reference
|
||||
|
||||
HTTP and WebSocket API for GREE Controller **0.9.3**.
|
||||
HTTP and WebSocket API for GREE Controller **0.9.4**.
|
||||
|
||||
[← Main documentation](../README.md)
|
||||
|
||||
@@ -139,7 +139,7 @@ Common statuses:
|
||||
| DELETE | `/api/groups/{id}` | Delete a group. |
|
||||
| POST | `/api/groups/{id}/control` | Group control enable/mode/preset/custom-temperature control. |
|
||||
| POST | `/api/house/control` | Set global thermostat mode. |
|
||||
| POST | `/api/house/power` | Send a one-shot ON/OFF command to all enabled units. |
|
||||
| POST | `/api/house/power` | Bulk ON/OFF for all thermostats and enabled units; no persistent global gate. |
|
||||
| POST | `/api/house/preset` | Set/clear whole-house preset override. |
|
||||
|
||||
### Schedules and automations
|
||||
@@ -195,7 +195,7 @@ Common statuses:
|
||||
| POST | `/api/integrations/home-assistant/groups/{id}/control` | Restricted group control. |
|
||||
| POST | `/api/integrations/home-assistant/house/control` | Restricted house mode. |
|
||||
| POST | `/api/integrations/home-assistant/house/preset` | Restricted house preset. |
|
||||
| POST | `/api/integrations/home-assistant/house/power` | Restricted one-shot all-units power action. |
|
||||
| POST | `/api/integrations/home-assistant/house/power` | Restricted bulk all-thermostat/all-unit power action. |
|
||||
| POST | `/api/integrations/home-assistant/zones/{id}/control` | Restricted thermostat-zone control. |
|
||||
|
||||
---
|
||||
@@ -212,7 +212,7 @@ Response:
|
||||
{
|
||||
"status": "ok",
|
||||
"name": "gree-controller",
|
||||
"version": "0.9.3",
|
||||
"version": "0.9.4",
|
||||
"uptime_seconds": 1234,
|
||||
"control_ready": true,
|
||||
"time": "2026-08-30T06:54:00Z"
|
||||
@@ -236,7 +236,7 @@ Returns the initial Web UI snapshot:
|
||||
"settings": {},
|
||||
"outdoor_temperature": null,
|
||||
"system": {
|
||||
"version": "0.9.3",
|
||||
"version": "0.9.4",
|
||||
"uptime_seconds": 1234,
|
||||
"auth_required": false,
|
||||
"control_ready": true,
|
||||
@@ -669,13 +669,13 @@ All fields optional:
|
||||
|
||||
A custom setpoint creates the same override for every member zone. For explicit Web/Home Assistant group control it stays active until the group is changed/released; scheduled group automation remains bounded by the normal schedule hand-back. Returns a group control/result object including updated members/state.
|
||||
|
||||
Group `power` is a scoped bulk-power/control action. `power=false` immediately powers member units off and releases `group:*` ownership; members are stored as individually-off thermostats rather than being blocked by membership in an OFF group. This group-created OFF is indefinite (no 15-minute local hand-back), so the regulator cannot restart the unit by itself; a user can still turn an individual thermostat back on independently. `power=true` clears that scoped thermostat-OFF state and immediately re-runs group thermostat arbitration. Explicit Web/Home Assistant group control has higher priority than house rules and schedules; scheduled group automations still respect higher-priority manual/local ownership. Group mode/preset/setpoint changes are accepted only while the group is ON. Global ON/OFF actions are one-shot physical commands and never create a persistent group/zone gate.
|
||||
Group `power` is a scoped bulk-power/control action. `power=false` immediately powers member units off and releases `group:*` ownership; members are stored as individually-off thermostats rather than being blocked by membership in an OFF group. This group-created OFF is indefinite (no 15-minute local hand-back), so the regulator cannot restart the unit by itself; a user can still turn an individual thermostat back on independently. `power=true` clears that scoped thermostat-OFF state and immediately re-runs group thermostat arbitration. Explicit Web/Home Assistant group control has higher priority than house rules and schedules; scheduled group automations still respect higher-priority manual/local ownership. Group mode/preset/setpoint changes are accepted only while the group is ON. Global ON/OFF never creates a persistent global gate. Global OFF does leave member thermostats locally OFF until they are explicitly re-enabled by local/group/global ON.
|
||||
|
||||
---
|
||||
|
||||
## Whole-house control
|
||||
|
||||
House thermostat rules and global power actions are intentionally separate. Global ON/OFF is one-shot; it is not an automation enable/disable state.
|
||||
House thermostat rules and global power actions are intentionally separate. Global ON/OFF does not create a master automation gate; OFF is persisted as per-zone local OFF so ordinary demand cannot immediately undo it.
|
||||
|
||||
### `POST /api/house/control`
|
||||
|
||||
@@ -700,7 +700,7 @@ Returns public runtime settings.
|
||||
}
|
||||
```
|
||||
|
||||
This endpoint is a **one-shot physical command**. `false` sends OFF to every technically enabled unit; `true` sends ON to every technically enabled unit. It does not change group enablement, local thermostat ownership, direct/manual takeover, schedules, house mode or automation rules. Those controllers may issue a later command independently. Pending compressor-protection tasks from before the global action are cleared and may be recreated by a later fresh thermostat decision. When compressor protection is enabled, a global `true` request for a recently stopped thermostat-managed unit is queued until its safe start deadline and appears in the compressor queue; `false` is never delayed by compressor protection.
|
||||
This endpoint is a **bulk thermostat power action without a persistent global gate**. `false` first stores every thermostat as an indefinite local OFF and then sends immediate OFF to every technically enabled unit, so the next normal demand cycle cannot turn the house back on. A later explicit local thermostat or manual group action may re-enable only its own scope. `true` releases local OFF markers for all thermostats and sends ON to all technically enabled units. Group enablement, profiles, schedules and house mode are preserved. Pending compressor-protection tasks from before the action are cleared. When compressor protection is enabled, protected global starts are queued until their safe deadline; OFF is never delayed.
|
||||
|
||||
Response includes:
|
||||
|
||||
@@ -1109,7 +1109,7 @@ Modes: `problems`, `important`. Providers: `pushover`, `slack`, `discord`.
|
||||
|
||||
Accepts the complete `RuntimeSettings` document. Important behavior:
|
||||
|
||||
- `house_mode` cannot be changed here; use House Control API. `house_power_enabled` is a legacy compatibility field and is normalized to `true` because global ON/OFF is one-shot.
|
||||
- `house_mode` cannot be changed here; use House Control API. `house_power_enabled` is a legacy compatibility field and is normalized to `true`; global ON/OFF no longer uses a persistent master gate.
|
||||
- polling interval is clamped `2..3600` seconds.
|
||||
- zone interval is clamped `2..3600` seconds.
|
||||
- discovery timeout is clamped `300..30000` ms.
|
||||
|
||||
@@ -135,7 +135,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** changes the house rule for zones that inherit the global mode. **Do not control** pauses only that inherited house rule; explicit local/group/manual control remains independent.
|
||||
- **All air conditioners** (`switch`) sends one-shot physical ON/OFF commands to all enabled units. It does not enable/disable automation or erase local/group/manual ownership. Its displayed state reflects whether all currently enabled units are physically ON.
|
||||
- **All air conditioners** (`switch`) performs bulk thermostat power control. OFF powers all enabled units down and leaves their thermostats locally OFF with no hand-back timer; later local/group/global ON can re-enable the selected scope. ON releases local OFF states and respects compressor protection. Its displayed state reflects whether all currently enabled units are physically ON.
|
||||
|
||||
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.9.3",
|
||||
"version": "0.9.4",
|
||||
"config_flow": true,
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
|
||||
+4
-4
@@ -549,11 +549,11 @@
|
||||
"house.noControl": "Do not control",
|
||||
"house.powerAllEyebrow": "Global power",
|
||||
"house.powerAllTitle": "All air conditioners",
|
||||
"house.powerAllHint": "One-shot power control for all air conditioners. It does not enable or disable automation: thermostats, groups, house rules and manual controls continue independently after the command.",
|
||||
"house.powerAllHint": "Bulk power control for all units. OFF leaves thermostats locally off until you explicitly turn a thermostat, group or the whole house back on.",
|
||||
"house.powerOnAll": "Turn all on",
|
||||
"house.powerOffAll": "Turn all off",
|
||||
"house.powerOnDone": "One-shot ON sent to all air conditioners",
|
||||
"house.powerOffDone": "One-shot OFF sent to all air conditioners",
|
||||
"house.powerOnDone": "All thermostats enabled and ON sent to all units",
|
||||
"house.powerOffDone": "All units turned off; thermostats remain locally off",
|
||||
"house.powerPartial": "Could not change power on {count} devices",
|
||||
"preset.manual": "Manual",
|
||||
"nav.groups": "Groups",
|
||||
@@ -728,7 +728,7 @@
|
||||
"history.zoomOut": "Zoom out",
|
||||
"history.zoomReset": "Reset zoom",
|
||||
"history.zoomIn": "Zoom in",
|
||||
"house.powerOffConfirm": "Turn all air conditioners off now? This is a one-shot command — thermostats, groups and automation stay active and may change unit state later.",
|
||||
"house.powerOffConfirm": "Turn all air conditioners off? All thermostats will remain locally OFF with no timer. You can later enable an individual thermostat, a group, or the whole house.",
|
||||
"zones.temporaryStarting": "Starting…",
|
||||
"notifications.applicationTokenPlaceholder": "Application token",
|
||||
"notifications.userKeyPlaceholder": "User/group key",
|
||||
|
||||
+4
-4
@@ -549,11 +549,11 @@
|
||||
"house.noControl": "Nie steruj",
|
||||
"house.powerAllEyebrow": "Zasilanie globalne",
|
||||
"house.powerAllTitle": "Wszystkie klimatyzatory",
|
||||
"house.powerAllHint": "Jednorazowe sterowanie zasilaniem wszystkich klimatyzatorów. Nie włącza ani nie wyłącza automatyki: po wykonaniu polecenia termostaty, grupy, zasady domu i sterowanie ręczne działają dalej niezależnie.",
|
||||
"house.powerAllHint": "Sterowanie zasilaniem wszystkich jednostek. WYŁ. pozostawia termostaty lokalnie wyłączone do ręcznego włączenia termostatu, grupy lub całego domu.",
|
||||
"house.powerOnAll": "Włącz wszystkie",
|
||||
"house.powerOffAll": "Wyłącz wszystkie",
|
||||
"house.powerOnDone": "Wysłano jednorazowe włączenie wszystkich klimatyzatorów",
|
||||
"house.powerOffDone": "Wysłano jednorazowe wyłączenie wszystkich klimatyzatorów",
|
||||
"house.powerOnDone": "Włączono wszystkie termostaty i wysłano polecenie włączenia jednostek",
|
||||
"house.powerOffDone": "Wyłączono wszystkie jednostki; termostaty pozostają lokalnie wyłączone",
|
||||
"house.powerPartial": "Nie udało się zmienić zasilania {count} urządzeń",
|
||||
"preset.manual": "Ręcznie",
|
||||
"nav.groups": "Grupy",
|
||||
@@ -728,7 +728,7 @@
|
||||
"history.zoomOut": "Pomniejsz wykres",
|
||||
"history.zoomReset": "Przywróć powiększenie",
|
||||
"history.zoomIn": "Powiększ wykres",
|
||||
"house.powerOffConfirm": "Wyłączyć teraz wszystkie klimatyzatory? To jednorazowe polecenie — termostaty, grupy i automatyka pozostają aktywne i mogą później ponownie zmienić stan jednostek.",
|
||||
"house.powerOffConfirm": "Wyłączyć wszystkie klimatyzatory? Wszystkie termostaty pozostaną lokalnie WYŁ. bez timera. Później możesz włączyć pojedynczy termostat, grupę albo cały dom.",
|
||||
"zones.temporaryStarting": "Uruchamianie…",
|
||||
"notifications.applicationTokenPlaceholder": "Token aplikacji",
|
||||
"notifications.userKeyPlaceholder": "Klucz użytkownika/grupy",
|
||||
|
||||
+34
-29
@@ -1,24 +1,6 @@
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct HouseControlPatch { mode: String }
|
||||
|
||||
async fn rearm_all_compressor_queues(state: &AppState) -> Result<(), AppError> {
|
||||
let mut zone_ids: Vec<String> = state.db.list_zones()?.into_iter().map(|zone| zone.id).collect();
|
||||
zone_ids.sort();
|
||||
zone_ids.dedup();
|
||||
for zone_id in zone_ids {
|
||||
let _zone_guard = state.lock_zone_operation(&zone_id).await;
|
||||
let Some(mut zone) = state.db.get_zone(&zone_id)? else { continue; };
|
||||
if zone.compressor_pending_action.is_none() && zone.compressor_cancelled_action.is_none()
|
||||
&& zone.lockout_until.is_none() && zone.lockout_reason.is_none() { continue; }
|
||||
engine::rearm_compressor_queue(&mut zone);
|
||||
zone.revision = zone.revision.saturating_add(1);
|
||||
zone.updated_at = Utc::now();
|
||||
state.db.save_zone(&zone)?;
|
||||
state.broadcast("zone.updated", serde_json::to_value(&zone)?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
async fn rearm_house_automation_compressor_queues(state: &AppState) -> Result<(), AppError> {
|
||||
let mut zone_ids: Vec<String> = state.db.list_zones()?.into_iter().map(|zone| zone.id).collect();
|
||||
@@ -42,12 +24,31 @@ async fn rearm_house_automation_compressor_queues(state: &AppState) -> Result<()
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn set_all_thermostat_power_state(state: &AppState, power: bool) -> Result<usize, AppError> {
|
||||
let mut zone_ids: Vec<String> = state.db.list_zones()?.into_iter().map(|zone| zone.id).collect();
|
||||
zone_ids.sort();
|
||||
zone_ids.dedup();
|
||||
let mut changed = 0usize;
|
||||
for zone_id in zone_ids {
|
||||
let _zone_guard = state.lock_zone_operation(&zone_id).await;
|
||||
let Some(mut zone) = state.db.get_zone(&zone_id)? else { continue; };
|
||||
engine::rearm_compressor_queue(&mut zone);
|
||||
if engine::set_house_bulk_thermostat_power(&mut zone, power) { changed += 1; }
|
||||
engine::refresh_control_ownership(&mut zone, true);
|
||||
zone.revision = zone.revision.saturating_add(1);
|
||||
zone.updated_at = Utc::now();
|
||||
state.db.save_zone(&zone)?;
|
||||
state.broadcast("zone.updated", serde_json::to_value(&zone)?);
|
||||
}
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
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 { continue; }
|
||||
// Global ON/OFF is deliberately a one-shot physical command. It does not mutate
|
||||
// thermostat/group/manual ownership, so those controllers may make a later decision.
|
||||
// The per-zone thermostat power state is persisted before these physical commands.
|
||||
// OFF is immediate; ON still respects compressor protection.
|
||||
let result = if power {
|
||||
engine::one_shot_house_power_on_device(state, &device.id).await
|
||||
} else {
|
||||
@@ -112,9 +113,10 @@ async fn update_house_power(State(state): State<AppState>, Json(input): Json<Hou
|
||||
let _house_guard = state.lock_house_operation().await;
|
||||
let _cycle_guard = state.lock_zone_control_cycle().await;
|
||||
|
||||
// v0.9.3: this endpoint is a one-shot physical action, not a persistent automation gate.
|
||||
// Preserve every zone/group/manual owner exactly as-is. A later thermostat, group, rule or
|
||||
// manual action is therefore free to issue its own command independently.
|
||||
// Global power is a bulk thermostat action, not a persistent master gate. OFF stores every
|
||||
// thermostat as an indefinite local OFF so the next regulator cycle cannot immediately
|
||||
// resurrect demand. ON releases that OFF state. Later explicit local/group/manual actions
|
||||
// remain independent and can re-enable only the selected scope.
|
||||
{
|
||||
let mut settings = state.settings.write().await;
|
||||
if !settings.house_power_enabled {
|
||||
@@ -124,10 +126,11 @@ async fn update_house_power(State(state): State<AppState>, Json(input): Json<Hou
|
||||
}
|
||||
}
|
||||
|
||||
// Drop stale protection tasks from the intent that existed before this global physical
|
||||
// command. They may be recreated by a later thermostat cycle if demand still exists.
|
||||
rearm_all_compressor_queues(&state).await?;
|
||||
let failed = command_all_enabled_devices_power(&state, input.power, "house_power_one_shot").await?;
|
||||
// Persist the thermostat power intent before touching devices. The cycle lock held by this
|
||||
// handler guarantees that no setpoint-modulation cycle can race between the marker and OFF.
|
||||
let changed_zones = set_all_thermostat_power_state(&state, input.power).await?;
|
||||
let failed = command_all_enabled_devices_power(&state, input.power, "house_power_bulk").await?;
|
||||
state.wake_zone_control();
|
||||
|
||||
let devices = state.db.list_devices()?;
|
||||
let groups = state.db.list_groups()?;
|
||||
@@ -135,13 +138,15 @@ async fn update_house_power(State(state): State<AppState>, Json(input): Json<Hou
|
||||
let settings_payload = public_settings(&settings);
|
||||
drop(settings);
|
||||
state.log("info", "house.power_all", if input.power {
|
||||
"One-shot whole-house ON sent; thermostat/group/manual ownership preserved"
|
||||
"Whole-house ON sent; local OFF state released for all thermostats"
|
||||
} else {
|
||||
"One-shot whole-house OFF sent; thermostat/group/manual ownership preserved"
|
||||
"Whole-house OFF sent; all thermostats left locally OFF until explicitly re-enabled"
|
||||
}, json!({
|
||||
"power": input.power,
|
||||
"failed": failed.len(),
|
||||
"changed_zones": changed_zones,
|
||||
"one_shot": true,
|
||||
"persistent_global_gate": false,
|
||||
}));
|
||||
Ok(Json(json!({
|
||||
"power": input.power,
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ async fn update_settings(State(state): State<AppState>, Json(mut input): Json<Ru
|
||||
"house_mode must be changed through the House Control API".into(),
|
||||
));
|
||||
}
|
||||
// Compatibility-only field: global ON/OFF is one-shot and never disables automation.
|
||||
// Compatibility-only field: global ON/OFF no longer uses a persistent master automation gate.
|
||||
input.house_power_enabled = true;
|
||||
input.poll_interval_seconds = input.poll_interval_seconds.clamp(2, 3600);
|
||||
input.zone_interval_seconds = input.zone_interval_seconds.clamp(2, 3600);
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
pub async fn send_command(state: &AppState, device_id: &str, command: DeviceCommand) -> Result<Device, AppError> {
|
||||
let _device_guard = state.lock_device_operation(device_id).await;
|
||||
send_command_locked(state, device_id, command).await
|
||||
}
|
||||
|
||||
async fn send_command_locked(state: &AppState, device_id: &str, command: DeviceCommand) -> Result<Device, AppError> {
|
||||
send_command_locked_inner(state, device_id, command, true, true).await
|
||||
}
|
||||
|
||||
@@ -70,8 +70,13 @@ pub async fn control_group(state: &AppState, group_id: &str, patch: GroupControl
|
||||
// releases group ownership: members are represented as individually OFF, not as
|
||||
// "blocked by a disabled group". ON clears that scoped OFF and lets group arbitration
|
||||
// take ownership again. The whole-house master remains independent.
|
||||
let explicit_group_power = patch.power.is_some() && source != "automation.group";
|
||||
let manual_group_control = source != "automation.group";
|
||||
let explicit_group_power = patch.power.is_some() && manual_group_control;
|
||||
// Applying a manual mode/profile/custom target to an already-enabled group is itself an
|
||||
// explicit scoped takeover. It must wake members that were left locally OFF by a previous
|
||||
// group/whole-house OFF; otherwise the UI says "group control" while every unit stays OFF.
|
||||
let manual_group_activation = manual_group_control && group.power_enabled && climate_change;
|
||||
let explicit_group_takeover = explicit_group_power || manual_group_activation;
|
||||
|
||||
let mut zones = Vec::new();
|
||||
let mut failed = Vec::new();
|
||||
@@ -89,7 +94,7 @@ pub async fn control_group(state: &AppState, group_id: &str, patch: GroupControl
|
||||
// or temporary ownership so all members react consistently to the bulk command.
|
||||
// Scheduled automations deliberately do not do this: manual/local ownership keeps
|
||||
// its higher priority and the automation can only affect currently free members.
|
||||
if explicit_group_power {
|
||||
if explicit_group_takeover {
|
||||
if zone.temporary_quick_thermostat.is_some() {
|
||||
if temporary_owns_zone {
|
||||
finish_temporary_quick_thermostat(&mut zone, &schedules, &state.settings.read().await.house_mode);
|
||||
@@ -161,7 +166,8 @@ pub async fn control_group(state: &AppState, group_id: &str, patch: GroupControl
|
||||
// Power ON clears this scoped OFF and returns the member to group arbitration.
|
||||
let mut force_power_off_after_save = false;
|
||||
let mut applied_group_power: Option<bool> = None;
|
||||
if let Some(power) = patch.power {
|
||||
let requested_member_power = patch.power.or(manual_group_activation.then_some(true));
|
||||
if let Some(power) = requested_member_power {
|
||||
let automatic_power_blocked = source == "automation.group"
|
||||
&& (zone.device_manual_override || zone.local_thermostat_power.is_some() || temporary_owns_zone);
|
||||
if automatic_power_blocked {
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
pub const LOCAL_THERMOSTAT_RESUME_DELAY_MINUTES: i64 = 15;
|
||||
|
||||
/// Apply the whole-house bulk power state to a thermostat without creating a persistent
|
||||
/// global gate. OFF leaves the zone explicitly/indefinitely off; ON releases that local OFF
|
||||
/// so the zone can immediately return to its group/manual/automatic controller.
|
||||
pub fn set_house_bulk_thermostat_power(zone: &mut Zone, power: bool) -> bool {
|
||||
if power {
|
||||
if zone.local_thermostat_power != Some(false) { return false; }
|
||||
zone.local_thermostat_power = None;
|
||||
zone.local_thermostat_resume_at = None;
|
||||
zone.local_thermostat_restore_zone_enabled = None;
|
||||
true
|
||||
} else {
|
||||
let changed = zone.local_thermostat_power != Some(false)
|
||||
|| zone.local_thermostat_resume_at.is_some()
|
||||
|| zone.local_thermostat_restore_zone_enabled.is_some()
|
||||
|| zone.demand
|
||||
|| zone.demand_since.is_some();
|
||||
zone.local_thermostat_power = Some(false);
|
||||
zone.local_thermostat_resume_at = None;
|
||||
zone.local_thermostat_restore_zone_enabled = None;
|
||||
zone.demand = false;
|
||||
zone.demand_since = None;
|
||||
zone.effective_mode = "off".into();
|
||||
zone.device_setpoint = None;
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply local quick-thermostat power ownership and keep the automatic hand-back
|
||||
/// deadline in one backend-owned place. Every fresh OFF action receives a fresh
|
||||
/// deadline; ON cancels any pending hand-back.
|
||||
|
||||
+5
-21
@@ -155,7 +155,7 @@ fn set_device_manual_override(state: &AppState, zone: &mut Zone, fields: Vec<Str
|
||||
zone.control_source = normalized_direct_source(source).into();
|
||||
zone.control_reason = "Direct/manual device control has priority".into();
|
||||
// Direct/pilot control is an explicit ownership takeover, not a temporary preset.
|
||||
// Keep it manual until the user explicitly chooses Resume automation. A one-shot global
|
||||
// Keep it manual until the user explicitly chooses Resume automation. A global
|
||||
// ON/OFF command changes physical power only and must never erase manual ownership.
|
||||
// A schedule boundary must never silently take the unit back from the person controlling it.
|
||||
zone.device_manual_override_until = None;
|
||||
@@ -262,9 +262,8 @@ pub async fn send_manual_command(state: &AppState, device_id: &str, command: Dev
|
||||
}
|
||||
|
||||
pub async fn force_house_power_off_device(state: &AppState, device_id: &str, _source: &str) -> Result<Device, AppError> {
|
||||
// Whole-house OFF is a one-shot physical action. Preserve every zone/manual/group owner,
|
||||
// while still taking zone -> device locks so a concurrent local/manual action cannot race
|
||||
// the forced OFF frame. Controllers are free to make a later independent decision.
|
||||
// Whole-house OFF physically forces the unit down after the API has persisted per-zone local OFF.
|
||||
// Keep zone -> device ordering so a concurrent local/manual action cannot race the frame.
|
||||
let mut zone_ids: Vec<String> = state.db.list_zones()?.into_iter()
|
||||
.filter(|zone| zone.device_id == device_id)
|
||||
.map(|zone| zone.id)
|
||||
@@ -280,7 +279,7 @@ pub async fn force_house_power_off_device(state: &AppState, device_id: &str, _so
|
||||
}
|
||||
|
||||
pub async fn one_shot_house_power_on_device(state: &AppState, device_id: &str) -> Result<Device, AppError> {
|
||||
// Global ON is also one-shot and must not take thermostat ownership. For thermostat-managed
|
||||
// Global ON releases per-zone OFF state in the API and must not create a local-ON ownership marker. For thermostat-managed
|
||||
// units it still respects compressor protection; a protected start is stored as a visible
|
||||
// queue item and executed at the protection deadline unless a newer intent cancels/replaces it.
|
||||
let mut zone_ids: Vec<String> = state.db.list_zones()?.into_iter()
|
||||
@@ -330,7 +329,7 @@ pub async fn one_shot_house_power_on_device(state: &AppState, device_id: &str) -
|
||||
}
|
||||
}
|
||||
|
||||
// The device lock is already held. This is physical one-shot power only: no manual marker.
|
||||
// The device lock is already held. This is physical global power only: no manual marker.
|
||||
send_command_locked(state, device_id, DeviceCommand { power: Some(true), ..Default::default() }).await
|
||||
}
|
||||
|
||||
@@ -365,18 +364,3 @@ pub async fn disable_device_safely(state: &AppState, device_id: &str) -> Result<
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
pub fn clear_all_device_manual_overrides(state: &AppState, source: &str) -> Result<usize, AppError> {
|
||||
let mut cleared = 0usize;
|
||||
for mut zone in state.db.list_zones()? {
|
||||
if !reset_device_manual_override(&mut zone) { continue; }
|
||||
zone.updated_at = Utc::now();
|
||||
state.db.save_zone(&zone)?;
|
||||
state.broadcast("zone.updated", serde_json::to_value(&zone)?);
|
||||
state.log("info", "zone.device_manual_override_cleared", &format!("Automation resumed for {}", zone.name), json!({
|
||||
"zone_id": zone.id, "device_id": zone.device_id, "source": source
|
||||
}));
|
||||
cleared += 1;
|
||||
}
|
||||
Ok(cleared)
|
||||
}
|
||||
|
||||
|
||||
@@ -780,4 +780,34 @@ mod tests {
|
||||
assert_eq!(outdoor_assist_offset("cool", None, 27.0, 23.0), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whole_house_off_is_indefinite_until_explicit_reenable() {
|
||||
let mut zone = test_zone("device");
|
||||
zone.local_thermostat_power = Some(true);
|
||||
zone.local_thermostat_resume_at = Some(Utc::now() + chrono::Duration::minutes(15));
|
||||
zone.demand = true;
|
||||
zone.demand_since = Some(Utc::now());
|
||||
|
||||
assert!(set_house_bulk_thermostat_power(&mut zone, false));
|
||||
assert_eq!(zone.local_thermostat_power, Some(false));
|
||||
assert!(zone.local_thermostat_resume_at.is_none());
|
||||
assert!(zone.local_thermostat_restore_zone_enabled.is_none());
|
||||
assert!(!zone.demand);
|
||||
assert!(zone.demand_since.is_none());
|
||||
assert_eq!(zone.effective_mode, "off");
|
||||
|
||||
// A normal cycle must see an indefinite local OFF. Global/local/group ON releases it.
|
||||
assert!(set_house_bulk_thermostat_power(&mut zone, true));
|
||||
assert!(zone.local_thermostat_power.is_none());
|
||||
assert!(zone.local_thermostat_resume_at.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whole_house_on_does_not_create_a_local_on_override() {
|
||||
let mut zone = test_zone("device");
|
||||
zone.local_thermostat_power = None;
|
||||
assert!(!set_house_bulk_thermostat_power(&mut zone, true));
|
||||
assert!(zone.local_thermostat_power.is_none());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ async fn control_zones(state: &AppState) -> Result<()> {
|
||||
let schedules = state.db.list_schedules()?;
|
||||
let settings = state.settings.read().await.clone();
|
||||
let mut zone_snapshot = state.db.list_zones()?;
|
||||
// Local/temporary thermostat ownership is independent from one-shot whole-house power
|
||||
// Local/temporary thermostat ownership is independent from the legacy whole-house master gate
|
||||
// commands. Expire/activate sessions on their own deadlines.
|
||||
expire_local_thermostat_overrides(state, &mut zone_snapshot, &schedules, &settings.house_mode).await?;
|
||||
let temporary_restored_disabled = expire_temporary_quick_thermostats(state, &mut zone_snapshot, &schedules, &settings.house_mode).await?;
|
||||
@@ -189,7 +189,7 @@ async fn control_zones(state: &AppState) -> Result<()> {
|
||||
zone.control_temperature_source = control_source;
|
||||
zone.updated_at = Utc::now();
|
||||
|
||||
// A queued whole-house ON is a delayed one-shot physical action, not thermostat
|
||||
// A queued whole-house ON is a delayed bulk physical action, not thermostat
|
||||
// ownership. It must survive local/group/manual state while compressor protection is
|
||||
// active, then execute once and hand control straight back to the existing owner.
|
||||
if zone.compressor_pending_action.as_deref() == Some("global_power_on") {
|
||||
@@ -216,7 +216,7 @@ async fn control_zones(state: &AppState) -> Result<()> {
|
||||
}
|
||||
Err(err) => {
|
||||
// Keep the user-visible task and retry on a bounded deadline instead of
|
||||
// spinning immediately or silently dropping a one-shot request.
|
||||
// spinning immediately or silently dropping the requested global start.
|
||||
zone.compressor_pending_until = Some(Utc::now() + chrono::Duration::seconds(10));
|
||||
zone.lockout_until = zone.compressor_pending_until;
|
||||
zone.lockout_reason = Some("global_start_retry".into());
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ async fn main() -> Result<()> {
|
||||
runtime_settings.discovery_broadcast = config.discovery_broadcast.clone();
|
||||
}
|
||||
config.apply_runtime_env_overrides(&mut runtime_settings);
|
||||
// v0.9.3: whole-house ON/OFF is a one-shot physical action, not a persistent master gate.
|
||||
// v0.9.4: whole-house ON/OFF is a bulk thermostat action, not a persistent master gate.
|
||||
// Normalize databases upgraded from older releases so a historical OFF cannot suppress
|
||||
// thermostats, groups or automations after restart.
|
||||
runtime_settings.house_power_enabled = true;
|
||||
|
||||
@@ -95,7 +95,7 @@ pub struct ControlPlan {
|
||||
pub house_mode: String,
|
||||
/// Uniform whole-house preset when every zone uses the same override; None for a mixed state.
|
||||
pub house_preset: Option<String>,
|
||||
/// Compatibility field. Global ON/OFF is a one-shot action; this is always true in v0.9.3+.
|
||||
/// Compatibility field. Global ON/OFF does not use a persistent master gate; this remains true in v0.9.4+.
|
||||
pub house_power: bool,
|
||||
pub outdoor_temperature: Option<f64>,
|
||||
pub control_strategy: String,
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct RuntimeSettings {
|
||||
/// Global seasonal mode. Zones follow this by default. Values: cool/heat/off; off pauses house-level thermostat control.
|
||||
#[serde(default = "default_house_mode")]
|
||||
pub house_mode: String,
|
||||
/// Legacy compatibility flag. Since v0.9.3 global ON/OFF is a one-shot command, not an
|
||||
/// Legacy compatibility flag. Since v0.9.4 global ON/OFF is a bulk thermostat action, not an
|
||||
/// automation gate. The controller normalizes this field to true on load/import.
|
||||
#[serde(default = "default_true")]
|
||||
pub house_power_enabled: bool,
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@
|
||||
</div>
|
||||
<div class="panel dashboard-global-panel">
|
||||
<div class="dashboard-global-power">
|
||||
<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">One-shot power command for all units. Thermostats, groups, house rules and manual controls remain independent afterwards.</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">Bulk power action for all units. OFF leaves thermostats locally off until you explicitly turn a thermostat, group or the whole house 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="dashboard-global-divider"></div>
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ function renderHouseClimate() {
|
||||
const preset = zonePresets.length && zonePresets.every(value => value === zonePresets[0]) ? zonePresets[0] : null;
|
||||
const outdoor = app.outdoorTemperature == null ? tr('common.unavailable') : fmtTemp(app.outdoorTemperature);
|
||||
const powerOn = $('#housePowerOn'), powerOff = $('#housePowerOff');
|
||||
// Global power buttons are one-shot actions, not a persistent ON/OFF mode.
|
||||
// Global power buttons are bulk thermostat actions, not a persistent master gate.
|
||||
if (powerOn) { powerOn.classList.remove('active'); powerOn.removeAttribute('aria-pressed'); }
|
||||
if (powerOff) { powerOff.classList.remove('active'); powerOff.removeAttribute('aria-pressed'); }
|
||||
node.innerHTML = `<div class="house-climate-head"><div><span class="eyebrow">${esc(tr('house.seasonMode'))}</span><h3>${esc(tr('house.smartThermostat'))}</h3><p>${esc(tr('house.setpointStrategy'))}</p></div><div class="outside-pill"><small>${esc(tr('house.outdoor'))}</small><strong>${esc(outdoor)}</strong></div></div>
|
||||
|
||||
Reference in New Issue
Block a user