diff --git a/BUILD_REPORT.md b/BUILD_REPORT.md index 0916f4b..ca07ab3 100644 --- a/BUILD_REPORT.md +++ b/BUILD_REPORT.md @@ -1,3 +1,22 @@ +# Build report — v0.3.8 + +## Fix in this release + +- Fixed multi-NIC LXC failures where `getifaddrs()` returned `EAFNOSUPPORT` (`os error 97`). +- The systemd sandbox now allows `AF_NETLINK`, which Linux `getifaddrs()` uses to enumerate interface addresses. +- Existing hardening remains enabled (`NoNewPrivileges`, `ProtectSystem`, restricted address families). +- With `GREE_CONTROLLER_GREE_INTERFACE=eth1`, GREE UDP sockets can now resolve and bind the IPv4 address assigned to `eth1`. + +## Validation + +- systemd unit includes `AF_NETLINK`. +- shell scripts pass `bash -n`. +- JSON language files parse successfully. +- all SQL remains centralized in `src/queries.rs`. +- Rust compilation is not available in the packaging environment; `scripts/update.sh` runs Cargo tests/build on the target LXC before installation. + +--- + # GREE Controller v0.3.6 - build and validation report ## Scope @@ -43,3 +62,10 @@ journalctl -u gree-controller -f ``` Expected request logs should show a concrete local address such as `10.87.65.27:` rather than `0.0.0.0:`. + +## v0.3.8 legacy V1 bind compatibility + +- Canonicalize the on-wire GREE device identifier to lowercase hexadecimal for `tcid` and inner `mac` fields. +- This specifically targets legacy V1 modules such as the `502cc6...` family which can answer discovery yet silently ignore bind packets when the identifier casing differs from the value returned by discovery. +- Database IDs, friendly names and stored MAC display values are unchanged. +- Existing V2/GCM crypto and the `eth1`/AF_NETLINK networking fixes are unchanged. diff --git a/Cargo.lock b/Cargo.lock index e75ee59..6435f68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -633,7 +633,7 @@ dependencies = [ [[package]] name = "gree-controller" -version = "0.3.6" +version = "0.3.8" dependencies = [ "aes", "aes-gcm", diff --git a/Cargo.toml b/Cargo.toml index 93e1d15..554fdca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gree-controller" -version = "0.3.6" +version = "0.3.8" 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 a83f605..a2ce75b 100644 --- a/FILE_MANIFEST.sha256 +++ b/FILE_MANIFEST.sha256 @@ -1,24 +1,31 @@ 6b10bf7903a32a6cdd902d990911105589690deafe7da12e5b3b9c994c1fd4f6 ./.env.example 2fe1cf4e544fead5ae58436145a5b45e7a5a105143dfd815d28e307c94d5d19b ./.gitignore -d75023129d63417a5af399567944d99a65c4570e1cef671ba2f68fdd734fd8d1 ./BUILD_REPORT.md -baad3cddff5a160ac1e5f5200838c83bd1edc2a3d4dec09b95b8858a1a4bfeb7 ./Cargo.toml +1e1867ebd5cd1633d511a4a65853da9a2ef1c018dc4e66d823e20ae032e0d1a7 ./BUILD_REPORT.md +6b2eacdaf0a8f285fd832b39617e849e992fd3f6ab094f13da9c453c0c99b79d ./Cargo.toml 19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE -349344b41f5bb6680079c5ed95f6294c9c9df6a73bdbda452d351033abf4768a ./README.md +3c552aab65ddba02d79835f2cec13c9cde5328774503c53e8461fa54bce9cc41 ./README.md a4fa9bfee9735ed8ed95ea31456e0cce503d82502ae3f550108ffca51b0f0c3d ./build.rs 37453ecce2c7290ecae5ada4249cfd36bc70fb50c479aa680b74e4f1bc707251 ./docs/API.md 234dd200e380a13ecd3e61b4ea455f6f08d64ce89382077dee80684acadb9703 ./docs/HOME_ASSISTANT_MIGRATION.md 7a88d6e76fda21e5d34ab351e26bc10dc1f8f7b3055505aefad1df7c56d65ae4 ./docs/LOCALIZATION.md -456b65737d3b4579e40edba2655c0cb1a63620d8a7bfa0f8844acf81b21fc374 ./docs/LXC.md +3d754c7120a086654f2666cac510b10a76529aa849c012a85427b0472a3ac261 ./docs/LXC.md a728ddc324613671932bd83522155b09c7503d4fbcd0d04d8e217676ea9e10e4 ./docs/PROJECT_SPEC.md 33214270b96ac4c64e3db41c11e54158792b4a87ee5668c651bad571766b591a ./home-assistant/README.md f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./home-assistant/custom_components/gree_controller/__init__.py +9151cd32bfcea786eb8a9af9ca11af775abdc6bf23b55d6a291b76433473eb2a ./home-assistant/custom_components/gree_controller/__pycache__/__init__.cpython-313.pyc +8c3753163d45895da5856525fe3e988a38c73bce4457b1358200453cc9a9ac2c ./home-assistant/custom_components/gree_controller/__pycache__/api.cpython-313.pyc +e0d8043f39885375cfecf8e82153a257f2166e5319e975dbbb648482ec77474e ./home-assistant/custom_components/gree_controller/__pycache__/climate.cpython-313.pyc +32097fe487c1c5fb428230d35a39f62c2bc17285036604315ec80545e8c185a5 ./home-assistant/custom_components/gree_controller/__pycache__/config_flow.cpython-313.pyc +05791fb1cb4345726506a20ef5b36d355e8235b8ccd46a81a2b8ce5b42e4c3f7 ./home-assistant/custom_components/gree_controller/__pycache__/const.cpython-313.pyc +bf1a90d03325dd37a86a05b586faf387bfd5a02732fd2c0e4d998369a13ba70f ./home-assistant/custom_components/gree_controller/__pycache__/coordinator.cpython-313.pyc +cc4f2ab07b1a641bdc36f3738280fa643baac617d753863c288e5788b1c3d455 ./home-assistant/custom_components/gree_controller/__pycache__/entity_map.cpython-313.pyc e0cf725c9f84be51cdbd83a5ab12b2b8288f90b36623cb4671cefcbf04378504 ./home-assistant/custom_components/gree_controller/api.py fde31b8e020fd36be2d9d9b1e554254f5da8a1cc593240ebb8d78820154dd790 ./home-assistant/custom_components/gree_controller/climate.py 5e4aef2143e81bedb5a15dd4be5c71b64a3ab448ec6d33a20851edd098e5f529 ./home-assistant/custom_components/gree_controller/config_flow.py e1821b74859bc40773a6ee39e6ccc9650980b62af50d6426b46cdb3e3a90d200 ./home-assistant/custom_components/gree_controller/const.py 2d27d7cb67c53e819b99a0302cd0bd339c27e3e2dfc85e07d30c31f505f740b5 ./home-assistant/custom_components/gree_controller/coordinator.py 5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./home-assistant/custom_components/gree_controller/entity_map.py -23fc6d9d7136492db8dc046e3f54c65070e5f65e1c6303ecab1bac5ef686661d ./home-assistant/custom_components/gree_controller/manifest.json +4e0f31984b94f3da7fd98e3804b591bbe1c79d2c05b04b7c4fb484a4caec587e ./home-assistant/custom_components/gree_controller/manifest.json 6bddb7b4620021ecd2099a86a77ef5c7f2c2dcd3d07d5db4e7b4c4ce6d3e8c03 ./home-assistant/custom_components/gree_controller/translations/en.json 13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./home-assistant/custom_components/gree_controller/translations/pl.json 4513070521d3dda0efb0d974a86ba674494cfb2b66fe9e5cac5b1b0430dede97 ./home-assistant/generated/gree_controller_entities.example.json @@ -26,6 +33,7 @@ e1821b74859bc40773a6ee39e6ccc9650980b62af50d6426b46cdb3e3a90d200 ./home-assista c604815c850574c573315306f368a8379ab87f5e2a99aca32be3131b05070afa ./lang/en.json 0b2d174258610440d5d4c2d6c5bec5ecc2528f41946bf7d59cc2206aab50ce58 ./lang/pl.json 09caaa48f9979f209b96e6d0feebbecda053b997c470a5d80abb80b7fb06a484 ./scripts/README.md +896caae04468f743d85d011638a0683253d8271c0db8365aea9bcdc646795cf1 ./scripts/__pycache__/generate_ha_migration.cpython-313.pyc be8c4bf17723d5e2e23e774a679ca75b02ab49825a93201237bf58cdd4974136 ./scripts/common.sh 6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh 054e6862857fd1d02dabedd977d4175c2011f451aba51b5d4fc3a3363e66e80f ./scripts/dev.sh @@ -45,11 +53,11 @@ f85cb4ba6435843431d93779aa0653fd24ec8987e06ef36efde096cbea321d7a ./src/home_ass 27eca87a9078aad7a7a4ef1fc39ce7ddaffcf05466acb995d571e0a35dfe898c ./src/main.rs a5923f8fb72ebac68c13e4767dd35012041e2783167969521e757bf502fafabd ./src/models.rs 7fc31fbf8841a073a1544b8c7a6390f1a15b56087486ca0596a8418340fa232a ./src/protocol/crypto.rs -53e6fdb575da4790cdf88013073b973461fb9ace362fbe51dda95c0eaadf2365 ./src/protocol/gree.rs +b66ae7c0186b6cc3939d922b0a83f763fd670662c170c97cebca8235d99f663c ./src/protocol/gree.rs a910bd9432a393740c0f6fab52bfcb551f0ea756718d66d290fd2610767cf07c ./src/protocol/mod.rs c80164631bba476db29469fa43b28d73f68412c06bafabe2143ab8d4cd330a52 ./src/queries.rs 521070a4c63bec73372cf4873f20c5fad23c37db4df7199bcc5bf6f6e566b3f6 ./src/state.rs -52aea29e17e7ce45ea10f7783be7278ecefe976141c5823360f10f0ea2bbcf33 ./systemd/gree-controller.service +b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service 9d05fe7219a4e9806239792169417d577c56a38bea4561e4064ae4cf41418257 ./web/app.js b6bba1e1e7127d06c885a9f8fffbf0becde339e0f2464da8b4725f11ddabe433 ./web/favicon.svg 45f0ddc583cf0ee3197dda5c72209f80389291e33c72a29e7555ebaa329819de ./web/index.html diff --git a/README.md b/README.md index 366a1b0..4eb2e66 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Standalone local GREE air-conditioner controller written in Rust. It runs on a regular Linux host or an LXC container and provides a mobile-first web interface without depending on the vendor cloud. -Current version: **0.3.6**. +Current version: **0.3.8**. ## Highlights @@ -35,7 +35,7 @@ See [`BUILD_REPORT.md`](BUILD_REPORT.md) for package validation details and [`do On Debian, Ubuntu or an LXC container: ```bash -unzip gree-controller-v0.3.6.zip +unzip gree-controller-v0.3.8.zip cd gree-controller chmod +x scripts/*.sh ./scripts/dev.sh @@ -92,7 +92,7 @@ The selected appearance is stored in the `gree_controller_theme` cookie. The int 5. New devices immediately open a naming step so you can enter room-friendly names such as **Salon** or **Sypialnia**. They can also be renamed later from **Devices -> Rename**. Re-discovery preserves your custom name. 6. If LXC/VLAN broadcast does not pass, add the unit manually with IP and MAC/CID. -Version 0.3.6 additionally uses forgiving GREE Base64 decoding for non-canonical GCM tags seen on real Wi-Fi modules, automatically selects the directly connected local IPv4 interface for unicast GREE traffic, and refreshes the short bind window with a subnet broadcast immediately before bind. A single lost UDP response no longer marks a device offline; three consecutive communication failures are required. +Version 0.3.7 additionally uses forgiving GREE Base64 decoding for non-canonical GCM tags seen on real Wi-Fi modules, automatically selects the directly connected local IPv4 interface for unicast GREE traffic, and refreshes the short bind window with a subnet broadcast immediately before bind. A single lost UDP response no longer marks a device offline; three consecutive communication failures are required. ## LXC/systemd installation and updates @@ -318,3 +318,11 @@ GREE_CONTROLLER_AUTO_SEED=false `GREE_CONTROLLER_GREE_INTERFACE` is resolved to the interface current IPv4 address whenever a UDP socket is created, so DHCP address changes do not require hard-coding the source IP. `GREE_CONTROLLER_DISCOVERY_BROADCAST=auto` derives the subnet broadcast from the selected interface and overrides an older runtime value stored in SQLite. Run `sudo ./scripts/network-debug.sh eth1 10.87.65.127` to verify addressing and routing. + +### v0.3.7 LXC networking fix + +The systemd service explicitly allows `AF_NETLINK`. This is required by Linux `getifaddrs()` when resolving `GREE_CONTROLLER_GREE_INTERFACE` on multi-NIC LXC installations. Without it, older releases could fail with `Address family not supported by protocol (os error 97)` even though the configured interface was correct. + +### v0.3.8 legacy V1 device compatibility + +GREE protocol identifiers are now emitted as canonical lowercase hexadecimal in `tcid` and `pack.mac`. This improves compatibility with older V1 Wi-Fi modules (including devices in the `502cc6...` family) that can answer discovery but silently ignore bind/status packets when MAC casing differs from their discovery identity. Stored device IDs and friendly names are not changed. diff --git a/docs/LXC.md b/docs/LXC.md index 4f345ae..c723fcd 100644 --- a/docs/LXC.md +++ b/docs/LXC.md @@ -118,7 +118,7 @@ When the LXC has a management interface and a dedicated GREE network, configure GREE_CONTROLLER_GREE_INTERFACE=eth1 GREE_CONTROLLER_DISCOVERY_BROADCAST=auto -This explicit interface configuration is recommended for predictable LXC deployments. Version 0.3.6 also automatically selects the directly connected local IPv4 address for a GREE device when the interface variable is omitted; for example, a target in `10.87.65.0/25` selects the local address on that subnet. +This explicit interface configuration is recommended for predictable LXC deployments. Version 0.3.7 also automatically selects the directly connected local IPv4 address for a GREE device when the interface variable is omitted; for example, a target in `10.87.65.0/25` selects the local address on that subnet. GREE_CONTROLLER_SIMULATE=false GREE_CONTROLLER_AUTO_SEED=false ``` @@ -141,6 +141,30 @@ Use `scripts/network-debug.sh` for routing diagnostics. ## Mixed GREE model generations -Version 0.3.6 can discover both AES-ECB and AES-GCM modules. In the Web UI choose **Discover -> Auto (V1 + V2)** and use 3-5 scan passes. If a family is still missing, repeat with V1-only and V2-only to see which protocol its Wi-Fi module answers with. +Version 0.3.7 can discover both AES-ECB and AES-GCM modules. In the Web UI choose **Discover -> Auto (V1 + V2)** and use 3-5 scan passes. If a family is still missing, repeat with V1-only and V2-only to see which protocol its Wi-Fi module answers with. A single command/status timeout no longer immediately flips a device offline; offline requires three consecutive communication failures. + +## Multi-NIC LXC and AF_NETLINK + +When `GREE_CONTROLLER_GREE_INTERFACE` is set, the controller enumerates IPv4 addresses with Linux `getifaddrs()`. On Linux this requires a Netlink socket. The systemd sandbox therefore allows `AF_NETLINK` in addition to `AF_UNIX`, `AF_INET`, and `AF_INET6`. + +If an older unit reports: + +```text +getifaddrs failed +Address family not supported by protocol (os error 97) +``` + +update the systemd unit or add `AF_NETLINK` to `RestrictAddressFamilies`, then run: + +```bash +systemctl daemon-reload +systemctl restart gree-controller +``` + +For a dedicated GREE NIC such as `eth1`, the expected startup/bind log should identify the IPv4 address of that interface instead of `0.0.0.0`. + +### Legacy V1 devices discovered but not binding + +Version 0.3.8 sends the GREE protocol `tcid` and inner `mac` identifiers in canonical lowercase hexadecimal. If an older `502cc6...` device is discovered on UDP/7000 but stays offline after bind timeouts, update to v0.3.8 before changing routing or firewall settings. With debug logging, `Sending GREE request` should show `wire_mac=502cc6...` in lowercase. diff --git a/home-assistant/custom_components/gree_controller/__pycache__/__init__.cpython-313.pyc b/home-assistant/custom_components/gree_controller/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..679355c Binary files /dev/null and b/home-assistant/custom_components/gree_controller/__pycache__/__init__.cpython-313.pyc differ diff --git a/home-assistant/custom_components/gree_controller/__pycache__/api.cpython-313.pyc b/home-assistant/custom_components/gree_controller/__pycache__/api.cpython-313.pyc new file mode 100644 index 0000000..f90a2dd Binary files /dev/null and b/home-assistant/custom_components/gree_controller/__pycache__/api.cpython-313.pyc differ diff --git a/home-assistant/custom_components/gree_controller/__pycache__/climate.cpython-313.pyc b/home-assistant/custom_components/gree_controller/__pycache__/climate.cpython-313.pyc new file mode 100644 index 0000000..cff36f2 Binary files /dev/null and b/home-assistant/custom_components/gree_controller/__pycache__/climate.cpython-313.pyc differ diff --git a/home-assistant/custom_components/gree_controller/__pycache__/config_flow.cpython-313.pyc b/home-assistant/custom_components/gree_controller/__pycache__/config_flow.cpython-313.pyc new file mode 100644 index 0000000..08ffd28 Binary files /dev/null and b/home-assistant/custom_components/gree_controller/__pycache__/config_flow.cpython-313.pyc differ diff --git a/home-assistant/custom_components/gree_controller/__pycache__/const.cpython-313.pyc b/home-assistant/custom_components/gree_controller/__pycache__/const.cpython-313.pyc new file mode 100644 index 0000000..4c64ebf Binary files /dev/null and b/home-assistant/custom_components/gree_controller/__pycache__/const.cpython-313.pyc differ diff --git a/home-assistant/custom_components/gree_controller/__pycache__/coordinator.cpython-313.pyc b/home-assistant/custom_components/gree_controller/__pycache__/coordinator.cpython-313.pyc new file mode 100644 index 0000000..a4267f5 Binary files /dev/null and b/home-assistant/custom_components/gree_controller/__pycache__/coordinator.cpython-313.pyc differ diff --git a/home-assistant/custom_components/gree_controller/__pycache__/entity_map.cpython-313.pyc b/home-assistant/custom_components/gree_controller/__pycache__/entity_map.cpython-313.pyc new file mode 100644 index 0000000..66d7f54 Binary files /dev/null and b/home-assistant/custom_components/gree_controller/__pycache__/entity_map.cpython-313.pyc differ diff --git a/home-assistant/custom_components/gree_controller/manifest.json b/home-assistant/custom_components/gree_controller/manifest.json index 9cdc2d4..5772359 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.3.6", + "version": "0.3.8", "config_flow": true, "integration_type": "hub", "iot_class": "local_polling", diff --git a/scripts/__pycache__/generate_ha_migration.cpython-313.pyc b/scripts/__pycache__/generate_ha_migration.cpython-313.pyc new file mode 100644 index 0000000..3c4dc67 Binary files /dev/null and b/scripts/__pycache__/generate_ha_migration.cpython-313.pyc differ diff --git a/src/protocol/gree.rs b/src/protocol/gree.rs index a9264ef..709f5f6 100644 --- a/src/protocol/gree.rs +++ b/src/protocol/gree.rs @@ -244,6 +244,15 @@ impl GreeClient { bail!("unable to bind device ({})", errors.join("; ")) } + /// GREE Wi-Fi modules use the 12-hex device id as a protocol identifier. + /// Older V1 modules (notably 502cc6...) can silently ignore bind/status + /// packets when tcid/mac casing differs from the lowercase value returned + /// by discovery. Keep the database/display representation independent from + /// the on-wire representation and always send canonical lowercase hex. + fn wire_mac(device: &Device) -> String { + device.mac.replace([':', '-'], "").to_ascii_lowercase() + } + async fn bind_attempt(&self, device: &Device, version: u8) -> Result { let target = self.device_target(device)?; let target_hint = match target { SocketAddr::V4(addr) => Some(*addr.ip()), SocketAddr::V6(_) => None }; @@ -271,7 +280,8 @@ impl GreeClient { } } - let inner = json!({"mac": device.mac, "t": "bind", "uid": 0}); + let wire_mac = Self::wire_mac(device); + let inner = json!({"mac": wire_mac, "t": "bind", "uid": 0}); let generic_key = if version == 2 { GENERIC_GREE_V2_KEY } else { GENERIC_GREE_V1_KEY }; let response = self.request_on_socket(device, &inner, generic_key, true, version, &socket).await?; let kind = response.get("t").and_then(Value::as_str).unwrap_or_default(); @@ -309,7 +319,7 @@ impl GreeClient { } async fn status_request(&self, device: &Device, key: &str, cols: &[&str]) -> Result { - let inner = json!({"cols": cols, "mac": device.mac, "t": "status"}); + let inner = json!({"cols": cols, "mac": Self::wire_mac(device), "t": "status"}); self.request(device, &inner, key, false, device.protocol_version).await } @@ -382,11 +392,12 @@ impl GreeClient { let target = self.device_target(device)?; let version = if protocol_version == 2 { 2 } else { 1 }; let inner_bytes = serde_json::to_vec(inner)?; + let wire_mac = Self::wire_mac(device); let mut outer = json!({ "cid": "app", "i": if binding { 1 } else { 0 }, "t": "pack", - "tcid": device.mac, + "tcid": wire_mac, "uid": 0 }); if version == 2 { @@ -397,7 +408,7 @@ impl GreeClient { outer["pack"] = json!(encrypt_v1(key, &inner_bytes)?); } let payload = serde_json::to_vec(&outer)?; - tracing::debug!(target=%target, local=%socket.local_addr()?, protocol=version, interface=%self.interface.as_deref().unwrap_or("auto"), binding, "Sending GREE request"); + tracing::debug!(target=%target, local=%socket.local_addr()?, protocol=version, wire_mac=%wire_mac, interface=%self.interface.as_deref().unwrap_or("auto"), binding, "Sending GREE request"); socket.send_to(&payload, target).await?; let deadline = Instant::now() + Duration::from_secs(4); diff --git a/systemd/gree-controller.service b/systemd/gree-controller.service index 6d85089..3949391 100644 --- a/systemd/gree-controller.service +++ b/systemd/gree-controller.service @@ -18,7 +18,7 @@ PrivateTmp=true ProtectSystem=strict ProtectHome=true ReadWritePaths=/var/lib/gree-controller -RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK LockPersonality=true MemoryDenyWriteExecute=true