diff --git a/Cargo.lock b/Cargo.lock index 5d1786b..e1801b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -633,7 +633,7 @@ dependencies = [ [[package]] name = "gree-controller" -version = "0.9.7" +version = "0.9.9" dependencies = [ "aes", "aes-gcm", diff --git a/Cargo.toml b/Cargo.toml index 28121fd..c0c522c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/FILE_MANIFEST.sha256 b/FILE_MANIFEST.sha256 index b94b5b6..19b0507 100644 --- a/FILE_MANIFEST.sha256 +++ b/FILE_MANIFEST.sha256 @@ -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 diff --git a/README.md b/README.md index adf0b32..ba1dc48 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/build.rs b/build.rs index 462016c..c36c299 100644 --- a/build.rs +++ b/build.rs @@ -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 = 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"); diff --git a/docs/API.md b/docs/API.md index 96eb3d6..be716f1 100644 --- a/docs/API.md +++ b/docs/API.md @@ -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, diff --git a/docs/FLOW.md b/docs/FLOW.md index 720a373..8e2540e 100644 --- a/docs/FLOW.md +++ b/docs/FLOW.md @@ -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/.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/.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. diff --git a/home-assistant/custom_components/gree_controller/manifest.json b/home-assistant/custom_components/gree_controller/manifest.json index fe9dbc6..9c5b79d 100644 --- a/home-assistant/custom_components/gree_controller/manifest.json +++ b/home-assistant/custom_components/gree_controller/manifest.json @@ -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", diff --git a/lang/en.json b/lang/en.json index 00ac09c..c111308 100644 --- a/lang/en.json +++ b/lang/en.json @@ -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" } diff --git a/lang/pl.json b/lang/pl.json index 5664c41..6a481ec 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -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" } diff --git a/presets/bedroom_window_night.json b/presets/bedroom_window_night.json new file mode 100644 index 0000000..177acdc --- /dev/null +++ b/presets/bedroom_window_night.json @@ -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" + } + ] + } +} diff --git a/presets/device_resilience.json b/presets/device_resilience.json new file mode 100644 index 0000000..43f9d56 --- /dev/null +++ b/presets/device_resilience.json @@ -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" + } + ] + } +} diff --git a/presets/dual_threshold_control.json b/presets/dual_threshold_control.json new file mode 100644 index 0000000..e0030fb --- /dev/null +++ b/presets/dual_threshold_control.json @@ -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" + } + ] + } +} diff --git a/presets/energy_price_eco.json b/presets/energy_price_eco.json new file mode 100644 index 0000000..13a361e --- /dev/null +++ b/presets/energy_price_eco.json @@ -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" + } + ] + } +} diff --git a/presets/frost_guard.json b/presets/frost_guard.json new file mode 100644 index 0000000..7ea1d6f --- /dev/null +++ b/presets/frost_guard.json @@ -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" + } + ] + } +} diff --git a/presets/ha_attribute_mode_guard.json b/presets/ha_attribute_mode_guard.json new file mode 100644 index 0000000..3d8b96d --- /dev/null +++ b/presets/ha_attribute_mode_guard.json @@ -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" + } + ] + } +} diff --git a/presets/ha_boiler_supply_boost.json b/presets/ha_boiler_supply_boost.json new file mode 100644 index 0000000..610fd3a --- /dev/null +++ b/presets/ha_boiler_supply_boost.json @@ -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" + } + ] + } +} diff --git a/presets/ha_external_heat_source_assist.json b/presets/ha_external_heat_source_assist.json new file mode 100644 index 0000000..c782e1c --- /dev/null +++ b/presets/ha_external_heat_source_assist.json @@ -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" + } + ] + } +} diff --git a/presets/ha_external_heat_source_off.json b/presets/ha_external_heat_source_off.json new file mode 100644 index 0000000..ec31017 --- /dev/null +++ b/presets/ha_external_heat_source_off.json @@ -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" + } + ] + } +} diff --git a/presets/ha_gas_backup_heat.json b/presets/ha_gas_backup_heat.json new file mode 100644 index 0000000..32dc262 --- /dev/null +++ b/presets/ha_gas_backup_heat.json @@ -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" + } + ] + } +} diff --git a/presets/ha_gas_heating_boost.json b/presets/ha_gas_heating_boost.json new file mode 100644 index 0000000..e4fe2f3 --- /dev/null +++ b/presets/ha_gas_heating_boost.json @@ -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" + } + ] + } +} diff --git a/presets/ha_gas_heating_off.json b/presets/ha_gas_heating_off.json new file mode 100644 index 0000000..634fd27 --- /dev/null +++ b/presets/ha_gas_heating_off.json @@ -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" + } + ] + } +} diff --git a/presets/ha_gas_heating_reduce.json b/presets/ha_gas_heating_reduce.json new file mode 100644 index 0000000..77cf466 --- /dev/null +++ b/presets/ha_gas_heating_reduce.json @@ -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" + } + ] + } +} diff --git a/presets/ha_heating_demand_follow.json b/presets/ha_heating_demand_follow.json new file mode 100644 index 0000000..1427e2e --- /dev/null +++ b/presets/ha_heating_demand_follow.json @@ -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" + } + ] + } +} diff --git a/presets/ha_thermostat_idle_fallback.json b/presets/ha_thermostat_idle_fallback.json new file mode 100644 index 0000000..828b094 --- /dev/null +++ b/presets/ha_thermostat_idle_fallback.json @@ -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" + } + ] + } +} diff --git a/presets/ha_window_guard.json b/presets/ha_window_guard.json new file mode 100644 index 0000000..3c1b66f --- /dev/null +++ b/presets/ha_window_guard.json @@ -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" + } + ] + } +} diff --git a/presets/humidity_guard.json b/presets/humidity_guard.json new file mode 100644 index 0000000..4b1416e --- /dev/null +++ b/presets/humidity_guard.json @@ -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" + } + ] + } +} diff --git a/presets/mild_weather_eco.json b/presets/mild_weather_eco.json new file mode 100644 index 0000000..1fafe1b --- /dev/null +++ b/presets/mild_weather_eco.json @@ -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" + } + ] + } +} diff --git a/presets/morning_boost.json b/presets/morning_boost.json new file mode 100644 index 0000000..6cd923d --- /dev/null +++ b/presets/morning_boost.json @@ -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" + } + ] + } +} diff --git a/presets/multi_room_group_guard.json b/presets/multi_room_group_guard.json new file mode 100644 index 0000000..f6f44bd --- /dev/null +++ b/presets/multi_room_group_guard.json @@ -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" + } + ] + } +} diff --git a/presets/nested_guard.json b/presets/nested_guard.json new file mode 100644 index 0000000..b7c9efe --- /dev/null +++ b/presets/nested_guard.json @@ -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" + } + ] + } +} diff --git a/presets/night_group.json b/presets/night_group.json new file mode 100644 index 0000000..e0ce2bd --- /dev/null +++ b/presets/night_group.json @@ -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" + } + ] + } +} diff --git a/presets/night_quiet.json b/presets/night_quiet.json new file mode 100644 index 0000000..2263fdd --- /dev/null +++ b/presets/night_quiet.json @@ -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" + } + ] + } +} diff --git a/presets/occupancy_weather_matrix.json b/presets/occupancy_weather_matrix.json new file mode 100644 index 0000000..4611f06 --- /dev/null +++ b/presets/occupancy_weather_matrix.json @@ -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" + } + ] + } +} diff --git a/presets/offline_safe_off.json b/presets/offline_safe_off.json new file mode 100644 index 0000000..9721b7d --- /dev/null +++ b/presets/offline_safe_off.json @@ -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" + } + ] + } +} diff --git a/presets/overheat_guard.json b/presets/overheat_guard.json new file mode 100644 index 0000000..38edb5f --- /dev/null +++ b/presets/overheat_guard.json @@ -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" + } + ] + } +} diff --git a/presets/peak_power_guard.json b/presets/peak_power_guard.json new file mode 100644 index 0000000..662cea8 --- /dev/null +++ b/presets/peak_power_guard.json @@ -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" + } + ] + } +} diff --git a/presets/presence_eco.json b/presets/presence_eco.json new file mode 100644 index 0000000..fcb8497 --- /dev/null +++ b/presets/presence_eco.json @@ -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" + } + ] + } +} diff --git a/presets/sensor_availability_guard.json b/presets/sensor_availability_guard.json new file mode 100644 index 0000000..c15ea1a --- /dev/null +++ b/presets/sensor_availability_guard.json @@ -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" + } + ] + } +} diff --git a/presets/sleep_temperature_guard.json b/presets/sleep_temperature_guard.json new file mode 100644 index 0000000..656db83 --- /dev/null +++ b/presets/sleep_temperature_guard.json @@ -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" + } + ] + } +} diff --git a/presets/smart_demand.json b/presets/smart_demand.json new file mode 100644 index 0000000..c851c69 --- /dev/null +++ b/presets/smart_demand.json @@ -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" + } + ] + } +} diff --git a/presets/thermostat_enabled_guard.json b/presets/thermostat_enabled_guard.json new file mode 100644 index 0000000..119a31d --- /dev/null +++ b/presets/thermostat_enabled_guard.json @@ -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" + } + ] + } +} diff --git a/presets/unoccupied_shutdown.json b/presets/unoccupied_shutdown.json new file mode 100644 index 0000000..2599225 --- /dev/null +++ b/presets/unoccupied_shutdown.json @@ -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" + } + ] + } +} diff --git a/presets/weather_comfort.json b/presets/weather_comfort.json new file mode 100644 index 0000000..b66b51d --- /dev/null +++ b/presets/weather_comfort.json @@ -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" + } + ] + } +} diff --git a/presets/weekend_comfort.json b/presets/weekend_comfort.json new file mode 100644 index 0000000..1a97616 --- /dev/null +++ b/presets/weekend_comfort.json @@ -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" + } + ] + } +} diff --git a/presets/window_available_guard.json b/presets/window_available_guard.json new file mode 100644 index 0000000..5c5ca51 --- /dev/null +++ b/presets/window_available_guard.json @@ -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" + } + ] + } +} diff --git a/presets/workday_comfort.json b/presets/workday_comfort.json new file mode 100644 index 0000000..77e6418 --- /dev/null +++ b/presets/workday_comfort.json @@ -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" + } + ] + } +} diff --git a/src/api.rs b/src/api.rs index 4a9cd0e..ee7307d 100644 --- a/src/api.rs +++ b/src/api.rs @@ -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); diff --git a/src/api/assets.rs b/src/api/assets.rs index 98cdc6c..d91be69 100644 --- a/src/api/assets.rs +++ b/src/api/assets.rs @@ -69,6 +69,18 @@ async fn language_file(Path(file): Path) -> 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) -> 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)); diff --git a/web/index.html b/web/index.html index 918373c..fbb0c76 100644 --- a/web/index.html +++ b/web/index.html @@ -184,7 +184,8 @@
One place for schedules and automations -

