v0.13.4
This commit is contained in:
Generated
+1
-1
@@ -700,7 +700,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gree-controller"
|
name = "gree-controller"
|
||||||
version = "0.13.2"
|
version = "0.13.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "gree-controller"
|
name = "gree-controller"
|
||||||
version = "0.13.2"
|
version = "0.13.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["GREE Controller contributors"]
|
authors = ["GREE Controller contributors"]
|
||||||
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
|
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
|
||||||
|
|||||||
+19
-19
@@ -1,13 +1,13 @@
|
|||||||
d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example
|
d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example
|
||||||
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
||||||
3ddb42eef37a20f8ace4cfca42bcb0a96b8e6de94f82fcf7f7a0fffc2c31b34a ./Cargo.lock
|
5827ea491858a5148407964f10d6570c3e9a7edfcf46d92053fc881b5bb87cc1 ./Cargo.lock
|
||||||
71ee004b46883be6d12098c1a6cde9caf8076e8beb041e2cafd38c7108859ba1 ./Cargo.toml
|
80b75728418ff0b6089b5eb46ad1acaf7a9bb20ca000a8027aeed5b56e11078f ./Cargo.toml
|
||||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||||
97d2ed6c330e71b243de9816b50c191361ede2968ce9c0ebd31efbee35eec003 ./README.md
|
43b365c82085970f78e8bce11cdfc8e12802b04e0ab5bb69116c38f49c41e025 ./README.md
|
||||||
d56cfff4179a6511aa764a91c6ddebe21fcd35f36b7298e62d012e11f62bfeb9 ./build.rs
|
d56cfff4179a6511aa764a91c6ddebe21fcd35f36b7298e62d012e11f62bfeb9 ./build.rs
|
||||||
ad559c9b776cb4dcc1febc06879d8c9154f7b8ff3e0e0b4b9b394f87ec571131 ./docs/API.md
|
714f6a2e2ef8fb9e7541334ee3709983e0b7db736dfb8be10d40fa65e59275ef ./docs/API.md
|
||||||
2e1e18fd8167dabfe2469c26e85cce62486c7cb6a502c63c6f6b0cd74d5885e0 ./docs/FLOW.md
|
2e1e18fd8167dabfe2469c26e85cce62486c7cb6a502c63c6f6b0cd74d5885e0 ./docs/FLOW.md
|
||||||
ceeef0504298dc11b5964aa898e2425fc0847e0d9c1c1436d113161431a75917 ./docs/openapi.json
|
304267d029ba6df73d1877be3c390709f35028a63c56e7265871df93b1889a4c ./docs/openapi.json
|
||||||
b11e581dd916676e24402112fe6896bc681abf3c94cafc85c4d3ded3f3a272a3 ./home-assistant/README.md
|
b11e581dd916676e24402112fe6896bc681abf3c94cafc85c4d3ded3f3a272a3 ./home-assistant/README.md
|
||||||
f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./home-assistant/custom_components/gree_controller/__init__.py
|
f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./home-assistant/custom_components/gree_controller/__init__.py
|
||||||
3f6ef15ef58456376ac53fde7cace1ef359d6a6f7a64c5b575ad77ea6e55ccc4 ./home-assistant/custom_components/gree_controller/api.py
|
3f6ef15ef58456376ac53fde7cace1ef359d6a6f7a64c5b575ad77ea6e55ccc4 ./home-assistant/custom_components/gree_controller/api.py
|
||||||
@@ -18,7 +18,7 @@ b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./home-assista
|
|||||||
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./home-assistant/custom_components/gree_controller/entity_map.py
|
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/icon.png
|
||||||
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./home-assistant/custom_components/gree_controller/logo.png
|
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./home-assistant/custom_components/gree_controller/logo.png
|
||||||
2cc326a9a839908eca36bc3f0c51f38c53c05c6093c678b80ac7b2ad3b323e9a ./home-assistant/custom_components/gree_controller/manifest.json
|
5006c0d96baf0760780861ead24125579b6cef719dc217ffd51e60bde06da9a9 ./home-assistant/custom_components/gree_controller/manifest.json
|
||||||
38d659d0273c0d1428679e44574a2666c605d1b3796d323b626baa40560788cf ./home-assistant/custom_components/gree_controller/number.py
|
38d659d0273c0d1428679e44574a2666c605d1b3796d323b626baa40560788cf ./home-assistant/custom_components/gree_controller/number.py
|
||||||
39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./home-assistant/custom_components/gree_controller/select.py
|
39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./home-assistant/custom_components/gree_controller/select.py
|
||||||
cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./home-assistant/custom_components/gree_controller/sensor.py
|
cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./home-assistant/custom_components/gree_controller/sensor.py
|
||||||
@@ -27,8 +27,8 @@ cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./home-assista
|
|||||||
13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./home-assistant/custom_components/gree_controller/translations/pl.json
|
13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./home-assistant/custom_components/gree_controller/translations/pl.json
|
||||||
c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./home-assistant/generated/gree_controller_entities.example.json
|
c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./home-assistant/generated/gree_controller_entities.example.json
|
||||||
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
||||||
810bf9a9483277cbde14454b6b88dbfe79b873a6f34737031a7e10d23fda9299 ./lang/en.json
|
48660321f53b5ff4f1fefef1423dde85ba43e1ea33421b9dad24d487df73c03a ./lang/en.json
|
||||||
ad48d11875aa041c0f593aa6e322f8d953aef137d4df4c369c5f82eec5aebd11 ./lang/pl.json
|
b0773fb211fccb037b170c70b1f655a3852dc582050dc1f726d79e132a277199 ./lang/pl.json
|
||||||
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
||||||
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
|
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
|
||||||
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
|
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
|
||||||
@@ -69,7 +69,7 @@ fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/wind
|
|||||||
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
|
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
|
||||||
01952aa92b217f8eae2493b88870e2dec595100cd15c4d561ff11ae2b936c46f ./regenerate-sha.sh
|
01952aa92b217f8eae2493b88870e2dec595100cd15c4d561ff11ae2b936c46f ./regenerate-sha.sh
|
||||||
c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md
|
c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md
|
||||||
93d28fcfb77b59548ac4f4f0e03372cae8fb286142e7eb3130b438fd180caf6e ./scripts/api_dev_test.py
|
a2424afcc056255a85769086ddbf1b8038b58015c2e4b9bef862ed8a7ede5340 ./scripts/api_dev_test.py
|
||||||
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
||||||
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
|
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
|
||||||
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
|
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
|
||||||
@@ -102,7 +102,7 @@ e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/hous
|
|||||||
9d157e1901081c077e32430d5d4d7f328ffb4933996579f584ce2959d5d10f47 ./src/api/settings.rs
|
9d157e1901081c077e32430d5d4d7f328ffb4933996579f584ce2959d5d10f47 ./src/api/settings.rs
|
||||||
df3297f36f0d0fa071b872967c5c124ed3887805d4f0f1031761cfba25331e97 ./src/api/system.rs
|
df3297f36f0d0fa071b872967c5c124ed3887805d4f0f1031761cfba25331e97 ./src/api/system.rs
|
||||||
badd52159fa5d19c236cf9022b623d15c6fbfc7f0b7fc59e269717eb5ee3d10b ./src/api/websocket.rs
|
badd52159fa5d19c236cf9022b623d15c6fbfc7f0b7fc59e269717eb5ee3d10b ./src/api/websocket.rs
|
||||||
c742634b5c8001b8e31ce313a1114abb1f44f18186a86860de4b6b1c01a4832f ./src/api/zones.rs
|
455c7238c9788d378e909a4fbc94d5390b67edd92e127f48b4b913ddcbe82fb0 ./src/api/zones.rs
|
||||||
f06014454d5062b1be17ac50d875a42c02fc778b59702ec455611265ab7de934 ./src/config.rs
|
f06014454d5062b1be17ac50d875a42c02fc778b59702ec455611265ab7de934 ./src/config.rs
|
||||||
e0340b0e5945d5a38a1054e549c819566d5c1ef6034ab0e4d44c68a3b9a249cf ./src/db.rs
|
e0340b0e5945d5a38a1054e549c819566d5c1ef6034ab0e4d44c68a3b9a249cf ./src/db.rs
|
||||||
6c278fc5728124ab2f53ac7789a733942a0462f397144a231cc878c8cfcf7426 ./src/db/climate.rs
|
6c278fc5728124ab2f53ac7789a733942a0462f397144a231cc878c8cfcf7426 ./src/db/climate.rs
|
||||||
@@ -116,7 +116,7 @@ c0a7fa3eac198737d7261ad00c542a614e6d7291f6581af8e7f7aef9dec1a049 ./src/db/sched
|
|||||||
1a209dc6d8df864778bb03c980f309a2ec86fc62df274880fbb95eff8470174e ./src/db/tests.rs
|
1a209dc6d8df864778bb03c980f309a2ec86fc62df274880fbb95eff8470174e ./src/db/tests.rs
|
||||||
f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs
|
f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs
|
||||||
4d2c6053586558afbca6b60fbbc9b1cd397f74f8284d7c01f3337eb42bf6d4f7 ./src/engine.rs
|
4d2c6053586558afbca6b60fbbc9b1cd397f74f8284d7c01f3337eb42bf6d4f7 ./src/engine.rs
|
||||||
31ed13a143a80f165982662667c1248213a4db7b7e60c4b4bc25c4a96163c184 ./src/engine/automations.rs
|
6046bdbc9c02ac74f47068229c59af69abc8bfa46cfeff6fe7ba573b493e128c ./src/engine/automations.rs
|
||||||
88f85acf692e12c7061e039dd7c54f101fe0b014c4a3d799150ad92eea9e2894 ./src/engine/commands.rs
|
88f85acf692e12c7061e039dd7c54f101fe0b014c4a3d799150ad92eea9e2894 ./src/engine/commands.rs
|
||||||
8ee70ad21d8ee32914dd90952b04dfa8355ee868252ef4fc682ec40503590a7a ./src/engine/control_plan.rs
|
8ee70ad21d8ee32914dd90952b04dfa8355ee868252ef4fc682ec40503590a7a ./src/engine/control_plan.rs
|
||||||
7b48a33343d519a2ff2d81e9d757eb56a0c1beacfc0b7b8ef61f49c2ad363f9a ./src/engine/deadlines.rs
|
7b48a33343d519a2ff2d81e9d757eb56a0c1beacfc0b7b8ef61f49c2ad363f9a ./src/engine/deadlines.rs
|
||||||
@@ -127,11 +127,11 @@ f0c8f3384b6174771aeb5640ee1eec87f3d6d9ff79a4fbd6a35554a4eec00cfa ./src/engine/h
|
|||||||
3d7e74a8180e14f398363af1a45b32a5e4c89f749d631afce556a1ecd5fe4e80 ./src/engine/polling.rs
|
3d7e74a8180e14f398363af1a45b32a5e4c89f749d631afce556a1ecd5fe4e80 ./src/engine/polling.rs
|
||||||
f0bcd3a56b600944ccc62fa24ce20838033eeb4354f937cdb534630c1a1f5a78 ./src/engine/runtime.rs
|
f0bcd3a56b600944ccc62fa24ce20838033eeb4354f937cdb534630c1a1f5a78 ./src/engine/runtime.rs
|
||||||
c1e8de5392681ba1255e3e2d871a6ee860fb8ca987a0a8f973d9ded3977a68fb ./src/engine/schedules.rs
|
c1e8de5392681ba1255e3e2d871a6ee860fb8ca987a0a8f973d9ded3977a68fb ./src/engine/schedules.rs
|
||||||
de80d9ff69f1c843fdac23fdc3e3a350fc1082a186261b3a38617af189132356 ./src/engine/targets.rs
|
b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/targets.rs
|
||||||
682c8b3956c657b655746cbd19ee97ee2f4cf953bc9de67d6d70a1b14f3dd746 ./src/engine/temperature.rs
|
682c8b3956c657b655746cbd19ee97ee2f4cf953bc9de67d6d70a1b14f3dd746 ./src/engine/temperature.rs
|
||||||
833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs
|
833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs
|
||||||
f4a9dc023cca9224c75ff1e9d91feac069918de93b4a344e72fef5050d6ff44f ./src/engine/tests.rs
|
338161bd94c868be6155efabdc63e81e9a572d559cc00493d6c69f15cc1612a3 ./src/engine/tests.rs
|
||||||
e6f8cfd1e4044b2f833d0f74c85ca664dd08dfb3e57bcddbafe7581cf7c63081 ./src/engine/zone_actions.rs
|
c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs
|
||||||
dd38a8b8f6e910e4475c42ae41eaad2c68237fb36b78a3d7dd5d147c08487568 ./src/engine/zone_control.rs
|
dd38a8b8f6e910e4475c42ae41eaad2c68237fb36b78a3d7dd5d147c08487568 ./src/engine/zone_control.rs
|
||||||
2276aca42bca712a55de9b6c5dbe64639675c84befa85a446a7d34613596ed7e ./src/error.rs
|
2276aca42bca712a55de9b6c5dbe64639675c84befa85a446a7d34613596ed7e ./src/error.rs
|
||||||
2731f629f22a9d4fdb831ea3b283f43900b190f7e6a08fdab5b8fbb2c288547b ./src/home_assistant.rs
|
2731f629f22a9d4fdb831ea3b283f43900b190f7e6a08fdab5b8fbb2c288547b ./src/home_assistant.rs
|
||||||
@@ -176,16 +176,16 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/
|
|||||||
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
||||||
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
||||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||||
d0388bf22f34ae28fb63074b0bc15c087a3be298bb702d006d4814ebb786b36e ./web/index.html
|
6be28fadaae2eacce9e6826a8b37e258dc48d8d101ae784ee332630614cb58cc ./web/index.html
|
||||||
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md
|
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md
|
||||||
45fb54541a117a8ee0dc484cd263b6af4aafab75d94d269b126bb603b5f0392c ./web/js/bootstrap.js
|
45fb54541a117a8ee0dc484cd263b6af4aafab75d94d269b126bb603b5f0392c ./web/js/bootstrap.js
|
||||||
badde8a5214a0322b2c45803c02a1cb723d53b25e57c687369cd804f509d9744 ./web/js/charts.js
|
badde8a5214a0322b2c45803c02a1cb723d53b25e57c687369cd804f509d9744 ./web/js/charts.js
|
||||||
dd596b3362643b96be8537b9415e8c1be90d7cf458124b61121016a6742951c7 ./web/js/core.js
|
dd596b3362643b96be8537b9415e8c1be90d7cf458124b61121016a6742951c7 ./web/js/core.js
|
||||||
d880ffdac6aa07cd10d79fbacf12f87eb4c69b48a80564bbfdd0248abe8884d5 ./web/js/dashboard.js
|
d880ffdac6aa07cd10d79fbacf12f87eb4c69b48a80564bbfdd0248abe8884d5 ./web/js/dashboard.js
|
||||||
d6fbd8ef4a67269afadb2f59f19fe9a359f7ec4d7bd1473ecff77df117b9558d ./web/js/entities.js
|
6c0d310e39a1e1f618a31fbdf5560a850023bf2219d897c69d636d0179dff004 ./web/js/entities.js
|
||||||
e332b3f811958a938105272c1e8e28d6ca8ba69303001da165b4b0b85727695c ./web/js/events.js
|
e332b3f811958a938105272c1e8e28d6ca8ba69303001da165b4b0b85727695c ./web/js/events.js
|
||||||
1b21b3ae43a6848f9fc13fd4eacaa6a1a50d44c65aa37ca737be60aa4eca8a38 ./web/js/flows.js
|
2e822a00b5a6941b6a251eaf2c54ca95a53399b5d1056f3d8240e11f82d6065f ./web/js/flows.js
|
||||||
f06557c6d338259059b990f8f79c91d68ac8ec1f245c302468f08929b5506901 ./web/js/forms.js
|
cf1ebf3b9d666f309a1add070b4da86be86d990b02c8d6474f6d121c6d332746 ./web/js/forms.js
|
||||||
dbf36223863ba882c03eccea4516ba8db7acf0c2e72c6c64490cb994f1e20f96 ./web/js/history.js
|
dbf36223863ba882c03eccea4516ba8db7acf0c2e72c6c64490cb994f1e20f96 ./web/js/history.js
|
||||||
7eaa04992ea828dc89f5eaebf674cbbfd160f53eb8d0d6ef0d4e47bd8c28c435 ./web/js/main.js
|
7eaa04992ea828dc89f5eaebf674cbbfd160f53eb8d0d6ef0d4e47bd8c28c435 ./web/js/main.js
|
||||||
733fcc595ed9c03c9639acc6caba46f0588f29e0b95bf72891e34443e7aa9db8 ./web/js/navigation.js
|
733fcc595ed9c03c9639acc6caba46f0588f29e0b95bf72891e34443e7aa9db8 ./web/js/navigation.js
|
||||||
@@ -194,6 +194,6 @@ c8c82e88c3715b1bfabf155e36266a4c94f5e2fc03eb39dcdd9d08a1985a997c ./web/js/route
|
|||||||
427376a35832cb3644206c6e89e1654bd807eec78352f80bd0dd43f74b4ec810 ./web/js/settings-ui.js
|
427376a35832cb3644206c6e89e1654bd807eec78352f80bd0dd43f74b4ec810 ./web/js/settings-ui.js
|
||||||
9dd9fbcc5f00584562bae6a840189495f830d1086d9679763dbad6e9c18c4fdb ./web/js/settings.js
|
9dd9fbcc5f00584562bae6a840189495f830d1086d9679763dbad6e9c18c4fdb ./web/js/settings.js
|
||||||
6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest
|
6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest
|
||||||
5c939ab91aa28ba386aefef6ba5f41b0c73fae3936f6e915fbc39e2336859605 ./web/styles.css
|
801bb151f7b6220587e22c3114fb224a540dcfbddd1c09870007b7b893a01301 ./web/styles.css
|
||||||
ae1b03f30b494f474a781a5d32072f1e73eba2b7c768109fc1cc8028cb6662a4 ./web/sw.js
|
ae1b03f30b494f474a781a5d32072f1e73eba2b7c768109fc1cc8028cb6662a4 ./web/sw.js
|
||||||
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
|
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
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.
|
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.13.2**
|
**Current release: 0.13.4**
|
||||||
|
|
||||||
Release 0.13.2 keeps the 0.13.x HTTP/UI contracts, retains the reduced WebSocket/control-plan churn from 0.13.1, and completes live synchronization for zone creation, schedules, automations, configuration imports, device bind/communication-health changes and automation runtime metadata. Real state changes remain pushed immediately while no-op regulator heartbeats stay deduplicated.
|
Release 0.13.4 keeps the 0.13.x HTTP/WS contracts and clarifies disabled-zone manual control in the Web UI: thermostat controls remain visually active when zone automation is disabled, while an explicit notice explains that temporary/manual thermostat and direct manual control remain available. The 0.1 °C logical thermostat precision introduced in 0.13.3 is unchanged.
|
||||||
|
|
||||||
> [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples.
|
> [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples.
|
||||||
|
|
||||||
|
|||||||
+11
-5
@@ -1,6 +1,6 @@
|
|||||||
# GREE Controller API reference
|
# GREE Controller API reference
|
||||||
|
|
||||||
HTTP and WebSocket API for GREE Controller **0.13.2**.
|
HTTP and WebSocket API for GREE Controller **0.13.4**.
|
||||||
|
|
||||||
[← Main documentation](../README.md)
|
[← Main documentation](../README.md)
|
||||||
|
|
||||||
@@ -231,7 +231,7 @@ Response:
|
|||||||
{
|
{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"name": "gree-controller",
|
"name": "gree-controller",
|
||||||
"version": "0.13.2",
|
"version": "0.13.4",
|
||||||
"uptime_seconds": 1234,
|
"uptime_seconds": 1234,
|
||||||
"control_ready": true,
|
"control_ready": true,
|
||||||
"time": "2026-08-30T06:54:00Z"
|
"time": "2026-08-30T06:54:00Z"
|
||||||
@@ -257,7 +257,7 @@ Returns the initial Web UI snapshot:
|
|||||||
"control_plan": {"generated_at": "2026-09-04T08:00:00Z", "zones": [], "rules": []},
|
"control_plan": {"generated_at": "2026-09-04T08:00:00Z", "zones": [], "rules": []},
|
||||||
"control_plan_revision": 42,
|
"control_plan_revision": 42,
|
||||||
"system": {
|
"system": {
|
||||||
"version": "0.13.2",
|
"version": "0.13.4",
|
||||||
"uptime_seconds": 1234,
|
"uptime_seconds": 1234,
|
||||||
"auth_required": false,
|
"auth_required": false,
|
||||||
"control_ready": true,
|
"control_ready": true,
|
||||||
@@ -613,11 +613,13 @@ curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: appl
|
|||||||
curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: application/json' \
|
curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: application/json' \
|
||||||
-d '{"preset":"auto"}'
|
-d '{"preset":"auto"}'
|
||||||
|
|
||||||
# Run a 90-minute temporary thermostat
|
# Run a 90-minute temporary thermostat at a decimal logical target
|
||||||
curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: application/json' \
|
curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: application/json' \
|
||||||
-d '{"temporary_quick_thermostat":{"start_kind":"now","finish_kind":"duration","duration_minutes":90,"target_temperature":23}}'
|
-d '{"temporary_quick_thermostat":{"start_kind":"now","finish_kind":"duration","duration_minutes":90,"target_temperature":24.2}}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Temporary Quick Thermostat targets are normalized to `0.1 °C`. The Web UI accepts both comma and dot decimal input (for example `24,2` and `24.2`). Physical GREE device setpoint rounding is unchanged.
|
||||||
|
|
||||||
### `POST /api/zones/{id}/schedule-template`
|
### `POST /api/zones/{id}/schedule-template`
|
||||||
|
|
||||||
Body:
|
Body:
|
||||||
@@ -1459,6 +1461,10 @@ log.created
|
|||||||
|
|
||||||
In `0.13.2`, the bundled Web UI mirrors entity CRUD events directly into its local state. Schedule-template replacement performs a targeted schedule resync, configuration import performs a full bootstrap resync, and runtime automation/device-health changes publish their updated snapshots immediately.
|
In `0.13.2`, the bundled Web UI mirrors entity CRUD events directly into its local state. Schedule-template replacement performs a targeted schedule resync, configuration import performs a full bootstrap resync, and runtime automation/device-health changes publish their updated snapshots immediately.
|
||||||
|
|
||||||
|
In `0.13.3`, logical thermostat targets use `0.1 °C` precision consistently. Temporary Quick Thermostat accepts comma/dot decimal input in the bundled Web UI; hardware-specific GREE target rounding is unchanged.
|
||||||
|
|
||||||
|
In `0.13.4`, disabled-zone thermostat controls remain visually active because explicit temporary/manual thermostat actions are still allowed while zone automation is disabled. The UI shows this distinction directly on the thermostat card; HTTP and WebSocket contracts are unchanged.
|
||||||
|
|
||||||
Additional engine/integration events may be introduced without changing the envelope.
|
Additional engine/integration events may be introduced without changing the envelope.
|
||||||
|
|
||||||
`api.request` data:
|
`api.request` data:
|
||||||
|
|||||||
+5
-4
@@ -2,9 +2,9 @@
|
|||||||
"openapi": "3.1.0",
|
"openapi": "3.1.0",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "GREE Controller API",
|
"title": "GREE Controller API",
|
||||||
"version": "0.13.2",
|
"version": "0.13.4",
|
||||||
"summary": "Local HTTP/WebSocket API for GREE HVAC control",
|
"summary": "Local HTTP/WebSocket API for GREE HVAC control",
|
||||||
"description": "Self-hosted API used by the GREE Controller Web UI and Home Assistant integration.\n\n### Authentication\nNormal administrator endpoints accept `Authorization: Bearer <APP_TOKEN>` or `x-api-token: <APP_TOKEN>` when `GREE_CONTROLLER_APP_TOKEN` is configured. If the app token is empty, those endpoints intentionally run in trusted-LAN mode without authentication.\n\nThe restricted `/api/integrations/home-assistant/*` control surface always requires either a generated access token or the administrator app token. Generated tokens do **not** grant normal administrator API access.\n\nIn Swagger UI use **Authorize** and enter the token in either the bearer field or `x-api-token` field.\n\n### Control ownership\nThermostat zones, groups, house rules, direct device control, schedules and visual Flows share an ownership/arbitration model. Prefer zone/group/house endpoints for thermostat intent; direct device commands are technical/manual control and can deliberately take ownership.\n\n### 0.12.0 breaking API\nRuntime settings are split into functional `/api/settings/*` resources. The former `/api/settings`, `/api/debug`, `/api/events/retention`, `/api/settings/export` and `/api/settings/import` endpoints were removed without compatibility aliases.\n\n### 0.13.0\n`control-plan` keeps its existing HTTP response shape but is materialized in memory and pushed to WebSocket clients as `control_plan.updated`; HTTP remains the fallback/resync path. A restricted Home Assistant snapshot endpoint combines devices, groups and the control plan.\n\n### 0.13.1\nRuntime WebSocket churn is reduced without changing contracts: no-op zone regulator heartbeats are deduplicated and device heartbeat polls only invalidate the materialized control plan when plan-relevant device state changes.\n\n### 0.13.2\nCompletes live WebSocket synchronization for entity creation, schedules, automations, configuration imports and runtime device/automation state while preserving the existing HTTP API contracts and control-plan fallback behavior.",
|
"description": "Self-hosted API used by the GREE Controller Web UI and Home Assistant integration.\n\n### Authentication\nNormal administrator endpoints accept `Authorization: Bearer <APP_TOKEN>` or `x-api-token: <APP_TOKEN>` when `GREE_CONTROLLER_APP_TOKEN` is configured. If the app token is empty, those endpoints intentionally run in trusted-LAN mode without authentication.\n\nThe restricted `/api/integrations/home-assistant/*` control surface always requires either a generated access token or the administrator app token. Generated tokens do **not** grant normal administrator API access.\n\nIn Swagger UI use **Authorize** and enter the token in either the bearer field or `x-api-token` field.\n\n### Control ownership\nThermostat zones, groups, house rules, direct device control, schedules and visual Flows share an ownership/arbitration model. Prefer zone/group/house endpoints for thermostat intent; direct device commands are technical/manual control and can deliberately take ownership.\n\n### 0.12.0 breaking API\nRuntime settings are split into functional `/api/settings/*` resources. The former `/api/settings`, `/api/debug`, `/api/events/retention`, `/api/settings/export` and `/api/settings/import` endpoints were removed without compatibility aliases.\n\n### 0.13.0\n`control-plan` keeps its existing HTTP response shape but is materialized in memory and pushed to WebSocket clients as `control_plan.updated`; HTTP remains the fallback/resync path. A restricted Home Assistant snapshot endpoint combines devices, groups and the control plan.\n\n### 0.13.1\nRuntime WebSocket churn is reduced without changing contracts: no-op zone regulator heartbeats are deduplicated and device heartbeat polls only invalidate the materialized control plan when plan-relevant device state changes.\n\n### 0.13.2\nCompletes live WebSocket synchronization for entity creation, schedules, automations, configuration imports and runtime device/automation state while preserving the existing HTTP API contracts and control-plan fallback behavior.\n\n### 0.13.3\nLogical thermostat targets are normalized to 0.1 C consistently, including Temporary Quick Thermostat and thermostat-managed automation/Flow actions. The bundled Web UI accepts comma or dot decimal input for the temporary thermostat target; physical GREE setpoint rounding is unchanged.\n\n### 0.13.4\nDisabled-zone thermostat controls remain visually active in the bundled Web UI because explicit temporary/manual thermostat and direct manual control remain available while zone automation is disabled. HTTP and WebSocket contracts are unchanged.",
|
||||||
"license": {
|
"license": {
|
||||||
"name": "MIT"
|
"name": "MIT"
|
||||||
}
|
}
|
||||||
@@ -4891,7 +4891,7 @@
|
|||||||
},
|
},
|
||||||
"version": {
|
"version": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "0.13.2"
|
"example": "0.13.4"
|
||||||
},
|
},
|
||||||
"uptime_seconds": {
|
"uptime_seconds": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@@ -5512,7 +5512,8 @@
|
|||||||
],
|
],
|
||||||
"minimum": 8,
|
"minimum": 8,
|
||||||
"maximum": 30,
|
"maximum": 30,
|
||||||
"example": 23.0
|
"example": 24.2,
|
||||||
|
"description": "Logical thermostat target in C. Values are normalized to 0.1 C by the controller."
|
||||||
},
|
},
|
||||||
"temperature_operator": {
|
"temperature_operator": {
|
||||||
"type": [
|
"type": [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "gree_controller",
|
"domain": "gree_controller",
|
||||||
"name": "GREE Controller",
|
"name": "GREE Controller",
|
||||||
"version": "0.13.2",
|
"version": "0.13.4",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
|
|||||||
@@ -721,6 +721,8 @@
|
|||||||
"zones.temporaryInvalidUntil": "Enter a valid finish date and time",
|
"zones.temporaryInvalidUntil": "Enter a valid finish date and time",
|
||||||
"zones.temporaryEndAfterStart": "The finish time must be after Quick Thermostat starts",
|
"zones.temporaryEndAfterStart": "The finish time must be after Quick Thermostat starts",
|
||||||
"zones.automationToggleHint": "Enables or disables thermostat automation for this zone. This is not temporary control.",
|
"zones.automationToggleHint": "Enables or disables thermostat automation for this zone. This is not temporary control.",
|
||||||
|
"zones.disabledManualControlTitle": "Zone is disabled",
|
||||||
|
"zones.disabledManualControlHint": "Zone automation is disabled. Temporary thermostat, manual thermostat and direct manual control remain available.",
|
||||||
"dashboard.sections": "Dashboard sections",
|
"dashboard.sections": "Dashboard sections",
|
||||||
"dashboard.tabMain": "Main",
|
"dashboard.tabMain": "Main",
|
||||||
"dashboard.tabThermostats": "Thermostats",
|
"dashboard.tabThermostats": "Thermostats",
|
||||||
|
|||||||
@@ -721,6 +721,8 @@
|
|||||||
"zones.temporaryInvalidUntil": "Podaj prawidłową datę i godzinę zakończenia",
|
"zones.temporaryInvalidUntil": "Podaj prawidłową datę i godzinę zakończenia",
|
||||||
"zones.temporaryEndAfterStart": "Czas zakończenia musi przypadać po uruchomieniu szybkiego termostatu",
|
"zones.temporaryEndAfterStart": "Czas zakończenia musi przypadać po uruchomieniu szybkiego termostatu",
|
||||||
"zones.automationToggleHint": "Włącza lub wyłącza automatykę termostatu tej strefy. Nie jest to chwilowe sterowanie.",
|
"zones.automationToggleHint": "Włącza lub wyłącza automatykę termostatu tej strefy. Nie jest to chwilowe sterowanie.",
|
||||||
|
"zones.disabledManualControlTitle": "Strefa jest wyłączona",
|
||||||
|
"zones.disabledManualControlHint": "Automatyka strefy jest wyłączona. Termostat czasowy, termostat ręczny i bezpośrednie sterowanie ręczne nadal działają.",
|
||||||
"dashboard.sections": "Sekcje pulpitu",
|
"dashboard.sections": "Sekcje pulpitu",
|
||||||
"dashboard.tabMain": "Główna",
|
"dashboard.tabMain": "Główna",
|
||||||
"dashboard.tabThermostats": "Termostaty",
|
"dashboard.tabThermostats": "Termostaty",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Developer smoke/integration tests for GREE Controller API 0.13.2.
|
"""Developer smoke/integration tests for GREE Controller API 0.13.4.
|
||||||
|
|
||||||
Default mode is read-only and safe to run against a real controller.
|
Default mode is read-only and safe to run against a real controller.
|
||||||
Use --settings-write to additionally round-trip all split settings resources and
|
Use --settings-write to additionally round-trip all split settings resources and
|
||||||
@@ -26,7 +26,7 @@ from typing import Any, Callable, Iterable, Optional
|
|||||||
|
|
||||||
DEFAULT_BASE_URL = os.environ.get("GREE_API_URL", "http://127.0.0.1:8787")
|
DEFAULT_BASE_URL = os.environ.get("GREE_API_URL", "http://127.0.0.1:8787")
|
||||||
DEFAULT_TOKEN = os.environ.get("GREE_API_TOKEN", "")
|
DEFAULT_TOKEN = os.environ.get("GREE_API_TOKEN", "")
|
||||||
DEFAULT_EXPECTED_VERSION = "0.13.2"
|
DEFAULT_EXPECTED_VERSION = "0.13.4"
|
||||||
|
|
||||||
SETTINGS_PATHS: dict[str, set[str]] = {
|
SETTINGS_PATHS: dict[str, set[str]] = {
|
||||||
"/api/settings/application": {"simulator_enabled"},
|
"/api/settings/application": {"simulator_enabled"},
|
||||||
@@ -519,7 +519,7 @@ def run_suite(args: argparse.Namespace) -> int:
|
|||||||
print(f"Settings write tests: {'ENABLED' if args.settings_write else 'disabled'}\n")
|
print(f"Settings write tests: {'ENABLED' if args.settings_write else 'disabled'}\n")
|
||||||
|
|
||||||
runner.run("public health", lambda: test_health(client, args.expected_version))
|
runner.run("public health", lambda: test_health(client, args.expected_version))
|
||||||
runner.run("OpenAPI 0.13.2 contract", lambda: test_openapi(client, args.expected_version))
|
runner.run("OpenAPI 0.13.4 contract", lambda: test_openapi(client, args.expected_version))
|
||||||
runner.run("protected API requires auth", lambda: test_protected_auth(client))
|
runner.run("protected API requires auth", lambda: test_protected_auth(client))
|
||||||
|
|
||||||
for path in SAFE_GET_PATHS:
|
for path in SAFE_GET_PATHS:
|
||||||
@@ -587,7 +587,7 @@ def run_suite(args: argparse.Namespace) -> int:
|
|||||||
|
|
||||||
def parse_args(argv: Optional[Iterable[str]] = None) -> argparse.Namespace:
|
def parse_args(argv: Optional[Iterable[str]] = None) -> argparse.Namespace:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Developer smoke/integration tests for GREE Controller API 0.13.2.",
|
description="Developer smoke/integration tests for GREE Controller API 0.13.4.",
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
epilog="""Examples:
|
epilog="""Examples:
|
||||||
python3 scripts/api_dev_test.py
|
python3 scripts/api_dev_test.py
|
||||||
|
|||||||
+1
-1
@@ -543,7 +543,7 @@ fn resolve_temporary_target(
|
|||||||
"temporary thermostat target must be between 8 and 30 C".into(),
|
"temporary thermostat target must be between 8 and 30 C".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let target = (target * 2.0).round() / 2.0;
|
let target = engine::normalize_thermostat_target(target);
|
||||||
let tolerance_mode = if zone.effective_mode.is_empty() {
|
let tolerance_mode = if zone.effective_mode.is_empty() {
|
||||||
zone.mode.as_str()
|
zone.mode.as_str()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -868,7 +868,7 @@ async fn apply_flow_zone_action(state: &AppState, zone_id: &str, preset: Option<
|
|||||||
"auto" => { zone.manual_preset = None; zone.manual_setpoint = None; zone.manual_override_until = None; }
|
"auto" => { zone.manual_preset = None; zone.manual_setpoint = None; zone.manual_override_until = None; }
|
||||||
"custom" => {
|
"custom" => {
|
||||||
let target = action.target_temperature.ok_or_else(|| AppError::BadRequest("Flow custom thermostat action has no target".into()))?;
|
let target = action.target_temperature.ok_or_else(|| AppError::BadRequest("Flow custom thermostat action has no target".into()))?;
|
||||||
zone.manual_preset = Some("custom".into()); zone.manual_setpoint = Some((target.clamp(8.0,30.0)*2.0).round()/2.0);
|
zone.manual_preset = Some("custom".into()); zone.manual_setpoint = Some(normalize_thermostat_target(target.clamp(8.0, 30.0)));
|
||||||
zone.manual_override_until = next_schedule_boundary_utc(&zone.id, &state.db.list_schedules()?, Local::now());
|
zone.manual_override_until = next_schedule_boundary_utc(&zone.id, &state.db.list_schedules()?, Local::now());
|
||||||
}
|
}
|
||||||
value @ ("comfort" | "sleep" | "away") => {
|
value @ ("comfort" | "sleep" | "away") => {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
pub(crate) fn normalize_thermostat_target(value: f64) -> f64 {
|
||||||
|
(value * 10.0).round() / 10.0
|
||||||
|
}
|
||||||
|
|
||||||
fn profile_setpoint(zone: &Zone, preset: &str, mode: &str) -> f64 {
|
fn profile_setpoint(zone: &Zone, preset: &str, mode: &str) -> f64 {
|
||||||
if zone.profile_version == 0 && preset == "comfort" {
|
if zone.profile_version == 0 && preset == "comfort" {
|
||||||
return zone.setpoint;
|
return zone.setpoint;
|
||||||
|
|||||||
@@ -3,6 +3,14 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use chrono::TimeZone;
|
use chrono::TimeZone;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn thermostat_target_normalization_preserves_tenths() {
|
||||||
|
assert!((normalize_thermostat_target(24.2) - 24.2).abs() < 1e-9);
|
||||||
|
assert!((normalize_thermostat_target(24.24) - 24.2).abs() < 1e-9);
|
||||||
|
assert!((normalize_thermostat_target(24.25) - 24.3).abs() < 1e-9);
|
||||||
|
assert!((normalize_thermostat_target(24.5) - 24.5).abs() < 1e-9);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn global_ha_sensor_stale_timeout_is_used_for_default_zone_value() {
|
fn global_ha_sensor_stale_timeout_is_used_for_default_zone_value() {
|
||||||
assert_eq!(effective_sensor_stale_after_seconds(300, 600), 600);
|
assert_eq!(effective_sensor_stale_after_seconds(300, 600), 600);
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ async fn apply_automatic_device_action(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(target) = command.target_temperature {
|
if let Some(target) = command.target_temperature {
|
||||||
zone.manual_setpoint = Some((target.clamp(8.0, 30.0) * 2.0).round() / 2.0);
|
zone.manual_setpoint = Some(normalize_thermostat_target(target.clamp(8.0, 30.0)));
|
||||||
zone.manual_override_until =
|
zone.manual_override_until =
|
||||||
next_schedule_boundary_utc(&zone.id, &state.db.list_schedules()?, Local::now());
|
next_schedule_boundary_utc(&zone.id, &state.db.list_schedules()?, Local::now());
|
||||||
domain_changed = true;
|
domain_changed = true;
|
||||||
|
|||||||
+9
-9
@@ -1189,16 +1189,16 @@
|
|||||||
<details class="profile-editor">
|
<details class="profile-editor">
|
||||||
<summary data-i18n="zones.profileTemperatures">Comfort / sleep / away temperatures</summary>
|
<summary data-i18n="zones.profileTemperatures">Comfort / sleep / away temperatures</summary>
|
||||||
<div class="profile-grid"><strong data-i18n="mode.cool">Cooling</strong><label><span
|
<div class="profile-grid"><strong data-i18n="mode.cool">Cooling</strong><label><span
|
||||||
data-i18n="preset.comfort">Comfort</span><input type="text" inputmode="decimal" step="0.5"
|
data-i18n="preset.comfort">Comfort</span><input type="text" inputmode="decimal" step="0.1"
|
||||||
name="cool_comfort_setpoint" value="23"></label><label><span data-i18n="preset.sleep">Sleep</span><input
|
name="cool_comfort_setpoint" value="23"></label><label><span data-i18n="preset.sleep">Sleep</span><input
|
||||||
type="text" inputmode="decimal" step="0.5" name="cool_sleep_setpoint" value="24.5"></label><label><span
|
type="text" inputmode="decimal" step="0.1" name="cool_sleep_setpoint" value="24.5"></label><label><span
|
||||||
data-i18n="preset.away">Away</span><input type="text" inputmode="decimal" step="0.5"
|
data-i18n="preset.away">Away</span><input type="text" inputmode="decimal" step="0.1"
|
||||||
name="cool_away_setpoint" value="27"></label></div>
|
name="cool_away_setpoint" value="27"></label></div>
|
||||||
<div class="profile-grid"><strong data-i18n="mode.heat">Heating</strong><label><span
|
<div class="profile-grid"><strong data-i18n="mode.heat">Heating</strong><label><span
|
||||||
data-i18n="preset.comfort">Comfort</span><input type="text" inputmode="decimal" step="0.5"
|
data-i18n="preset.comfort">Comfort</span><input type="text" inputmode="decimal" step="0.1"
|
||||||
name="heat_comfort_setpoint" value="21"></label><label><span data-i18n="preset.sleep">Sleep</span><input
|
name="heat_comfort_setpoint" value="21"></label><label><span data-i18n="preset.sleep">Sleep</span><input
|
||||||
type="text" inputmode="decimal" step="0.5" name="heat_sleep_setpoint" value="19"></label><label><span
|
type="text" inputmode="decimal" step="0.1" name="heat_sleep_setpoint" value="19"></label><label><span
|
||||||
data-i18n="preset.away">Away</span><input type="text" inputmode="decimal" step="0.5"
|
data-i18n="preset.away">Away</span><input type="text" inputmode="decimal" step="0.1"
|
||||||
name="heat_away_setpoint" value="17"></label></div>
|
name="heat_away_setpoint" value="17"></label></div>
|
||||||
</details>
|
</details>
|
||||||
<div class="two zone-hysteresis-row">
|
<div class="two zone-hysteresis-row">
|
||||||
@@ -1280,8 +1280,8 @@
|
|||||||
<button type="button" class="danger" id="temporaryThermostatStop" data-i18n="zones.temporaryStop">Zakończ
|
<button type="button" class="danger" id="temporaryThermostatStop" data-i18n="zones.temporaryStop">Zakończ
|
||||||
teraz</button>
|
teraz</button>
|
||||||
</div>
|
</div>
|
||||||
<label><span data-i18n="zones.temporaryTarget">Temperatura szybkiego termostatu</span><input type="number"
|
<label><span data-i18n="zones.temporaryTarget">Temperatura szybkiego termostatu</span><input type="text"
|
||||||
name="target_temperature" min="8" max="30" step="0.5" required></label>
|
inputmode="decimal" name="target_temperature" min="8" max="30" step="0.1" required></label>
|
||||||
<label><span data-i18n="zones.temporaryStartRule">Kiedy uruchomić</span><select name="start_kind"
|
<label><span data-i18n="zones.temporaryStartRule">Kiedy uruchomić</span><select name="start_kind"
|
||||||
id="temporaryStartKind">
|
id="temporaryStartKind">
|
||||||
<option value="now" data-i18n="zones.temporaryStartNow">Od razu</option>
|
<option value="now" data-i18n="zones.temporaryStartNow">Od razu</option>
|
||||||
@@ -1404,7 +1404,7 @@
|
|||||||
<option value="custom" data-i18n="preset.custom">Custom temperature</option>
|
<option value="custom" data-i18n="preset.custom">Custom temperature</option>
|
||||||
</select></label>
|
</select></label>
|
||||||
<label id="scheduleSetpointField"><span data-i18n="common.temperatureC">Custom temperature °C</span><input
|
<label id="scheduleSetpointField"><span data-i18n="common.temperatureC">Custom temperature °C</span><input
|
||||||
type="text" inputmode="decimal" name="setpoint" step="0.5" min="8" max="30" value="23"></label>
|
type="text" inputmode="decimal" name="setpoint" step="0.1" min="8" max="30" value="23"></label>
|
||||||
<label class="check"><input type="checkbox" name="enabled" checked> <span
|
<label class="check"><input type="checkbox" name="enabled" checked> <span
|
||||||
data-i18n="common.enabled">Enabled</span></label>
|
data-i18n="common.enabled">Enabled</span></label>
|
||||||
<div class="form-actions"><button type="button" class="secondary" data-close
|
<div class="form-actions"><button type="button" class="secondary" data-close
|
||||||
|
|||||||
@@ -337,6 +337,7 @@ function zoneCard(zone, detailed = true) {
|
|||||||
const temporaryThermostatControl = temporarySession
|
const temporaryThermostatControl = temporarySession
|
||||||
? `<button type="button" class="temporary-thermostat-toggle active ${temporaryStatus?.pending ? 'scheduled' : ''}" data-action="zone-open-temporary" data-id="${esc(zone.id)}" title="${esc(temporaryStatus?.detail || tr('zones.temporaryActive'))}"><b data-temporary-countdown-zone="${esc(zone.id)}">${esc(temporaryStatus?.countdown || '—')}</b></button>`
|
? `<button type="button" class="temporary-thermostat-toggle active ${temporaryStatus?.pending ? 'scheduled' : ''}" data-action="zone-open-temporary" data-id="${esc(zone.id)}" title="${esc(temporaryStatus?.detail || tr('zones.temporaryActive'))}"><b data-temporary-countdown-zone="${esc(zone.id)}">${esc(temporaryStatus?.countdown || '—')}</b></button>`
|
||||||
: `<button type="button" class="temporary-thermostat-toggle" data-action="zone-open-temporary" data-id="${esc(zone.id)}" title="${esc(tr('zones.temporaryStartHint'))}"${localPowerDisabled}><b>${esc(tr('zones.temporaryShort'))}</b></button>`;
|
: `<button type="button" class="temporary-thermostat-toggle" data-action="zone-open-temporary" data-id="${esc(zone.id)}" title="${esc(tr('zones.temporaryStartHint'))}"${localPowerDisabled}><b>${esc(tr('zones.temporaryShort'))}</b></button>`;
|
||||||
|
const disabledManualControlNotice = zone.enabled ? '' : `<div class="manual-zone-warning zone-disabled-manual-note" role="note"><strong>${esc(tr('zones.disabledManualControlTitle'))}</strong><p>${esc(tr('zones.disabledManualControlHint'))}</p></div>`;
|
||||||
|
|
||||||
if (detailed) {
|
if (detailed) {
|
||||||
const groupNames = (app.groups || []).filter(group => (group.zone_ids || []).includes(zone.id)).map(group => group.name);
|
const groupNames = (app.groups || []).filter(group => (group.zone_ids || []).includes(zone.id)).map(group => group.name);
|
||||||
@@ -364,6 +365,7 @@ function zoneCard(zone, detailed = true) {
|
|||||||
const runtimeStatus = zoneRuntimeStatusLabel(zone, effectiveMode, device);
|
const runtimeStatus = zoneRuntimeStatusLabel(zone, effectiveMode, device);
|
||||||
return `<article class="list-card zone-thermostat quick-control-card quick-thermostat-control ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''}" data-zone-card="${esc(zone.id)}"${groupControlStyle(visualGroup)}>
|
return `<article class="list-card zone-thermostat quick-control-card quick-thermostat-control ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''}" data-zone-card="${esc(zone.id)}"${groupControlStyle(visualGroup)}>
|
||||||
<div class="list-card-head"><div><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(zonePresetLabel(displayPreset))}</p></div><div class="zone-quick-actions">${localThermostatPowerControl}${temporaryThermostatControl}</div></div>
|
<div class="list-card-head"><div><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(zonePresetLabel(displayPreset))}</p></div><div class="zone-quick-actions">${localThermostatPowerControl}${temporaryThermostatControl}</div></div>
|
||||||
|
${disabledManualControlNotice}
|
||||||
<div class="thermostat-main"><div><small>${esc(tr('zones.measurement'))}</small><strong>${fmtTemp(roomTemperature)}</strong></div><div class="temperature-control compact"><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="-0.5">−</button><div class="target-temp compact editable-target" data-temperature-kind="zone" data-id="${esc(zone.id)}" data-value="${Number.isFinite(target) ? target : ''}" tabindex="0" role="button" title="${esc(tr('common.clickTemperatureToEdit'))}">${Number.isFinite(target) ? target.toFixed(1) : '--'}<small>°C</small></div><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="0.5">+</button></div><div><small>${esc(tr('zones.deviceTarget'))}</small><strong>${fmtTemp(zone.device_setpoint)}</strong></div></div>
|
<div class="thermostat-main"><div><small>${esc(tr('zones.measurement'))}</small><strong>${fmtTemp(roomTemperature)}</strong></div><div class="temperature-control compact"><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="-0.5">−</button><div class="target-temp compact editable-target" data-temperature-kind="zone" data-id="${esc(zone.id)}" data-value="${Number.isFinite(target) ? target : ''}" tabindex="0" role="button" title="${esc(tr('common.clickTemperatureToEdit'))}">${Number.isFinite(target) ? target.toFixed(1) : '--'}<small>°C</small></div><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="0.5">+</button></div><div><small>${esc(tr('zones.deviceTarget'))}</small><strong>${fmtTemp(zone.device_setpoint)}</strong></div></div>
|
||||||
<div class="preset-row quick-control-row quick-control-row-4">
|
<div class="preset-row quick-control-row quick-control-row-4">
|
||||||
${['auto', 'comfort', 'sleep', 'away'].map(preset => `<button class="${manual === preset ? 'active' : ''}" data-action="zone-preset" data-id="${esc(zone.id)}" data-value="${preset}">${esc(preset === 'sleep' ? tr('zones.sleepNow') : zonePresetLabel(preset))}</button>`).join('')}
|
${['auto', 'comfort', 'sleep', 'away'].map(preset => `<button class="${manual === preset ? 'active' : ''}" data-action="zone-preset" data-id="${esc(zone.id)}" data-value="${preset}">${esc(preset === 'sleep' ? tr('zones.sleepNow') : zonePresetLabel(preset))}</button>`).join('')}
|
||||||
|
|||||||
+2
-2
@@ -530,7 +530,7 @@ function renderFlowInspector() {
|
|||||||
else if (node.kind === 'logic_and') fields = `<p class="field-note">${esc(tr('flow.andHint'))}</p>`;
|
else if (node.kind === 'logic_and') fields = `<p class="field-note">${esc(tr('flow.andHint'))}</p>`;
|
||||||
else if (node.kind === 'logic_or') fields = `<p class="field-note">${esc(tr('flow.orHint'))}</p>`;
|
else if (node.kind === 'logic_or') fields = `<p class="field-note">${esc(tr('flow.orHint'))}</p>`;
|
||||||
else if (node.kind === 'logic_not') fields = `<p class="field-note">${esc(tr('flow.notHint'))}</p>`;
|
else if (node.kind === 'logic_not') fields = `<p class="field-note">${esc(tr('flow.notHint'))}</p>`;
|
||||||
else if (node.kind === 'zone_thermostat') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-flow-config="zone_id">${flowSelectOptions(app.zones, c.zone_id)}</select></label><div class="two"><label><span>${esc(tr('schedules.profile'))}</span><select data-flow-config="preset">${['auto', 'comfort', 'sleep', 'away', 'custom'].map(v => `<option value="${v}" ${c.preset === v ? 'selected' : ''}>${esc(zonePresetLabel(v))}</option>`).join('')}</select></label><label><span>${esc(tr('common.targetC'))}</span><input type="number" min="8" max="30" step="0.5" data-flow-config="setpoint" value="${Number(c.setpoint ?? 21)}"></label></div><div class="two"><label><span>${esc(tr('common.mode'))}</span><select data-flow-config="mode"><option value="auto" ${c.mode === 'auto' ? 'selected' : ''}>Auto</option><option value="heat" ${c.mode === 'heat' ? 'selected' : ''}>${esc(tr('mode.heat'))}</option><option value="cool" ${c.mode === 'cool' ? 'selected' : ''}>${esc(tr('mode.cool'))}</option></select></label><label><span>${esc(tr('common.power'))}</span><select data-flow-config="power"><option value="" ${c.power == null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option><option value="true" ${c.power === true ? 'selected' : ''}>${esc(tr('common.on'))}</option><option value="false" ${c.power === false ? 'selected' : ''}>${esc(tr('common.off'))}</option></select></label></div><label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
|
else if (node.kind === 'zone_thermostat') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-flow-config="zone_id">${flowSelectOptions(app.zones, c.zone_id)}</select></label><div class="two"><label><span>${esc(tr('schedules.profile'))}</span><select data-flow-config="preset">${['auto', 'comfort', 'sleep', 'away', 'custom'].map(v => `<option value="${v}" ${c.preset === v ? 'selected' : ''}>${esc(zonePresetLabel(v))}</option>`).join('')}</select></label><label><span>${esc(tr('common.targetC'))}</span><input type="number" min="8" max="30" step="0.1" data-flow-config="setpoint" value="${Number(c.setpoint ?? 21)}"></label></div><div class="two"><label><span>${esc(tr('common.mode'))}</span><select data-flow-config="mode"><option value="auto" ${c.mode === 'auto' ? 'selected' : ''}>Auto</option><option value="heat" ${c.mode === 'heat' ? 'selected' : ''}>${esc(tr('mode.heat'))}</option><option value="cool" ${c.mode === 'cool' ? 'selected' : ''}>${esc(tr('mode.cool'))}</option></select></label><label><span>${esc(tr('common.power'))}</span><select data-flow-config="power"><option value="" ${c.power == null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option><option value="true" ${c.power === true ? 'selected' : ''}>${esc(tr('common.on'))}</option><option value="false" ${c.power === false ? 'selected' : ''}>${esc(tr('common.off'))}</option></select></label></div><label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
|
||||||
else if (node.kind === 'device_action') fields = `<label><span>${esc(tr('common.device'))}</span><select data-flow-config="device_id">${flowSelectOptions(app.devices, c.device_id)}</select></label>${flowActionFields(c, false)}`;
|
else if (node.kind === 'device_action') fields = `<label><span>${esc(tr('common.device'))}</span><select data-flow-config="device_id">${flowSelectOptions(app.devices, c.device_id)}</select></label>${flowActionFields(c, false)}`;
|
||||||
else if (node.kind === 'group_action') fields = `<label><span>${esc(tr('groups.group'))}</span><select data-flow-config="group_id">${flowSelectOptions(app.groups, c.group_id)}</select></label>${flowActionFields(c, true)}`;
|
else if (node.kind === 'group_action') fields = `<label><span>${esc(tr('groups.group'))}</span><select data-flow-config="group_id">${flowSelectOptions(app.groups, c.group_id)}</select></label>${flowActionFields(c, true)}`;
|
||||||
else if (node.kind === 'ha_service_action') fields = `<div class="two"><label><span>domain</span><input data-flow-config="domain" value="${esc(c.domain || 'switch')}" placeholder="switch"></label><label><span>service</span><input data-flow-config="service" value="${esc(c.service || 'turn_on')}" placeholder="turn_on"></label></div><p class="field-note">${esc(tr('flow.serviceExample'))}</p><label><span>entity_id</span><input data-flow-config="entity_id" value="${esc(c.entity_id || '')}" placeholder="switch.gas"></label><label><span>${esc(tr('flow.serviceData'))}</span><textarea rows="5" data-flow-config="data_json">${esc(JSON.stringify(c.data || {}, null, 2))}</textarea></label><label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
|
else if (node.kind === 'ha_service_action') fields = `<div class="two"><label><span>domain</span><input data-flow-config="domain" value="${esc(c.domain || 'switch')}" placeholder="switch"></label><label><span>service</span><input data-flow-config="service" value="${esc(c.service || 'turn_on')}" placeholder="turn_on"></label></div><p class="field-note">${esc(tr('flow.serviceExample'))}</p><label><span>entity_id</span><input data-flow-config="entity_id" value="${esc(c.entity_id || '')}" placeholder="switch.gas"></label><label><span>${esc(tr('flow.serviceData'))}</span><textarea rows="5" data-flow-config="data_json">${esc(JSON.stringify(c.data || {}, null, 2))}</textarea></label><label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
|
||||||
@@ -547,7 +547,7 @@ function flowActionFields(c, group) {
|
|||||||
const modeOptions = modes.map(v => `<option value="${v}" ${c.mode === v ? 'selected' : ''}>${esc(v === 'auto' ? 'Auto' : v === 'house' ? tr('flow.houseMode') : (tr(`mode.${v}`) || v))}</option>`).join('');
|
const modeOptions = modes.map(v => `<option value="${v}" ${c.mode === v ? 'selected' : ''}>${esc(v === 'auto' ? 'Auto' : v === 'house' ? tr('flow.houseMode') : (tr(`mode.${v}`) || v))}</option>`).join('');
|
||||||
const base = `<div class="two"><label><span>${esc(tr('common.power'))}</span><select data-flow-config="power"><option value="" ${c.power == null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option><option value="true" ${c.power === true ? 'selected' : ''}>${esc(tr('common.on'))}</option><option value="false" ${c.power === false ? 'selected' : ''}>${esc(tr('common.off'))}</option></select></label><label><span>${esc(tr('common.mode'))}</span><select data-flow-config="mode"><option value="">${esc(tr('actions.noChange'))}</option>${modeOptions}</select></label></div>`;
|
const base = `<div class="two"><label><span>${esc(tr('common.power'))}</span><select data-flow-config="power"><option value="" ${c.power == null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option><option value="true" ${c.power === true ? 'selected' : ''}>${esc(tr('common.on'))}</option><option value="false" ${c.power === false ? 'selected' : ''}>${esc(tr('common.off'))}</option></select></label><label><span>${esc(tr('common.mode'))}</span><select data-flow-config="mode"><option value="">${esc(tr('actions.noChange'))}</option>${modeOptions}</select></label></div>`;
|
||||||
const target = group
|
const target = group
|
||||||
? `<label><span>${esc(tr('groups.profile'))}</span><select data-flow-config="preset"><option value="">${esc(tr('actions.noChange'))}</option>${['auto', 'comfort', 'sleep', 'away', 'custom'].map(v => `<option value="${v}" ${c.preset === v ? 'selected' : ''}>${esc(zonePresetLabel(v))}</option>`).join('')}</select></label><label><span>${esc(tr('common.targetC'))}</span><input type="number" min="8" max="30" step="0.5" data-flow-config="setpoint" value="${c.setpoint ?? 21}"></label>`
|
? `<label><span>${esc(tr('groups.profile'))}</span><select data-flow-config="preset"><option value="">${esc(tr('actions.noChange'))}</option>${['auto', 'comfort', 'sleep', 'away', 'custom'].map(v => `<option value="${v}" ${c.preset === v ? 'selected' : ''}>${esc(zonePresetLabel(v))}</option>`).join('')}</select></label><label><span>${esc(tr('common.targetC'))}</span><input type="number" min="8" max="30" step="0.1" data-flow-config="setpoint" value="${c.setpoint ?? 21}"></label>`
|
||||||
: `<label><span>${esc(tr('common.targetC'))}</span><input type="number" min="8" max="30" step="0.5" data-flow-config="target_temperature" value="${c.target_temperature ?? ''}"></label>`;
|
: `<label><span>${esc(tr('common.targetC'))}</span><input type="number" min="8" max="30" step="0.5" data-flow-config="target_temperature" value="${c.target_temperature ?? ''}"></label>`;
|
||||||
const deviceOptions = group ? '' : `<details class="flow-device-options"><summary>${esc(tr('flow.deviceOptions'))}</summary><div class="two"><label><span>fan_speed</span><select data-flow-config="fan_speed"><option value="" ${c.fan_speed == null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option>${[0, 1, 2, 3, 4, 5].map(v => `<option value="${v}" ${Number(c.fan_speed) === v ? 'selected' : ''}>${v}</option>`).join('')}</select></label>${flowOptionalBoolField(c, 'swing_vertical')}${flowOptionalBoolField(c, 'swing_horizontal')}${flowOptionalBoolField(c, 'quiet')}${flowOptionalBoolField(c, 'turbo')}${flowOptionalBoolField(c, 'light')}${flowOptionalBoolField(c, 'air')}${flowOptionalBoolField(c, 'xfan')}${flowOptionalBoolField(c, 'health')}${flowOptionalBoolField(c, 'sleep')}</div><p class="field-note">${esc(tr('flow.deviceOptionsHint'))}</p></details>`;
|
const deviceOptions = group ? '' : `<details class="flow-device-options"><summary>${esc(tr('flow.deviceOptions'))}</summary><div class="two"><label><span>fan_speed</span><select data-flow-config="fan_speed"><option value="" ${c.fan_speed == null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option>${[0, 1, 2, 3, 4, 5].map(v => `<option value="${v}" ${Number(c.fan_speed) === v ? 'selected' : ''}>${v}</option>`).join('')}</select></label>${flowOptionalBoolField(c, 'swing_vertical')}${flowOptionalBoolField(c, 'swing_horizontal')}${flowOptionalBoolField(c, 'quiet')}${flowOptionalBoolField(c, 'turbo')}${flowOptionalBoolField(c, 'light')}${flowOptionalBoolField(c, 'air')}${flowOptionalBoolField(c, 'xfan')}${flowOptionalBoolField(c, 'health')}${flowOptionalBoolField(c, 'sleep')}</div><p class="field-note">${esc(tr('flow.deviceOptionsHint'))}</p></details>`;
|
||||||
return `${base}${target}${deviceOptions}<label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
|
return `${base}${target}${deviceOptions}<label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
|
||||||
|
|||||||
@@ -126,6 +126,10 @@ function validateForm(form) {
|
|||||||
else if (!httpUrlValid(url.value)) { showFieldError(url, tr('validation.url')); valid = false; }
|
else if (!httpUrlValid(url.value)) { showFieldError(url, tr('validation.url')); valid = false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (form.id === 'temporaryThermostatForm') {
|
||||||
|
if (!validateDecimalField(form, 'target_temperature', 8, 30)) valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (form.id === 'zoneForm') {
|
if (form.id === 'zoneForm') {
|
||||||
const source = form.elements.sensor_source?.value;
|
const source = form.elements.sensor_source?.value;
|
||||||
const entity = form.elements.ha_entity_id;
|
const entity = form.elements.ha_entity_id;
|
||||||
|
|||||||
+5
-4
@@ -750,11 +750,12 @@ h3 {
|
|||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zone-thermostat.zone-disabled .thermostat-main,
|
|
||||||
.zone-thermostat.zone-disabled .preset-row,
|
|
||||||
.zone-thermostat.zone-disabled .zone-mode-row,
|
|
||||||
.zone-thermostat.zone-disabled .zone-state-line {
|
.zone-thermostat.zone-disabled .zone-state-line {
|
||||||
opacity: .5;
|
opacity: .68;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone-disabled-manual-note {
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-stats {
|
.card-stats {
|
||||||
|
|||||||
Reference in New Issue
Block a user