first commit
This commit is contained in:
+112
@@ -0,0 +1,112 @@
|
||||
# API examples
|
||||
|
||||
`TOKEN` is optional when `GREE_CONTROLLER_APP_TOKEN` is empty.
|
||||
|
||||
```bash
|
||||
AUTH='Authorization: Bearer TOKEN'
|
||||
BASE='http://127.0.0.1:8787'
|
||||
```
|
||||
|
||||
|
||||
## Home Assistant access tokens
|
||||
|
||||
Create and revoke integration tokens from the controller Web UI under **Settings -> Home Assistant integration access**. The clear-text secret is returned only once and the SQLite database stores only its SHA-256 hash.
|
||||
|
||||
Administrator endpoints:
|
||||
|
||||
```text
|
||||
GET /api/access-tokens
|
||||
POST /api/access-tokens
|
||||
DELETE /api/access-tokens/{id}
|
||||
```
|
||||
|
||||
The Home Assistant custom integration uses a restricted API surface:
|
||||
|
||||
```text
|
||||
GET /api/integrations/home-assistant/devices
|
||||
POST /api/integrations/home-assistant/devices/{id}/command
|
||||
```
|
||||
|
||||
These two endpoints always require `Authorization: Bearer <generated-token>` (or the administrator `GREE_CONTROLLER_APP_TOKEN`). A generated HA token cannot update settings, run discovery, delete devices, manage tokens, or use the controller WebSocket.
|
||||
|
||||
## Discovery
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/api/discovery" -H "$AUTH" -H 'Content-Type: application/json' \
|
||||
-d '{"timeout_ms":3000,"broadcast":"255.255.255.255:7000"}'
|
||||
```
|
||||
|
||||
## Device command
|
||||
|
||||
```json
|
||||
{
|
||||
"power": true,
|
||||
"mode": "cool",
|
||||
"target_temperature": 22.0,
|
||||
"fan_speed": 3,
|
||||
"swing_vertical": true,
|
||||
"quiet": false,
|
||||
"turbo": false,
|
||||
"light": true
|
||||
}
|
||||
```
|
||||
|
||||
Supported modes: `auto`, `cool`, `dry`, `fan`, `heat`. Fan speed: `0..5`.
|
||||
|
||||
## Zone
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Living room",
|
||||
"device_id": "gree-aabbccddeeff",
|
||||
"enabled": true,
|
||||
"mode": "cool",
|
||||
"setpoint": 23.0,
|
||||
"hysteresis": 0.6,
|
||||
"min_on_seconds": 180,
|
||||
"min_off_seconds": 180,
|
||||
"sensor_source": "combined",
|
||||
"ha_entity_id": "sensor.living_room_temperature",
|
||||
"external_sensor_weight": 0.4,
|
||||
"max_sensor_difference": 3.0
|
||||
}
|
||||
```
|
||||
|
||||
`sensor_source` supports:
|
||||
|
||||
- `device` — GREE indoor sensor only,
|
||||
- `combined` — GREE + this zone's HA room sensor,
|
||||
- `home_assistant` — this zone's HA room sensor, with GREE fallback.
|
||||
|
||||
For `combined` and `home_assistant`, set a per-zone `ha_entity_id`. `external_sensor_weight` is `0.0..1.0`. If a combined sensor pair differs by more than `max_sensor_difference`, the controller falls back to GREE. The returned zone object includes `device_temperature`, `external_temperature`, `current_temperature`, and `control_temperature_source`.
|
||||
|
||||
## Schedule
|
||||
|
||||
Weekdays use ISO numbers: Monday `1`, Sunday `7`.
|
||||
|
||||
```json
|
||||
{
|
||||
"zone_id": "UUID",
|
||||
"name": "Night",
|
||||
"enabled": true,
|
||||
"weekdays": [1,2,3,4,5,6,7],
|
||||
"start_time": "22:00",
|
||||
"end_time": "06:00",
|
||||
"setpoint": 24.0
|
||||
}
|
||||
```
|
||||
|
||||
## WebSocket
|
||||
|
||||
Connect to `ws://HOST:8787/ws?token=TOKEN`. The first message uses event type `bootstrap`; later events include `device.updated`, `zone.updated`, `settings.updated` and `log.created`.
|
||||
|
||||
## Localization assets
|
||||
|
||||
Localization endpoints are public because the login dialog also needs translations. Language packs are embedded in the Rust binary at build time.
|
||||
|
||||
```bash
|
||||
curl "$BASE/lang/index.json"
|
||||
curl "$BASE/lang/en.json"
|
||||
```
|
||||
|
||||
`GET /lang/index.json` returns the automatically generated language catalog. `GET /lang/<code>.json` returns the corresponding language pack. Add a valid `lang/<code>.json` file and rebuild to expose a new language.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Home Assistant entity-ID migration
|
||||
|
||||
## Goal
|
||||
|
||||
Replace an existing GREE climate entity while preserving its `entity_id`.
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
Before: climate.klima_salon -> default GREE integration -> AC
|
||||
After: climate.klima_salon -> GREE Controller integration -> Rust controller -> AC
|
||||
```
|
||||
|
||||
Keeping the same entity ID allows existing dashboards, scripts, scenes and automations that reference the entity by ID to continue working.
|
||||
|
||||
## Why a takeover step is required
|
||||
|
||||
Home Assistant's entity registry reserves entity IDs. A second integration cannot create another active `climate.klima_salon` while the original entity still exists. The new integration therefore checks for conflicts and stops setup rather than allowing HA to generate a suffixed name such as `climate.klima_salon_2`.
|
||||
|
||||
## Generate the mapping
|
||||
|
||||
For one entity:
|
||||
|
||||
```bash
|
||||
./scripts/generate_ha_migration.py \
|
||||
--entity climate.klima_salon \
|
||||
--device gree-aabbccddeeff
|
||||
```
|
||||
|
||||
For several:
|
||||
|
||||
```bash
|
||||
./scripts/generate_ha_migration.py \
|
||||
--map climate.klima_salon=gree-aabbccddeeff \
|
||||
--map climate.klima_sypialnia=gree-112233445566
|
||||
```
|
||||
|
||||
Generated structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"entities": [
|
||||
{
|
||||
"entity_id": "climate.klima_salon",
|
||||
"device_id": "gree-aabbccddeeff"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Copy it to `/config/gree_controller_entities.json` on the Home Assistant host.
|
||||
|
||||
## Migration sequence
|
||||
|
||||
1. Add the physical AC to the standalone Rust application.
|
||||
2. Test power, mode and target temperature from its web UI.
|
||||
3. Generate the HA entity mapping.
|
||||
4. Copy the custom component to `/config/custom_components/gree_controller/`.
|
||||
5. Disable/remove the previous GREE integration in HA so it no longer controls or publishes the old climate entity.
|
||||
6. Remove any stale entity registry entry only after the old integration is unloaded.
|
||||
7. Add the **GREE Controller** integration and provide its URL/token.
|
||||
8. Confirm the exact old entity ID is present again.
|
||||
9. Test `climate.set_temperature`, HVAC modes and power from HA.
|
||||
10. Verify automations and dashboards that use the preserved entity ID.
|
||||
|
||||
The standalone controller remains available during the HA migration, so the AC can still be controlled from its own web UI if HA is restarting.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Localization
|
||||
|
||||
The web interface uses JSON language packs from `lang/`. Language files are discovered at Rust build time and embedded in the application binary.
|
||||
|
||||
English (`lang/en.json`) is required and is always the fallback language. Polish (`lang/pl.json`) is included by default.
|
||||
|
||||
## Add a language
|
||||
|
||||
1. Copy `lang/en.json` to a file named with the new language code, for example `lang/de.json`.
|
||||
2. Update the `meta` object.
|
||||
3. Translate values inside `translations`. Do not rename translation keys.
|
||||
4. Run `./scripts/dev.sh --check` or build the project again.
|
||||
5. Start the rebuilt binary. The new language appears automatically in the language selector.
|
||||
|
||||
Example structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"meta": {
|
||||
"code": "de",
|
||||
"name": "German",
|
||||
"native_name": "Deutsch",
|
||||
"locale": "de-DE"
|
||||
},
|
||||
"translations": {
|
||||
"controls.language": "Sprache",
|
||||
"controls.theme": "Darstellung"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The filename and `meta.code` must match (`de.json` -> `"code": "de"`). File names may contain ASCII letters, digits, `-` and `_` only.
|
||||
|
||||
## Fallback behavior
|
||||
|
||||
A language pack does not have to duplicate every English key while it is being developed. If a key is missing from the selected language, the UI uses the value from `en.json`. If a key is also missing from English, the translation key itself is shown, which makes incomplete strings visible during development.
|
||||
|
||||
## Build-time validation
|
||||
|
||||
`build.rs` checks that:
|
||||
|
||||
- at least one JSON language file exists,
|
||||
- `en.json` exists,
|
||||
- every language file contains valid JSON,
|
||||
- every file has `meta.code`, `meta.name`, `meta.native_name` and `meta.locale`,
|
||||
- `meta.code` matches the filename,
|
||||
- `translations` is a JSON object.
|
||||
|
||||
A malformed language pack fails the Rust build instead of producing a broken selector at runtime.
|
||||
|
||||
## Runtime endpoints
|
||||
|
||||
The embedded language catalog is available at:
|
||||
|
||||
```text
|
||||
GET /lang/index.json
|
||||
```
|
||||
|
||||
Individual embedded packs are available at:
|
||||
|
||||
```text
|
||||
GET /lang/en.json
|
||||
GET /lang/pl.json
|
||||
GET /lang/<code>.json
|
||||
```
|
||||
|
||||
These endpoints are intentionally public so that localization also works before API authentication is completed.
|
||||
|
||||
## Browser preference
|
||||
|
||||
The selected language code is stored for one year in the `gree_controller_language` cookie. If the stored language is no longer present in a later build, the UI falls back to English.
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
# LXC installation and update
|
||||
|
||||
This document describes the supported Debian/Ubuntu systemd deployment used for LXC testing.
|
||||
|
||||
## First installation
|
||||
|
||||
Unpack the source archive inside the container and run:
|
||||
|
||||
```bash
|
||||
cd gree-controller
|
||||
chmod +x scripts/*.sh
|
||||
sudo ./scripts/install.sh
|
||||
```
|
||||
|
||||
The installer:
|
||||
|
||||
1. installs required build packages when missing,
|
||||
2. installs stable Rust with rustup when Cargo is unavailable,
|
||||
3. runs `cargo test --all-targets`,
|
||||
4. builds `target/release/gree-controller`,
|
||||
5. creates the `gree-controller` system user/group,
|
||||
6. creates `/var/lib/gree-controller`, `/opt/gree-controller` and `/var/backups/gree-controller`,
|
||||
7. installs the systemd unit,
|
||||
8. creates `/etc/gree-controller.env` only when it does not already exist,
|
||||
9. enables/restarts the service,
|
||||
10. verifies `GET /api/health`.
|
||||
|
||||
Installed state is intentionally outside the extracted source directory:
|
||||
|
||||
```text
|
||||
/opt/gree-controller/gree-controller installed binary
|
||||
/etc/gree-controller.env persistent configuration/secrets
|
||||
/var/lib/gree-controller/gree-controller.db persistent SQLite database
|
||||
/var/backups/gree-controller/ update backups
|
||||
/etc/systemd/system/gree-controller.service systemd service
|
||||
```
|
||||
|
||||
The default installation starts in simulator mode. Change `GREE_CONTROLLER_SIMULATE=false` and `GREE_CONTROLLER_AUTO_SEED=false` in `/etc/gree-controller.env` when moving to physical units, then restart the service.
|
||||
|
||||
## Update
|
||||
|
||||
Unpack a newer source archive and run from that new directory:
|
||||
|
||||
```bash
|
||||
sudo ./scripts/update.sh
|
||||
```
|
||||
|
||||
The update workflow is designed to minimize downtime and preserve the database:
|
||||
|
||||
1. build dependencies/Rust are checked,
|
||||
2. tests run while the currently installed controller stays online,
|
||||
3. the new release binary is built while the old service stays online,
|
||||
4. the service is stopped,
|
||||
5. the installed binary, service unit, environment file and SQLite database are copied to `/var/backups/gree-controller/<UTC timestamp>/`,
|
||||
6. the new binary/unit is installed,
|
||||
7. systemd starts the new version,
|
||||
8. `/api/health` is checked,
|
||||
9. on failure the old binary, unit and database are restored automatically.
|
||||
|
||||
The updater does not overwrite `/etc/gree-controller.env` during a successful update.
|
||||
|
||||
Use `--skip-tests` only for deliberate fast testing:
|
||||
|
||||
```bash
|
||||
sudo ./scripts/update.sh --skip-tests
|
||||
```
|
||||
|
||||
## Service helper
|
||||
|
||||
```bash
|
||||
./scripts/service.sh status
|
||||
./scripts/service.sh health
|
||||
./scripts/service.sh logs
|
||||
sudo ./scripts/service.sh restart
|
||||
sudo ./scripts/service.sh stop
|
||||
sudo ./scripts/service.sh start
|
||||
```
|
||||
|
||||
Equivalent native commands remain available:
|
||||
|
||||
```bash
|
||||
systemctl status gree-controller
|
||||
journalctl -u gree-controller -f
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit:
|
||||
|
||||
```text
|
||||
/etc/gree-controller.env
|
||||
```
|
||||
|
||||
and restart:
|
||||
|
||||
```bash
|
||||
sudo ./scripts/service.sh restart
|
||||
```
|
||||
|
||||
Do not store controller or Home Assistant secrets in the source tree.
|
||||
|
||||
## Database and SQL layout
|
||||
|
||||
Runtime persistence uses SQLite. All schema definitions and SQL statements in the Rust application are centralized in:
|
||||
|
||||
```text
|
||||
src/queries.rs
|
||||
```
|
||||
|
||||
`src/db.rs` contains connection/transaction logic and maps database rows to Rust domain models, but it does not embed SQL statements.
|
||||
@@ -0,0 +1,162 @@
|
||||
# GREE Controller project specification
|
||||
|
||||
## Product goal
|
||||
|
||||
Build an autonomous local GREE HVAC controller running primarily as a Rust service in a dedicated Linux/LXC environment. Home Assistant is optional: it can provide external sensor data and can consume controller devices through a thin custom integration, but it must not contain the core GREE protocol or heating logic.
|
||||
|
||||
## Architecture
|
||||
|
||||
```text
|
||||
Home Assistant / Web UI / REST clients
|
||||
|
|
||||
REST + WebSocket
|
||||
|
|
||||
GREE Controller (Rust)
|
||||
+--------------------------------+
|
||||
| Device/state manager |
|
||||
| GREE protocol UDP/AES |
|
||||
| Zone/heating engine |
|
||||
| Scheduler |
|
||||
| Automation engine |
|
||||
| SQLite |
|
||||
| Mobile-first web UI |
|
||||
+--------------------------------+
|
||||
|
|
||||
UDP/7000
|
||||
|
|
||||
GREE AC
|
||||
```
|
||||
|
||||
## Independence requirements
|
||||
|
||||
- GREE control must work when Home Assistant is stopped.
|
||||
- Local schedules and automations must continue without HA.
|
||||
- HA sensor failures must never remove basic access to the AC.
|
||||
- Device state is persisted by the controller and can be restored to clients after reconnect/restart.
|
||||
|
||||
## GREE protocol layer
|
||||
|
||||
The protocol implementation is isolated from application logic and covers:
|
||||
|
||||
- discovery,
|
||||
- packet encoding/decoding,
|
||||
- AES-128-ECB support,
|
||||
- AES-128-GCM envelope support,
|
||||
- bind/key acquisition,
|
||||
- status polling,
|
||||
- command transport,
|
||||
- reconnect/offline handling.
|
||||
|
||||
The rest of the application works with generic device state and commands rather than raw protocol packets.
|
||||
|
||||
## Device model
|
||||
|
||||
Each device stores:
|
||||
|
||||
- stable controller ID,
|
||||
- MAC/CID,
|
||||
- name,
|
||||
- IP/port,
|
||||
- protocol version,
|
||||
- model/firmware when available,
|
||||
- encryption key when bound,
|
||||
- enabled/simulated flags,
|
||||
- power and HVAC mode,
|
||||
- target/current/outdoor temperature,
|
||||
- fan, swing, quiet, turbo, light,
|
||||
- online/last-seen/error state.
|
||||
|
||||
## Temperature zones
|
||||
|
||||
A zone connects an AC to a temperature-control policy. It contains:
|
||||
|
||||
- target setpoint,
|
||||
- heat/cool mode,
|
||||
- hysteresis,
|
||||
- minimum ON time,
|
||||
- minimum OFF time,
|
||||
- temperature source,
|
||||
- optional Home Assistant sensor entity.
|
||||
|
||||
Each zone can assign its own Home Assistant room sensor. Combined mode fuses the GREE and room measurements with a configurable weight (40% room sensor by default), while GREE remains the primary input. If HA is unavailable or the two sensors differ beyond the configured limit, control falls back to the GREE sensor.
|
||||
|
||||
## Scheduler and automations
|
||||
|
||||
Schedules are weekly time windows and may cross midnight. Automations currently support time or temperature triggers and send device commands with cooldown protection.
|
||||
|
||||
The automation engine runs in Rust and does not require Home Assistant YAML automation logic.
|
||||
|
||||
## API
|
||||
|
||||
The controller exposes REST for configuration/commands and WebSocket for live state updates. The HA integration and web UI use the same controller API.
|
||||
|
||||
## Home Assistant direction 1: external sensor input
|
||||
|
||||
The Rust application can read a specifically configured HA entity using a Long-Lived Access Token. This input is optional and is not a prerequisite for GREE control.
|
||||
|
||||
## Home Assistant direction 2: native climate entities
|
||||
|
||||
A custom integration creates HA climate entities and translates HA service calls to controller API commands. It does not communicate with GREE directly.
|
||||
|
||||
For migrations from the default GREE integration, a mapping file can request an existing entity ID such as `climate.klima_salon`. The previous integration must release that ID before takeover.
|
||||
|
||||
## Web UI
|
||||
|
||||
Primary use is from a phone. Requirements:
|
||||
|
||||
- responsive mobile-first layout,
|
||||
- touch-friendly controls,
|
||||
- live state updates,
|
||||
- device, zone, schedule, automation, history and diagnostics screens,
|
||||
- JSON-based language packs from `lang/*.json`, with English as the required default/fallback and Polish included,
|
||||
- automatic language discovery at build time, so a new valid `<code>.json` file adds a language without JavaScript changes,
|
||||
- language stored in the `gree_controller_language` cookie,
|
||||
- system/light/dark appearance modes,
|
||||
- appearance stored in a cookie,
|
||||
- flat visual design without decorative shadows,
|
||||
- no letter-logo badge next to the application name.
|
||||
|
||||
## Storage
|
||||
|
||||
SQLite stores configuration, state, readings and events. PostgreSQL or other external database services are not required for the single-node deployment target.
|
||||
|
||||
## Deployment
|
||||
|
||||
Primary target:
|
||||
|
||||
```text
|
||||
LXC / Debian or Ubuntu
|
||||
/opt/gree-controller/gree-controller
|
||||
/etc/gree-controller.env
|
||||
/var/lib/gree-controller/gree-controller.db
|
||||
systemd: gree-controller.service
|
||||
```
|
||||
|
||||
A development script prepares dependencies/build/runtime configuration, and a separate installer creates the systemd deployment.
|
||||
|
||||
## Security
|
||||
|
||||
- optional administrator Bearer token for the controller Web/API,
|
||||
- separately generated, revocable Home Assistant client tokens stored only as SHA-256 hashes,
|
||||
- Home Assistant client tokens are restricted to device read/control endpoints,
|
||||
- secrets kept outside source control,
|
||||
- no direct public Internet exposure,
|
||||
- TLS delegated to a trusted reverse proxy/VPN when required,
|
||||
- local device keys and HA token protected in environment/database files.
|
||||
|
||||
|
||||
## Per-zone room sensors
|
||||
|
||||
A zone represents one room and normally maps one GREE indoor unit to one optional external room-temperature sensor. External sensors are not global. For example, Living Room can use `sensor.living_room_temperature` while Bedroom independently uses `sensor.bedroom_temperature`.
|
||||
|
||||
The zone controller supports `device`, `combined`, and `home_assistant` temperature strategies. Combined mode uses a configurable external-sensor weight (40% by default), validates the difference between sensors, and falls back to the GREE sensor when the external source is missing or outside the configured discrepancy limit. The local GREE measurement therefore remains available even when Home Assistant is offline.
|
||||
|
||||
|
||||
## Operational packaging conventions
|
||||
|
||||
- All operator-facing shell/Python utilities live under `scripts/`.
|
||||
- `scripts/install.sh` performs the first systemd/LXC installation.
|
||||
- `scripts/update.sh` performs an in-place update with a stopped SQLite backup, health check and automatic rollback.
|
||||
- `scripts/service.sh` provides common systemd operations.
|
||||
- Cargo's `build.rs` stays at the package root because Cargo requires that location.
|
||||
- Every SQLite statement and schema definition is centralized in `src/queries.rs`; database/domain code must not embed SQL strings elsewhere.
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user