Build the logic from blocks. GREE Controller translates Flow into generated schedules and automations. Generated entries are read-only outside this editor.

+

Build the logic from blocks. GREE Controller translates Flow into generated schedules and automations. Generated entries are read-only outside this editor.

+
Flow = source of truth
@@ -394,8 +395,8 @@
-
-

Wspólne wejścia Flow

Zdefiniuj stałe i warunki czujników raz, a potem używaj ich w wielu Flow.

+
+

Wspólne wejścia Flow

Zdefiniuj stałe i warunki czujników raz, a potem używaj ich w wielu Flow.

@@ -727,13 +728,14 @@
-
+
Flow library

Templates

Ready-made layouts create an editable graph using existing zones, devices and groups.

+
diff --git a/web/js/flows.js b/web/js/flows.js index 987f559..23091b2 100644 --- a/web/js/flows.js +++ b/web/js/flows.js @@ -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); - } - return { titleKey, descriptionKey, nodes, edges }; + +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') || ''; } -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() { +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, + }; + } + 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 }; +} + +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 => ``).join(''); + const active = byCategory.get(flowPresetActiveCategory) || []; + host.innerHTML = `
${esc(tr(`flow.templateCategory.${flowPresetActiveCategory}`) || flowPresetActiveCategory)}${esc(tr(`flow.templateCategory.${flowPresetActiveCategory}.hint`) || '')}
${active.map(preset => ``).join('')}
`; +} + +async function openFlowTemplates() { if (!app.flowDraft) return; - const host = $('#flowTemplateList'); - host.innerHTML = FLOW_TEMPLATE_CATEGORIES.map(([category, keys]) => `
${esc(tr(`flow.templateCategory.${category}`))}${esc(tr(`flow.templateCategory.${category}.hint`))}
${keys.map(key => ``).join('')}
`).join(''); + const host = $('#flowTemplateList'), tabs = $('#flowTemplateTabs'); + if (!host) return; + if (tabs) tabs.innerHTML = ''; + host.innerHTML = `

${esc(tr('common.loading'))}

`; $('#flowTemplateDialog')?.showModal(); + try { + await loadFlowPresets(); + renderFlowPresetBrowser(flowPresetActiveCategory); + } catch (error) { + if (tabs) tabs.innerHTML = ''; + host.innerHTML = `
${esc(tr('flow.templatesLoadFailed') || 'Unable to load presets')}${esc(error.message)}
`; + } } + 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 => { diff --git a/web/styles.css b/web/styles.css index e748d09..83aff56 100644 --- a/web/styles.css +++ b/web/styles.css @@ -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; } +}