This commit is contained in:
Mateusz Gruszczyński
2026-09-02 08:50:51 +02:00
parent a161d8785d
commit 9c6e51fc35
52 changed files with 3763 additions and 285 deletions
Generated
+1 -1
View File
@@ -633,7 +633,7 @@ dependencies = [
[[package]]
name = "gree-controller"
version = "0.9.7"
version = "0.9.9"
dependencies = [
"aes",
"aes-gcm",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "gree-controller"
version = "0.9.7"
version = "0.9.9"
edition = "2021"
authors = ["GREE Controller contributors"]
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
+51 -14
View File
@@ -1,12 +1,12 @@
d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
53a52a76fbcec088d3aded0f4e6c9a80f3dbce7c17a682e424fb06ba335fbb38 ./Cargo.lock
dbeeca58fcd5786b2dfb005f1d13262e22dd4e75d6ee0ad94231132eddc145f0 ./Cargo.toml
b0a17a6d6445239d1e307f5e685a6b87dbf7e700a6a362f9a57e713de204a802 ./Cargo.lock
3ec8130c8c6266652fae17f214c2e57c8faf161709c2cba4d9c7da95db281031 ./Cargo.toml
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
ffab397a9ddbde4c9956aa1eeec6fe00ead649da2d8516fd0afb6ad5d3a976c2 ./README.md
07acba9eab602e8d5b7387b16476308799ad629565fe501b18f3c1ea024a3ccf ./build.rs
1b83a141db1d69c100f74517b2ee122d51c960fa958fe76741600d42a22d96aa ./docs/API.md
9712c0ed25f278d442c8a09cd6477ed39e801aa6c18904572f588f5faf76f4e9 ./docs/FLOW.md
081853b6c3874cda25374f86ba30f1a3796aeefc498bfb486f752f2741e59fc0 ./README.md
41dfdc6d099b54f87d4dd51f696122b3c88d3bf3ddf420bea8f4bc621ba920b0 ./build.rs
6275b530ace5c60b43036665feb5e5fd29f5bda784ec48f56d3f9969caeb70d1 ./docs/API.md
e0642446a28e7241d5e418896850b0ecd61d272c2df74fd0bf3cd3d9addde17a ./docs/FLOW.md
a0893b2a56eb1523f1a72871842e9be2139a5fafba1f51ae942fc407a6e4ca34 ./home-assistant/README.md
f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./home-assistant/custom_components/gree_controller/__init__.py
6910589f27960a28d4de9735884a7e5376e455cd885f19ce2b55947fcd135114 ./home-assistant/custom_components/gree_controller/api.py
@@ -17,7 +17,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
6392b1b9178cfb1394d20575adc5219476e2fa3ee65a588e0de41effe316672b ./home-assistant/custom_components/gree_controller/manifest.json
2302987becea57c561706fdd3f4571db4632225e11e8281b146a1fd59a3478a7 ./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
@@ -26,9 +26,46 @@ 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
a26d514b839deff2e49c352e4056a561c4af9e48626b008e0ad524cdf086fa8f ./lang/en.json
e1a2ca5aa42158267bdf4fcbee4992eccc611e6ec3ecc5093a3567e166275fbc ./lang/pl.json
c9f0244b7b908d57fa4b9cf32fc1a999527fcfbb469e302fd62b75743f887753 ./lang/en.json
b7bcd0bfc806c4bf488ca9771635066ccea8503c9a41ab00e97fe07ee6aaf4e9 ./lang/pl.json
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
c5276fecc6a33c5de912e5a73b0a829d4e18c18d57b9c0ee49179ef96f133fc7 ./presets/dual_threshold_control.json
aa190f51cb20bd0f3749c080e39b4182d8d843db51d9bf7c2315528f67b45a7e ./presets/energy_price_eco.json
19d001dc0703dccf9c9ba3c78d866afafae94591ddb6d0d9e2d8c7703eabeea2 ./presets/frost_guard.json
a86718e22f79769b40b363f32ec669bf77974e6a6ade081ed4b90792d5803ef7 ./presets/ha_attribute_mode_guard.json
cc0d35fb5ad4bd52f1a8a122745bbda19922119cec48338f391f8c0619dab896 ./presets/ha_boiler_supply_boost.json
515b2580221fb38975c7f1ac46f267af7d8ac418efd52d30b40d5ce3e4fae1d2 ./presets/ha_external_heat_source_assist.json
07907f47c6c5c8dbd142b161135eae123889fb8845cd846fa7d1b05eda7b0740 ./presets/ha_external_heat_source_off.json
349df3ee451da70958529bf6c8e8c09e717a26f69fb56e9d6b73831087055966 ./presets/ha_gas_backup_heat.json
6fddede93fcb97913139791e6a75fb62322f425c59365b48bd8bb32a48c4ddee ./presets/ha_gas_heating_boost.json
40e6bb0a22e29cc9e32844b90eafffb753df4c368ab55b5112c5bf25593491da ./presets/ha_gas_heating_off.json
b6dce178fb517509f31e3ce58f6fa512d038ffcea98fae10dd41844b1d954144 ./presets/ha_gas_heating_reduce.json
eb95423754fc798b0b5db01757f0fbe97f07bae9f16a95f5ca51ce379809ab7a ./presets/ha_heating_demand_follow.json
0e34f595cebea8662646293b75e7422ac2b01c92fe75f152d71b13f6c8fb53d1 ./presets/ha_thermostat_idle_fallback.json
e8946176bf86975c04ef40e82565d7f7a4c171bcf8b245c001121ed8aae938c9 ./presets/ha_window_guard.json
a87a919d73003f774becc2d48aab646c21af43e50ceb54577cd37f26606a1674 ./presets/humidity_guard.json
e78a5a9c02bc47fa6530aad3803581b10b3a0676705cb41f9d003b4da455aa14 ./presets/mild_weather_eco.json
c3c10990e992948c2792bb71fa6606e616abb2a5455e1f11d9209c2f05132b86 ./presets/morning_boost.json
664afa80d27130799899e54f5a2152a27955e6e2d8c035c2a594c8da6bd902a9 ./presets/multi_room_group_guard.json
7518e0c694208c6ff69209edf8eec605fcf9e9abe8130d092c8c89524d7a24b3 ./presets/nested_guard.json
863e557fe9c798a5aaa175f29d71752e148bb8d49910485d738bba00671d1198 ./presets/night_group.json
c675cf84430f3c2d058c69dbfea1b5c6fcd82e927d53a301b3e517409a324f9a ./presets/night_quiet.json
3b87def6c5fdd7f50cd9e6003bd8e0628b1365e1de8bbc414d5a65d1d7d76298 ./presets/occupancy_weather_matrix.json
2360e1939cb46352042bd914a1a125fc98252ea80b68c89cc6ce3f5f197cc464 ./presets/offline_safe_off.json
0f00f10c2d11984827738cd2910aa24910acbc8f36e754627a76fd5a0aeef7eb ./presets/overheat_guard.json
f118ab957389bc4a47fe8c36b2911fb717a23970ec97d14c37a65f65eb479fc5 ./presets/peak_power_guard.json
dfb2f5f382a7f83b7a6fe475a26bae73a4510a46e1e7949a99ac752e39f58b4b ./presets/presence_eco.json
8e40d01a07911455c3c6607ba3435488fd6e520bebfc84cbf5eaad481883a4ed ./presets/sensor_availability_guard.json
45183d75ce254315cd4032acd42fbb0d505df0e7ab1fefecad931a74db2f43c4 ./presets/sleep_temperature_guard.json
7e819f2976ff48d89ccb636d08439d6e9736e7e95837c150dca157fb5bc78cb1 ./presets/smart_demand.json
6d16f0a35b023afccd0f2011056ed8b15b5e5d2a3be6a52d122733aac862e85d ./presets/thermostat_enabled_guard.json
fffa18bd989a0dcc1c5458975522517d40af4af40cc1ca30640e79d3db711053 ./presets/unoccupied_shutdown.json
993b8e8151647cbda6e62312266d37d89178b3a4bb13f7df0950840e1ce647a8 ./presets/weather_comfort.json
ae21459a261712bcb8d57594528b1648432e8d03a8a38502234829c0dbfef774 ./presets/weekend_comfort.json
fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/window_available_guard.json
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
01952aa92b217f8eae2493b88870e2dec595100cd15c4d561ff11ae2b936c46f ./regenerate-sha.sh
6408d99fbb6154afa65a9bda016dab9c9236e57cf4ae4ccb89943c1a4c5a5a56 ./scripts/FILE_MANIFEST.sha256
bb89bac237e750e9b1bf73761d7df97a6b81853091615878c03f13d7b6399aa7 ./scripts/README.md
@@ -43,8 +80,8 @@ e00d211e3885e30d7fed1e43b44e6fdad40a67019060156c0641816a93e3365f ./scripts/netw
81345b6a0b51736bdbc98fd23199b62e4c721b4e7437e02dab7ea79b97dff29a ./scripts/service.sh
8ccca8ed49576cf0b8e3453436ac3ff9c2a222cd41b27db98ceb62bd5f87aeef ./scripts/smoke.sh
b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/update.sh
037cc98e65772dade518aea33e13270c9c91afd639126a8a95be97495aecc447 ./src/api.rs
50b653f5ae1c9d24eb2e158fb4af713f5ccabe259c83053fa3cd2a310c7780da ./src/api/assets.rs
51b26ee9a02278f086f69d2ca5874feabcc90ebeb32149d1fa4b9f4fd55d91c2 ./src/api.rs
825370f409124a719043200742c01c3441bc65f483108106140d13fe7bbcba91 ./src/api/assets.rs
6c34b1294d76b0eba4c56bfd0bf69bdd502de0e40838104f6ddf2db4e922683d ./src/api/auth.rs
5643425214a0ad228d6229398b715d9a6823c8c5823cdb4fbf73912d9b1eb74b ./src/api/automations.rs
2317b7cd0c2437c128ac4982d64412c8184ecb5e774825761d61aab6e45fae4a ./src/api/debug_tokens.rs
@@ -134,7 +171,7 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
64dad888573f60e94175c93171eb3f0352c8b480958b7ac2b5e50caffd8ee801 ./web/index.html
142c46fa8e1b9b726c2c05f50b0b0d19ab5667683f63d8d50fde922771dce6af ./web/index.html
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md
21b662914590e720dafefe4dc3dccc91dbeb963cce9ae91f007c835d2d0c1fe5 ./web/js/bootstrap.js
72fa280996ba331a05bdb29a1f49b4e61bdd8964953c8ce1db952156620f08fe ./web/js/charts.js
@@ -142,7 +179,7 @@ e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.
d34953aec21ade81ca9770c9c99873b0b2a4862a1bf671a6e13b507a5dd5b7ed ./web/js/dashboard.js
2887856e35e086bb575e9fc0d8b0ed1160b6912297a2957535d5416bda68b4b7 ./web/js/entities.js
9a0e87911f78cd8a969f4c57ce74774f5fa829fdecd79494d9a7f9d5804f9792 ./web/js/events.js
40a5f342ed1447b09ed9fcbf65ee2c1035128fc608b4b743d259d51cc6741680 ./web/js/flows.js
b7d4d428f0f40ce84d358fa90086c3161d9cac604281211ea46a11f6406fbbf6 ./web/js/flows.js
f06557c6d338259059b990f8f79c91d68ac8ec1f245c302468f08929b5506901 ./web/js/forms.js
dbf36223863ba882c03eccea4516ba8db7acf0c2e72c6c64490cb994f1e20f96 ./web/js/history.js
7eaa04992ea828dc89f5eaebf674cbbfd160f53eb8d0d6ef0d4e47bd8c28c435 ./web/js/main.js
@@ -152,6 +189,6 @@ c8c82e88c3715b1bfabf155e36266a4c94f5e2fc03eb39dcdd9d08a1985a997c ./web/js/route
566702c0dafd304581c0438597578bccce1cf21c428171a36415728eb4867d04 ./web/js/settings-ui.js
d299216bb707f494be77e0d46f87b203cf1a2c020e0036af86d8f5818ba03c88 ./web/js/settings.js
6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest
f1f028e197f6caef639ef0ddb20950d00bd821f94e0e4a0ef2ad8bab1bb1fd24 ./web/styles.css
a1590b58b6a0e3fc632489de55a41261787f1340f7d9bf778a44d6f8ab1a19d6 ./web/styles.css
ae1b03f30b494f474a781a5d32072f1e73eba2b7c768109fc1cc8028cb6662a4 ./web/sw.js
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
+3 -1
View File
@@ -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.7**
**Current release: 0.9.9**
> [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples.
@@ -207,6 +207,8 @@ Schedules select profiles or a custom target for selected ISO weekdays (`1=Monda
**Flow** is the authoring layer for combined schedules and automations. The full-screen editor stores a block graph as the source of truth and compiles it automatically: a pure weekday + time-range + thermostat path becomes a native schedule, while richer logic becomes a generated automation. Generated schedule/automation rows carry their Flow provenance and are read-only in the legacy editors.
The reusable Flow preset library is stored as standalone JSON files in `presets/`. Each file contains its name, description and complete Flow graph, while the UI loads the embedded preset catalog dynamically instead of hard-coding graphs in JavaScript.
Available blocks include weekday/time/date ranges, the application Night mode, outdoor/device/zone temperature, house/device/zone/group state, arbitrary Home Assistant state/numeric/attribute/availability checks, a constant diagnostic source, AND/OR/NOT, thermostat actions, direct GREE actions and group actions. The GREE action uses the existing `DeviceCommand` fields (power, mode, target, fan, swing, quiet, turbo, light, air, xfan, health and sleep). Multiple branches can feed an action; the compiled runtime preserves the graph logic.
Quick preset/setpoint overrides normally hand control back at the next schedule boundary. Temporary Quick Thermostat adds explicit start/finish rules such as duration, exact time, temperature reached/stable or next schedule boundary.
+99
View File
@@ -11,6 +11,16 @@ fn required_string<'a>(meta: &'a Value, key: &str, file: &str) -> &'a str {
.unwrap_or_else(|| panic!("{file}: meta.{key} must be a non-empty string"))
}
fn has_text(value: Option<&Value>) -> bool {
match value {
Some(Value::String(text)) => !text.trim().is_empty(),
Some(Value::Object(items)) => items
.values()
.any(|item| item.as_str().is_some_and(|text| !text.trim().is_empty())),
_ => false,
}
}
fn fnv1a_update(mut hash: u64, bytes: &[u8]) -> u64 {
for byte in bytes {
hash ^= u64::from(*byte);
@@ -62,8 +72,10 @@ fn bundle_app_js(web_dir: &Path) -> String {
fn main() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
let lang_dir = manifest_dir.join("lang");
let preset_dir = manifest_dir.join("presets");
let web_dir = manifest_dir.join("web");
println!("cargo:rerun-if-changed={}", lang_dir.display());
println!("cargo:rerun-if-changed={}", preset_dir.display());
let theme_init_path = web_dir.join("theme-init.js");
let styles_path = web_dir.join("styles.css");
@@ -191,12 +203,86 @@ fn main() {
panic!("lang/en.json is required as the default fallback language");
}
let mut preset_files: Vec<PathBuf> = fs::read_dir(&preset_dir)
.unwrap_or_else(|error| panic!("cannot read {}: {error}", preset_dir.display()))
.filter_map(Result::ok)
.map(|entry| entry.path())
.filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some("json"))
.collect();
preset_files.sort();
if preset_files.is_empty() {
panic!("no flow preset files found in {}", preset_dir.display());
}
let mut preset_assets = Vec::new();
let mut preset_manifest = Vec::new();
for path in preset_files {
println!("cargo:rerun-if-changed={}", path.display());
let filename = path
.file_name()
.and_then(|value| value.to_str())
.expect("UTF-8 preset filename");
let stem = path
.file_stem()
.and_then(|value| value.to_str())
.expect("UTF-8 preset id");
if !stem.chars().all(|ch| ch.is_ascii_alphanumeric() || ch == '-' || ch == '_') {
panic!("{filename}: filename may only contain ASCII letters, digits, '-' and '_'");
}
let source = fs::read_to_string(&path)
.unwrap_or_else(|error| panic!("cannot read {}: {error}", path.display()));
build_hash = fnv1a_update(build_hash, source.as_bytes());
let document: Value = serde_json::from_str(&source)
.unwrap_or_else(|error| panic!("{filename}: invalid JSON: {error}"));
let object = document
.as_object()
.unwrap_or_else(|| panic!("{filename}: preset root must be an object"));
let id = object
.get("id")
.and_then(Value::as_str)
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| panic!("{filename}: id must be a non-empty string"));
if id != stem {
panic!("{filename}: id '{id}' must match filename '{stem}.json'");
}
let category = object
.get("category")
.and_then(Value::as_str)
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| panic!("{filename}: category must be a non-empty string"));
if !has_text(object.get("name")) {
panic!("{filename}: name must be a string or localized object with text");
}
if !has_text(object.get("description")) {
panic!("{filename}: description must be a string or localized object with text");
}
let flow = object
.get("flow")
.and_then(Value::as_object)
.unwrap_or_else(|| panic!("{filename}: flow must be an object"));
if !flow.get("nodes").is_some_and(Value::is_array) || !flow.get("edges").is_some_and(Value::is_array) {
panic!("{filename}: flow.nodes and flow.edges must be arrays");
}
preset_manifest.push(json!({
"id": id,
"category": category,
"file": filename,
}));
preset_assets.push((filename.to_owned(), path));
}
let manifest_json = serde_json::to_string(&json!({
"default": "en",
"languages": manifest_languages
}))
.expect("serialize language manifest");
let preset_manifest_json = serde_json::to_string(&json!({
"version": 1,
"presets": preset_manifest,
}))
.expect("serialize preset manifest");
let mut generated = String::new();
generated.push_str("// @generated by build.rs - do not edit.\n");
generated.push_str(&format!(
@@ -228,6 +314,19 @@ fn main() {
));
}
generated.push_str("];\n");
generated.push_str(&format!(
"pub const PRESET_MANIFEST_JSON: &str = {:?};\n",
preset_manifest_json
));
generated.push_str("pub const PRESET_ASSETS: &[(&str, &str)] = &[\n");
for (filename, path) in preset_assets {
generated.push_str(&format!(
" ({:?}, include_str!({:?})),\n",
filename,
path.to_string_lossy()
));
}
generated.push_str("];\n");
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR"));
fs::write(out_dir.join("app.bundle.js"), app_js).expect("write generated app.bundle.js");
+3 -3
View File
@@ -1,6 +1,6 @@
# GREE Controller API reference
HTTP and WebSocket API for GREE Controller **0.9.7**.
HTTP and WebSocket API for GREE Controller **0.9.9**.
[← Main documentation](../README.md)
@@ -212,7 +212,7 @@ Response:
{
"status": "ok",
"name": "gree-controller",
"version": "0.9.7",
"version": "0.9.9",
"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.7",
"version": "0.9.9",
"uptime_seconds": 1234,
"auth_required": false,
"control_ready": true,
+4 -2
View File
@@ -61,9 +61,11 @@ Condition/source blocks currently include weekday, time range, date range, appli
Actions include thermostat zone, GREE device and climate group. Direct GREE actions expose power, HVAC mode, target temperature, fan speed, vertical/horizontal swing, quiet, turbo, light, air, xfan, health and sleep.
## Built-in templates
## Built-in presets
The editor ships 37 editable templates grouped into seven categories: Comfort, Energy & cost, Protection & safety, Night, Reliability, Home Assistant / heat sources and Advanced. The library includes workday/weekend comfort, weather-aware demand, morning boost, presence and energy-price eco modes, peak-power limiting, open-window/humidity/overheat/frost guards, night profiles, sensor/device resilience, dual heating/cooling thresholds, multi-room group control, nested AND/OR/NOT Home Assistant scenarios, and gas-boiler/external-heat-source coordination including off, reduced-target, boost and fallback heating patterns.
The editor ships 37 editable presets grouped into seven categories: Comfort, Energy & cost, Protection & safety, Night, Reliability, Home Assistant / heat sources and Advanced. The library includes workday/weekend comfort, weather-aware demand, morning boost, presence and energy-price eco modes, peak-power limiting, open-window/humidity/overheat/frost guards, night profiles, sensor/device resilience, dual heating/cooling thresholds, multi-room group control, nested AND/OR/NOT Home Assistant scenarios, and gas-boiler/external-heat-source coordination including off, reduced-target, boost and fallback heating patterns.
Presets are data files, not JavaScript definitions. Every preset lives in `presets/<id>.json` and contains `id`, `category`, localized `name`, localized `description`, and the complete `flow` graph (`nodes` + `edges`). `build.rs` validates and embeds the directory, exposes `/presets/index.json` plus `/presets/<file>.json`, and the Flow editor loads that library dynamically. Device/zone/group placeholders such as `$zone1`, `$zone2`, `$device1` and `$group1` are resolved when a preset is applied. Adding a preset therefore does not require adding graph-building code to `web/js/flows.js`.
The canvas supports multi-selection (Shift/Ctrl/Cmd), `Ctrl/Cmd+A`, Select all / Clear selection toolbar actions, and dragging the whole selected group while preserving relative positions.
@@ -1,7 +1,7 @@
{
"domain": "gree_controller",
"name": "GREE Controller",
"version": "0.9.7",
"version": "0.9.9",
"config_flow": true,
"integration_type": "hub",
"iot_class": "local_polling",
+8 -79
View File
@@ -882,10 +882,10 @@
"flow.exported": "Flow exported.",
"flow.imported": "Flow imported and compiled.",
"flow.importFailed": "Flow import failed",
"flow.templates": "Templates",
"flow.templateLibrary": "Flow library",
"flow.templatesHint": "Ready-made layouts create an editable graph using existing zones, devices and groups.",
"flow.templateReplaceConfirm": "Replace the current blocks with this template?",
"flow.templates": "Presets",
"flow.templateLibrary": "Flow preset library",
"flow.templatesHint": "Ready-made presets create an editable graph using existing zones, devices and groups.",
"flow.templateReplaceConfirm": "Replace the current blocks with this preset?",
"flow.dryRun": "Dry-run",
"flow.logs": "Logs",
"flow.testMode": "Flow diagnostics",
@@ -914,30 +914,10 @@
"flow.node.deviceState": "Device state",
"flow.node.zoneState": "Thermostat state",
"flow.node.constant": "Test constant",
"flow.template.workday_comfort.title": "Workday comfort",
"flow.template.workday_comfort.description": "Weekday morning comfort preset for the selected thermostat zone.",
"flow.template.weather_comfort.title": "Weather-aware comfort",
"flow.template.weather_comfort.description": "Combines weekday, time and outdoor temperature with AND before thermostat control.",
"flow.template.smart_demand.title": "Smart demand",
"flow.template.smart_demand.description": "Complex time, house-mode and outdoor-or-room temperature logic.",
"flow.template.device_resilience.title": "Only when device is healthy",
"flow.template.device_resilience.description": "Runs the thermostat only when the device is enabled, online and house mode allows operation.",
"flow.template.night_group.title": "Night group",
"flow.template.night_group.description": "Night time + house mode applies sleep preset to a group or zone.",
"flow.template.ha_window_guard.title": "Open window — stop HVAC",
"flow.template.ha_window_guard.description": "Home Assistant binary_sensor example: an open window turns the thermostat off. Set the correct entity_id.",
"flow.node.groupState": "Group state",
"flow.node.nightMode": "Night mode",
"flow.nightModeActive": "active according to app settings",
"flow.nightModeHint": "This condition is true while the Night mode configured in the app is currently active. Use NOT to match time outside Night mode.",
"flow.template.presence_eco.title": "Presence: comfort / eco",
"flow.template.presence_eco.description": "Two explicit Home Assistant branches: home enables comfort while not_home selects the away preset. Set the correct person or presence entity_id.",
"flow.template.frost_guard.title": "Frost protection",
"flow.template.frost_guard.description": "In heating mode, low outdoor and room temperatures select a safe low thermostat target.",
"flow.template.nested_guard.title": "Nested OR and NOT guard",
"flow.template.nested_guard.description": "Weekdays and time + outdoor OR room temperature + NOT open window. An example of a multi-level graph.",
"flow.template.night_quiet.title": "Night group profile",
"flow.template.night_quiet.description": "Uses the app Night mode and group state directly to apply the sleep preset.",
"flow.node.haAvailable": "HA: availability",
"flow.available": "available",
"flow.haAvailableHint": "This condition is true only when the Home Assistant entity returns a state other than unknown/unavailable. Connection errors fail closed as false. Useful as a guard before NOT and safety logic.",
@@ -983,61 +963,10 @@
"flow.templateCategory.reliability.hint": "Flows resilient to unavailable sensors, offline devices and disabled thermostats.",
"flow.templateCategory.advanced": "Advanced",
"flow.templateCategory.advanced.hint": "Nested AND/OR/NOT logic, multiple rooms and Home Assistant states.",
"flow.template.morning_boost.title": "Cold-weather morning boost",
"flow.template.morning_boost.description": "On workday mornings, only in heating mode and during cold weather, raises the target to 22.5°C.",
"flow.template.weekend_comfort.title": "Weekend comfort",
"flow.template.weekend_comfort.description": "Keeps daytime comfort on Saturday and Sunday while the house climate mode is not off.",
"flow.template.energy_price_eco.title": "Expensive energy + nobody home",
"flow.template.energy_price_eco.description": "When energy is expensive and nobody is home, switches the selected zone to an economical away profile.",
"flow.template.peak_power_guard.title": "Peak power guard",
"flow.template.peak_power_guard.description": "Turns HVAC off when household power draw exceeds the configured threshold while climate control is active.",
"flow.template.mild_weather_eco.title": "Mild weather — HVAC off",
"flow.template.mild_weather_eco.description": "When outdoor temperature is within a comfortable band and the window is closed, turns HVAC off.",
"flow.template.unoccupied_shutdown.title": "Shut down while away",
"flow.template.unoccupied_shutdown.description": "During daytime hours, turns off a group or zone when the selected person is away.",
"flow.template.humidity_guard.title": "High humidity cooling",
"flow.template.humidity_guard.description": "With high humidity, occupancy and cooling house mode, applies moderate cooling.",
"flow.template.overheat_guard.title": "Overheat guard",
"flow.template.overheat_guard.description": "If a zone is very warm, house mode is cooling and the window is closed, starts protective cooling.",
"flow.template.window_available_guard.title": "Window availability + open state",
"flow.template.window_available_guard.description": "Turns HVAC off only when the window sensor is available and reports open, avoiding false reactions to unavailable.",
"flow.template.sleep_temperature_guard.title": "Night cooling only when needed",
"flow.template.sleep_temperature_guard.description": "In night mode, cools only when room temperature is above the threshold and the house is in cooling mode.",
"flow.template.bedroom_window_night.title": "Bedroom: night + closed window",
"flow.template.bedroom_window_night.description": "Combines night mode with NOT open-window logic so the sleep profile runs only with a closed window.",
"flow.template.sensor_availability_guard.title": "Cooling with sensor validation",
"flow.template.sensor_availability_guard.description": "Checks HA sensor availability first, then temperature and house mode before starting cooling.",
"flow.template.offline_safe_off.title": "Offline device safe-off",
"flow.template.offline_safe_off.description": "When a device is offline while its thermostat remains active, issues a conservative off command with a long cooldown.",
"flow.template.thermostat_enabled_guard.title": "Control only an active thermostat",
"flow.template.thermostat_enabled_guard.description": "Requires an enabled zone, active demand and a non-off house mode before the action executes.",
"flow.template.occupancy_weather_matrix.title": "Occupancy + weather + window",
"flow.template.occupancy_weather_matrix.description": "Comfort runs only when someone is home, outdoor or room demand exists, and the window is not open.",
"flow.template.dual_threshold_control.title": "Dual threshold heating/cooling",
"flow.template.dual_threshold_control.description": "Two independent branches heat below the lower threshold and cool above the upper threshold.",
"flow.template.multi_room_group_guard.title": "Multiple rooms → one group",
"flow.template.multi_room_group_guard.description": "If either of two zones is too warm and house mode is cooling, starts a shared group or zone.",
"flow.template.ha_attribute_mode_guard.title": "HA attribute + window + availability",
"flow.template.ha_attribute_mode_guard.description": "Reads hvac_action, entity availability and window state to stop HVAC only when the data is trustworthy.",
"flow.templateCategory.home_assistant": "Home Assistant / heat sources",
"flow.templateCategory.home_assistant.hint": "Coordinate GREE with a gas boiler, external heat source, heating demand and temperatures read from Home Assistant.",
"flow.template.ha_gas_heating_off.title": "Gas boiler heating → turn GREE off",
"flow.template.ha_gas_heating_off.description": "Reads hvac_action from climate.gas_boiler. When HA reports heating and the entity is available, the selected GREE zone is turned off.",
"flow.template.ha_gas_heating_boost.title": "Gas boiler heating → GREE heat boost",
"flow.template.ha_gas_heating_boost.description": "When the gas boiler is actively heating, the room is below 20°C and house mode is heat, GREE assists with a 23.5°C target.",
"flow.template.ha_gas_heating_reduce.title": "Boiler heating + room warm → reduce GREE",
"flow.template.ha_gas_heating_reduce.description": "When gas heat is already active and the zone reaches at least 21°C, lowers the GREE target to 18°C so the heat sources do not fight each other.",
"flow.template.ha_gas_backup_heat.title": "Gas not heating + room cold → GREE backup",
"flow.template.ha_gas_backup_heat.description": "When the boiler entity is available but does not report heating and the room drops below 19°C, GREE provides backup heat to 22°C.",
"flow.template.ha_external_heat_source_off.title": "Other heat source ON → turn GREE off",
"flow.template.ha_external_heat_source_off.description": "Generic fireplace, furnace or other source preset. binary_sensor.external_heat_source = on turns the GREE group or zone off.",
"flow.template.ha_external_heat_source_assist.title": "Other heat source ON → assist with GREE",
"flow.template.ha_external_heat_source_assist.description": "When an external heat source is running but the room is still below 20°C, GREE assists in heat mode with a 23°C target.",
"flow.template.ha_heating_demand_follow.title": "Follow Home Assistant heating demand",
"flow.template.ha_heating_demand_follow.description": "binary_sensor.heating_demand = on starts GREE heating to 22.5°C while the house is in heat mode.",
"flow.template.ha_boiler_supply_boost.title": "Hot boiler supply + cold room → boost",
"flow.template.ha_boiler_supply_boost.description": "Reads sensor.boiler_supply_temperature. Above 45°C with a cold room, GREE is set to 23.5°C.",
"flow.template.ha_thermostat_idle_fallback.title": "Gas thermostat idle → GREE fallback heat",
"flow.template.ha_thermostat_idle_fallback.description": "If climate.gas_boiler is available, reports hvac_action=idle and the room falls below 19°C, GREE heats to 21.5°C."
}
"flow.templatesLoadFailed": "Unable to load preset library."
},
"flow.sharedInputsSettingsLink": "Shared Flow inputs in Home Assistant →",
"flow.templateCategories": "Preset categories"
}
+6 -77
View File
@@ -883,8 +883,8 @@
"flow.imported": "Flow zaimportowany i skompilowany.",
"flow.importFailed": "Nie udało się zaimportować Flow",
"flow.templates": "Presety",
"flow.templateLibrary": "Biblioteka Flow",
"flow.templatesHint": "Gotowe układy tworzą edytowalny graf z użyciem istniejących stref, urządzeń i grup.",
"flow.templateLibrary": "Biblioteka presetów Flow",
"flow.templatesHint": "Gotowe presety tworzą edytowalny graf z użyciem istniejących stref, urządzeń i grup.",
"flow.templateReplaceConfirm": "Zastąpić obecne bloki wybranym presetem?",
"flow.dryRun": "Dry-run",
"flow.logs": "Logi",
@@ -914,30 +914,10 @@
"flow.node.deviceState": "Stan urządzenia",
"flow.node.zoneState": "Stan termostatu",
"flow.node.constant": "Stała testowa",
"flow.template.workday_comfort.title": "Komfort w dni robocze",
"flow.template.workday_comfort.description": "Pon.pt. rano uruchamia preset komfortu w wybranej strefie.",
"flow.template.weather_comfort.title": "Komfort zależny od pogody",
"flow.template.weather_comfort.description": "Łączy dzień, godzinę i temperaturę zewnętrzną przez AND przed sterowaniem termostatem.",
"flow.template.smart_demand.title": "Inteligentne zapotrzebowanie",
"flow.template.smart_demand.description": "Złożona logika czasu, trybu domu i alternatywy temperatury zewnętrznej lub pokojowej.",
"flow.template.device_resilience.title": "Sterowanie tylko przy sprawnym urządzeniu",
"flow.template.device_resilience.description": "Uruchamia termostat tylko gdy urządzenie jest włączone, online i tryb domu pozwala na pracę.",
"flow.template.night_group.title": "Noc dla grupy",
"flow.template.night_group.description": "Nocny przedział czasu + tryb domu sterują presetem sleep dla grupy lub strefy.",
"flow.template.ha_window_guard.title": "Otwarte okno — wyłącz HVAC",
"flow.template.ha_window_guard.description": "Przykład integracji z binary_sensor Home Assistant: otwarte okno wyłącza termostat. Ustaw właściwy entity_id.",
"flow.node.groupState": "Stan grupy",
"flow.node.nightMode": "Tryb nocny",
"flow.nightModeActive": "aktywny zgodnie z ustawieniami aplikacji",
"flow.nightModeHint": "Warunek jest prawdziwy, gdy skonfigurowany w aplikacji Tryb nocny jest aktualnie aktywny. Użyj NOT, aby sprawdzić czas poza trybem nocnym.",
"flow.template.presence_eco.title": "Obecność: komfort / eco",
"flow.template.presence_eco.description": "Dwie jawne gałęzie Home Assistant: stan home włącza komfort, a not_home ustawia preset away. Ustaw właściwy entity_id osoby lub czujnika obecności.",
"flow.template.frost_guard.title": "Ochrona przeciwzamrożeniowa",
"flow.template.frost_guard.description": "W trybie grzania, przy niskiej temperaturze zewnętrznej i pokojowej, ustawia bezpieczny niski cel termostatu.",
"flow.template.nested_guard.title": "Złożony warunek z OR i NOT",
"flow.template.nested_guard.description": "Dni i godziny + temperatura zewnętrzna LUB pokojowa + NOT dla otwartego okna. Przykład wielopoziomowego grafu.",
"flow.template.night_quiet.title": "Nocny profil grupy",
"flow.template.night_quiet.description": "Korzysta bezpośrednio z istniejącego Trybu nocnego aplikacji i stanu grupy, aby zastosować preset sleep.",
"flow.node.haAvailable": "HA: dostępność",
"flow.available": "dostępna",
"flow.haAvailableHint": "Warunek jest prawdziwy tylko wtedy, gdy encja Home Assistant odpowiada stanem innym niż unknown/unavailable. Błąd połączenia jest traktowany bezpiecznie jako false. Przydatne jako bramka przed NOT i logiką bezpieczeństwa.",
@@ -983,61 +963,10 @@
"flow.templateCategory.reliability.hint": "Automatyzacje odporne na niedostępne sensory, urządzenia offline i wyłączone termostaty.",
"flow.templateCategory.advanced": "Zaawansowane",
"flow.templateCategory.advanced.hint": "Wielopoziomowe AND/OR/NOT, wiele pomieszczeń i stany Home Assistant.",
"flow.template.morning_boost.title": "Poranne dogrzanie przy mrozie",
"flow.template.morning_boost.description": "W dni robocze rano, tylko w trybie grzania i przy mrozie na zewnątrz, podnosi cel do 22,5°C.",
"flow.template.weekend_comfort.title": "Weekendowy komfort",
"flow.template.weekend_comfort.description": "W sobotę i niedzielę utrzymuje komfort w ciągu dnia, jeśli tryb domu nie jest wyłączony.",
"flow.template.energy_price_eco.title": "Droga energia + brak domowników",
"flow.template.energy_price_eco.description": "Przy wysokiej cenie energii i nieobecności przełącza wybraną strefę w oszczędny profil away.",
"flow.template.peak_power_guard.title": "Ochrona przed szczytem mocy",
"flow.template.peak_power_guard.description": "Wyłącza HVAC, gdy pobór mocy domu przekroczy ustawiony próg i system klimatu jest aktywny.",
"flow.template.mild_weather_eco.title": "Łagodna pogoda — HVAC off",
"flow.template.mild_weather_eco.description": "Gdy temperatura zewnętrzna mieści się w komfortowym zakresie i okno jest zamknięte, wyłącza HVAC.",
"flow.template.unoccupied_shutdown.title": "Wyłącz podczas nieobecności",
"flow.template.unoccupied_shutdown.description": "W godzinach dziennych wyłącza grupę lub strefę, gdy wskazana osoba jest poza domem.",
"flow.template.humidity_guard.title": "Wysoka wilgotność — chłodzenie",
"flow.template.humidity_guard.description": "Przy wysokiej wilgotności, obecności domowników i trybie chłodzenia wymusza umiarkowane chłodzenie.",
"flow.template.overheat_guard.title": "Ochrona przed przegrzaniem",
"flow.template.overheat_guard.description": "Jeśli strefa jest bardzo ciepła, tryb domu to chłodzenie i okno jest zamknięte, uruchamia chłodzenie ochronne.",
"flow.template.window_available_guard.title": "Okno: dostępność + otwarcie",
"flow.template.window_available_guard.description": "Wyłącza HVAC dopiero gdy czujnik okna jest dostępny i zgłasza otwarcie — bez fałszywej reakcji na unavailable.",
"flow.template.sleep_temperature_guard.title": "Nocne chłodzenie tylko gdy trzeba",
"flow.template.sleep_temperature_guard.description": "W trybie nocnym chłodzi tylko wtedy, gdy temperatura pokoju przekracza próg i dom jest w trybie cool.",
"flow.template.bedroom_window_night.title": "Sypialnia: noc + zamknięte okno",
"flow.template.bedroom_window_night.description": "Łączy tryb nocny z NOT otwartego okna, aby profil sleep działał tylko przy zamkniętym oknie.",
"flow.template.sensor_availability_guard.title": "Chłodzenie z kontrolą sensora",
"flow.template.sensor_availability_guard.description": "Najpierw sprawdza dostępność sensora HA, potem temperaturę i tryb domu; dopiero wtedy uruchamia chłodzenie.",
"flow.template.offline_safe_off.title": "Urządzenie offline — bezpieczne wyłączenie",
"flow.template.offline_safe_off.description": "Gdy urządzenie jest offline, a termostat nadal aktywny, wydaje bezpieczne polecenie wyłączenia z długim cooldownem.",
"flow.template.thermostat_enabled_guard.title": "Steruj tylko aktywnym termostatem",
"flow.template.thermostat_enabled_guard.description": "Wymaga włączonej strefy, aktywnego zapotrzebowania i niezerowego trybu domu przed akcją.",
"flow.template.occupancy_weather_matrix.title": "Obecność + pogoda + okno",
"flow.template.occupancy_weather_matrix.description": "Komfort działa tylko gdy ktoś jest w domu, występuje zapotrzebowanie pogodowe lub pokojowe i okno nie jest otwarte.",
"flow.template.dual_threshold_control.title": "Dwa progi: grzanie i chłodzenie",
"flow.template.dual_threshold_control.description": "Dwie niezależne gałęzie sterują grzaniem poniżej dolnego progu i chłodzeniem powyżej górnego.",
"flow.template.multi_room_group_guard.title": "Wiele pokoi → jedna grupa",
"flow.template.multi_room_group_guard.description": "Jeśli dowolna z dwóch stref jest za ciepła i dom jest w trybie cool, uruchamia wspólną grupę lub strefę.",
"flow.template.ha_attribute_mode_guard.title": "Atrybut HA + okno + dostępność",
"flow.template.ha_attribute_mode_guard.description": "Czyta atrybut hvac_action, dostępność encji i stan okna, aby zatrzymać HVAC tylko przy wiarygodnych danych.",
"flow.templateCategory.home_assistant": "Home Assistant / źródła ciepła",
"flow.templateCategory.home_assistant.hint": "Koordynacja GREE z kotłem gazowym, zewnętrznym ogrzewaniem, zapotrzebowaniem i temperaturami pobieranymi z Home Assistant.",
"flow.template.ha_gas_heating_off.title": "Kocioł gazowy grzeje → wyłącz GREE",
"flow.template.ha_gas_heating_off.description": "Czyta hvac_action z climate.gas_boiler. Gdy HA zgłasza heating i encja jest dostępna, wyłącza wybraną strefę GREE.",
"flow.template.ha_gas_heating_boost.title": "Kocioł gazowy grzeje → dogrzewanie GREE",
"flow.template.ha_gas_heating_boost.description": "Gdy kocioł gazowy aktywnie grzeje, pokój ma mniej niż 20°C i dom jest w trybie heat, GREE wspomaga ogrzewanie celem 23,5°C.",
"flow.template.ha_gas_heating_reduce.title": "Kocioł grzeje + pokój ciepły → obniż GREE",
"flow.template.ha_gas_heating_reduce.description": "Jeśli gaz już grzeje, a strefa osiągnęła co najmniej 21°C, obniża cel GREE do 18°C, aby źródła ciepła nie walczyły ze sobą.",
"flow.template.ha_gas_backup_heat.title": "Gaz nie grzeje + zimno → GREE jako backup",
"flow.template.ha_gas_backup_heat.description": "Gdy encja kotła jest dostępna, ale nie zgłasza heating, a w pokoju jest poniżej 19°C, GREE przejmuje awaryjne grzanie do 22°C.",
"flow.template.ha_external_heat_source_off.title": "Inne źródło ciepła ON → wyłącz GREE",
"flow.template.ha_external_heat_source_off.description": "Ogólny preset dla kominka, pieca lub innego źródła. Stan binary_sensor.external_heat_source = on wyłącza grupę albo strefę GREE.",
"flow.template.ha_external_heat_source_assist.title": "Inne źródło ciepła ON → wspomagaj GREE",
"flow.template.ha_external_heat_source_assist.description": "Gdy zewnętrzne źródło działa, ale pokój nadal ma mniej niż 20°C, GREE wspomaga je w trybie heat z celem 23°C.",
"flow.template.ha_heating_demand_follow.title": "Podążaj za żądaniem grzania z HA",
"flow.template.ha_heating_demand_follow.description": "Stan binary_sensor.heating_demand = on uruchamia ogrzewanie GREE do 22,5°C, jeśli dom jest w trybie heat.",
"flow.template.ha_boiler_supply_boost.title": "Gorące zasilanie kotła + zimny pokój → boost",
"flow.template.ha_boiler_supply_boost.description": "Czyta sensor.boiler_supply_temperature. Przy temperaturze zasilania powyżej 45°C i zimnym pokoju ustawia GREE na 23,5°C.",
"flow.template.ha_thermostat_idle_fallback.title": "Termostat gazowy idle → awaryjne grzanie GREE",
"flow.template.ha_thermostat_idle_fallback.description": "Jeśli climate.gas_boiler jest dostępny, ma hvac_action=idle i pokój spadnie poniżej 19°C, GREE uruchamia grzanie do 21,5°C."
}
"flow.templatesLoadFailed": "Nie udało się wczytać biblioteki presetów."
},
"flow.sharedInputsSettingsLink": "Wspólne wejścia Flow w Home Assistant →",
"flow.templateCategories": "Kategorie presetów"
}
+83
View File
@@ -0,0 +1,83 @@
{
"id": "bedroom_window_night",
"category": "night",
"name": {
"pl": "Sypialnia: noc + zamknięte okno",
"en": "Bedroom: night + closed window"
},
"description": {
"pl": "Łączy tryb nocny z NOT otwartego okna, aby profil sleep działał tylko przy zamkniętym oknie.",
"en": "Combines night mode with NOT open-window logic so the sleep profile runs only with a closed window."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000148",
"kind": "night_mode",
"x": 35,
"y": 65,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000149",
"kind": "ha_state",
"x": 35,
"y": 185,
"config": {
"entity_id": "binary_sensor.bedroom_window",
"operator": "eq",
"value": "on"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000150",
"kind": "logic_not",
"x": 275,
"y": 185,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000151",
"kind": "logic_and",
"x": 500,
"y": 125,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000152",
"kind": "zone_thermostat",
"x": 745,
"y": 125,
"config": {
"zone_id": "$zone1",
"preset": "sleep",
"setpoint": 20,
"mode": "auto",
"cooldown_seconds": 180
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000153",
"from": "node-00000000-0000-4000-8000-000000000149",
"to": "node-00000000-0000-4000-8000-000000000150"
},
{
"id": "edge-00000000-0000-4000-8000-000000000154",
"from": "node-00000000-0000-4000-8000-000000000148",
"to": "node-00000000-0000-4000-8000-000000000151"
},
{
"id": "edge-00000000-0000-4000-8000-000000000155",
"from": "node-00000000-0000-4000-8000-000000000150",
"to": "node-00000000-0000-4000-8000-000000000151"
},
{
"id": "edge-00000000-0000-4000-8000-000000000156",
"from": "node-00000000-0000-4000-8000-000000000151",
"to": "node-00000000-0000-4000-8000-000000000152"
}
]
}
}
+92
View File
@@ -0,0 +1,92 @@
{
"id": "device_resilience",
"category": "reliability",
"name": {
"pl": "Sterowanie tylko przy sprawnym urządzeniu",
"en": "Only when device is healthy"
},
"description": {
"pl": "Uruchamia termostat tylko gdy urządzenie jest włączone, online i tryb domu pozwala na pracę.",
"en": "Runs the thermostat only when the device is enabled, online and house mode allows operation."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000157",
"kind": "device_state",
"x": 40,
"y": 55,
"config": {
"device_id": "$device1",
"field": "online",
"operator": "eq",
"value": "true"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000158",
"kind": "device_state",
"x": 40,
"y": 175,
"config": {
"device_id": "$device1",
"field": "enabled",
"operator": "eq",
"value": "true"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000159",
"kind": "house_mode",
"x": 40,
"y": 295,
"config": {
"operator": "neq",
"value": "off"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000160",
"kind": "logic_and",
"x": 315,
"y": 175,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000161",
"kind": "zone_thermostat",
"x": 570,
"y": 175,
"config": {
"zone_id": "$zone1",
"preset": "auto",
"setpoint": 21,
"mode": "auto",
"cooldown_seconds": 90
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000162",
"from": "node-00000000-0000-4000-8000-000000000157",
"to": "node-00000000-0000-4000-8000-000000000160"
},
{
"id": "edge-00000000-0000-4000-8000-000000000163",
"from": "node-00000000-0000-4000-8000-000000000158",
"to": "node-00000000-0000-4000-8000-000000000160"
},
{
"id": "edge-00000000-0000-4000-8000-000000000164",
"from": "node-00000000-0000-4000-8000-000000000159",
"to": "node-00000000-0000-4000-8000-000000000160"
},
{
"id": "edge-00000000-0000-4000-8000-000000000165",
"from": "node-00000000-0000-4000-8000-000000000160",
"to": "node-00000000-0000-4000-8000-000000000161"
}
]
}
}
+130
View File
@@ -0,0 +1,130 @@
{
"id": "dual_threshold_control",
"category": "advanced",
"name": {
"pl": "Dwa progi: grzanie i chłodzenie",
"en": "Dual threshold heating/cooling"
},
"description": {
"pl": "Dwie niezależne gałęzie sterują grzaniem poniżej dolnego progu i chłodzeniem powyżej górnego.",
"en": "Two independent branches heat below the lower threshold and cool above the upper threshold."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000312",
"kind": "zone_temperature",
"x": 35,
"y": 70,
"config": {
"zone_id": "$zone1",
"operator": "lt",
"value": 19
}
},
{
"id": "node-00000000-0000-4000-8000-000000000313",
"kind": "zone_temperature",
"x": 35,
"y": 245,
"config": {
"zone_id": "$zone1",
"operator": "gt",
"value": 25
}
},
{
"id": "node-00000000-0000-4000-8000-000000000314",
"kind": "house_mode",
"x": 250,
"y": 70,
"config": {
"operator": "eq",
"value": "heat"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000315",
"kind": "house_mode",
"x": 250,
"y": 245,
"config": {
"operator": "eq",
"value": "cool"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000316",
"kind": "logic_and",
"x": 470,
"y": 70,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000317",
"kind": "logic_and",
"x": 470,
"y": 245,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000318",
"kind": "zone_thermostat",
"x": 715,
"y": 70,
"config": {
"zone_id": "$zone1",
"preset": "comfort",
"setpoint": 21,
"mode": "heat",
"cooldown_seconds": 90
}
},
{
"id": "node-00000000-0000-4000-8000-000000000319",
"kind": "zone_thermostat",
"x": 715,
"y": 245,
"config": {
"zone_id": "$zone1",
"preset": "comfort",
"setpoint": 21,
"mode": "cool",
"cooldown_seconds": 90
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000320",
"from": "node-00000000-0000-4000-8000-000000000312",
"to": "node-00000000-0000-4000-8000-000000000316"
},
{
"id": "edge-00000000-0000-4000-8000-000000000321",
"from": "node-00000000-0000-4000-8000-000000000314",
"to": "node-00000000-0000-4000-8000-000000000316"
},
{
"id": "edge-00000000-0000-4000-8000-000000000322",
"from": "node-00000000-0000-4000-8000-000000000313",
"to": "node-00000000-0000-4000-8000-000000000317"
},
{
"id": "edge-00000000-0000-4000-8000-000000000323",
"from": "node-00000000-0000-4000-8000-000000000315",
"to": "node-00000000-0000-4000-8000-000000000317"
},
{
"id": "edge-00000000-0000-4000-8000-000000000324",
"from": "node-00000000-0000-4000-8000-000000000316",
"to": "node-00000000-0000-4000-8000-000000000318"
},
{
"id": "edge-00000000-0000-4000-8000-000000000325",
"from": "node-00000000-0000-4000-8000-000000000317",
"to": "node-00000000-0000-4000-8000-000000000319"
}
]
}
}
+75
View File
@@ -0,0 +1,75 @@
{
"id": "energy_price_eco",
"category": "energy",
"name": {
"pl": "Droga energia + brak domowników",
"en": "Expensive energy + nobody home"
},
"description": {
"pl": "Przy wysokiej cenie energii i nieobecności przełącza wybraną strefę w oszczędny profil away.",
"en": "When energy is expensive and nobody is home, switches the selected zone to an economical away profile."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000054",
"kind": "ha_numeric",
"x": 40,
"y": 70,
"config": {
"entity_id": "sensor.energy_price",
"operator": "gt",
"value": 0.8
}
},
{
"id": "node-00000000-0000-4000-8000-000000000055",
"kind": "ha_state",
"x": 40,
"y": 190,
"config": {
"entity_id": "person.someone",
"operator": "neq",
"value": "home"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000056",
"kind": "logic_and",
"x": 315,
"y": 130,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000057",
"kind": "zone_thermostat",
"x": 570,
"y": 130,
"config": {
"zone_id": "$zone1",
"preset": "away",
"setpoint": 18,
"mode": "auto",
"cooldown_seconds": 300
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000058",
"from": "node-00000000-0000-4000-8000-000000000054",
"to": "node-00000000-0000-4000-8000-000000000056"
},
{
"id": "edge-00000000-0000-4000-8000-000000000059",
"from": "node-00000000-0000-4000-8000-000000000055",
"to": "node-00000000-0000-4000-8000-000000000056"
},
{
"id": "edge-00000000-0000-4000-8000-000000000060",
"from": "node-00000000-0000-4000-8000-000000000056",
"to": "node-00000000-0000-4000-8000-000000000057"
}
]
}
}
+89
View File
@@ -0,0 +1,89 @@
{
"id": "frost_guard",
"category": "safety",
"name": {
"pl": "Ochrona przeciwzamrożeniowa",
"en": "Frost protection"
},
"description": {
"pl": "W trybie grzania, przy niskiej temperaturze zewnętrznej i pokojowej, ustawia bezpieczny niski cel termostatu.",
"en": "In heating mode, low outdoor and room temperatures select a safe low thermostat target."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000084",
"kind": "house_mode",
"x": 35,
"y": 50,
"config": {
"operator": "eq",
"value": "heat"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000085",
"kind": "outdoor_temperature",
"x": 35,
"y": 170,
"config": {
"operator": "lt",
"value": 3
}
},
{
"id": "node-00000000-0000-4000-8000-000000000086",
"kind": "zone_temperature",
"x": 35,
"y": 290,
"config": {
"zone_id": "$zone1",
"operator": "lt",
"value": 9
}
},
{
"id": "node-00000000-0000-4000-8000-000000000087",
"kind": "logic_and",
"x": 315,
"y": 170,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000088",
"kind": "zone_thermostat",
"x": 575,
"y": 170,
"config": {
"zone_id": "$zone1",
"preset": "custom",
"setpoint": 12,
"mode": "heat",
"cooldown_seconds": 300
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000089",
"from": "node-00000000-0000-4000-8000-000000000084",
"to": "node-00000000-0000-4000-8000-000000000087"
},
{
"id": "edge-00000000-0000-4000-8000-000000000090",
"from": "node-00000000-0000-4000-8000-000000000085",
"to": "node-00000000-0000-4000-8000-000000000087"
},
{
"id": "edge-00000000-0000-4000-8000-000000000091",
"from": "node-00000000-0000-4000-8000-000000000086",
"to": "node-00000000-0000-4000-8000-000000000087"
},
{
"id": "edge-00000000-0000-4000-8000-000000000092",
"from": "node-00000000-0000-4000-8000-000000000087",
"to": "node-00000000-0000-4000-8000-000000000088"
}
]
}
}
+91
View File
@@ -0,0 +1,91 @@
{
"id": "ha_attribute_mode_guard",
"category": "advanced",
"name": {
"pl": "Atrybut HA + okno + dostępność",
"en": "HA attribute + window + availability"
},
"description": {
"pl": "Czyta atrybut hvac_action, dostępność encji i stan okna, aby zatrzymać HVAC tylko przy wiarygodnych danych.",
"en": "Reads hvac_action, entity availability and window state to stop HVAC only when the data is trustworthy."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000337",
"kind": "ha_attribute",
"x": 35,
"y": 60,
"config": {
"entity_id": "climate.living_room",
"attribute": "hvac_action",
"operator": "neq",
"value": "idle"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000338",
"kind": "ha_state",
"x": 35,
"y": 180,
"config": {
"entity_id": "binary_sensor.window",
"operator": "eq",
"value": "on"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000339",
"kind": "ha_available",
"x": 35,
"y": 300,
"config": {
"entity_id": "climate.living_room"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000340",
"kind": "logic_and",
"x": 315,
"y": 180,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000341",
"kind": "zone_thermostat",
"x": 575,
"y": 180,
"config": {
"zone_id": "$zone1",
"preset": "auto",
"setpoint": 21,
"mode": "auto",
"cooldown_seconds": 60,
"power": false
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000342",
"from": "node-00000000-0000-4000-8000-000000000337",
"to": "node-00000000-0000-4000-8000-000000000340"
},
{
"id": "edge-00000000-0000-4000-8000-000000000343",
"from": "node-00000000-0000-4000-8000-000000000338",
"to": "node-00000000-0000-4000-8000-000000000340"
},
{
"id": "edge-00000000-0000-4000-8000-000000000344",
"from": "node-00000000-0000-4000-8000-000000000339",
"to": "node-00000000-0000-4000-8000-000000000340"
},
{
"id": "edge-00000000-0000-4000-8000-000000000345",
"from": "node-00000000-0000-4000-8000-000000000340",
"to": "node-00000000-0000-4000-8000-000000000341"
}
]
}
}
+105
View File
@@ -0,0 +1,105 @@
{
"id": "ha_boiler_supply_boost",
"category": "home_assistant",
"name": {
"pl": "Gorące zasilanie kotła + zimny pokój → boost",
"en": "Hot boiler supply + cold room → boost"
},
"description": {
"pl": "Czyta sensor.boiler_supply_temperature. Przy temperaturze zasilania powyżej 45°C i zimnym pokoju ustawia GREE na 23,5°C.",
"en": "Reads sensor.boiler_supply_temperature. Above 45°C with a cold room, GREE is set to 23.5°C."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000256",
"kind": "ha_available",
"x": 25,
"y": 45,
"config": {
"entity_id": "sensor.boiler_supply_temperature"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000257",
"kind": "ha_numeric",
"x": 25,
"y": 160,
"config": {
"entity_id": "sensor.boiler_supply_temperature",
"operator": "gt",
"value": 45
}
},
{
"id": "node-00000000-0000-4000-8000-000000000258",
"kind": "zone_temperature",
"x": 25,
"y": 275,
"config": {
"zone_id": "$zone1",
"operator": "lt",
"value": 20
}
},
{
"id": "node-00000000-0000-4000-8000-000000000259",
"kind": "house_mode",
"x": 25,
"y": 390,
"config": {
"operator": "eq",
"value": "heat"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000260",
"kind": "logic_and",
"x": 320,
"y": 215,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000261",
"kind": "zone_thermostat",
"x": 590,
"y": 215,
"config": {
"zone_id": "$zone1",
"preset": "custom",
"setpoint": 23.5,
"mode": "heat",
"cooldown_seconds": 180,
"power": true
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000262",
"from": "node-00000000-0000-4000-8000-000000000256",
"to": "node-00000000-0000-4000-8000-000000000260"
},
{
"id": "edge-00000000-0000-4000-8000-000000000263",
"from": "node-00000000-0000-4000-8000-000000000257",
"to": "node-00000000-0000-4000-8000-000000000260"
},
{
"id": "edge-00000000-0000-4000-8000-000000000264",
"from": "node-00000000-0000-4000-8000-000000000258",
"to": "node-00000000-0000-4000-8000-000000000260"
},
{
"id": "edge-00000000-0000-4000-8000-000000000265",
"from": "node-00000000-0000-4000-8000-000000000259",
"to": "node-00000000-0000-4000-8000-000000000260"
},
{
"id": "edge-00000000-0000-4000-8000-000000000266",
"from": "node-00000000-0000-4000-8000-000000000260",
"to": "node-00000000-0000-4000-8000-000000000261"
}
]
}
}
+105
View File
@@ -0,0 +1,105 @@
{
"id": "ha_external_heat_source_assist",
"category": "home_assistant",
"name": {
"pl": "Inne źródło ciepła ON → wspomagaj GREE",
"en": "Other heat source ON → assist with GREE"
},
"description": {
"pl": "Gdy zewnętrzne źródło działa, ale pokój nadal ma mniej niż 20°C, GREE wspomaga je w trybie heat z celem 23°C.",
"en": "When an external heat source is running but the room is still below 20°C, GREE assists in heat mode with a 23°C target."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000236",
"kind": "ha_available",
"x": 25,
"y": 40,
"config": {
"entity_id": "binary_sensor.external_heat_source"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000237",
"kind": "ha_state",
"x": 25,
"y": 155,
"config": {
"entity_id": "binary_sensor.external_heat_source",
"operator": "eq",
"value": "on"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000238",
"kind": "zone_temperature",
"x": 25,
"y": 270,
"config": {
"zone_id": "$zone1",
"operator": "lt",
"value": 20
}
},
{
"id": "node-00000000-0000-4000-8000-000000000239",
"kind": "house_mode",
"x": 25,
"y": 385,
"config": {
"operator": "eq",
"value": "heat"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000240",
"kind": "logic_and",
"x": 320,
"y": 210,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000241",
"kind": "zone_thermostat",
"x": 590,
"y": 210,
"config": {
"zone_id": "$zone1",
"preset": "custom",
"setpoint": 23,
"mode": "heat",
"cooldown_seconds": 180,
"power": true
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000242",
"from": "node-00000000-0000-4000-8000-000000000236",
"to": "node-00000000-0000-4000-8000-000000000240"
},
{
"id": "edge-00000000-0000-4000-8000-000000000243",
"from": "node-00000000-0000-4000-8000-000000000237",
"to": "node-00000000-0000-4000-8000-000000000240"
},
{
"id": "edge-00000000-0000-4000-8000-000000000244",
"from": "node-00000000-0000-4000-8000-000000000238",
"to": "node-00000000-0000-4000-8000-000000000240"
},
{
"id": "edge-00000000-0000-4000-8000-000000000245",
"from": "node-00000000-0000-4000-8000-000000000239",
"to": "node-00000000-0000-4000-8000-000000000240"
},
{
"id": "edge-00000000-0000-4000-8000-000000000246",
"from": "node-00000000-0000-4000-8000-000000000240",
"to": "node-00000000-0000-4000-8000-000000000241"
}
]
}
}
+74
View File
@@ -0,0 +1,74 @@
{
"id": "ha_external_heat_source_off",
"category": "home_assistant",
"name": {
"pl": "Inne źródło ciepła ON → wyłącz GREE",
"en": "Other heat source ON → turn GREE off"
},
"description": {
"pl": "Ogólny preset dla kominka, pieca lub innego źródła. Stan binary_sensor.external_heat_source = on wyłącza grupę albo strefę GREE.",
"en": "Generic fireplace, furnace or other source preset. binary_sensor.external_heat_source = on turns the GREE group or zone off."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000229",
"kind": "ha_available",
"x": 35,
"y": 70,
"config": {
"entity_id": "binary_sensor.external_heat_source"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000230",
"kind": "ha_state",
"x": 35,
"y": 200,
"config": {
"entity_id": "binary_sensor.external_heat_source",
"operator": "eq",
"value": "on"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000231",
"kind": "logic_and",
"x": 315,
"y": 135,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000232",
"kind": "group_action",
"x": 575,
"y": 135,
"config": {
"group_id": "$group1",
"power": false,
"mode": "auto",
"preset": "auto",
"setpoint": 21,
"cooldown_seconds": 120
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000233",
"from": "node-00000000-0000-4000-8000-000000000229",
"to": "node-00000000-0000-4000-8000-000000000231"
},
{
"id": "edge-00000000-0000-4000-8000-000000000234",
"from": "node-00000000-0000-4000-8000-000000000230",
"to": "node-00000000-0000-4000-8000-000000000231"
},
{
"id": "edge-00000000-0000-4000-8000-000000000235",
"from": "node-00000000-0000-4000-8000-000000000231",
"to": "node-00000000-0000-4000-8000-000000000232"
}
]
}
}
+106
View File
@@ -0,0 +1,106 @@
{
"id": "ha_gas_backup_heat",
"category": "home_assistant",
"name": {
"pl": "Gaz nie grzeje + zimno → GREE jako backup",
"en": "Gas not heating + room cold → GREE backup"
},
"description": {
"pl": "Gdy encja kotła jest dostępna, ale nie zgłasza heating, a w pokoju jest poniżej 19°C, GREE przejmuje awaryjne grzanie do 22°C.",
"en": "When the boiler entity is available but does not report heating and the room drops below 19°C, GREE provides backup heat to 22°C."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000218",
"kind": "ha_available",
"x": 25,
"y": 40,
"config": {
"entity_id": "climate.gas_boiler"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000219",
"kind": "ha_attribute",
"x": 25,
"y": 155,
"config": {
"entity_id": "climate.gas_boiler",
"attribute": "hvac_action",
"operator": "neq",
"value": "heating"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000220",
"kind": "zone_temperature",
"x": 25,
"y": 270,
"config": {
"zone_id": "$zone1",
"operator": "lt",
"value": 19
}
},
{
"id": "node-00000000-0000-4000-8000-000000000221",
"kind": "house_mode",
"x": 25,
"y": 385,
"config": {
"operator": "eq",
"value": "heat"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000222",
"kind": "logic_and",
"x": 320,
"y": 210,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000223",
"kind": "zone_thermostat",
"x": 590,
"y": 210,
"config": {
"zone_id": "$zone1",
"preset": "custom",
"setpoint": 22,
"mode": "heat",
"cooldown_seconds": 180,
"power": true
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000224",
"from": "node-00000000-0000-4000-8000-000000000218",
"to": "node-00000000-0000-4000-8000-000000000222"
},
{
"id": "edge-00000000-0000-4000-8000-000000000225",
"from": "node-00000000-0000-4000-8000-000000000219",
"to": "node-00000000-0000-4000-8000-000000000222"
},
{
"id": "edge-00000000-0000-4000-8000-000000000226",
"from": "node-00000000-0000-4000-8000-000000000220",
"to": "node-00000000-0000-4000-8000-000000000222"
},
{
"id": "edge-00000000-0000-4000-8000-000000000227",
"from": "node-00000000-0000-4000-8000-000000000221",
"to": "node-00000000-0000-4000-8000-000000000222"
},
{
"id": "edge-00000000-0000-4000-8000-000000000228",
"from": "node-00000000-0000-4000-8000-000000000222",
"to": "node-00000000-0000-4000-8000-000000000223"
}
]
}
}
+106
View File
@@ -0,0 +1,106 @@
{
"id": "ha_gas_heating_boost",
"category": "home_assistant",
"name": {
"pl": "Kocioł gazowy grzeje → dogrzewanie GREE",
"en": "Gas boiler heating → GREE heat boost"
},
"description": {
"pl": "Gdy kocioł gazowy aktywnie grzeje, pokój ma mniej niż 20°C i dom jest w trybie heat, GREE wspomaga ogrzewanie celem 23,5°C.",
"en": "When the gas boiler is actively heating, the room is below 20°C and house mode is heat, GREE assists with a 23.5°C target."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000198",
"kind": "ha_available",
"x": 25,
"y": 35,
"config": {
"entity_id": "climate.gas_boiler"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000199",
"kind": "ha_attribute",
"x": 25,
"y": 145,
"config": {
"entity_id": "climate.gas_boiler",
"attribute": "hvac_action",
"operator": "eq",
"value": "heating"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000200",
"kind": "zone_temperature",
"x": 25,
"y": 255,
"config": {
"zone_id": "$zone1",
"operator": "lt",
"value": 20
}
},
{
"id": "node-00000000-0000-4000-8000-000000000201",
"kind": "house_mode",
"x": 25,
"y": 365,
"config": {
"operator": "eq",
"value": "heat"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000202",
"kind": "logic_and",
"x": 320,
"y": 200,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000203",
"kind": "zone_thermostat",
"x": 590,
"y": 200,
"config": {
"zone_id": "$zone1",
"preset": "custom",
"setpoint": 23.5,
"mode": "heat",
"cooldown_seconds": 180,
"power": true
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000204",
"from": "node-00000000-0000-4000-8000-000000000198",
"to": "node-00000000-0000-4000-8000-000000000202"
},
{
"id": "edge-00000000-0000-4000-8000-000000000205",
"from": "node-00000000-0000-4000-8000-000000000199",
"to": "node-00000000-0000-4000-8000-000000000202"
},
{
"id": "edge-00000000-0000-4000-8000-000000000206",
"from": "node-00000000-0000-4000-8000-000000000200",
"to": "node-00000000-0000-4000-8000-000000000202"
},
{
"id": "edge-00000000-0000-4000-8000-000000000207",
"from": "node-00000000-0000-4000-8000-000000000201",
"to": "node-00000000-0000-4000-8000-000000000202"
},
{
"id": "edge-00000000-0000-4000-8000-000000000208",
"from": "node-00000000-0000-4000-8000-000000000202",
"to": "node-00000000-0000-4000-8000-000000000203"
}
]
}
}
+75
View File
@@ -0,0 +1,75 @@
{
"id": "ha_gas_heating_off",
"category": "home_assistant",
"name": {
"pl": "Kocioł gazowy grzeje → wyłącz GREE",
"en": "Gas boiler heating → turn GREE off"
},
"description": {
"pl": "Czyta hvac_action z climate.gas_boiler. Gdy HA zgłasza heating i encja jest dostępna, wyłącza wybraną strefę GREE.",
"en": "Reads hvac_action from climate.gas_boiler. When HA reports heating and the entity is available, the selected GREE zone is turned off."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000191",
"kind": "ha_available",
"x": 35,
"y": 70,
"config": {
"entity_id": "climate.gas_boiler"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000192",
"kind": "ha_attribute",
"x": 35,
"y": 200,
"config": {
"entity_id": "climate.gas_boiler",
"attribute": "hvac_action",
"operator": "eq",
"value": "heating"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000193",
"kind": "logic_and",
"x": 315,
"y": 135,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000194",
"kind": "zone_thermostat",
"x": 575,
"y": 135,
"config": {
"zone_id": "$zone1",
"preset": "auto",
"setpoint": 21,
"mode": "auto",
"cooldown_seconds": 120,
"power": false
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000195",
"from": "node-00000000-0000-4000-8000-000000000191",
"to": "node-00000000-0000-4000-8000-000000000193"
},
{
"id": "edge-00000000-0000-4000-8000-000000000196",
"from": "node-00000000-0000-4000-8000-000000000192",
"to": "node-00000000-0000-4000-8000-000000000193"
},
{
"id": "edge-00000000-0000-4000-8000-000000000197",
"from": "node-00000000-0000-4000-8000-000000000193",
"to": "node-00000000-0000-4000-8000-000000000194"
}
]
}
}
+91
View File
@@ -0,0 +1,91 @@
{
"id": "ha_gas_heating_reduce",
"category": "home_assistant",
"name": {
"pl": "Kocioł grzeje + pokój ciepły → obniż GREE",
"en": "Boiler heating + room warm → reduce GREE"
},
"description": {
"pl": "Jeśli gaz już grzeje, a strefa osiągnęła co najmniej 21°C, obniża cel GREE do 18°C, aby źródła ciepła nie walczyły ze sobą.",
"en": "When gas heat is already active and the zone reaches at least 21°C, lowers the GREE target to 18°C so the heat sources do not fight each other."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000209",
"kind": "ha_available",
"x": 35,
"y": 50,
"config": {
"entity_id": "climate.gas_boiler"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000210",
"kind": "ha_attribute",
"x": 35,
"y": 165,
"config": {
"entity_id": "climate.gas_boiler",
"attribute": "hvac_action",
"operator": "eq",
"value": "heating"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000211",
"kind": "zone_temperature",
"x": 35,
"y": 280,
"config": {
"zone_id": "$zone1",
"operator": "gte",
"value": 21
}
},
{
"id": "node-00000000-0000-4000-8000-000000000212",
"kind": "logic_and",
"x": 315,
"y": 165,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000213",
"kind": "zone_thermostat",
"x": 575,
"y": 165,
"config": {
"zone_id": "$zone1",
"preset": "custom",
"setpoint": 18,
"mode": "heat",
"cooldown_seconds": 180,
"power": true
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000214",
"from": "node-00000000-0000-4000-8000-000000000209",
"to": "node-00000000-0000-4000-8000-000000000212"
},
{
"id": "edge-00000000-0000-4000-8000-000000000215",
"from": "node-00000000-0000-4000-8000-000000000210",
"to": "node-00000000-0000-4000-8000-000000000212"
},
{
"id": "edge-00000000-0000-4000-8000-000000000216",
"from": "node-00000000-0000-4000-8000-000000000211",
"to": "node-00000000-0000-4000-8000-000000000212"
},
{
"id": "edge-00000000-0000-4000-8000-000000000217",
"from": "node-00000000-0000-4000-8000-000000000212",
"to": "node-00000000-0000-4000-8000-000000000213"
}
]
}
}
+89
View File
@@ -0,0 +1,89 @@
{
"id": "ha_heating_demand_follow",
"category": "home_assistant",
"name": {
"pl": "Podążaj za żądaniem grzania z HA",
"en": "Follow Home Assistant heating demand"
},
"description": {
"pl": "Stan binary_sensor.heating_demand = on uruchamia ogrzewanie GREE do 22,5°C, jeśli dom jest w trybie heat.",
"en": "binary_sensor.heating_demand = on starts GREE heating to 22.5°C while the house is in heat mode."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000247",
"kind": "ha_available",
"x": 35,
"y": 60,
"config": {
"entity_id": "binary_sensor.heating_demand"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000248",
"kind": "ha_state",
"x": 35,
"y": 180,
"config": {
"entity_id": "binary_sensor.heating_demand",
"operator": "eq",
"value": "on"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000249",
"kind": "house_mode",
"x": 35,
"y": 300,
"config": {
"operator": "eq",
"value": "heat"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000250",
"kind": "logic_and",
"x": 315,
"y": 180,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000251",
"kind": "zone_thermostat",
"x": 575,
"y": 180,
"config": {
"zone_id": "$zone1",
"preset": "custom",
"setpoint": 22.5,
"mode": "heat",
"cooldown_seconds": 120,
"power": true
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000252",
"from": "node-00000000-0000-4000-8000-000000000247",
"to": "node-00000000-0000-4000-8000-000000000250"
},
{
"id": "edge-00000000-0000-4000-8000-000000000253",
"from": "node-00000000-0000-4000-8000-000000000248",
"to": "node-00000000-0000-4000-8000-000000000250"
},
{
"id": "edge-00000000-0000-4000-8000-000000000254",
"from": "node-00000000-0000-4000-8000-000000000249",
"to": "node-00000000-0000-4000-8000-000000000250"
},
{
"id": "edge-00000000-0000-4000-8000-000000000255",
"from": "node-00000000-0000-4000-8000-000000000250",
"to": "node-00000000-0000-4000-8000-000000000251"
}
]
}
}
+106
View File
@@ -0,0 +1,106 @@
{
"id": "ha_thermostat_idle_fallback",
"category": "home_assistant",
"name": {
"pl": "Termostat gazowy idle → awaryjne grzanie GREE",
"en": "Gas thermostat idle → GREE fallback heat"
},
"description": {
"pl": "Jeśli climate.gas_boiler jest dostępny, ma hvac_action=idle i pokój spadnie poniżej 19°C, GREE uruchamia grzanie do 21,5°C.",
"en": "If climate.gas_boiler is available, reports hvac_action=idle and the room falls below 19°C, GREE heats to 21.5°C."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000267",
"kind": "ha_available",
"x": 25,
"y": 40,
"config": {
"entity_id": "climate.gas_boiler"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000268",
"kind": "ha_attribute",
"x": 25,
"y": 155,
"config": {
"entity_id": "climate.gas_boiler",
"attribute": "hvac_action",
"operator": "eq",
"value": "idle"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000269",
"kind": "zone_temperature",
"x": 25,
"y": 270,
"config": {
"zone_id": "$zone1",
"operator": "lt",
"value": 19
}
},
{
"id": "node-00000000-0000-4000-8000-000000000270",
"kind": "house_mode",
"x": 25,
"y": 385,
"config": {
"operator": "eq",
"value": "heat"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000271",
"kind": "logic_and",
"x": 320,
"y": 210,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000272",
"kind": "zone_thermostat",
"x": 590,
"y": 210,
"config": {
"zone_id": "$zone1",
"preset": "custom",
"setpoint": 21.5,
"mode": "heat",
"cooldown_seconds": 240,
"power": true
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000273",
"from": "node-00000000-0000-4000-8000-000000000267",
"to": "node-00000000-0000-4000-8000-000000000271"
},
{
"id": "edge-00000000-0000-4000-8000-000000000274",
"from": "node-00000000-0000-4000-8000-000000000268",
"to": "node-00000000-0000-4000-8000-000000000271"
},
{
"id": "edge-00000000-0000-4000-8000-000000000275",
"from": "node-00000000-0000-4000-8000-000000000269",
"to": "node-00000000-0000-4000-8000-000000000271"
},
{
"id": "edge-00000000-0000-4000-8000-000000000276",
"from": "node-00000000-0000-4000-8000-000000000270",
"to": "node-00000000-0000-4000-8000-000000000271"
},
{
"id": "edge-00000000-0000-4000-8000-000000000277",
"from": "node-00000000-0000-4000-8000-000000000271",
"to": "node-00000000-0000-4000-8000-000000000272"
}
]
}
}
+75
View File
@@ -0,0 +1,75 @@
{
"id": "ha_window_guard",
"category": "safety",
"name": {
"pl": "Otwarte okno — wyłącz HVAC",
"en": "Open window — stop HVAC"
},
"description": {
"pl": "Przykład integracji z binary_sensor Home Assistant: otwarte okno wyłącza termostat. Ustaw właściwy entity_id.",
"en": "Home Assistant binary_sensor example: an open window turns the thermostat off. Set the correct entity_id."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000093",
"kind": "ha_state",
"x": 45,
"y": 90,
"config": {
"entity_id": "binary_sensor.window",
"operator": "eq",
"value": "on"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000094",
"kind": "house_mode",
"x": 45,
"y": 220,
"config": {
"operator": "neq",
"value": "off"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000095",
"kind": "logic_and",
"x": 305,
"y": 155,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000096",
"kind": "zone_thermostat",
"x": 560,
"y": 155,
"config": {
"zone_id": "$zone1",
"preset": "auto",
"setpoint": 21,
"mode": "auto",
"cooldown_seconds": 60,
"power": false
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000097",
"from": "node-00000000-0000-4000-8000-000000000093",
"to": "node-00000000-0000-4000-8000-000000000095"
},
{
"id": "edge-00000000-0000-4000-8000-000000000098",
"from": "node-00000000-0000-4000-8000-000000000094",
"to": "node-00000000-0000-4000-8000-000000000095"
},
{
"id": "edge-00000000-0000-4000-8000-000000000099",
"from": "node-00000000-0000-4000-8000-000000000095",
"to": "node-00000000-0000-4000-8000-000000000096"
}
]
}
}
+90
View File
@@ -0,0 +1,90 @@
{
"id": "humidity_guard",
"category": "safety",
"name": {
"pl": "Wysoka wilgotność — chłodzenie",
"en": "High humidity cooling"
},
"description": {
"pl": "Przy wysokiej wilgotności, obecności domowników i trybie chłodzenia wymusza umiarkowane chłodzenie.",
"en": "With high humidity, occupancy and cooling house mode, applies moderate cooling."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000100",
"kind": "ha_numeric",
"x": 40,
"y": 60,
"config": {
"entity_id": "sensor.living_room_humidity",
"operator": "gt",
"value": 70
}
},
{
"id": "node-00000000-0000-4000-8000-000000000101",
"kind": "house_mode",
"x": 40,
"y": 180,
"config": {
"operator": "eq",
"value": "cool"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000102",
"kind": "ha_state",
"x": 40,
"y": 300,
"config": {
"entity_id": "person.someone",
"operator": "eq",
"value": "home"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000103",
"kind": "logic_and",
"x": 315,
"y": 180,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000104",
"kind": "zone_thermostat",
"x": 575,
"y": 180,
"config": {
"zone_id": "$zone1",
"preset": "custom",
"setpoint": 22,
"mode": "cool",
"cooldown_seconds": 180
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000105",
"from": "node-00000000-0000-4000-8000-000000000100",
"to": "node-00000000-0000-4000-8000-000000000103"
},
{
"id": "edge-00000000-0000-4000-8000-000000000106",
"from": "node-00000000-0000-4000-8000-000000000101",
"to": "node-00000000-0000-4000-8000-000000000103"
},
{
"id": "edge-00000000-0000-4000-8000-000000000107",
"from": "node-00000000-0000-4000-8000-000000000102",
"to": "node-00000000-0000-4000-8000-000000000103"
},
{
"id": "edge-00000000-0000-4000-8000-000000000108",
"from": "node-00000000-0000-4000-8000-000000000103",
"to": "node-00000000-0000-4000-8000-000000000104"
}
]
}
}
+90
View File
@@ -0,0 +1,90 @@
{
"id": "mild_weather_eco",
"category": "energy",
"name": {
"pl": "Łagodna pogoda — HVAC off",
"en": "Mild weather — HVAC off"
},
"description": {
"pl": "Gdy temperatura zewnętrzna mieści się w komfortowym zakresie i okno jest zamknięte, wyłącza HVAC.",
"en": "When outdoor temperature is within a comfortable band and the window is closed, turns HVAC off."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000068",
"kind": "outdoor_temperature",
"x": 35,
"y": 55,
"config": {
"operator": "gte",
"value": 17
}
},
{
"id": "node-00000000-0000-4000-8000-000000000069",
"kind": "outdoor_temperature",
"x": 35,
"y": 175,
"config": {
"operator": "lte",
"value": 24
}
},
{
"id": "node-00000000-0000-4000-8000-000000000070",
"kind": "ha_state",
"x": 35,
"y": 295,
"config": {
"entity_id": "binary_sensor.window",
"operator": "eq",
"value": "off"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000071",
"kind": "logic_and",
"x": 310,
"y": 175,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000072",
"kind": "zone_thermostat",
"x": 565,
"y": 175,
"config": {
"zone_id": "$zone1",
"preset": "auto",
"setpoint": 21,
"mode": "auto",
"cooldown_seconds": 300,
"power": false
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000073",
"from": "node-00000000-0000-4000-8000-000000000068",
"to": "node-00000000-0000-4000-8000-000000000071"
},
{
"id": "edge-00000000-0000-4000-8000-000000000074",
"from": "node-00000000-0000-4000-8000-000000000069",
"to": "node-00000000-0000-4000-8000-000000000071"
},
{
"id": "edge-00000000-0000-4000-8000-000000000075",
"from": "node-00000000-0000-4000-8000-000000000070",
"to": "node-00000000-0000-4000-8000-000000000071"
},
{
"id": "edge-00000000-0000-4000-8000-000000000076",
"from": "node-00000000-0000-4000-8000-000000000071",
"to": "node-00000000-0000-4000-8000-000000000072"
}
]
}
}
+108
View File
@@ -0,0 +1,108 @@
{
"id": "morning_boost",
"category": "comfort",
"name": {
"pl": "Poranne dogrzanie przy mrozie",
"en": "Cold-weather morning boost"
},
"description": {
"pl": "W dni robocze rano, tylko w trybie grzania i przy mrozie na zewnątrz, podnosi cel do 22,5°C.",
"en": "On workday mornings, only in heating mode and during cold weather, raises the target to 22.5°C."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000028",
"kind": "weekday",
"x": 35,
"y": 50,
"config": {
"days": [
1,
2,
3,
4,
5
]
}
},
{
"id": "node-00000000-0000-4000-8000-000000000029",
"kind": "time_range",
"x": 35,
"y": 170,
"config": {
"start": "05:30",
"end": "07:30"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000030",
"kind": "outdoor_temperature",
"x": 35,
"y": 290,
"config": {
"operator": "lt",
"value": 5
}
},
{
"id": "node-00000000-0000-4000-8000-000000000031",
"kind": "house_mode",
"x": 35,
"y": 410,
"config": {
"operator": "eq",
"value": "heat"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000032",
"kind": "logic_and",
"x": 320,
"y": 220,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000033",
"kind": "zone_thermostat",
"x": 585,
"y": 220,
"config": {
"zone_id": "$zone1",
"preset": "custom",
"setpoint": 22.5,
"mode": "heat",
"cooldown_seconds": 180
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000034",
"from": "node-00000000-0000-4000-8000-000000000028",
"to": "node-00000000-0000-4000-8000-000000000032"
},
{
"id": "edge-00000000-0000-4000-8000-000000000035",
"from": "node-00000000-0000-4000-8000-000000000029",
"to": "node-00000000-0000-4000-8000-000000000032"
},
{
"id": "edge-00000000-0000-4000-8000-000000000036",
"from": "node-00000000-0000-4000-8000-000000000030",
"to": "node-00000000-0000-4000-8000-000000000032"
},
{
"id": "edge-00000000-0000-4000-8000-000000000037",
"from": "node-00000000-0000-4000-8000-000000000031",
"to": "node-00000000-0000-4000-8000-000000000032"
},
{
"id": "edge-00000000-0000-4000-8000-000000000038",
"from": "node-00000000-0000-4000-8000-000000000032",
"to": "node-00000000-0000-4000-8000-000000000033"
}
]
}
}
+103
View File
@@ -0,0 +1,103 @@
{
"id": "multi_room_group_guard",
"category": "advanced",
"name": {
"pl": "Wiele pokoi → jedna grupa",
"en": "Multiple rooms → one group"
},
"description": {
"pl": "Jeśli dowolna z dwóch stref jest za ciepła i dom jest w trybie cool, uruchamia wspólną grupę lub strefę.",
"en": "If either of two zones is too warm and house mode is cooling, starts a shared group or zone."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000326",
"kind": "zone_temperature",
"x": 30,
"y": 55,
"config": {
"zone_id": "$zone1",
"operator": "gt",
"value": 25
}
},
{
"id": "node-00000000-0000-4000-8000-000000000327",
"kind": "zone_temperature",
"x": 30,
"y": 175,
"config": {
"zone_id": "$zone2",
"operator": "gt",
"value": 25
}
},
{
"id": "node-00000000-0000-4000-8000-000000000328",
"kind": "logic_or",
"x": 280,
"y": 115,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000329",
"kind": "house_mode",
"x": 280,
"y": 245,
"config": {
"operator": "eq",
"value": "cool"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000330",
"kind": "logic_and",
"x": 505,
"y": 175,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000331",
"kind": "group_action",
"x": 760,
"y": 175,
"config": {
"group_id": "$group1",
"power": true,
"mode": "auto",
"preset": "comfort",
"setpoint": 22,
"cooldown_seconds": 120
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000332",
"from": "node-00000000-0000-4000-8000-000000000326",
"to": "node-00000000-0000-4000-8000-000000000328"
},
{
"id": "edge-00000000-0000-4000-8000-000000000333",
"from": "node-00000000-0000-4000-8000-000000000327",
"to": "node-00000000-0000-4000-8000-000000000328"
},
{
"id": "edge-00000000-0000-4000-8000-000000000334",
"from": "node-00000000-0000-4000-8000-000000000328",
"to": "node-00000000-0000-4000-8000-000000000330"
},
{
"id": "edge-00000000-0000-4000-8000-000000000335",
"from": "node-00000000-0000-4000-8000-000000000329",
"to": "node-00000000-0000-4000-8000-000000000330"
},
{
"id": "edge-00000000-0000-4000-8000-000000000336",
"from": "node-00000000-0000-4000-8000-000000000330",
"to": "node-00000000-0000-4000-8000-000000000331"
}
]
}
}
+163
View File
@@ -0,0 +1,163 @@
{
"id": "nested_guard",
"category": "advanced",
"name": {
"pl": "Złożony warunek z OR i NOT",
"en": "Nested OR and NOT guard"
},
"description": {
"pl": "Dni i godziny + temperatura zewnętrzna LUB pokojowa + NOT dla otwartego okna. Przykład wielopoziomowego grafu.",
"en": "Weekdays and time + outdoor OR room temperature + NOT open window. An example of a multi-level graph."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000278",
"kind": "weekday",
"x": 25,
"y": 35,
"config": {
"days": [
1,
2,
3,
4,
5
]
}
},
{
"id": "node-00000000-0000-4000-8000-000000000279",
"kind": "time_range",
"x": 25,
"y": 145,
"config": {
"start": "06:00",
"end": "22:30"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000280",
"kind": "outdoor_temperature",
"x": 25,
"y": 255,
"config": {
"operator": "lt",
"value": 8
}
},
{
"id": "node-00000000-0000-4000-8000-000000000281",
"kind": "zone_temperature",
"x": 25,
"y": 365,
"config": {
"zone_id": "$zone1",
"operator": "lt",
"value": 20
}
},
{
"id": "node-00000000-0000-4000-8000-000000000282",
"kind": "logic_or",
"x": 275,
"y": 310,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000283",
"kind": "ha_available",
"x": 275,
"y": 430,
"config": {
"entity_id": "binary_sensor.window"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000284",
"kind": "ha_state",
"x": 275,
"y": 540,
"config": {
"entity_id": "binary_sensor.window",
"operator": "eq",
"value": "on"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000285",
"kind": "logic_not",
"x": 495,
"y": 540,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000286",
"kind": "logic_and",
"x": 510,
"y": 235,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000287",
"kind": "zone_thermostat",
"x": 770,
"y": 235,
"config": {
"zone_id": "$zone1",
"preset": "comfort",
"setpoint": 21,
"mode": "auto",
"cooldown_seconds": 90
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000288",
"from": "node-00000000-0000-4000-8000-000000000280",
"to": "node-00000000-0000-4000-8000-000000000282"
},
{
"id": "edge-00000000-0000-4000-8000-000000000289",
"from": "node-00000000-0000-4000-8000-000000000281",
"to": "node-00000000-0000-4000-8000-000000000282"
},
{
"id": "edge-00000000-0000-4000-8000-000000000290",
"from": "node-00000000-0000-4000-8000-000000000284",
"to": "node-00000000-0000-4000-8000-000000000285"
},
{
"id": "edge-00000000-0000-4000-8000-000000000291",
"from": "node-00000000-0000-4000-8000-000000000278",
"to": "node-00000000-0000-4000-8000-000000000286"
},
{
"id": "edge-00000000-0000-4000-8000-000000000292",
"from": "node-00000000-0000-4000-8000-000000000279",
"to": "node-00000000-0000-4000-8000-000000000286"
},
{
"id": "edge-00000000-0000-4000-8000-000000000293",
"from": "node-00000000-0000-4000-8000-000000000282",
"to": "node-00000000-0000-4000-8000-000000000286"
},
{
"id": "edge-00000000-0000-4000-8000-000000000294",
"from": "node-00000000-0000-4000-8000-000000000283",
"to": "node-00000000-0000-4000-8000-000000000286"
},
{
"id": "edge-00000000-0000-4000-8000-000000000295",
"from": "node-00000000-0000-4000-8000-000000000285",
"to": "node-00000000-0000-4000-8000-000000000286"
},
{
"id": "edge-00000000-0000-4000-8000-000000000296",
"from": "node-00000000-0000-4000-8000-000000000286",
"to": "node-00000000-0000-4000-8000-000000000287"
}
]
}
}
+74
View File
@@ -0,0 +1,74 @@
{
"id": "night_group",
"category": "night",
"name": {
"pl": "Noc dla grupy",
"en": "Night group"
},
"description": {
"pl": "Nocny przedział czasu + tryb domu sterują presetem sleep dla grupy lub strefy.",
"en": "Night time + house mode applies sleep preset to a group or zone."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000125",
"kind": "time_range",
"x": 50,
"y": 100,
"config": {
"start": "22:30",
"end": "06:00"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000126",
"kind": "house_mode",
"x": 50,
"y": 230,
"config": {
"operator": "neq",
"value": "off"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000127",
"kind": "logic_and",
"x": 315,
"y": 165,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000128",
"kind": "group_action",
"x": 570,
"y": 165,
"config": {
"group_id": "$group1",
"power": true,
"mode": "auto",
"preset": "sleep",
"setpoint": 20,
"cooldown_seconds": 120
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000129",
"from": "node-00000000-0000-4000-8000-000000000125",
"to": "node-00000000-0000-4000-8000-000000000127"
},
{
"id": "edge-00000000-0000-4000-8000-000000000130",
"from": "node-00000000-0000-4000-8000-000000000126",
"to": "node-00000000-0000-4000-8000-000000000127"
},
{
"id": "edge-00000000-0000-4000-8000-000000000131",
"from": "node-00000000-0000-4000-8000-000000000127",
"to": "node-00000000-0000-4000-8000-000000000128"
}
]
}
}
+73
View File
@@ -0,0 +1,73 @@
{
"id": "night_quiet",
"category": "night",
"name": {
"pl": "Nocny profil grupy",
"en": "Night group profile"
},
"description": {
"pl": "Korzysta bezpośrednio z istniejącego Trybu nocnego aplikacji i stanu grupy, aby zastosować preset sleep.",
"en": "Uses the app Night mode and group state directly to apply the sleep preset."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000132",
"kind": "night_mode",
"x": 45,
"y": 90,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000133",
"kind": "logic_and",
"x": 310,
"y": 155,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000134",
"kind": "group_action",
"x": 565,
"y": 155,
"config": {
"group_id": "$group1",
"power": null,
"mode": "auto",
"preset": "sleep",
"setpoint": 20,
"cooldown_seconds": 180
}
},
{
"id": "node-00000000-0000-4000-8000-000000000136",
"kind": "group_state",
"x": 45,
"y": 225,
"config": {
"group_id": "$group1",
"field": "power_enabled",
"operator": "eq",
"value": "true"
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000135",
"from": "node-00000000-0000-4000-8000-000000000132",
"to": "node-00000000-0000-4000-8000-000000000133"
},
{
"id": "edge-00000000-0000-4000-8000-000000000137",
"from": "node-00000000-0000-4000-8000-000000000136",
"to": "node-00000000-0000-4000-8000-000000000133"
},
{
"id": "edge-00000000-0000-4000-8000-000000000138",
"from": "node-00000000-0000-4000-8000-000000000133",
"to": "node-00000000-0000-4000-8000-000000000134"
}
]
}
}
+130
View File
@@ -0,0 +1,130 @@
{
"id": "occupancy_weather_matrix",
"category": "advanced",
"name": {
"pl": "Obecność + pogoda + okno",
"en": "Occupancy + weather + window"
},
"description": {
"pl": "Komfort działa tylko gdy ktoś jest w domu, występuje zapotrzebowanie pogodowe lub pokojowe i okno nie jest otwarte.",
"en": "Comfort runs only when someone is home, outdoor or room demand exists, and the window is not open."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000297",
"kind": "ha_state",
"x": 25,
"y": 40,
"config": {
"entity_id": "person.someone",
"operator": "eq",
"value": "home"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000298",
"kind": "outdoor_temperature",
"x": 25,
"y": 155,
"config": {
"operator": "lt",
"value": 9
}
},
{
"id": "node-00000000-0000-4000-8000-000000000299",
"kind": "zone_temperature",
"x": 25,
"y": 270,
"config": {
"zone_id": "$zone1",
"operator": "lt",
"value": 20
}
},
{
"id": "node-00000000-0000-4000-8000-000000000300",
"kind": "logic_or",
"x": 275,
"y": 215,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000301",
"kind": "ha_state",
"x": 275,
"y": 345,
"config": {
"entity_id": "binary_sensor.window",
"operator": "eq",
"value": "on"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000302",
"kind": "logic_not",
"x": 495,
"y": 345,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000303",
"kind": "logic_and",
"x": 505,
"y": 150,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000304",
"kind": "zone_thermostat",
"x": 765,
"y": 150,
"config": {
"zone_id": "$zone1",
"preset": "comfort",
"setpoint": 21,
"mode": "auto",
"cooldown_seconds": 90
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000305",
"from": "node-00000000-0000-4000-8000-000000000298",
"to": "node-00000000-0000-4000-8000-000000000300"
},
{
"id": "edge-00000000-0000-4000-8000-000000000306",
"from": "node-00000000-0000-4000-8000-000000000299",
"to": "node-00000000-0000-4000-8000-000000000300"
},
{
"id": "edge-00000000-0000-4000-8000-000000000307",
"from": "node-00000000-0000-4000-8000-000000000301",
"to": "node-00000000-0000-4000-8000-000000000302"
},
{
"id": "edge-00000000-0000-4000-8000-000000000308",
"from": "node-00000000-0000-4000-8000-000000000297",
"to": "node-00000000-0000-4000-8000-000000000303"
},
{
"id": "edge-00000000-0000-4000-8000-000000000309",
"from": "node-00000000-0000-4000-8000-000000000300",
"to": "node-00000000-0000-4000-8000-000000000303"
},
{
"id": "edge-00000000-0000-4000-8000-000000000310",
"from": "node-00000000-0000-4000-8000-000000000302",
"to": "node-00000000-0000-4000-8000-000000000303"
},
{
"id": "edge-00000000-0000-4000-8000-000000000311",
"from": "node-00000000-0000-4000-8000-000000000303",
"to": "node-00000000-0000-4000-8000-000000000304"
}
]
}
}
+78
View File
@@ -0,0 +1,78 @@
{
"id": "offline_safe_off",
"category": "reliability",
"name": {
"pl": "Urządzenie offline — bezpieczne wyłączenie",
"en": "Offline device safe-off"
},
"description": {
"pl": "Gdy urządzenie jest offline, a termostat nadal aktywny, wydaje bezpieczne polecenie wyłączenia z długim cooldownem.",
"en": "When a device is offline while its thermostat remains active, issues a conservative off command with a long cooldown."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000175",
"kind": "device_state",
"x": 35,
"y": 85,
"config": {
"device_id": "$device1",
"field": "online",
"operator": "eq",
"value": "false"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000176",
"kind": "zone_state",
"x": 35,
"y": 215,
"config": {
"zone_id": "$zone1",
"field": "enabled",
"operator": "eq",
"value": "true"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000177",
"kind": "logic_and",
"x": 310,
"y": 150,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000178",
"kind": "zone_thermostat",
"x": 565,
"y": 150,
"config": {
"zone_id": "$zone1",
"preset": "auto",
"setpoint": 21,
"mode": "auto",
"cooldown_seconds": 300,
"power": false
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000179",
"from": "node-00000000-0000-4000-8000-000000000175",
"to": "node-00000000-0000-4000-8000-000000000177"
},
{
"id": "edge-00000000-0000-4000-8000-000000000180",
"from": "node-00000000-0000-4000-8000-000000000176",
"to": "node-00000000-0000-4000-8000-000000000177"
},
{
"id": "edge-00000000-0000-4000-8000-000000000181",
"from": "node-00000000-0000-4000-8000-000000000177",
"to": "node-00000000-0000-4000-8000-000000000178"
}
]
}
}
+90
View File
@@ -0,0 +1,90 @@
{
"id": "overheat_guard",
"category": "safety",
"name": {
"pl": "Ochrona przed przegrzaniem",
"en": "Overheat guard"
},
"description": {
"pl": "Jeśli strefa jest bardzo ciepła, tryb domu to chłodzenie i okno jest zamknięte, uruchamia chłodzenie ochronne.",
"en": "If a zone is very warm, house mode is cooling and the window is closed, starts protective cooling."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000109",
"kind": "zone_temperature",
"x": 35,
"y": 65,
"config": {
"zone_id": "$zone1",
"operator": "gt",
"value": 28
}
},
{
"id": "node-00000000-0000-4000-8000-000000000110",
"kind": "house_mode",
"x": 35,
"y": 185,
"config": {
"operator": "eq",
"value": "cool"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000111",
"kind": "ha_state",
"x": 35,
"y": 305,
"config": {
"entity_id": "binary_sensor.window",
"operator": "eq",
"value": "off"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000112",
"kind": "logic_and",
"x": 310,
"y": 185,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000113",
"kind": "zone_thermostat",
"x": 570,
"y": 185,
"config": {
"zone_id": "$zone1",
"preset": "custom",
"setpoint": 22,
"mode": "cool",
"cooldown_seconds": 120
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000114",
"from": "node-00000000-0000-4000-8000-000000000109",
"to": "node-00000000-0000-4000-8000-000000000112"
},
{
"id": "edge-00000000-0000-4000-8000-000000000115",
"from": "node-00000000-0000-4000-8000-000000000110",
"to": "node-00000000-0000-4000-8000-000000000112"
},
{
"id": "edge-00000000-0000-4000-8000-000000000116",
"from": "node-00000000-0000-4000-8000-000000000111",
"to": "node-00000000-0000-4000-8000-000000000112"
},
{
"id": "edge-00000000-0000-4000-8000-000000000117",
"from": "node-00000000-0000-4000-8000-000000000112",
"to": "node-00000000-0000-4000-8000-000000000113"
}
]
}
}
+75
View File
@@ -0,0 +1,75 @@
{
"id": "peak_power_guard",
"category": "energy",
"name": {
"pl": "Ochrona przed szczytem mocy",
"en": "Peak power guard"
},
"description": {
"pl": "Wyłącza HVAC, gdy pobór mocy domu przekroczy ustawiony próg i system klimatu jest aktywny.",
"en": "Turns HVAC off when household power draw exceeds the configured threshold while climate control is active."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000061",
"kind": "ha_numeric",
"x": 40,
"y": 75,
"config": {
"entity_id": "sensor.house_power",
"operator": "gt",
"value": 5000
}
},
{
"id": "node-00000000-0000-4000-8000-000000000062",
"kind": "house_mode",
"x": 40,
"y": 200,
"config": {
"operator": "neq",
"value": "off"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000063",
"kind": "logic_and",
"x": 315,
"y": 140,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000064",
"kind": "zone_thermostat",
"x": 570,
"y": 140,
"config": {
"zone_id": "$zone1",
"preset": "auto",
"setpoint": 21,
"mode": "auto",
"cooldown_seconds": 180,
"power": false
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000065",
"from": "node-00000000-0000-4000-8000-000000000061",
"to": "node-00000000-0000-4000-8000-000000000063"
},
{
"id": "edge-00000000-0000-4000-8000-000000000066",
"from": "node-00000000-0000-4000-8000-000000000062",
"to": "node-00000000-0000-4000-8000-000000000063"
},
{
"id": "edge-00000000-0000-4000-8000-000000000067",
"from": "node-00000000-0000-4000-8000-000000000063",
"to": "node-00000000-0000-4000-8000-000000000064"
}
]
}
}
+76
View File
@@ -0,0 +1,76 @@
{
"id": "presence_eco",
"category": "energy",
"name": {
"pl": "Obecność: komfort / eco",
"en": "Presence: comfort / eco"
},
"description": {
"pl": "Dwie jawne gałęzie Home Assistant: stan home włącza komfort, a not_home ustawia preset away. Ustaw właściwy entity_id osoby lub czujnika obecności.",
"en": "Two explicit Home Assistant branches: home enables comfort while not_home selects the away preset. Set the correct person or presence entity_id."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000048",
"kind": "ha_state",
"x": 45,
"y": 75,
"config": {
"entity_id": "person.someone",
"operator": "eq",
"value": "home"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000049",
"kind": "ha_state",
"x": 45,
"y": 245,
"config": {
"entity_id": "person.someone",
"operator": "eq",
"value": "not_home"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000050",
"kind": "zone_thermostat",
"x": 360,
"y": 65,
"config": {
"zone_id": "$zone1",
"preset": "comfort",
"setpoint": 21,
"mode": "auto",
"cooldown_seconds": 90
}
},
{
"id": "node-00000000-0000-4000-8000-000000000051",
"kind": "zone_thermostat",
"x": 360,
"y": 245,
"config": {
"zone_id": "$zone1",
"preset": "away",
"setpoint": 18,
"mode": "auto",
"cooldown_seconds": 180
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000052",
"from": "node-00000000-0000-4000-8000-000000000048",
"to": "node-00000000-0000-4000-8000-000000000050"
},
{
"id": "edge-00000000-0000-4000-8000-000000000053",
"from": "node-00000000-0000-4000-8000-000000000049",
"to": "node-00000000-0000-4000-8000-000000000051"
}
]
}
}
+88
View File
@@ -0,0 +1,88 @@
{
"id": "sensor_availability_guard",
"category": "reliability",
"name": {
"pl": "Chłodzenie z kontrolą sensora",
"en": "Cooling with sensor validation"
},
"description": {
"pl": "Najpierw sprawdza dostępność sensora HA, potem temperaturę i tryb domu; dopiero wtedy uruchamia chłodzenie.",
"en": "Checks HA sensor availability first, then temperature and house mode before starting cooling."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000166",
"kind": "ha_available",
"x": 35,
"y": 55,
"config": {
"entity_id": "sensor.room_temperature"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000167",
"kind": "ha_numeric",
"x": 35,
"y": 175,
"config": {
"entity_id": "sensor.room_temperature",
"operator": "gt",
"value": 26
}
},
{
"id": "node-00000000-0000-4000-8000-000000000168",
"kind": "house_mode",
"x": 35,
"y": 295,
"config": {
"operator": "eq",
"value": "cool"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000169",
"kind": "logic_and",
"x": 310,
"y": 175,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000170",
"kind": "zone_thermostat",
"x": 570,
"y": 175,
"config": {
"zone_id": "$zone1",
"preset": "comfort",
"setpoint": 21,
"mode": "cool",
"cooldown_seconds": 120
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000171",
"from": "node-00000000-0000-4000-8000-000000000166",
"to": "node-00000000-0000-4000-8000-000000000169"
},
{
"id": "edge-00000000-0000-4000-8000-000000000172",
"from": "node-00000000-0000-4000-8000-000000000167",
"to": "node-00000000-0000-4000-8000-000000000169"
},
{
"id": "edge-00000000-0000-4000-8000-000000000173",
"from": "node-00000000-0000-4000-8000-000000000168",
"to": "node-00000000-0000-4000-8000-000000000169"
},
{
"id": "edge-00000000-0000-4000-8000-000000000174",
"from": "node-00000000-0000-4000-8000-000000000169",
"to": "node-00000000-0000-4000-8000-000000000170"
}
]
}
}
+86
View File
@@ -0,0 +1,86 @@
{
"id": "sleep_temperature_guard",
"category": "night",
"name": {
"pl": "Nocne chłodzenie tylko gdy trzeba",
"en": "Night cooling only when needed"
},
"description": {
"pl": "W trybie nocnym chłodzi tylko wtedy, gdy temperatura pokoju przekracza próg i dom jest w trybie cool.",
"en": "In night mode, cools only when room temperature is above the threshold and the house is in cooling mode."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000139",
"kind": "night_mode",
"x": 35,
"y": 55,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000140",
"kind": "zone_temperature",
"x": 35,
"y": 175,
"config": {
"zone_id": "$zone1",
"operator": "gt",
"value": 23.5
}
},
{
"id": "node-00000000-0000-4000-8000-000000000141",
"kind": "house_mode",
"x": 35,
"y": 295,
"config": {
"operator": "eq",
"value": "cool"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000142",
"kind": "logic_and",
"x": 310,
"y": 175,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000143",
"kind": "zone_thermostat",
"x": 570,
"y": 175,
"config": {
"zone_id": "$zone1",
"preset": "sleep",
"setpoint": 20,
"mode": "cool",
"cooldown_seconds": 180
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000144",
"from": "node-00000000-0000-4000-8000-000000000139",
"to": "node-00000000-0000-4000-8000-000000000142"
},
{
"id": "edge-00000000-0000-4000-8000-000000000145",
"from": "node-00000000-0000-4000-8000-000000000140",
"to": "node-00000000-0000-4000-8000-000000000142"
},
{
"id": "edge-00000000-0000-4000-8000-000000000146",
"from": "node-00000000-0000-4000-8000-000000000141",
"to": "node-00000000-0000-4000-8000-000000000142"
},
{
"id": "edge-00000000-0000-4000-8000-000000000147",
"from": "node-00000000-0000-4000-8000-000000000142",
"to": "node-00000000-0000-4000-8000-000000000143"
}
]
}
}
+116
View File
@@ -0,0 +1,116 @@
{
"id": "smart_demand",
"category": "comfort",
"name": {
"pl": "Inteligentne zapotrzebowanie",
"en": "Smart demand"
},
"description": {
"pl": "Złożona logika czasu, trybu domu i alternatywy temperatury zewnętrznej lub pokojowej.",
"en": "Complex time, house-mode and outdoor-or-room temperature logic."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000015",
"kind": "time_range",
"x": 30,
"y": 40,
"config": {
"start": "05:30",
"end": "23:00"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000016",
"kind": "outdoor_temperature",
"x": 30,
"y": 160,
"config": {
"operator": "lt",
"value": 10
}
},
{
"id": "node-00000000-0000-4000-8000-000000000017",
"kind": "zone_temperature",
"x": 30,
"y": 280,
"config": {
"zone_id": "$zone1",
"operator": "lt",
"value": 20
}
},
{
"id": "node-00000000-0000-4000-8000-000000000018",
"kind": "logic_or",
"x": 280,
"y": 220,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000019",
"kind": "house_mode",
"x": 280,
"y": 70,
"config": {
"operator": "neq",
"value": "off"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000020",
"kind": "logic_and",
"x": 510,
"y": 155,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000021",
"kind": "zone_thermostat",
"x": 760,
"y": 155,
"config": {
"zone_id": "$zone1",
"preset": "comfort",
"setpoint": 21,
"mode": "auto",
"cooldown_seconds": 90
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000022",
"from": "node-00000000-0000-4000-8000-000000000016",
"to": "node-00000000-0000-4000-8000-000000000018"
},
{
"id": "edge-00000000-0000-4000-8000-000000000023",
"from": "node-00000000-0000-4000-8000-000000000017",
"to": "node-00000000-0000-4000-8000-000000000018"
},
{
"id": "edge-00000000-0000-4000-8000-000000000024",
"from": "node-00000000-0000-4000-8000-000000000015",
"to": "node-00000000-0000-4000-8000-000000000020"
},
{
"id": "edge-00000000-0000-4000-8000-000000000025",
"from": "node-00000000-0000-4000-8000-000000000019",
"to": "node-00000000-0000-4000-8000-000000000020"
},
{
"id": "edge-00000000-0000-4000-8000-000000000026",
"from": "node-00000000-0000-4000-8000-000000000018",
"to": "node-00000000-0000-4000-8000-000000000020"
},
{
"id": "edge-00000000-0000-4000-8000-000000000027",
"from": "node-00000000-0000-4000-8000-000000000020",
"to": "node-00000000-0000-4000-8000-000000000021"
}
]
}
}
+92
View File
@@ -0,0 +1,92 @@
{
"id": "thermostat_enabled_guard",
"category": "reliability",
"name": {
"pl": "Steruj tylko aktywnym termostatem",
"en": "Control only an active thermostat"
},
"description": {
"pl": "Wymaga włączonej strefy, aktywnego zapotrzebowania i niezerowego trybu domu przed akcją.",
"en": "Requires an enabled zone, active demand and a non-off house mode before the action executes."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000182",
"kind": "zone_state",
"x": 35,
"y": 55,
"config": {
"zone_id": "$zone1",
"field": "enabled",
"operator": "eq",
"value": "true"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000183",
"kind": "zone_state",
"x": 35,
"y": 175,
"config": {
"zone_id": "$zone1",
"field": "demand",
"operator": "eq",
"value": "true"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000184",
"kind": "house_mode",
"x": 35,
"y": 295,
"config": {
"operator": "neq",
"value": "off"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000185",
"kind": "logic_and",
"x": 310,
"y": 175,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000186",
"kind": "zone_thermostat",
"x": 570,
"y": 175,
"config": {
"zone_id": "$zone1",
"preset": "auto",
"setpoint": 21,
"mode": "auto",
"cooldown_seconds": 120
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000187",
"from": "node-00000000-0000-4000-8000-000000000182",
"to": "node-00000000-0000-4000-8000-000000000185"
},
{
"id": "edge-00000000-0000-4000-8000-000000000188",
"from": "node-00000000-0000-4000-8000-000000000183",
"to": "node-00000000-0000-4000-8000-000000000185"
},
{
"id": "edge-00000000-0000-4000-8000-000000000189",
"from": "node-00000000-0000-4000-8000-000000000184",
"to": "node-00000000-0000-4000-8000-000000000185"
},
{
"id": "edge-00000000-0000-4000-8000-000000000190",
"from": "node-00000000-0000-4000-8000-000000000185",
"to": "node-00000000-0000-4000-8000-000000000186"
}
]
}
}
+75
View File
@@ -0,0 +1,75 @@
{
"id": "unoccupied_shutdown",
"category": "energy",
"name": {
"pl": "Wyłącz podczas nieobecności",
"en": "Shut down while away"
},
"description": {
"pl": "W godzinach dziennych wyłącza grupę lub strefę, gdy wskazana osoba jest poza domem.",
"en": "During daytime hours, turns off a group or zone when the selected person is away."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000077",
"kind": "ha_state",
"x": 35,
"y": 70,
"config": {
"entity_id": "person.someone",
"operator": "eq",
"value": "not_home"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000078",
"kind": "time_range",
"x": 35,
"y": 190,
"config": {
"start": "09:00",
"end": "16:00"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000079",
"kind": "logic_and",
"x": 310,
"y": 130,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000080",
"kind": "group_action",
"x": 565,
"y": 130,
"config": {
"group_id": "$group1",
"power": false,
"mode": "",
"preset": "",
"setpoint": 21,
"cooldown_seconds": 300
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000081",
"from": "node-00000000-0000-4000-8000-000000000077",
"to": "node-00000000-0000-4000-8000-000000000079"
},
{
"id": "edge-00000000-0000-4000-8000-000000000082",
"from": "node-00000000-0000-4000-8000-000000000078",
"to": "node-00000000-0000-4000-8000-000000000079"
},
{
"id": "edge-00000000-0000-4000-8000-000000000083",
"from": "node-00000000-0000-4000-8000-000000000079",
"to": "node-00000000-0000-4000-8000-000000000080"
}
]
}
}
+93
View File
@@ -0,0 +1,93 @@
{
"id": "weather_comfort",
"category": "comfort",
"name": {
"pl": "Komfort zależny od pogody",
"en": "Weather-aware comfort"
},
"description": {
"pl": "Łączy dzień, godzinę i temperaturę zewnętrzną przez AND przed sterowaniem termostatem.",
"en": "Combines weekday, time and outdoor temperature with AND before thermostat control."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000006",
"kind": "weekday",
"x": 35,
"y": 40,
"config": {
"days": [
1,
2,
3,
4,
5
]
}
},
{
"id": "node-00000000-0000-4000-8000-000000000007",
"kind": "time_range",
"x": 35,
"y": 155,
"config": {
"start": "06:00",
"end": "22:30"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000008",
"kind": "outdoor_temperature",
"x": 35,
"y": 270,
"config": {
"operator": "lt",
"value": 12
}
},
{
"id": "node-00000000-0000-4000-8000-000000000009",
"kind": "logic_and",
"x": 300,
"y": 155,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000010",
"kind": "zone_thermostat",
"x": 555,
"y": 155,
"config": {
"zone_id": "$zone1",
"preset": "comfort",
"setpoint": 21,
"mode": "auto",
"cooldown_seconds": 90
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000011",
"from": "node-00000000-0000-4000-8000-000000000006",
"to": "node-00000000-0000-4000-8000-000000000009"
},
{
"id": "edge-00000000-0000-4000-8000-000000000012",
"from": "node-00000000-0000-4000-8000-000000000007",
"to": "node-00000000-0000-4000-8000-000000000009"
},
{
"id": "edge-00000000-0000-4000-8000-000000000013",
"from": "node-00000000-0000-4000-8000-000000000008",
"to": "node-00000000-0000-4000-8000-000000000009"
},
{
"id": "edge-00000000-0000-4000-8000-000000000014",
"from": "node-00000000-0000-4000-8000-000000000009",
"to": "node-00000000-0000-4000-8000-000000000010"
}
]
}
}
+90
View File
@@ -0,0 +1,90 @@
{
"id": "weekend_comfort",
"category": "comfort",
"name": {
"pl": "Weekendowy komfort",
"en": "Weekend comfort"
},
"description": {
"pl": "W sobotę i niedzielę utrzymuje komfort w ciągu dnia, jeśli tryb domu nie jest wyłączony.",
"en": "Keeps daytime comfort on Saturday and Sunday while the house climate mode is not off."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000039",
"kind": "weekday",
"x": 40,
"y": 90,
"config": {
"days": [
6,
7
]
}
},
{
"id": "node-00000000-0000-4000-8000-000000000040",
"kind": "time_range",
"x": 250,
"y": 90,
"config": {
"start": "08:00",
"end": "23:00"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000041",
"kind": "house_mode",
"x": 40,
"y": 220,
"config": {
"operator": "neq",
"value": "off"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000042",
"kind": "logic_and",
"x": 465,
"y": 145,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000043",
"kind": "zone_thermostat",
"x": 710,
"y": 145,
"config": {
"zone_id": "$zone1",
"preset": "comfort",
"setpoint": 21,
"mode": "auto",
"cooldown_seconds": 90
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000044",
"from": "node-00000000-0000-4000-8000-000000000039",
"to": "node-00000000-0000-4000-8000-000000000042"
},
{
"id": "edge-00000000-0000-4000-8000-000000000045",
"from": "node-00000000-0000-4000-8000-000000000040",
"to": "node-00000000-0000-4000-8000-000000000042"
},
{
"id": "edge-00000000-0000-4000-8000-000000000046",
"from": "node-00000000-0000-4000-8000-000000000041",
"to": "node-00000000-0000-4000-8000-000000000042"
},
{
"id": "edge-00000000-0000-4000-8000-000000000047",
"from": "node-00000000-0000-4000-8000-000000000042",
"to": "node-00000000-0000-4000-8000-000000000043"
}
]
}
}
+74
View File
@@ -0,0 +1,74 @@
{
"id": "window_available_guard",
"category": "safety",
"name": {
"pl": "Okno: dostępność + otwarcie",
"en": "Window availability + open state"
},
"description": {
"pl": "Wyłącza HVAC dopiero gdy czujnik okna jest dostępny i zgłasza otwarcie — bez fałszywej reakcji na unavailable.",
"en": "Turns HVAC off only when the window sensor is available and reports open, avoiding false reactions to unavailable."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000118",
"kind": "ha_available",
"x": 35,
"y": 80,
"config": {
"entity_id": "binary_sensor.window"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000119",
"kind": "ha_state",
"x": 35,
"y": 210,
"config": {
"entity_id": "binary_sensor.window",
"operator": "eq",
"value": "on"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000120",
"kind": "logic_and",
"x": 310,
"y": 145,
"config": {}
},
{
"id": "node-00000000-0000-4000-8000-000000000121",
"kind": "zone_thermostat",
"x": 565,
"y": 145,
"config": {
"zone_id": "$zone1",
"preset": "auto",
"setpoint": 21,
"mode": "auto",
"cooldown_seconds": 60,
"power": false
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000122",
"from": "node-00000000-0000-4000-8000-000000000118",
"to": "node-00000000-0000-4000-8000-000000000120"
},
{
"id": "edge-00000000-0000-4000-8000-000000000123",
"from": "node-00000000-0000-4000-8000-000000000119",
"to": "node-00000000-0000-4000-8000-000000000120"
},
{
"id": "edge-00000000-0000-4000-8000-000000000124",
"from": "node-00000000-0000-4000-8000-000000000120",
"to": "node-00000000-0000-4000-8000-000000000121"
}
]
}
}
+66
View File
@@ -0,0 +1,66 @@
{
"id": "workday_comfort",
"category": "comfort",
"name": {
"pl": "Komfort w dni robocze",
"en": "Workday comfort"
},
"description": {
"pl": "Pon.pt. rano uruchamia preset komfortu w wybranej strefie.",
"en": "Weekday morning comfort preset for the selected thermostat zone."
},
"flow": {
"nodes": [
{
"id": "node-00000000-0000-4000-8000-000000000001",
"kind": "weekday",
"x": 40,
"y": 70,
"config": {
"days": [
1,
2,
3,
4,
5
]
}
},
{
"id": "node-00000000-0000-4000-8000-000000000002",
"kind": "time_range",
"x": 250,
"y": 70,
"config": {
"start": "06:00",
"end": "08:30"
}
},
{
"id": "node-00000000-0000-4000-8000-000000000003",
"kind": "zone_thermostat",
"x": 500,
"y": 70,
"config": {
"zone_id": "$zone1",
"preset": "comfort",
"setpoint": 21,
"mode": "auto",
"cooldown_seconds": 90
}
}
],
"edges": [
{
"id": "edge-00000000-0000-4000-8000-000000000004",
"from": "node-00000000-0000-4000-8000-000000000001",
"to": "node-00000000-0000-4000-8000-000000000002"
},
{
"id": "edge-00000000-0000-4000-8000-000000000005",
"from": "node-00000000-0000-4000-8000-000000000002",
"to": "node-00000000-0000-4000-8000-000000000003"
}
]
}
}
+2
View File
@@ -135,6 +135,8 @@ pub fn router(state: AppState) -> Router {
.route("/flows/:id", get(index))
.route("/lang/index.json", get(language_index))
.route("/lang/:file", get(language_file))
.route("/presets/index.json", get(preset_index))
.route("/presets/:file", get(preset_file))
.merge(protected)
.merge(home_assistant_api);
+12
View File
@@ -69,6 +69,18 @@ async fn language_file(Path(file): Path<String>) -> Response {
response.headers_mut().insert(header::CONTENT_TYPE, HeaderValue::from_static("text/plain; charset=utf-8"));
response
}
async fn preset_index() -> Response {
static_response(PRESET_MANIFEST_JSON, "application/json; charset=utf-8", "no-cache")
}
async fn preset_file(Path(file): Path<String>) -> Response {
if let Some((_, body)) = PRESET_ASSETS.iter().find(|(filename, _)| *filename == file) {
return static_response(*body, "application/json; charset=utf-8", "no-cache");
}
let mut response = Response::new(Body::from("Preset not found"));
*response.status_mut() = StatusCode::NOT_FOUND;
response.headers_mut().insert(header::CONTENT_TYPE, HeaderValue::from_static("text/plain; charset=utf-8"));
response
}
fn static_response(body: &'static str, content_type: &'static str, cache: &'static str) -> Response {
let mut response = Response::new(Body::from(body));
response.headers_mut().insert(header::CONTENT_TYPE, HeaderValue::from_static(content_type));
+6 -4
View File
@@ -184,7 +184,8 @@
</div>
<div class="panel flow-info-panel">
<div><strong data-i18n="flow.infoTitle">One place for schedules and automations</strong>
<p data-i18n="flow.infoText">Build the logic from blocks. GREE Controller translates Flow into generated schedules and automations. Generated entries are read-only outside this editor.</p></div>
<p data-i18n="flow.infoText">Build the logic from blocks. GREE Controller translates Flow into generated schedules and automations. Generated entries are read-only outside this editor.</p>
<button type="button" class="secondary flow-shared-settings-link" data-action="flow-shared-input-settings" data-i18n="flow.sharedInputsSettingsLink">Wspólne wejścia Flow w Home Assistant →</button></div>
<span class="badge" data-i18n="flow.sourceOfTruth">Flow = source of truth</span>
</div>
<section class="flow-list-section">
@@ -394,8 +395,8 @@
</div>
</section>
<section class="panel settings-block flow-shared-settings">
<div class="settings-block-head"><div><h3 data-i18n="flow.sharedInputsTitle">Wspólne wejścia Flow</h3><p data-i18n="flow.sharedInputsHint">Zdefiniuj stałe i warunki czujników raz, a potem używaj ich w wielu Flow.</p></div><button type="button" class="primary" id="addFlowSharedInput" data-i18n="flow.sharedInputAdd">Dodaj wejście</button></div>
<section class="panel settings-block flow-shared-settings" id="flowSharedInputsSettings">
<div class="settings-block-head flow-shared-settings-head"><div><h3 data-i18n="flow.sharedInputsTitle">Wspólne wejścia Flow</h3><p data-i18n="flow.sharedInputsHint">Zdefiniuj stałe i warunki czujników raz, a potem używaj ich w wielu Flow.</p></div><button type="button" class="primary flow-shared-add-button" id="addFlowSharedInput" data-i18n="flow.sharedInputAdd">Dodaj wejście</button></div>
<input type="hidden" id="flowSharedInputsRevision" name="flow_inputs_revision" value="[]"><div id="flowSharedInputList" class="flow-shared-input-list"></div>
</section>
@@ -727,13 +728,14 @@
<label><span data-i18n="common.name">Nazwa</span><input name="name" maxlength="100" required placeholder="Okno w salonie zamknięte"></label>
<label><span data-i18n="flow.sharedInputType">Typ źródła</span><select name="kind" id="flowSharedInputKind"></select></label>
<div id="flowSharedInputFields" class="flow-shared-input-fields"></div>
<div class="dialog-actions"><button type="button" class="secondary" data-close data-i18n="actions.cancel">Anuluj</button><button type="submit" class="primary" data-i18n="actions.save">Zapisz</button></div>
<div class="dialog-actions flow-shared-dialog-actions"><button type="button" class="secondary" data-close data-i18n="actions.cancel">Anuluj</button><button type="submit" class="primary" data-i18n="actions.save">Zapisz</button></div>
</form>
</dialog>
<dialog id="flowTemplateDialog" class="flow-tool-dialog">
<div class="dialog-head"><div><span class="eyebrow" data-i18n="flow.templateLibrary">Flow library</span><h2 data-i18n="flow.templates">Templates</h2></div><button type="button" data-close>×</button></div>
<p class="field-note" data-i18n="flow.templatesHint">Ready-made layouts create an editable graph using existing zones, devices and groups.</p>
<div id="flowTemplateTabs" class="flow-template-tabs" role="tablist" data-i18n-aria="flow.templateCategories" aria-label="Preset categories"></div>
<div id="flowTemplateList" class="flow-template-list"></div>
</dialog>
+125 -103
View File
@@ -302,117 +302,128 @@ async function importFlowFile(file) {
finally { const input = $('#flowImportFile'); if (input) input.value = ''; }
}
function flowTemplateGraph(key) {
const zone = app.zones[0]?.id || '', zone2 = app.zones[1]?.id || zone, device = app.devices[0]?.id || '', group = app.groups[0]?.id || '';
const nodes = [], edges = [];
const add = (kind, x, y, config = flowDefaultConfig(kind)) => { const node = { id:newFlowId('node'), kind, x, y, config:{ ...config } }; nodes.push(node); return node; };
const link = (a, b) => edges.push({ id:newFlowId('edge'), from:a.id, to:b.id });
const links = (items, target) => items.forEach(item => link(item, target));
const thermostat = (x, y, config = {}) => add('zone_thermostat', x, y, { zone_id:zone, preset:'comfort', setpoint:21, mode:'auto', cooldown_seconds:90, ...config });
const groupOrZone = (x, y, config = {}) => group
? add('group_action', x, y, { group_id:group, power:null, mode:'auto', preset:'comfort', setpoint:21, cooldown_seconds:120, ...config })
: thermostat(x, y, config);
const titleKey = `flow.template.${key}.title`, descriptionKey = `flow.template.${key}.description`;
const FLOW_PRESET_CATEGORY_ORDER = ['comfort','energy','safety','night','reliability','home_assistant','advanced'];
let flowPresetLoadPromise = null;
let flowPresetActiveCategory = '';
if (key === 'workday_comfort') {
const days=add('weekday',40,70), time=add('time_range',250,70,{start:'06:00',end:'08:30'}), action=thermostat(500,70); link(days,time); link(time,action);
} else if (key === 'weather_comfort') {
const days=add('weekday',35,40), time=add('time_range',35,155,{start:'06:00',end:'22:30'}), outside=add('outdoor_temperature',35,270,{operator:'lt',value:12}), and=add('logic_and',300,155), action=thermostat(555,155); links([days,time,outside],and); link(and,action);
} else if (key === 'smart_demand') {
const time=add('time_range',30,40,{start:'05:30',end:'23:00'}), outside=add('outdoor_temperature',30,160,{operator:'lt',value:10}), room=add('zone_temperature',30,280,{zone_id:zone,operator:'lt',value:20}), or=add('logic_or',280,220), mode=add('house_mode',280,70,{operator:'neq',value:'off'}), and=add('logic_and',510,155), action=thermostat(760,155); links([outside,room],or); links([time,mode,or],and); link(and,action);
} else if (key === 'morning_boost') {
const days=add('weekday',35,50), time=add('time_range',35,170,{start:'05:30',end:'07:30'}), cold=add('outdoor_temperature',35,290,{operator:'lt',value:5}), heat=add('house_mode',35,410,{operator:'eq',value:'heat'}), and=add('logic_and',320,220), action=thermostat(585,220,{preset:'custom',setpoint:22.5,mode:'heat',cooldown_seconds:180}); links([days,time,cold,heat],and); link(and,action);
} else if (key === 'weekend_comfort') {
const days=add('weekday',40,90,{days:[6,7]}), time=add('time_range',250,90,{start:'08:00',end:'23:00'}), mode=add('house_mode',40,220,{operator:'neq',value:'off'}), and=add('logic_and',465,145), action=thermostat(710,145); links([days,time,mode],and); link(and,action);
} else if (key === 'presence_eco') {
const home=add('ha_state',45,75,{entity_id:'person.someone',operator:'eq',value:'home'}), away=add('ha_state',45,245,{entity_id:'person.someone',operator:'eq',value:'not_home'}), comfort=thermostat(360,65), eco=thermostat(360,245,{preset:'away',setpoint:18,cooldown_seconds:180}); link(home,comfort); link(away,eco);
} else if (key === 'energy_price_eco') {
const price=add('ha_numeric',40,70,{entity_id:'sensor.energy_price',operator:'gt',value:0.8}), occupied=add('ha_state',40,190,{entity_id:'person.someone',operator:'neq',value:'home'}), and=add('logic_and',315,130), action=thermostat(570,130,{preset:'away',setpoint:18,cooldown_seconds:300}); links([price,occupied],and); link(and,action);
} else if (key === 'peak_power_guard') {
const power=add('ha_numeric',40,75,{entity_id:'sensor.house_power',operator:'gt',value:5000}), mode=add('house_mode',40,200,{operator:'neq',value:'off'}), and=add('logic_and',315,140), action=thermostat(570,140,{power:false,preset:'auto',cooldown_seconds:180}); links([power,mode],and); link(and,action);
} else if (key === 'mild_weather_eco') {
const low=add('outdoor_temperature',35,55,{operator:'gte',value:17}), high=add('outdoor_temperature',35,175,{operator:'lte',value:24}), window=add('ha_state',35,295,{entity_id:'binary_sensor.window',operator:'eq',value:'off'}), and=add('logic_and',310,175), action=thermostat(565,175,{power:false,preset:'auto',cooldown_seconds:300}); links([low,high,window],and); link(and,action);
} else if (key === 'unoccupied_shutdown') {
const away=add('ha_state',35,70,{entity_id:'person.someone',operator:'eq',value:'not_home'}), time=add('time_range',35,190,{start:'09:00',end:'16:00'}), and=add('logic_and',310,130), action=group ? add('group_action',565,130,{group_id:group,power:false,mode:'',preset:'',setpoint:21,cooldown_seconds:300}) : thermostat(565,130,{power:false,preset:'away',cooldown_seconds:300}); links([away,time],and); link(and,action);
} else if (key === 'frost_guard') {
const heat=add('house_mode',35,50,{operator:'eq',value:'heat'}), outside=add('outdoor_temperature',35,170,{operator:'lt',value:3}), room=add('zone_temperature',35,290,{zone_id:zone,operator:'lt',value:9}), and=add('logic_and',315,170), action=thermostat(575,170,{preset:'custom',setpoint:12,mode:'heat',cooldown_seconds:300}); links([heat,outside,room],and); link(and,action);
} else if (key === 'ha_window_guard') {
const open=add('ha_state',45,90,{entity_id:'binary_sensor.window',operator:'eq',value:'on'}), mode=add('house_mode',45,220,{operator:'neq',value:'off'}), and=add('logic_and',305,155), action=thermostat(560,155,{power:false,preset:'auto',cooldown_seconds:60}); links([open,mode],and); link(and,action);
} else if (key === 'humidity_guard') {
const humidity=add('ha_numeric',40,60,{entity_id:'sensor.living_room_humidity',operator:'gt',value:70}), cool=add('house_mode',40,180,{operator:'eq',value:'cool'}), occupied=add('ha_state',40,300,{entity_id:'person.someone',operator:'eq',value:'home'}), and=add('logic_and',315,180), action=thermostat(575,180,{preset:'custom',setpoint:22,mode:'cool',cooldown_seconds:180}); links([humidity,cool,occupied],and); link(and,action);
} else if (key === 'overheat_guard') {
const room=add('zone_temperature',35,65,{zone_id:zone,operator:'gt',value:28}), cool=add('house_mode',35,185,{operator:'eq',value:'cool'}), window=add('ha_state',35,305,{entity_id:'binary_sensor.window',operator:'eq',value:'off'}), and=add('logic_and',310,185), action=thermostat(570,185,{preset:'custom',setpoint:22,mode:'cool',cooldown_seconds:120}); links([room,cool,window],and); link(and,action);
} else if (key === 'window_available_guard') {
const available=add('ha_available',35,80,{entity_id:'binary_sensor.window'}), open=add('ha_state',35,210,{entity_id:'binary_sensor.window',operator:'eq',value:'on'}), and=add('logic_and',310,145), action=thermostat(565,145,{power:false,preset:'auto',cooldown_seconds:60}); links([available,open],and); link(and,action);
} else if (key === 'night_group') {
const time=add('time_range',50,100,{start:'22:30',end:'06:00'}), mode=add('house_mode',50,230,{operator:'neq',value:'off'}), and=add('logic_and',315,165), action=groupOrZone(570,165,{power:true,preset:'sleep',setpoint:20,cooldown_seconds:120}); links([time,mode],and); link(and,action);
} else if (key === 'night_quiet') {
const night=add('night_mode',45,90), and=add('logic_and',310,155), action=groupOrZone(565,155,{preset:'sleep',setpoint:20,cooldown_seconds:180}); link(night,and); if (group) { const on=add('group_state',45,225,{group_id:group,field:'power_enabled',operator:'eq',value:'true'}); link(on,and); } link(and,action);
} else if (key === 'sleep_temperature_guard') {
const night=add('night_mode',35,55), room=add('zone_temperature',35,175,{zone_id:zone,operator:'gt',value:23.5}), cool=add('house_mode',35,295,{operator:'eq',value:'cool'}), and=add('logic_and',310,175), action=thermostat(570,175,{preset:'sleep',setpoint:20,mode:'cool',cooldown_seconds:180}); links([night,room,cool],and); link(and,action);
} else if (key === 'bedroom_window_night') {
const night=add('night_mode',35,65), open=add('ha_state',35,185,{entity_id:'binary_sensor.bedroom_window',operator:'eq',value:'on'}), not=add('logic_not',275,185), and=add('logic_and',500,125), action=thermostat(745,125,{preset:'sleep',setpoint:20,cooldown_seconds:180}); link(open,not); links([night,not],and); link(and,action);
} else if (key === 'device_resilience') {
const online=add('device_state',40,55,{device_id:device,field:'online',operator:'eq',value:'true'}), enabled=add('device_state',40,175,{device_id:device,field:'enabled',operator:'eq',value:'true'}), mode=add('house_mode',40,295,{operator:'neq',value:'off'}), and=add('logic_and',315,175), action=thermostat(570,175,{preset:'auto'}); links([online,enabled,mode],and); link(and,action);
} else if (key === 'sensor_availability_guard') {
const available=add('ha_available',35,55,{entity_id:'sensor.room_temperature'}), temp=add('ha_numeric',35,175,{entity_id:'sensor.room_temperature',operator:'gt',value:26}), cool=add('house_mode',35,295,{operator:'eq',value:'cool'}), and=add('logic_and',310,175), action=thermostat(570,175,{preset:'comfort',mode:'cool',cooldown_seconds:120}); links([available,temp,cool],and); link(and,action);
} else if (key === 'offline_safe_off') {
const offline=add('device_state',35,85,{device_id:device,field:'online',operator:'eq',value:'false'}), enabled=add('zone_state',35,215,{zone_id:zone,field:'enabled',operator:'eq',value:'true'}), and=add('logic_and',310,150), action=thermostat(565,150,{power:false,preset:'auto',cooldown_seconds:300}); links([offline,enabled],and); link(and,action);
} else if (key === 'thermostat_enabled_guard') {
const enabled=add('zone_state',35,55,{zone_id:zone,field:'enabled',operator:'eq',value:'true'}), demand=add('zone_state',35,175,{zone_id:zone,field:'demand',operator:'eq',value:'true'}), mode=add('house_mode',35,295,{operator:'neq',value:'off'}), and=add('logic_and',310,175), action=thermostat(570,175,{preset:'auto',cooldown_seconds:120}); links([enabled,demand,mode],and); link(and,action);
} else if (key === 'nested_guard') {
const days=add('weekday',25,35), time=add('time_range',25,145,{start:'06:00',end:'22:30'}), outside=add('outdoor_temperature',25,255,{operator:'lt',value:8}), room=add('zone_temperature',25,365,{zone_id:zone,operator:'lt',value:20}), or=add('logic_or',275,310), available=add('ha_available',275,430,{entity_id:'binary_sensor.window'}), window=add('ha_state',275,540,{entity_id:'binary_sensor.window',operator:'eq',value:'on'}), not=add('logic_not',495,540), and=add('logic_and',510,235), action=thermostat(770,235); links([outside,room],or); link(window,not); links([days,time,or,available,not],and); link(and,action);
} else if (key === 'occupancy_weather_matrix') {
const home=add('ha_state',25,40,{entity_id:'person.someone',operator:'eq',value:'home'}), outside=add('outdoor_temperature',25,155,{operator:'lt',value:9}), room=add('zone_temperature',25,270,{zone_id:zone,operator:'lt',value:20}), need=add('logic_or',275,215), window=add('ha_state',275,345,{entity_id:'binary_sensor.window',operator:'eq',value:'on'}), not=add('logic_not',495,345), and=add('logic_and',505,150), action=thermostat(765,150); links([outside,room],need); link(window,not); links([home,need,not],and); link(and,action);
} else if (key === 'dual_threshold_control') {
const cold=add('zone_temperature',35,70,{zone_id:zone,operator:'lt',value:19}), hot=add('zone_temperature',35,245,{zone_id:zone,operator:'gt',value:25}), heat=add('house_mode',250,70,{operator:'eq',value:'heat'}), cool=add('house_mode',250,245,{operator:'eq',value:'cool'}), andHeat=add('logic_and',470,70), andCool=add('logic_and',470,245), heatAction=thermostat(715,70,{preset:'comfort',mode:'heat'}), coolAction=thermostat(715,245,{preset:'comfort',mode:'cool'}); links([cold,heat],andHeat); links([hot,cool],andCool); link(andHeat,heatAction); link(andCool,coolAction);
} else if (key === 'multi_room_group_guard') {
const roomA=add('zone_temperature',30,55,{zone_id:zone,operator:'gt',value:25}), roomB=add('zone_temperature',30,175,{zone_id:zone2,operator:'gt',value:25}), need=add('logic_or',280,115), cool=add('house_mode',280,245,{operator:'eq',value:'cool'}), and=add('logic_and',505,175), action=groupOrZone(760,175,{power:true,preset:'comfort',setpoint:22,cooldown_seconds:120}); links([roomA,roomB],need); links([need,cool],and); link(and,action);
} else if (key === 'ha_attribute_mode_guard') {
const running=add('ha_attribute',35,60,{entity_id:'climate.living_room',attribute:'hvac_action',operator:'neq',value:'idle'}), window=add('ha_state',35,180,{entity_id:'binary_sensor.window',operator:'eq',value:'on'}), available=add('ha_available',35,300,{entity_id:'climate.living_room'}), and=add('logic_and',315,180), action=thermostat(575,180,{power:false,preset:'auto',cooldown_seconds:60}); links([running,window,available],and); link(and,action);
} else if (key === 'ha_gas_heating_off') {
const available=add('ha_available',35,70,{entity_id:'climate.gas_boiler'}), heating=add('ha_attribute',35,200,{entity_id:'climate.gas_boiler',attribute:'hvac_action',operator:'eq',value:'heating'}), and=add('logic_and',315,135), action=thermostat(575,135,{power:false,preset:'auto',cooldown_seconds:120}); links([available,heating],and); link(and,action);
} else if (key === 'ha_gas_heating_boost') {
const available=add('ha_available',25,35,{entity_id:'climate.gas_boiler'}), heating=add('ha_attribute',25,145,{entity_id:'climate.gas_boiler',attribute:'hvac_action',operator:'eq',value:'heating'}), cold=add('zone_temperature',25,255,{zone_id:zone,operator:'lt',value:20}), heat=add('house_mode',25,365,{operator:'eq',value:'heat'}), and=add('logic_and',320,200), action=thermostat(590,200,{power:true,preset:'custom',setpoint:23.5,mode:'heat',cooldown_seconds:180}); links([available,heating,cold,heat],and); link(and,action);
} else if (key === 'ha_gas_heating_reduce') {
const available=add('ha_available',35,50,{entity_id:'climate.gas_boiler'}), heating=add('ha_attribute',35,165,{entity_id:'climate.gas_boiler',attribute:'hvac_action',operator:'eq',value:'heating'}), warm=add('zone_temperature',35,280,{zone_id:zone,operator:'gte',value:21}), and=add('logic_and',315,165), action=thermostat(575,165,{power:true,preset:'custom',setpoint:18,mode:'heat',cooldown_seconds:180}); links([available,heating,warm],and); link(and,action);
} else if (key === 'ha_gas_backup_heat') {
const available=add('ha_available',25,40,{entity_id:'climate.gas_boiler'}), idle=add('ha_attribute',25,155,{entity_id:'climate.gas_boiler',attribute:'hvac_action',operator:'neq',value:'heating'}), cold=add('zone_temperature',25,270,{zone_id:zone,operator:'lt',value:19}), heat=add('house_mode',25,385,{operator:'eq',value:'heat'}), and=add('logic_and',320,210), action=thermostat(590,210,{power:true,preset:'custom',setpoint:22,mode:'heat',cooldown_seconds:180}); links([available,idle,cold,heat],and); link(and,action);
} else if (key === 'ha_external_heat_source_off') {
const available=add('ha_available',35,70,{entity_id:'binary_sensor.external_heat_source'}), active=add('ha_state',35,200,{entity_id:'binary_sensor.external_heat_source',operator:'eq',value:'on'}), and=add('logic_and',315,135), action=groupOrZone(575,135,{power:false,preset:'auto',cooldown_seconds:120}); links([available,active],and); link(and,action);
} else if (key === 'ha_external_heat_source_assist') {
const available=add('ha_available',25,40,{entity_id:'binary_sensor.external_heat_source'}), active=add('ha_state',25,155,{entity_id:'binary_sensor.external_heat_source',operator:'eq',value:'on'}), cold=add('zone_temperature',25,270,{zone_id:zone,operator:'lt',value:20}), heat=add('house_mode',25,385,{operator:'eq',value:'heat'}), and=add('logic_and',320,210), action=thermostat(590,210,{power:true,preset:'custom',setpoint:23,mode:'heat',cooldown_seconds:180}); links([available,active,cold,heat],and); link(and,action);
} else if (key === 'ha_heating_demand_follow') {
const available=add('ha_available',35,60,{entity_id:'binary_sensor.heating_demand'}), demand=add('ha_state',35,180,{entity_id:'binary_sensor.heating_demand',operator:'eq',value:'on'}), heat=add('house_mode',35,300,{operator:'eq',value:'heat'}), and=add('logic_and',315,180), action=thermostat(575,180,{power:true,preset:'custom',setpoint:22.5,mode:'heat',cooldown_seconds:120}); links([available,demand,heat],and); link(and,action);
} else if (key === 'ha_boiler_supply_boost') {
const available=add('ha_available',25,45,{entity_id:'sensor.boiler_supply_temperature'}), supply=add('ha_numeric',25,160,{entity_id:'sensor.boiler_supply_temperature',operator:'gt',value:45}), cold=add('zone_temperature',25,275,{zone_id:zone,operator:'lt',value:20}), heat=add('house_mode',25,390,{operator:'eq',value:'heat'}), and=add('logic_and',320,215), action=thermostat(590,215,{power:true,preset:'custom',setpoint:23.5,mode:'heat',cooldown_seconds:180}); links([available,supply,cold,heat],and); link(and,action);
} else if (key === 'ha_thermostat_idle_fallback') {
const available=add('ha_available',25,40,{entity_id:'climate.gas_boiler'}), idle=add('ha_attribute',25,155,{entity_id:'climate.gas_boiler',attribute:'hvac_action',operator:'eq',value:'idle'}), cold=add('zone_temperature',25,270,{zone_id:zone,operator:'lt',value:19}), heat=add('house_mode',25,385,{operator:'eq',value:'heat'}), and=add('logic_and',320,210), action=thermostat(590,210,{power:true,preset:'custom',setpoint:21.5,mode:'heat',cooldown_seconds:240}); links([available,idle,cold,heat],and); link(and,action);
function flowPresetText(value) {
if (typeof value === 'string') return value;
if (!value || typeof value !== 'object') return '';
return value[app.language] || value.en || value.pl || Object.values(value).find(item => typeof item === 'string') || '';
}
async function loadFlowPresets({ force = false } = {}) {
if (!force && Array.isArray(app.flowPresets)) return app.flowPresets;
if (!force && flowPresetLoadPromise) return flowPresetLoadPromise;
flowPresetLoadPromise = (async () => {
const indexResponse = await fetch(withBase('/presets/index.json'), { headers: { Accept: 'application/json' }, cache: 'no-store' });
if (!indexResponse.ok) throw new Error(`Presets HTTP ${indexResponse.status}`);
const manifest = await indexResponse.json();
const entries = Array.isArray(manifest?.presets) ? manifest.presets : [];
const presets = await Promise.all(entries.map(async entry => {
const filename = String(entry?.file || '');
if (!filename || !/^[a-zA-Z0-9_-]+\.json$/.test(filename)) throw new Error('Invalid preset filename');
const response = await fetch(withBase(`/presets/${encodeURIComponent(filename)}`), { headers: { Accept: 'application/json' }, cache: 'no-store' });
if (!response.ok) throw new Error(`${filename}: HTTP ${response.status}`);
const preset = await response.json();
if (!preset?.id || !preset?.flow || !Array.isArray(preset.flow.nodes) || !Array.isArray(preset.flow.edges)) throw new Error(`${filename}: invalid preset`);
return preset;
}));
app.flowPresets = presets;
return presets;
})();
try { return await flowPresetLoadPromise; }
finally { flowPresetLoadPromise = null; }
}
function materializeFlowPreset(preset) {
const zone1 = app.zones[0]?.id || '';
const variables = {
'$zone1': zone1,
'$zone2': app.zones[1]?.id || zone1,
'$device1': app.devices[0]?.id || '',
'$group1': app.groups[0]?.id || '',
'$shared1': app.flowSharedInputs?.[0]?.id || '',
};
const replaceValue = value => {
if (typeof value === 'string' && Object.prototype.hasOwnProperty.call(variables, value)) return variables[value];
if (Array.isArray(value)) return value.map(replaceValue);
if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, replaceValue(item)]));
return value;
};
const sourceNodes = Array.isArray(preset?.flow?.nodes) ? preset.flow.nodes : [];
const sourceEdges = Array.isArray(preset?.flow?.edges) ? preset.flow.edges : [];
const skipped = new Set();
const idMap = new Map();
const nodes = [];
sourceNodes.forEach(source => {
const originalConfig = source.config || {};
if (source.kind === 'group_state' && originalConfig.group_id === '$group1' && !variables.$group1) { skipped.add(source.id); return; }
const node = { ...source, id: newFlowId('node'), config: replaceValue(originalConfig) };
if (source.kind === 'group_action' && originalConfig.group_id === '$group1' && !variables.$group1) {
node.kind = 'zone_thermostat';
node.config = {
zone_id: zone1,
preset: node.config.preset || 'comfort',
setpoint: Number(node.config.setpoint ?? 21),
mode: ['heat','cool'].includes(node.config.mode) ? node.config.mode : 'auto',
cooldown_seconds: Number(node.config.cooldown_seconds || 60),
power: node.config.power ?? null,
};
}
return { titleKey, descriptionKey, nodes, edges };
idMap.set(source.id, node.id);
nodes.push(node);
});
const edges = sourceEdges
.filter(edge => !skipped.has(edge.from) && !skipped.has(edge.to) && idMap.has(edge.from) && idMap.has(edge.to))
.map(edge => ({ id:newFlowId('edge'), from:idMap.get(edge.from), to:idMap.get(edge.to) }));
return { nodes, edges };
}
const FLOW_TEMPLATE_CATEGORIES = [
['comfort', ['workday_comfort','weather_comfort','smart_demand','morning_boost','weekend_comfort']],
['energy', ['presence_eco','energy_price_eco','peak_power_guard','mild_weather_eco','unoccupied_shutdown']],
['safety', ['frost_guard','ha_window_guard','humidity_guard','overheat_guard','window_available_guard']],
['night', ['night_group','night_quiet','sleep_temperature_guard','bedroom_window_night']],
['reliability', ['device_resilience','sensor_availability_guard','offline_safe_off','thermostat_enabled_guard']],
['home_assistant', ['ha_gas_heating_off','ha_gas_heating_boost','ha_gas_heating_reduce','ha_gas_backup_heat','ha_external_heat_source_off','ha_external_heat_source_assist','ha_heating_demand_follow','ha_boiler_supply_boost','ha_thermostat_idle_fallback']],
['advanced', ['nested_guard','occupancy_weather_matrix','dual_threshold_control','multi_room_group_guard','ha_attribute_mode_guard']],
];
function openFlowTemplates() {
if (!app.flowDraft) return;
const host = $('#flowTemplateList');
host.innerHTML = FLOW_TEMPLATE_CATEGORIES.map(([category, keys]) => `<section class="flow-template-category"><div class="flow-template-category-head"><strong>${esc(tr(`flow.templateCategory.${category}`))}</strong><span>${esc(tr(`flow.templateCategory.${category}.hint`))}</span></div><div class="flow-template-category-grid">${keys.map(key => `<button type="button" class="flow-template-card" data-flow-template="${esc(key)}"><strong>${esc(tr(`flow.template.${key}.title`))}</strong><span>${esc(tr(`flow.template.${key}.description`))}</span></button>`).join('')}</div></section>`).join('');
$('#flowTemplateDialog')?.showModal();
function renderFlowPresetBrowser(category = '') {
const tabs = $('#flowTemplateTabs'), host = $('#flowTemplateList');
if (!tabs || !host) return;
const presets = Array.isArray(app.flowPresets) ? app.flowPresets : [];
const byCategory = new Map();
presets.forEach(preset => {
const key = preset.category || 'advanced';
if (!byCategory.has(key)) byCategory.set(key, []);
byCategory.get(key).push(preset);
});
const categories = [...new Set([...FLOW_PRESET_CATEGORY_ORDER, ...byCategory.keys()])].filter(key => byCategory.has(key));
if (!categories.length) { tabs.innerHTML = ''; host.innerHTML = ''; return; }
flowPresetActiveCategory = categories.includes(category) ? category : (categories.includes(flowPresetActiveCategory) ? flowPresetActiveCategory : categories[0]);
tabs.innerHTML = categories.map(key => `<button type="button" class="flow-template-tab ${key === flowPresetActiveCategory ? 'active' : ''}" role="tab" aria-selected="${key === flowPresetActiveCategory}" data-flow-template-category="${esc(key)}">${esc(tr(`flow.templateCategory.${key}`) || key)} <span class="badge">${byCategory.get(key).length}</span></button>`).join('');
const active = byCategory.get(flowPresetActiveCategory) || [];
host.innerHTML = `<section class="flow-template-category-single"><div class="flow-template-category-head"><strong>${esc(tr(`flow.templateCategory.${flowPresetActiveCategory}`) || flowPresetActiveCategory)}</strong><span>${esc(tr(`flow.templateCategory.${flowPresetActiveCategory}.hint`) || '')}</span></div><div class="flow-template-category-grid">${active.map(preset => `<button type="button" class="flow-template-card" data-flow-template="${esc(preset.id)}"><strong>${esc(flowPresetText(preset.name) || preset.id)}</strong><span>${esc(flowPresetText(preset.description))}</span></button>`).join('')}</div></section>`;
}
async function openFlowTemplates() {
if (!app.flowDraft) return;
const host = $('#flowTemplateList'), tabs = $('#flowTemplateTabs');
if (!host) return;
if (tabs) tabs.innerHTML = '';
host.innerHTML = `<p class="field-note">${esc(tr('common.loading'))}</p>`;
$('#flowTemplateDialog')?.showModal();
try {
await loadFlowPresets();
renderFlowPresetBrowser(flowPresetActiveCategory);
} catch (error) {
if (tabs) tabs.innerHTML = '';
host.innerHTML = `<div class="empty compact"><strong>${esc(tr('flow.templatesLoadFailed') || 'Unable to load presets')}</strong><span>${esc(error.message)}</span></div>`;
}
}
function applyFlowTemplate(key) {
if (!app.flowDraft) return;
const preset = (app.flowPresets || []).find(item => item.id === key);
if (!preset) return toast(tr('flow.templatesLoadFailed') || 'Preset not found', true);
if (app.flowDraft.nodes.length && !confirm(tr('flow.templateReplaceConfirm'))) return;
const template = flowTemplateGraph(key); app.flowDraft.nodes = template.nodes; app.flowDraft.edges = template.edges;
app.flowDraft.description = tr(template.descriptionKey); app.flowSelectedNodeId = null; app.flowSelectedNodeIds = []; app.flowDirty = true;
if (!app.flowDraft.id || $('#flowName').value === tr('flow.newDefaultName')) $('#flowName').value = tr(template.titleKey);
const graph = materializeFlowPreset(preset);
app.flowDraft.nodes = graph.nodes; app.flowDraft.edges = graph.edges;
app.flowDraft.description = flowPresetText(preset.description); app.flowSelectedNodeId = null; app.flowSelectedNodeIds = []; app.flowDirty = true;
if (!app.flowDraft.id || $('#flowName').value === tr('flow.newDefaultName')) $('#flowName').value = flowPresetText(preset.name) || preset.id;
$('#flowTemplateDialog')?.close(); renderFlowEditor();
}
@@ -585,6 +596,7 @@ document.addEventListener('keydown', event => {
document.addEventListener('click', event => {
const edge = event.target.closest?.('[data-flow-edge]'); if (edge) { removeFlowEdge(edge.dataset.flowEdge); return; }
const add = event.target.closest?.('[data-flow-add]'); if (add) { addFlowNode(add.dataset.flowAdd); return; }
const templateCategory = event.target.closest?.('[data-flow-template-category]'); if (templateCategory) { renderFlowPresetBrowser(templateCategory.dataset.flowTemplateCategory); return; }
const template = event.target.closest?.('[data-flow-template]'); if (template) { applyFlowTemplate(template.dataset.flowTemplate); return; }
const remove = event.target.closest?.('[data-flow-remove]'); if (remove) { removeFlowNode(remove.dataset.flowRemove); return; }
const output = event.target.closest?.('[data-flow-output]'); if (output) { app.flowConnectFrom = output.dataset.flowOutput; renderFlowEditor(); return; }
@@ -603,6 +615,16 @@ document.addEventListener('click', event => {
else if (action === 'flow-logs') openFlowLogs();
else if (action === 'flow-select-all') selectAllFlowNodes();
else if (action === 'flow-clear-selection') clearFlowSelection();
else if (action === 'flow-shared-input-settings') {
if (!showView('homeassistant')) return;
requestAnimationFrame(() => {
const target = $('#flowSharedInputsSettings');
if (!target) return;
target.scrollIntoView({ behavior:'smooth', block:'start' });
target.classList.add('is-linked-target');
setTimeout(() => target.classList.remove('is-linked-target'), 1800);
});
}
else if (action === 'close-flow-editor') closeFlowEditor();
});
document.addEventListener('change', event => {
+20
View File
@@ -6259,3 +6259,23 @@ body.flow-editor-open { overflow:hidden; }
.flow-shared-input-actions button { flex:1; }
.flow-shared-input-fields .two { grid-template-columns:1fr; }
}
/* v0.9.9: Flow shared-input navigation and preset category tabs */
.flow-shared-settings-head { justify-content:space-between; align-items:flex-start; }
.flow-shared-settings-head > div { min-width:0; }
.flow-shared-add-button { flex:0 0 auto; align-self:flex-start; margin-left:auto; }
.flow-shared-dialog-actions { display:flex; align-items:center; justify-content:space-between; gap:24px; padding-top:14px; margin-top:4px; border-top:1px solid var(--line); }
.flow-shared-dialog-actions > button { min-width:112px; }
.flow-shared-settings-link { margin-top:12px; width:max-content; max-width:100%; }
.flow-shared-settings.is-linked-target { outline:2px solid var(--accent); outline-offset:3px; }
.flow-template-tabs { display:flex; gap:8px; overflow-x:auto; padding:4px 0 10px; margin-top:14px; border-bottom:1px solid var(--line); scrollbar-width:thin; }
.flow-template-tab { flex:0 0 auto; border:1px solid var(--line); border-radius:999px; background:var(--surface-2); color:var(--muted); padding:8px 12px; white-space:nowrap; }
.flow-template-tab.active { border-color:var(--accent-border); background:var(--accent-soft); color:var(--accent); }
.flow-template-list { margin-top:14px; }
.flow-template-category-single { display:grid; gap:12px; }
.flow-template-category-single .flow-template-category-head { padding-bottom:2px; }
@media (max-width:760px) {
.flow-shared-settings-head { gap:12px; }
.flow-shared-add-button { width:auto; }
.flow-shared-dialog-actions { gap:16px; }
}