diff --git a/Cargo.lock b/Cargo.lock index 9aeabb3..8b50698 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -633,7 +633,7 @@ dependencies = [ [[package]] name = "gree-controller" -version = "0.8.15" +version = "0.8.16" dependencies = [ "aes", "aes-gcm", diff --git a/Cargo.toml b/Cargo.toml index 1c4877a..d64679f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gree-controller" -version = "0.8.15" +version = "0.8.16" 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 c8bf529..237475a 100644 --- a/FILE_MANIFEST.sha256 +++ b/FILE_MANIFEST.sha256 @@ -1,11 +1,11 @@ d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore -18b9dd7ee3a7ef1f63c8511a90ff6b0dbc6e71c53c0d049b4a764ecd3a1e4beb ./Cargo.lock -0449927a38faccafff1163e6cbdc0fb6f4b3ab7148f477ed0dc8a67e8188fea7 ./Cargo.toml +d3ee62701b27064418df4f62f43ef4a00528275e8338c2f48b4ea2010e11c10d ./Cargo.lock +6ab182c92c822b5f1685538d1d7f9e22e17ad1a748932fdda6e04444b4eefaf8 ./Cargo.toml 19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE -c51172d362ed76291c3d4bf213d7717764e2aa71d2a15994cd0569d6aec62e7b ./README.md +4a40bef9464c7da8e46c3a7396b417bafee37fea8e0d89406d025eb4e8e6c47e ./README.md ec0f495ee6a618bdec10b260ee7838f7a872d8c5b0aa91c546226c1ad818f601 ./build.rs -6f7b93c47d62660c470fd410eb687c26af118cc3eb6f2c413d0eabf709e4c363 ./docs/API.md +b99221eeadfd0f42122d570b0b402f698f97622ebafea5c97045fa7a2151dbd0 ./docs/API.md 95e3c8c99ba439a86a2fb05cfd8ef00c75cb959a024a349a082da935b726f500 ./home-assistant/README.md f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./home-assistant/custom_components/gree_controller/__init__.py 6910589f27960a28d4de9735884a7e5376e455cd885f19ce2b55947fcd135114 ./home-assistant/custom_components/gree_controller/api.py @@ -16,7 +16,7 @@ ab08fbe40e9bb48ebcbfff98760aaa0c9434b61b705aaaadea0c09c255d99b0e ./home-assista 5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./home-assistant/custom_components/gree_controller/entity_map.py c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./home-assistant/custom_components/gree_controller/icon.png c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./home-assistant/custom_components/gree_controller/logo.png -bb37ecd282ddd592d9d515765d90620c7e5e84dccddc24b2cf8a1058f5e4a550 ./home-assistant/custom_components/gree_controller/manifest.json +548759fff3e2f96352db7b3f96caced9f5d3a57fa4165ab27f22640ad53c1fab ./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 1cae12876eb81085910907cf1fcf340a18dcba4f8c967ef222c9496325057849 ./home-assistant/custom_components/gree_controller/sensor.py @@ -81,11 +81,11 @@ dad8930c3d811f02cfeced3aa4b9a404fe273ba1b99539bb174b9430575f3e22 ./src/engine/l 3d48608a065aa52a1fd8b9c59356a74d8216ecee3d47f8ff8ef7d3d47170c617 ./src/engine/runtime.rs ffa72b93502eece187a96adb4ec94aec6048d4a6dc7af66e7c7af0931318787f ./src/engine/schedules.rs e0d5347f73066404f9df9d211e059fd10cf054466ae35bdad6533a862b5a816a ./src/engine/targets.rs -cb274cf9247a3976b2ba321f903fde5fea0f6b96e5bbf0be28b893c17c553efd ./src/engine/temperature.rs +a9188b588b2617ce1547a45d46141f7e5f1bb0027a48675dd86b274f807d0875 ./src/engine/temperature.rs b453a8841494fd82d8fa4133dcd47dc59715a6a327375b5e13ffbaef431be700 ./src/engine/temporary_thermostat.rs -80fd12f3d0f9ea2836c82c0e495a10b832815e9662f071f3d78590c5dc091452 ./src/engine/tests.rs +acfb8569299dd4ee80ec49a5cbd7e14645580ba61936d4640c11c414626ffd4e ./src/engine/tests.rs c7e07dcf4419a4a64566620ba2006c5532a6d4576c90cb92855bca4c2deddcf6 ./src/engine/zone_actions.rs -05764fcbc4193266d980fa5d3194816d64037811be9af8023717a508789787e2 ./src/engine/zone_control.rs +6eed87586db85d18b64ab41c359ae9a7196091069f4e98c20b181b42863ba67c ./src/engine/zone_control.rs 4b271b6fc365b1078c01d6178eb563841b2ecaed5d8639196f58e1312d2236fe ./src/error.rs c6ff66da9ad08506f839ec56a869ead3abc824b5311e0ea9244517d11f7f4207 ./src/home_assistant.rs 6f9ef85cd53ba030b477bc3c186f0f7e762674124210eabcb0686f2806eaada1 ./src/influxdb.rs diff --git a/README.md b/README.md index 54d6665..255c953 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.8.15** +**Current release: 0.8.16** > [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples. diff --git a/docs/API.md b/docs/API.md index 0b764b6..f2c66cd 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.8.15**. +HTTP and WebSocket API for GREE Controller **0.8.16**. [← Main documentation](../README.md) @@ -212,7 +212,7 @@ Response: { "status": "ok", "name": "gree-controller", - "version": "0.8.15", + "version": "0.8.16", "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.8.15", + "version": "0.8.16", "uptime_seconds": 1234, "auth_required": false, "control_ready": true, diff --git a/home-assistant/custom_components/gree_controller/manifest.json b/home-assistant/custom_components/gree_controller/manifest.json index 42127a7..455b578 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.8.15", + "version": "0.8.16", "config_flow": true, "integration_type": "hub", "iot_class": "local_polling", diff --git a/src/engine/temperature.rs b/src/engine/temperature.rs index 400617a..61e0113 100644 --- a/src/engine/temperature.rs +++ b/src/engine/temperature.rs @@ -32,7 +32,7 @@ fn adjustment_allowed(zone: &Zone) -> bool { } fn external_room_sensor_cooling_assist(mode: &str, control_source: &str) -> f64 { - if mode == "cool" && matches!(control_source, "external" | "combined") { 0.5 } else { 0.0 } + if mode == "cool" && matches!(control_source, "external" | "combined") { 1.0 } else { 0.0 } } fn effective_sensor_stale_after_seconds(zone_value: u64, global_value: u64) -> u64 { diff --git a/src/engine/tests.rs b/src/engine/tests.rs index aea0a55..eb29848 100644 --- a/src/engine/tests.rs +++ b/src/engine/tests.rs @@ -588,13 +588,22 @@ mod tests { #[test] fn external_room_sensor_selects_lower_cooling_setpoint_only_when_used() { - assert_eq!(external_room_sensor_cooling_assist("cool", "external"), 0.5); - assert_eq!(external_room_sensor_cooling_assist("cool", "combined"), 0.5); + assert_eq!(external_room_sensor_cooling_assist("cool", "external"), 1.0); + assert_eq!(external_room_sensor_cooling_assist("cool", "combined"), 1.0); assert_eq!(external_room_sensor_cooling_assist("cool", "device_fallback"), 0.0); assert_eq!(external_room_sensor_cooling_assist("cool", "device_discrepancy_fallback"), 0.0); assert_eq!(external_room_sensor_cooling_assist("heat", "external"), 0.0); } + #[test] + fn external_room_sensor_cooling_keeps_full_step_below_half_degree_target() { + let assist = external_room_sensor_cooling_assist("cool", "external"); + assert_eq!(round_device_setpoint("cool", true, 23.0 - assist), 22.0); + assert_eq!(round_device_setpoint("cool", true, 23.5 - assist), 22.0); + assert_eq!(round_device_setpoint("cool", true, 24.0 - assist), 23.0); + assert_eq!(round_device_setpoint("cool", true, 24.5 - assist), 23.0); + } + #[test] fn smart_fan_uses_low_speed_when_zone_is_satisfied() { assert_eq!(smart_fan_speed("heat", 21.0, 21.0, None, false), 1); diff --git a/src/engine/zone_control.rs b/src/engine/zone_control.rs index d3dbc51..e5340f1 100644 --- a/src/engine/zone_control.rs +++ b/src/engine/zone_control.rs @@ -373,10 +373,10 @@ async fn control_zones(state: &AppState) -> Result<()> { // stop naturally when we move the target to the satisfied side of room temperature. let outdoor_assist = outdoor_assist_offset(effective_mode, outdoor_assist_temperature, temp, target); // When an independent room sensor is actually driving cooling, the indoor unit's - // own sensor can satisfy too early. Apply a half-degree pre-rounding bias: because - // GREE setpoints are sent as whole degrees, this selects the next lower whole-degree - // target (0.5-1.0 C below the room target). Do not stack it with outdoor assist and - // do not use it during device/fallback control. + // own sensor can satisfy too early. Apply a full-degree pre-rounding bias: because + // GREE setpoints are sent as whole degrees, this keeps the unit at least one full + // degree below the room target, including half-degree thermostat setpoints. Do not + // stack it with outdoor assist or use it during device/fallback control. let room_sensor_assist = external_room_sensor_cooling_assist(effective_mode, &zone.control_temperature_source); let demand_assist = outdoor_assist.max(room_sensor_assist); let active_target = match effective_mode {