1653 lines
55 KiB
Markdown
1653 lines
55 KiB
Markdown
# GREE Controller API reference
|
||
|
||
HTTP and WebSocket API for GREE Controller **0.13.7**.
|
||
|
||
[← Main documentation](../README.md)
|
||
|
||
## Base URL and content type
|
||
|
||
Default local address:
|
||
|
||
```text
|
||
http://127.0.0.1:8787
|
||
```
|
||
|
||
JSON requests use:
|
||
|
||
```text
|
||
Content-Type: application/json
|
||
```
|
||
|
||
If `GREE_CONTROLLER_BASE_PATH=/gree` is configured, every HTTP and WebSocket path below is prefixed with `/gree`.
|
||
|
||
Interactive and machine-readable API documentation is served by the application itself:
|
||
|
||
```text
|
||
GET /api-docs Swagger UI
|
||
GET /api-docs/openapi.json OpenAPI 3.1 document
|
||
```
|
||
|
||
The OpenAPI document contains endpoint descriptions, authentication schemes, parameters,
|
||
request/response models, examples and common error responses. Its `servers` entry follows the
|
||
configured `GREE_CONTROLLER_BASE_PATH`, so requests sent from Swagger UI target the current
|
||
controller instance correctly.
|
||
|
||
## Authentication
|
||
|
||
There are three access levels.
|
||
|
||
### Public
|
||
|
||
No token is required for:
|
||
|
||
```text
|
||
GET /api/health
|
||
GET /api-docs
|
||
GET /api-docs/openapi.json
|
||
GET /
|
||
GET /index.html
|
||
GET /app.js
|
||
GET /theme-init.js
|
||
GET /styles.css
|
||
GET /manifest.webmanifest
|
||
GET /sw.js
|
||
GET /favicon.svg
|
||
GET /lang/index.json
|
||
GET /lang/{file}
|
||
```
|
||
|
||
### Administrator API
|
||
|
||
All normal `/api/*` routes are administrator routes. If `GREE_CONTROLLER_APP_TOKEN` is empty, the controller intentionally operates in trusted-LAN mode and these routes do not require authentication.
|
||
|
||
When an app token is configured, send either:
|
||
|
||
```text
|
||
Authorization: Bearer APP_TOKEN
|
||
```
|
||
|
||
or:
|
||
|
||
```text
|
||
x-api-token: APP_TOKEN
|
||
```
|
||
|
||
Example:
|
||
|
||
```bash
|
||
BASE='http://127.0.0.1:8787'
|
||
TOKEN='replace-me'
|
||
curl -H "Authorization: Bearer $TOKEN" "$BASE/api/bootstrap"
|
||
```
|
||
|
||
### Restricted Home Assistant API
|
||
|
||
Generated Home Assistant access tokens always authenticate only the restricted integration surface under `/api/integrations/home-assistant/*`. The administrator app token is also accepted there.
|
||
|
||
Generated token secrets are returned only at creation time. SQLite stores their SHA-256 hash and a display prefix.
|
||
|
||
## Errors and HTTP status codes
|
||
|
||
API errors are JSON:
|
||
|
||
```json
|
||
{
|
||
"error": "message"
|
||
}
|
||
```
|
||
|
||
Common statuses:
|
||
|
||
| Status | Meaning |
|
||
| --- | --- |
|
||
| `200 OK` | Successful read/update/action. |
|
||
| `201 Created` | Resource created. |
|
||
| `204 No Content` | Successful delete/revoke. |
|
||
| `400 Bad Request` | Validation error or unsafe/invalid operation. |
|
||
| `401 Unauthorized` | Missing/incorrect token. |
|
||
| `404 Not Found` | Resource ID does not exist. |
|
||
| `409 Conflict` | Revision/concurrency conflict. |
|
||
| `502 Bad Gateway` | GREE/HA/integration communication failure. |
|
||
| `500 Internal Server Error` | Unexpected server/storage error. |
|
||
|
||
## Endpoint index
|
||
|
||
### Public and system
|
||
|
||
| Method | Endpoint | Description |
|
||
| --- | --- | --- |
|
||
| GET | `/api/health` | Lightweight process/control-engine health. |
|
||
| GET | `/api/bootstrap` | Complete initial application snapshot. |
|
||
| GET | `/api/system/info` | Runtime/system diagnostic information. |
|
||
| GET | `/ws` | Live WebSocket event stream. |
|
||
|
||
### Devices and discovery
|
||
|
||
| Method | Endpoint | Description |
|
||
| --- | --- | --- |
|
||
| POST | `/api/discovery` | Discover/bind GREE devices. |
|
||
| GET | `/api/devices` | List devices. |
|
||
| POST | `/api/devices` | Add a device manually. |
|
||
| GET | `/api/devices/{id}` | Read a device. |
|
||
| PATCH | `/api/devices/{id}` | Edit technical device configuration. |
|
||
| DELETE | `/api/devices/{id}` | Delete a device after safety checks. |
|
||
| POST | `/api/devices/{id}/bind` | Bind/re-bind a physical unit. |
|
||
| POST | `/api/devices/{id}/probe` | Minimal non-mutating GREE round-trip diagnostic. |
|
||
| POST | `/api/devices/{id}/poll` | Poll one unit immediately. |
|
||
| POST | `/api/devices/{id}/command` | Send a direct/manual device command. |
|
||
|
||
### Thermostat zones, groups and house
|
||
|
||
| Method | Endpoint | Description |
|
||
| --- | --- | --- |
|
||
| GET | `/api/zones` | List zones. |
|
||
| POST | `/api/zones` | Create a zone. |
|
||
| GET | `/api/zones/{id}` | Read a zone. |
|
||
| PUT | `/api/zones/{id}` | Replace editable zone configuration. |
|
||
| DELETE | `/api/zones/{id}` | Delete zone after safe device shutdown. |
|
||
| POST | `/api/zones/{id}/control` | Quick/thermostat control of a zone. |
|
||
| POST | `/api/zones/{id}/schedule-template` | Replace zone schedules with a built-in template. |
|
||
| GET | `/api/groups` | List climate groups. |
|
||
| POST | `/api/groups` | Create a climate group. |
|
||
| GET | `/api/groups/{id}` | Read a group. |
|
||
| PUT | `/api/groups/{id}` | Replace group definition. |
|
||
| DELETE | `/api/groups/{id}` | Delete a group. |
|
||
| POST | `/api/groups/{id}/control` | Group control enable/mode/preset/custom-temperature control. |
|
||
| POST | `/api/house/control` | Set global thermostat mode. |
|
||
| POST | `/api/house/power` | Bulk ON/OFF for all thermostats and enabled units; no persistent global gate. |
|
||
| POST | `/api/house/preset` | Set/clear whole-house preset override. |
|
||
|
||
### Schedules and automations
|
||
|
||
| Method | Endpoint | Description |
|
||
| --- | --- | --- |
|
||
| GET | `/api/schedules` | List schedules. |
|
||
| POST | `/api/schedules` | Create schedule. |
|
||
| GET | `/api/schedules/{id}` | Read schedule. |
|
||
| PUT | `/api/schedules/{id}` | Replace schedule. |
|
||
| DELETE | `/api/schedules/{id}` | Delete schedule. |
|
||
| GET | `/api/automations` | List automations. |
|
||
| POST | `/api/automations` | Create automation. |
|
||
| GET | `/api/automations/{id}` | Read automation. |
|
||
| PUT | `/api/automations/{id}` | Replace automation. |
|
||
| DELETE | `/api/automations/{id}` | Delete automation. |
|
||
|
||
### History, control plan and events
|
||
|
||
| Method | Endpoint | Description |
|
||
| --- | --- | --- |
|
||
| GET | `/api/readings` | Legacy/device reading history. |
|
||
| GET | `/api/history` | Rich device/zone/HA history. |
|
||
| GET | `/api/control-plan` | Current resolved thermostat plan. |
|
||
| GET | `/api/events` | Event/debug log. |
|
||
|
||
### Settings, configuration and diagnostics
|
||
|
||
| Method | Endpoint | Description |
|
||
| --- | --- | --- |
|
||
| GET/PUT | `/api/settings/application` | Application runtime switches. |
|
||
| GET/PUT | `/api/settings/gree` | GREE controller, polling, discovery and compressor settings. |
|
||
| GET/PUT | `/api/settings/history` | Metric/event retention and compaction settings. |
|
||
| GET/PUT | `/api/settings/influxdb` | InfluxDB history settings. |
|
||
| GET/PUT | `/api/settings/notifications` | Notification provider and alert settings. |
|
||
| GET/PUT | `/api/settings/night` | Night mode settings. |
|
||
| GET/PUT | `/api/settings/home-assistant` | Home Assistant, aliases, shared Flow inputs and outdoor assist. |
|
||
| GET/PUT | `/api/settings/debug` | Debug overlay/GREE frame settings. |
|
||
| GET | `/api/configuration/export` | Export full application configuration. |
|
||
| POST | `/api/configuration/import` | Import/replace application configuration. |
|
||
| POST | `/api/integrations/home-assistant/test` | Test HA temperature read. |
|
||
| POST | `/api/integrations/home-assistant/entity` | Read raw HA entity state/attributes for shared Flow input diagnostics. |
|
||
| POST | `/api/integrations/notifications/test` | Send a test notification. |
|
||
|
||
### Access tokens and restricted Home Assistant API
|
||
|
||
| Method | Endpoint | Description |
|
||
| --- | --- | --- |
|
||
| GET | `/api/access-tokens` | List generated HA tokens without secrets. |
|
||
| POST | `/api/access-tokens` | Create restricted HA token. |
|
||
| DELETE | `/api/access-tokens/{id}` | Revoke token. |
|
||
| GET | `/api/integrations/home-assistant/snapshot` | Restricted devices + groups + control-plan snapshot in one request. |
|
||
| GET | `/api/integrations/home-assistant/devices` | Restricted device list. |
|
||
| POST | `/api/integrations/home-assistant/devices/{id}/command` | Restricted direct device command. |
|
||
| GET | `/api/integrations/home-assistant/control-plan` | Restricted control plan. |
|
||
| GET | `/api/integrations/home-assistant/groups` | HA-oriented group state. |
|
||
| POST | `/api/integrations/home-assistant/groups/{id}/control` | Restricted group control. |
|
||
| POST | `/api/integrations/home-assistant/house/control` | Restricted house mode. |
|
||
| POST | `/api/integrations/home-assistant/house/preset` | Restricted house preset. |
|
||
| POST | `/api/integrations/home-assistant/house/power` | Restricted bulk all-thermostat/all-unit power action. |
|
||
| POST | `/api/integrations/home-assistant/zones/{id}/control` | Restricted thermostat-zone control. |
|
||
|
||
---
|
||
|
||
## System endpoints
|
||
|
||
### `GET /api/health`
|
||
|
||
Public lightweight health check.
|
||
|
||
Response:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"name": "gree-controller",
|
||
"version": "0.13.7",
|
||
"uptime_seconds": 1234,
|
||
"control_ready": true,
|
||
"time": "2026-08-30T06:54:00Z"
|
||
}
|
||
```
|
||
|
||
`control_ready=false` means the process is running but the thermostat engine has not yet completed its initial physical device synchronization.
|
||
|
||
### `GET /api/bootstrap`
|
||
|
||
Returns the initial Web UI snapshot:
|
||
|
||
```json
|
||
{
|
||
"devices": [],
|
||
"zones": [],
|
||
"groups": [],
|
||
"schedules": [],
|
||
"automations": [],
|
||
"flows": [],
|
||
"access_tokens": [],
|
||
"settings": {
|
||
"application": {"simulator_enabled": false},
|
||
"gree": {
|
||
"controller_id": "gree-controller",
|
||
"poll_interval_seconds": 10,
|
||
"zone_interval_seconds": 10,
|
||
"discovery_timeout_ms": 3000,
|
||
"discovery_broadcast": "auto",
|
||
"suppress_device_beep": false,
|
||
"compressor_protection_enabled": true,
|
||
"compressor_protection_seconds": 180
|
||
},
|
||
"history": {"retention_days": 30, "compaction_enabled": true, "event_retention_days": 30},
|
||
"influxdb": {
|
||
"enabled": false, "version": "2", "url": "", "database": "gree_controller",
|
||
"username": "", "password_configured": false, "org": "", "bucket": "",
|
||
"token_configured": false, "history_threshold_days": 30
|
||
},
|
||
"notifications": {
|
||
"enabled": false, "mode": "problems", "provider": "pushover",
|
||
"pushover_configured": false, "slack_configured": false, "discord_configured": false,
|
||
"cooldown_seconds": 300, "communication_failure_threshold": 3,
|
||
"target_timeout_minutes": 60, "alert_types": {}
|
||
},
|
||
"night": {
|
||
"enabled": false, "start_time": "22:00", "end_time": "06:00",
|
||
"max_fan_speed": 1, "force_quiet": true, "use_native_sleep": true
|
||
},
|
||
"home_assistant": {
|
||
"url": "", "token_configured": false, "default_entity_id": "",
|
||
"outdoor_entity_id": "", "sensor_stale_after_seconds": 300,
|
||
"allow_invalid_tls": false, "sensor_aliases": {}, "flow_inputs": [],
|
||
"outdoor_assist_enabled": true
|
||
},
|
||
"debug": {"overlay_enabled": false, "gree_frames": false}
|
||
},
|
||
"house": {"mode": "cool"},
|
||
"outdoor_temperature": null,
|
||
"control_plan": {"generated_at": "2026-09-04T08:00:00Z", "zones": [], "rules": []},
|
||
"control_plan_revision": 42,
|
||
"system": {
|
||
"version": "0.13.7",
|
||
"uptime_seconds": 1234,
|
||
"auth_required": false,
|
||
"control_ready": true,
|
||
"database": "./data/gree-controller.db",
|
||
"device_count": 2,
|
||
"online_count": 2,
|
||
"simulator_count": 0,
|
||
"bind": "0.0.0.0:8787",
|
||
"base_path": "/",
|
||
"gree_interface": "auto",
|
||
"gree_received_frames": 809,
|
||
"gree_received_frames_by_device": {}
|
||
}
|
||
}
|
||
```
|
||
|
||
`settings` is a single startup snapshot composed from the same response models as the eight `/api/settings/*` GET endpoints. Secret values are never included; only `*_configured` flags are exposed for stored credentials. The split settings endpoints remain the canonical resources for independent reads and updates.
|
||
|
||
### `GET /api/system/info`
|
||
|
||
Returns the `system` diagnostic object independently of the full bootstrap. Useful for monitoring and **Settings → System status**.
|
||
|
||
---
|
||
|
||
## Device API
|
||
|
||
### Device object
|
||
|
||
A device response contains:
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `id` | string | Stable controller ID. |
|
||
| `mac` | string | Normalized GREE MAC/CID identity. |
|
||
| `name` | string | User-visible name. |
|
||
| `ip` | string | Device IPv4 address. |
|
||
| `port` | integer | Usually `7000`. |
|
||
| `protocol_version` | integer | `0` unknown/auto, `1` legacy AES-ECB, `2` AES-GCM. |
|
||
| `model`, `firmware` | string | Discovered metadata when available. |
|
||
| `key` | string/null | GREE binding key. Treat as secret. |
|
||
| `cid` | string/null | GREE client/device identifier. |
|
||
| `enabled` | boolean | Technical device enable state. |
|
||
| `simulated` | boolean | Simulated vs physical. |
|
||
| `power` | boolean | Last known power. |
|
||
| `mode` | string | `auto`, `cool`, `dry`, `fan`, `heat`. |
|
||
| `target_temperature` | number | Last known unit setpoint. |
|
||
| `fan_speed` | integer | `0..5`; `0` is Auto. |
|
||
| `swing_vertical`, `swing_horizontal` | boolean | Swing state. |
|
||
| `quiet`, `turbo`, `light`, `air`, `xfan`, `health`, `sleep` | boolean | Optional GREE features. |
|
||
| `supports_*` | boolean/null | Capability learned from device status. |
|
||
| `current_temperature` | number/null | GREE indoor temperature. |
|
||
| `outdoor_temperature` | number/null | GREE outdoor temperature if available. |
|
||
| `temperature_sensor_offset` | boolean/null | Whether +40 °C wire offset behavior was detected. |
|
||
| `online` | boolean | Current communication state. |
|
||
| `response_time_ms` | integer/null | Latest successful controller round-trip. |
|
||
| `last_seen` | ISO-8601/null | Last successful communication. |
|
||
| `last_error` | string/null | Latest communication error. |
|
||
| `communication_failures` | integer | Consecutive/recorded communication failure counter. |
|
||
| `created_at`, `updated_at` | ISO-8601 | Resource timestamps. |
|
||
|
||
### `POST /api/discovery`
|
||
|
||
Request body, all fields optional:
|
||
|
||
```json
|
||
{
|
||
"timeout_ms": 6000,
|
||
"broadcast": "255.255.255.255:7000",
|
||
"protocol_version": 0,
|
||
"passes": 3
|
||
}
|
||
```
|
||
|
||
Rules:
|
||
|
||
- `timeout_ms`: effective range `500..30000` ms.
|
||
- `protocol_version`: `0` auto/both, `1` AES-ECB only, `2` AES-GCM only.
|
||
- `passes`: `1..10`.
|
||
- Missing values use runtime GREE settings.
|
||
|
||
Successful discovery merges known devices, tries binding devices that do not have a key, persists results and returns:
|
||
|
||
```json
|
||
{
|
||
"count": 1,
|
||
"devices": [],
|
||
"new_device_ids": ["gree-aabbccddeeff"]
|
||
}
|
||
```
|
||
|
||
### `GET /api/devices`
|
||
|
||
Returns `Device[]`.
|
||
|
||
### `POST /api/devices`
|
||
|
||
Manual add request:
|
||
|
||
```json
|
||
{
|
||
"name": "Living room",
|
||
"mac": "AABBCCDDEEFF",
|
||
"ip": "192.168.50.30",
|
||
"port": 7000,
|
||
"protocol_version": 1,
|
||
"key": null,
|
||
"simulated": false
|
||
}
|
||
```
|
||
|
||
Defaults: `port=7000`, `protocol_version=1`, `simulated=false`. MAC values are normalized. Duplicate MACs are rejected.
|
||
|
||
Returns `201 Created` with `Device`.
|
||
|
||
### `GET /api/devices/{id}`
|
||
|
||
Returns one `Device` or `404`.
|
||
|
||
### `PATCH /api/devices/{id}`
|
||
|
||
All fields optional:
|
||
|
||
```json
|
||
{
|
||
"name": "Bedroom",
|
||
"ip": "192.168.50.31",
|
||
"port": 7000,
|
||
"protocol_version": 2,
|
||
"key": "optional-binding-key",
|
||
"enabled": true
|
||
}
|
||
```
|
||
|
||
`key:null` clears the key. Changing protocol version clears the existing key/capability cache so the unit can be re-bound cleanly. Disabling a device goes through the controller's safe disable path.
|
||
|
||
### `DELETE /api/devices/{id}`
|
||
|
||
Returns `204`. Deletion is rejected if the device is referenced by an automation or cannot be safely detached from thermostat ownership. Associated zones/groups are cleaned only after the safety checks pass.
|
||
|
||
### `POST /api/devices/{id}/bind`
|
||
|
||
Performs/repeats GREE binding and returns updated `Device`. Simulated devices return unchanged.
|
||
|
||
### `POST /api/devices/{id}/probe`
|
||
|
||
Performs a minimal, non-mutating GREE status round-trip and returns `response_time_ms`. It does **not** update device online/error counters, readings, capabilities, thermostat ownership, or persisted device state. A real device must already be bound.
|
||
|
||
```json
|
||
{
|
||
"device_id": "gree-aabbccddeeff",
|
||
"response_time_ms": 18,
|
||
"ok": true
|
||
}
|
||
```
|
||
|
||
### `POST /api/devices/{id}/poll`
|
||
|
||
Immediately polls one unit and returns updated `Device`.
|
||
|
||
### `POST /api/devices/{id}/command`
|
||
|
||
Direct/manual device control. This is deliberately different from thermostat-zone control.
|
||
|
||
All fields optional; at least one meaningful field should be sent:
|
||
|
||
```json
|
||
{
|
||
"power": true,
|
||
"mode": "cool",
|
||
"target_temperature": 22,
|
||
"fan_speed": 3,
|
||
"swing_vertical": true,
|
||
"swing_horizontal": false,
|
||
"quiet": false,
|
||
"turbo": false,
|
||
"light": true,
|
||
"air": false,
|
||
"xfan": false,
|
||
"health": false,
|
||
"sleep": false
|
||
}
|
||
```
|
||
|
||
Rules:
|
||
|
||
- modes: `auto`, `cool`, `dry`, `fan`, `heat`,
|
||
- target temperature is normalized to the supported GREE Celsius range `8..30`,
|
||
- fan speed is `0..5`,
|
||
- optional feature commands should be used only when the corresponding `supports_*` capability is true.
|
||
|
||
The backend sends only properties that differ from the last known device state. Climate-relevant direct commands can create/continue a manual-device takeover for an enabled thermostat zone so automation does not immediately fight the user.
|
||
|
||
When the physical device belongs to a disabled thermostat zone, direct web/API control requires `"manual_override": true`. The web UI asks for explicit confirmation before sending that flag. Home Assistant direct-device control remains blocked for disabled zones.
|
||
|
||
---
|
||
|
||
## Zones
|
||
|
||
### Zone configuration
|
||
|
||
`POST /api/zones` and `PUT /api/zones/{id}` use this editable shape:
|
||
|
||
```json
|
||
{
|
||
"name": "Living room",
|
||
"device_id": "gree-aabbccddeeff",
|
||
"enabled": true,
|
||
"mode": "cool",
|
||
"inherit_house_mode": true,
|
||
"setpoint": 23.0,
|
||
"cool_comfort_setpoint": 23.0,
|
||
"cool_sleep_setpoint": 24.5,
|
||
"cool_away_setpoint": 27.0,
|
||
"heat_comfort_setpoint": 21.0,
|
||
"heat_sleep_setpoint": 19.0,
|
||
"heat_away_setpoint": 17.0,
|
||
"hysteresis": 0.6,
|
||
"separate_hysteresis": false,
|
||
"cool_hysteresis": 0.6,
|
||
"heat_hysteresis": 0.6,
|
||
"min_on_seconds": 180,
|
||
"min_off_seconds": 180,
|
||
"min_adjust_seconds": 120,
|
||
"standby_offset_c": 2.0,
|
||
"smart_fan": true,
|
||
"sensor_source": "combined",
|
||
"ha_entity_id": "sensor.living_room_temperature",
|
||
"external_sensor_weight": 0.4,
|
||
"max_sensor_difference": 3.0,
|
||
"sensor_stale_after_seconds": 300,
|
||
"revision": 12
|
||
}
|
||
```
|
||
|
||
Important rules:
|
||
|
||
- `name` is required.
|
||
- `device_id` must reference an existing device and thermostat ownership must remain valid/safe.
|
||
- `mode`: `cool` or `heat` when not inheriting house mode.
|
||
- temperature/profile values are constrained to the supported thermostat range.
|
||
- `hysteresis`: shared controller hysteresis, valid range `0.1..5.0` °C.
|
||
- `separate_hysteresis`: when `true`, cooling uses `cool_hysteresis` and heating uses `heat_hysteresis`; both use the same `0.1..5.0` °C range.
|
||
- `standby_offset_c`: bounded thermostat offset.
|
||
- `sensor_source`: `device`, `home_assistant` or `combined`.
|
||
- `external_sensor_weight`: `0..1`.
|
||
- `revision` is used for optimistic concurrency where supplied; stale updates can return `409`.
|
||
|
||
The returned `Zone` also contains runtime state including sensor readings, resolved/effective setpoints, demand, current preset, manual overrides, local Quick Thermostat ownership, temporary session state, device-manual takeover, control owner/source/reason, lockout timestamps and `created_at`/`updated_at`.
|
||
|
||
### `GET /api/zones`
|
||
|
||
Returns `Zone[]`.
|
||
|
||
### `POST /api/zones`
|
||
|
||
Creates a zone and returns `201 Created` with `Zone`.
|
||
|
||
### `GET /api/zones/{id}`
|
||
|
||
Returns one `Zone`.
|
||
|
||
### `PUT /api/zones/{id}`
|
||
|
||
Replaces editable zone configuration while preserving/reconciling runtime safety state. Returns updated `Zone`.
|
||
|
||
### `DELETE /api/zones/{id}`
|
||
|
||
Safely powers the owned device off before detaching thermostat ownership. Returns `204`.
|
||
|
||
### `POST /api/zones/{id}/control`
|
||
|
||
Manual `setpoint` values are retained with 0.1 °C precision. The physical GREE unit setpoint is still rounded to the whole-degree resolution supported by the protocol.
|
||
|
||
Quick thermostat endpoint. Body fields are optional and can be combined:
|
||
|
||
```json
|
||
{
|
||
"setpoint": 22.5,
|
||
"power": true,
|
||
"mode": "house",
|
||
"enabled": true,
|
||
"preset": "comfort",
|
||
"clear_override": false,
|
||
"clear_device_manual_override": false,
|
||
"clear_local_thermostat_override": false,
|
||
"temporary_quick_thermostat": null,
|
||
"clear_temporary_quick_thermostat": false
|
||
}
|
||
```
|
||
|
||
Semantics:
|
||
|
||
- `setpoint`: creates a quick custom thermostat target.
|
||
- `preset`: `auto`, `comfort`, `sleep`, `away`, `custom`; `auto` clears the profile override.
|
||
- `mode`: `house`, `cool`, `heat`; `house` restores global mode inheritance.
|
||
- `enabled`: zone automation enable state.
|
||
- `power:true`: local Quick Thermostat ownership — this zone can run through full thermostat logic regardless of whether group-level control is enabled for its climate group.
|
||
- `power:false`: turns this zone off and creates a fresh backend-owned local hand-back timer (currently 15 minutes).
|
||
- `clear_local_thermostat_override:true`: immediately return local Quick Thermostat ownership to normal automation. If there is no active schedule, manual preset/setpoint, Temporary Quick Thermostat or other explicit thermostat intent, the zone remains physically OFF instead of falling back to implicit Comfort.
|
||
- `clear_device_manual_override:true`: explicitly hand a physical/direct manual takeover back to the thermostat.
|
||
- `clear_override:true`: clear ordinary quick preset/setpoint override.
|
||
- `temporary_quick_thermostat`: start/replace a persisted temporary session.
|
||
- `clear_temporary_quick_thermostat:true`: cancel that temporary session only.
|
||
|
||
Returns updated `Zone`.
|
||
|
||
#### Temporary Quick Thermostat request
|
||
|
||
```json
|
||
{
|
||
"start_kind": "now",
|
||
"start_delay_minutes": null,
|
||
"start_at": null,
|
||
"finish_kind": "duration",
|
||
"duration_minutes": 90,
|
||
"until": null,
|
||
"target_temperature": 23.0,
|
||
"temperature_operator": "within",
|
||
"tolerance_c": 0.3,
|
||
"hold_minutes": 60,
|
||
"max_duration_minutes": 240
|
||
}
|
||
```
|
||
|
||
`start_kind`:
|
||
|
||
- `now`
|
||
- `delay` + `start_delay_minutes`
|
||
- `at` + ISO-8601 `start_at`
|
||
|
||
`finish_kind`:
|
||
|
||
- `duration` + `duration_minutes`
|
||
- `until` + ISO-8601 `until`
|
||
- `temperature_reached`
|
||
- `temperature_stable` + `hold_minutes`
|
||
- `schedule_boundary`
|
||
|
||
Temperature operators:
|
||
|
||
- `within`
|
||
- `at_or_below`
|
||
- `at_or_above`
|
||
|
||
`max_duration_minutes` is an optional fail-safe for temperature-based sessions. Delayed sessions do not own the zone until their effective start. Runtime session state is persisted and exposed inside the returned zone.
|
||
|
||
Examples:
|
||
|
||
```bash
|
||
# Preset until next schedule boundary
|
||
curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: application/json' \
|
||
-d '{"preset":"sleep"}'
|
||
|
||
# Return to schedule
|
||
curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: application/json' \
|
||
-d '{"preset":"auto"}'
|
||
|
||
# Run a 90-minute temporary thermostat at a decimal logical target
|
||
curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: application/json' \
|
||
-d '{"temporary_quick_thermostat":{"start_kind":"now","finish_kind":"duration","duration_minutes":90,"target_temperature":24.2}}'
|
||
```
|
||
|
||
Temporary Quick Thermostat targets are normalized to `0.1 °C`. The Web UI accepts both comma and dot decimal input (for example `24,2` and `24.2`). Physical GREE device setpoint rounding is unchanged.
|
||
|
||
### `POST /api/zones/{id}/schedule-template`
|
||
|
||
Body:
|
||
|
||
```json
|
||
{
|
||
"template": "family"
|
||
}
|
||
```
|
||
|
||
Built-in templates:
|
||
|
||
| Template | Result |
|
||
| --- | --- |
|
||
| `family` | Comfort `06:30–22:30`, Sleep overnight. |
|
||
| `child` | Comfort `06:30–20:30`, Sleep overnight. |
|
||
| `bedroom` | Comfort `06:30–22:00`, Sleep overnight. |
|
||
| `workday` | Weekday morning/away/evening/sleep plus weekend blocks. |
|
||
| `always` | 24-hour Comfort. |
|
||
|
||
Existing schedules for the zone are replaced after overlap validation. Response:
|
||
|
||
```json
|
||
{
|
||
"zone": {},
|
||
"schedules": []
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Groups
|
||
|
||
### Group object
|
||
|
||
```json
|
||
{
|
||
"id": "uuid",
|
||
"name": "Bedrooms",
|
||
"zone_ids": ["zone-1", "zone-2"],
|
||
"power_enabled": true,
|
||
"created_at": "...",
|
||
"updated_at": "..."
|
||
}
|
||
```
|
||
|
||
### `GET /api/groups`
|
||
|
||
Returns groups.
|
||
|
||
### `POST /api/groups`
|
||
|
||
```json
|
||
{
|
||
"name": "Bedrooms",
|
||
"zone_ids": ["zone-1", "zone-2"],
|
||
"power_enabled": true
|
||
}
|
||
```
|
||
|
||
A group must contain at least one existing zone. Returns `201 Created`.
|
||
|
||
### `GET /api/groups/{id}` / `PUT /api/groups/{id}` / `DELETE /api/groups/{id}`
|
||
|
||
Read, replace or delete a group. `DELETE` returns `204`.
|
||
|
||
### `POST /api/groups/{id}/control`
|
||
|
||
```json
|
||
{
|
||
"power": true,
|
||
"mode": "house",
|
||
"preset": "custom",
|
||
"setpoint": 22.3
|
||
}
|
||
```
|
||
|
||
All fields optional:
|
||
|
||
- `power`: enable/disable group-level control (legacy field name; this is not a device power gate),
|
||
- `mode`: `house`, `cool`, `heat`,
|
||
- `preset`: `auto`, `comfort`, `sleep`, `away`, `custom`,
|
||
- `setpoint`: custom group target in the `8–30°C` range; requires `preset: "custom"`.
|
||
|
||
A custom setpoint creates the same override for every member zone. For explicit Web/Home Assistant group control it stays active until the group is changed/released; scheduled group automation remains bounded by the normal schedule hand-back. Returns a group control/result object including updated members/state.
|
||
|
||
Group `power` is a scoped bulk-power/control action. `power=false` immediately powers member units off and releases `group:*` ownership; members are stored as individually-off thermostats rather than being blocked by membership in an OFF group. This group-created OFF is indefinite (no 15-minute local hand-back), so the regulator cannot restart the unit by itself; a user can still turn an individual thermostat back on independently. `power=true` clears that scoped thermostat-OFF state and immediately re-runs group thermostat arbitration. Explicit Web/Home Assistant group control has higher priority than house rules and schedules; scheduled group automations still respect higher-priority manual/local ownership. Group mode/preset/setpoint changes are accepted only while the group is ON. Global ON/OFF never creates a persistent global gate. Global OFF does leave member thermostats locally OFF until they are explicitly re-enabled by local/group/global ON.
|
||
|
||
---
|
||
|
||
## Whole-house control
|
||
|
||
House thermostat rules and global power actions are intentionally separate. Global ON/OFF does not create a master automation gate; OFF is persisted as per-zone local OFF so ordinary demand cannot immediately undo it.
|
||
|
||
### `POST /api/house/control`
|
||
|
||
```json
|
||
{
|
||
"mode": "cool"
|
||
}
|
||
```
|
||
|
||
Valid modes: `cool`, `heat`, `off`.
|
||
|
||
- `cool`/`heat` select the house rule used by zones that inherit the global mode and immediately re-run arbitration for free zones. Explicit local/group/direct ownership is preserved.
|
||
- `off` means **do not perform house-level thermostat control** for inherited free zones. It does not block local thermostats, groups, device-manual control or controller automations.
|
||
|
||
Returns `{ "mode": "cool|heat|off" }`.
|
||
|
||
### `POST /api/house/power`
|
||
|
||
```json
|
||
{
|
||
"power": false
|
||
}
|
||
```
|
||
|
||
This endpoint is a **bulk thermostat power action without a persistent blocking global gate**. `false` first stores every thermostat as an indefinite local OFF and then sends immediate OFF to every technically enabled unit, so the next normal demand cycle cannot turn the house back on. A later per-zone **Resume automation** clears that local OFF but does not invent a Comfort demand: without an active schedule/override/temporary or another explicit intent the zone stays OFF. `true` is itself an explicit whole-house ON intent, releases local OFF markers for all thermostats and sends ON to all technically enabled units. Group enablement, profiles, schedules and house mode are preserved. Pending compressor-protection tasks from before the action are cleared. When compressor protection is enabled, protected global starts are queued until their safe deadline; OFF is never delayed.
|
||
|
||
Response includes:
|
||
|
||
```json
|
||
{
|
||
"power": false,
|
||
"one_shot": true,
|
||
"devices": [],
|
||
"groups": [],
|
||
"failed": []
|
||
}
|
||
```
|
||
|
||
### `POST /api/house/preset`
|
||
|
||
```json
|
||
{
|
||
"preset": "sleep"
|
||
}
|
||
```
|
||
|
||
Valid: `auto`, `comfort`, `sleep`, `away`.
|
||
|
||
A non-`auto` preset creates overrides for free house-controlled zones and normally expires at each zone's next schedule boundary. `auto` clears those free-zone overrides. Explicit local thermostat, group, temporary thermostat and direct/manual ownership is not overwritten by a house profile action.
|
||
|
||
Response contains `preset`, the updated `zones`, current `devices`, and a `failed` array for immediate-control errors. It does not embed settings; settings are available only from the functional `/api/settings/*` resources.
|
||
|
||
---
|
||
|
||
## Schedules
|
||
|
||
Schedule object/request:
|
||
|
||
```json
|
||
{
|
||
"zone_id": "zone-1",
|
||
"name": "Night",
|
||
"enabled": true,
|
||
"weekdays": [1, 2, 3, 4, 5, 6, 7],
|
||
"start_time": "22:30",
|
||
"end_time": "06:30",
|
||
"preset": "sleep",
|
||
"setpoint": 24.5
|
||
}
|
||
```
|
||
|
||
Rules:
|
||
|
||
- weekdays use ISO numbers `1=Monday ... 7=Sunday`,
|
||
- times use local `HH:MM`,
|
||
- crossing midnight is supported,
|
||
- `start_time == end_time` represents a 24-hour window for selected weekdays,
|
||
- preset: `comfort`, `sleep`, `away`, `custom`,
|
||
- custom setpoint: `8..30` °C,
|
||
- enabled schedules for the same zone cannot overlap.
|
||
|
||
For non-custom presets, the effective target comes from the zone's seasonal profile; `setpoint` is retained for compatibility.
|
||
|
||
Routes:
|
||
|
||
```text
|
||
GET /api/schedules
|
||
POST /api/schedules
|
||
GET /api/schedules/{id}
|
||
PUT /api/schedules/{id}
|
||
DELETE /api/schedules/{id}
|
||
```
|
||
|
||
Create returns `201`; delete returns `204`.
|
||
|
||
---
|
||
|
||
## Automations
|
||
|
||
Automation request/object fields:
|
||
|
||
```json
|
||
{
|
||
"name": "Hot room",
|
||
"enabled": true,
|
||
"trigger_kind": "temperature_above",
|
||
"trigger_device_id": "gree-aabbccddeeff",
|
||
"threshold": 27.0,
|
||
"at_time": null,
|
||
"action_device_id": "gree-aabbccddeeff",
|
||
"action_group_id": null,
|
||
"action_preset": null,
|
||
"action": {
|
||
"power": true,
|
||
"mode": "cool",
|
||
"target_temperature": 23
|
||
},
|
||
"cooldown_seconds": 300
|
||
}
|
||
```
|
||
|
||
Triggers:
|
||
|
||
- `temperature_above`: requires `trigger_device_id` + `threshold`,
|
||
- `temperature_below`: requires `trigger_device_id` + `threshold`,
|
||
- `time`: requires local `at_time` in `HH:MM`.
|
||
|
||
Action target is either:
|
||
|
||
- direct device: `action_device_id` + full `DeviceCommand`, or
|
||
- group: `action_group_id`; group automation supports only power, `house`/`cool`/`heat` mode and optional `action_preset` (`auto|comfort|sleep|away`).
|
||
|
||
The response also contains runtime `last_fired_at`, `created_at`, `updated_at`.
|
||
|
||
Routes:
|
||
|
||
```text
|
||
GET /api/automations
|
||
POST /api/automations
|
||
GET /api/automations/{id}
|
||
PUT /api/automations/{id}
|
||
DELETE /api/automations/{id}
|
||
```
|
||
|
||
Create returns `201`; delete returns `204`.
|
||
|
||
---
|
||
|
||
## History and readings
|
||
|
||
### `GET /api/readings`
|
||
|
||
Legacy/lightweight device history.
|
||
|
||
Query parameters:
|
||
|
||
| Parameter | Default | Description |
|
||
| --- | --- | --- |
|
||
| `device_id` | all | Optional device filter. |
|
||
| `hours` | `24` | Clamped to `1..87600` (10 years). |
|
||
| `limit` | `1500` | Row limit. |
|
||
|
||
Response:
|
||
|
||
```json
|
||
{
|
||
"readings": [
|
||
{
|
||
"id": 1,
|
||
"device_id": "gree-aabbccddeeff",
|
||
"timestamp": "...",
|
||
"indoor_temperature": 23.4,
|
||
"outdoor_temperature": 30.1,
|
||
"target_temperature": 23,
|
||
"power": true,
|
||
"source": "poll"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### `GET /api/history`
|
||
|
||
Rich chart/history API.
|
||
|
||
Query parameters:
|
||
|
||
| Parameter | Description |
|
||
| --- | --- |
|
||
| `scope` | `overview`, `zones`/`zone`, `devices`, `sensors`; default `zones`. |
|
||
| `zone_id` | Zone filter for zone scope. |
|
||
| `device_id` | Device filter for device scope. |
|
||
| `entity_id` | Home Assistant entity filter for sensor scope. |
|
||
| `hours` | Default `24`, clamped to 10 years. |
|
||
| `limit` | Default `12000`, clamped to `1..20000`. |
|
||
|
||
Bucket resolution:
|
||
|
||
| Range | Bucket |
|
||
| --- | --- |
|
||
| ≤ 6 h | 30 s |
|
||
| ≤ 24 h | 2 min |
|
||
| ≤ 7 d | 10 min |
|
||
| ≤ 30 d | 30 min |
|
||
| ≤ 90 d | 2 h |
|
||
| ≤ 1 y | 6 h |
|
||
| > 1 y | 24 h |
|
||
|
||
Zone reading fields:
|
||
|
||
```text
|
||
id, zone_id, device_id, timestamp,
|
||
gree_temperature, external_temperature, control_temperature,
|
||
target_temperature, device_setpoint, outdoor_temperature,
|
||
power, mode, fan_speed, demand, control_source, active_preset
|
||
```
|
||
|
||
Device reading fields are the `Reading` fields documented above. HA sensor rows contain:
|
||
|
||
```text
|
||
id, entity_id, zone_id, kind, timestamp, temperature
|
||
```
|
||
|
||
`scope=overview` returns all three families plus counts and per-family storage source.
|
||
|
||
When InfluxDB is enabled, older history can be read from Influx and merged with recent SQLite rows. A failed Influx query falls back to available SQLite data and reports `storage_warning` rather than failing the entire chart response.
|
||
|
||
---
|
||
|
||
## Control plan
|
||
|
||
### `GET /api/control-plan`
|
||
|
||
Returns the latest materialized machine-readable thermostat plan. The response shape is unchanged; the bundled Web UI receives plan updates primarily through WebSocket and uses this endpoint for fallback/resynchronization.
|
||
|
||
|
||
Top-level fields:
|
||
|
||
```text
|
||
generated_at
|
||
house_mode
|
||
house_preset
|
||
house_power
|
||
outdoor_temperature
|
||
control_strategy
|
||
night_mode_active
|
||
night_mode_start
|
||
night_mode_end
|
||
night_mode_max_fan_speed
|
||
next_events[]
|
||
zones[]
|
||
rules[]
|
||
```
|
||
|
||
Each zone plan includes:
|
||
|
||
```text
|
||
zone_id, zone_name, device_id, device_name,
|
||
enabled, effective_enabled,
|
||
mode, configured_mode, inherit_house_mode,
|
||
preset, preset_override,
|
||
current_temperature, target_temperature, device_setpoint,
|
||
desired_power, desired_mode,
|
||
actual_power, actual_mode, actual_setpoint,
|
||
demand, control_source,
|
||
manual_override_until,
|
||
local_thermostat_power, local_thermostat_resume_at,
|
||
device_manual_override, device_manual_override_until,
|
||
control_owner, control_command_source, control_since, resume_at, control_reason,
|
||
blocked_reason, lockout_until,
|
||
current_schedule_id, current_schedule_name,
|
||
next_events[]
|
||
```
|
||
|
||
This endpoint is the best way for another client to understand **desired vs actual state**, who owns control, and why a zone is blocked/paused.
|
||
|
||
---
|
||
|
||
## Events and retention
|
||
|
||
### `GET /api/events?limit=100`
|
||
|
||
Response:
|
||
|
||
```json
|
||
{
|
||
"events": [
|
||
{
|
||
"id": 1,
|
||
"timestamp": "...",
|
||
"level": "info",
|
||
"kind": "device.updated",
|
||
"message": "...",
|
||
"metadata": {}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
For events that are eligible for external notification, `metadata.notification` records the delivery result. `status` is `sent`, `silent` or `failed`. A `silent` event includes a `reason` such as `alert_type_disabled`, `notifications_disabled`, `mode_filtered` or `cooldown`; the Web UI shows these rows with a **SILENT** badge. Notification-status changes are also emitted as `log.updated` WebSocket events.
|
||
|
||
---
|
||
|
||
## Runtime settings — 0.12.0
|
||
|
||
Version `0.12.0` replaces the monolithic settings document with functional resources. There are no compatibility aliases for the removed `/api/settings`, `/api/debug` or `/api/events/retention` endpoints.
|
||
|
||
Every settings resource supports `GET` and `PUT`. A `PUT` replaces only that functional section; it never requires or overwrites unrelated settings.
|
||
|
||
### `/api/settings/application`
|
||
|
||
```json
|
||
{
|
||
"simulator_enabled": false
|
||
}
|
||
```
|
||
|
||
### `/api/settings/gree`
|
||
|
||
```json
|
||
{
|
||
"controller_id": "gree-controller",
|
||
"poll_interval_seconds": 15,
|
||
"zone_interval_seconds": 5,
|
||
"discovery_timeout_ms": 3000,
|
||
"discovery_broadcast": "255.255.255.255:7000",
|
||
"suppress_device_beep": false,
|
||
"compressor_protection_enabled": true,
|
||
"compressor_protection_seconds": 180
|
||
}
|
||
```
|
||
|
||
Validation/normalization:
|
||
|
||
- `controller_id` cannot be empty;
|
||
- polling and zone intervals: `2..3600` seconds;
|
||
- discovery timeout: `300..30000` ms;
|
||
- discovery broadcast: `auto`, `auto:*` or a socket address;
|
||
- compressor protection: `30..1800` seconds.
|
||
|
||
Changing compressor protection clears pending compressor runtime queues before thermostat control continues.
|
||
|
||
### `/api/settings/history`
|
||
|
||
```json
|
||
{
|
||
"retention_days": 30,
|
||
"compaction_enabled": true,
|
||
"event_retention_days": 30
|
||
}
|
||
```
|
||
|
||
Retention values are clamped to `1..3650` days. Updating this section immediately prunes expired event rows. The former `/api/events/retention` endpoint no longer exists.
|
||
|
||
### `/api/settings/influxdb`
|
||
|
||
`GET` returns a secret-safe view:
|
||
|
||
```json
|
||
{
|
||
"enabled": true,
|
||
"version": "2",
|
||
"url": "http://influxdb:8086",
|
||
"database": "gree_controller",
|
||
"username": "",
|
||
"password_configured": false,
|
||
"org": "home",
|
||
"bucket": "gree_controller",
|
||
"token_configured": true,
|
||
"history_threshold_days": 30
|
||
}
|
||
```
|
||
|
||
`PUT` uses the same non-secret fields plus optional `password` and `token`. Omitting either field (or sending `null`) preserves the stored secret. Sending an explicit empty string clears it. `history_threshold_days` is clamped to `1..3650`; the complete InfluxDB configuration is validated before persistence.
|
||
|
||
### `/api/settings/notifications`
|
||
|
||
`GET` returns provider state without secret values:
|
||
|
||
```json
|
||
{
|
||
"enabled": true,
|
||
"mode": "problems",
|
||
"provider": "pushover",
|
||
"pushover_configured": true,
|
||
"slack_configured": false,
|
||
"discord_configured": false,
|
||
"cooldown_seconds": 300,
|
||
"communication_failure_threshold": 3,
|
||
"target_timeout_minutes": 60,
|
||
"alert_types": {
|
||
"stale_sensor": true,
|
||
"sensor_errors": true,
|
||
"communication": true,
|
||
"target_timeout": true,
|
||
"automation": true,
|
||
"sensor_discrepancy": true,
|
||
"control_errors": true,
|
||
"important_events": true,
|
||
"other": true
|
||
}
|
||
}
|
||
```
|
||
|
||
`PUT` accepts the same behavioral fields plus optional `pushover_app_token`, `pushover_user_key`, `slack_webhook_url` and `discord_webhook_url`. Omitted/`null` secrets are preserved; an explicit empty string clears them. Modes: `problems`, `important`. Providers: `pushover`, `slack`, `discord`. Cooldown is clamped to `30..86400` seconds, failure threshold to `2..100`, target timeout to `5..1440` minutes. `alert_types.sensor_discrepancy` controls only notifications about GREE vs Home Assistant room-temperature divergence; the safety fallback to the GREE sensor remains active.
|
||
|
||
### `/api/settings/night`
|
||
|
||
```json
|
||
{
|
||
"enabled": false,
|
||
"start_time": "22:00",
|
||
"end_time": "06:00",
|
||
"max_fan_speed": 1,
|
||
"force_quiet": true,
|
||
"use_native_sleep": true
|
||
}
|
||
```
|
||
|
||
Times must use `HH:MM`; maximum fan speed is clamped to `1..5`.
|
||
|
||
### `/api/settings/home-assistant`
|
||
|
||
`GET` returns:
|
||
|
||
```json
|
||
{
|
||
"url": "http://homeassistant.local:8123",
|
||
"token_configured": true,
|
||
"default_entity_id": "sensor.room_temperature",
|
||
"outdoor_entity_id": "sensor.outdoor_temperature",
|
||
"sensor_stale_after_seconds": 300,
|
||
"allow_invalid_tls": false,
|
||
"sensor_aliases": {
|
||
"sensor.room_temperature": "Living room"
|
||
},
|
||
"flow_inputs": [],
|
||
"outdoor_assist_enabled": true
|
||
}
|
||
```
|
||
|
||
`PUT` replaces `token_configured` with optional `token`. Omitted/`null` token preserves the saved token; an explicit empty string clears it. Sensor age is clamped to `30..86400` seconds. URLs, aliases, entity IDs and shared Flow inputs are normalized/validated before persistence. Shared inputs are value sources only; comparison operators and thresholds belong to Flow nodes.
|
||
|
||
### `/api/settings/debug`
|
||
|
||
```json
|
||
{
|
||
"overlay_enabled": true,
|
||
"gree_frames": true
|
||
}
|
||
```
|
||
|
||
When `overlay_enabled=true`, live HTTP diagnostics can emit `api.request`; `gree_frames=true` enables sanitized `gree.frame` events.
|
||
|
||
### Settings WebSocket events
|
||
|
||
Each section has its own event and payload:
|
||
|
||
```text
|
||
settings.application.updated
|
||
settings.gree.updated
|
||
settings.history.updated
|
||
settings.influxdb.updated
|
||
settings.notifications.updated
|
||
settings.night.updated
|
||
settings.home_assistant.updated
|
||
settings.debug.updated
|
||
```
|
||
|
||
The generic `settings.updated` and `debug.settings` events were removed in `0.12.0`.
|
||
|
||
---
|
||
|
||
## Configuration backup and restore
|
||
|
||
### `GET /api/configuration/export`
|
||
|
||
Returns configuration format version `3`:
|
||
|
||
```text
|
||
format_version
|
||
exported_at
|
||
settings
|
||
devices[]
|
||
zones[]
|
||
groups[]
|
||
schedules[]
|
||
automations[]
|
||
flows[]
|
||
```
|
||
|
||
The export contains GREE binding keys and integration credentials. It excludes metric history, event rows and generated API-token records. Treat it as a secret backup.
|
||
|
||
### `POST /api/configuration/import`
|
||
|
||
Accepts only format version `3` and the `setpoint` control strategy used by `0.12.0`. The backend validates IDs/references (including shared Flow inputs against resources inside the backup), Flow draft safety, schedules, settings and ownership relationships; safely powers off devices being detached; clears transient runtime/ownership state; replaces configuration; re-polls imported devices; then resumes thermostat control.
|
||
|
||
Metric/event history and generated access-token records are preserved.
|
||
|
||
Response:
|
||
|
||
```json
|
||
{
|
||
"ok": true
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Integration tests
|
||
|
||
### `POST /api/integrations/home-assistant/test`
|
||
|
||
```json
|
||
{
|
||
"entity_id": "sensor.room_temperature"
|
||
}
|
||
```
|
||
|
||
`entity_id` is optional; controller defaults/aliases are resolved. Response:
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"temperature_c": 23.4,
|
||
"entity_id": "sensor.room_temperature"
|
||
}
|
||
```
|
||
|
||
### `POST /api/integrations/home-assistant/entity`
|
||
|
||
Reads the current raw Home Assistant entity document used by the shared-Flow-input diagnostics UI.
|
||
|
||
```json
|
||
{
|
||
"entity_id": "climate.gas_boiler"
|
||
}
|
||
```
|
||
|
||
Response includes the raw state, availability, attributes and timestamps:
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"entity_id": "climate.gas_boiler",
|
||
"state": "heat",
|
||
"available": true,
|
||
"attributes": { "hvac_action": "heating" },
|
||
"last_changed": "2026-09-02T06:30:00Z",
|
||
"last_updated": "2026-09-02T06:30:05Z"
|
||
}
|
||
```
|
||
|
||
### `POST /api/integrations/notifications/test`
|
||
|
||
Accepts a `NotificationSettings` object. Blank secret/webhook fields reuse saved secrets for the test.
|
||
|
||
Response:
|
||
|
||
```json
|
||
{
|
||
"ok": true
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Access tokens
|
||
|
||
### `GET /api/access-tokens`
|
||
|
||
Returns:
|
||
|
||
```json
|
||
[
|
||
{
|
||
"id": "uuid",
|
||
"name": "Home Assistant",
|
||
"token_prefix": "gree_controller_abc...",
|
||
"created_at": "..."
|
||
}
|
||
]
|
||
```
|
||
|
||
### `POST /api/access-tokens`
|
||
|
||
```json
|
||
{
|
||
"name": "Home Assistant"
|
||
}
|
||
```
|
||
|
||
Name length: `1..80`. If omitted, default is `Home Assistant`.
|
||
|
||
Response `201 Created`:
|
||
|
||
```json
|
||
{
|
||
"token": "gree_controller_FULL_SECRET_SHOWN_ONCE",
|
||
"item": {
|
||
"id": "uuid",
|
||
"name": "Home Assistant",
|
||
"token_prefix": "gree_controller_...",
|
||
"created_at": "..."
|
||
}
|
||
}
|
||
```
|
||
|
||
### `DELETE /api/access-tokens/{id}`
|
||
|
||
Revokes token and returns `204`.
|
||
|
||
---
|
||
|
||
## Restricted Home Assistant API
|
||
|
||
These routes always require a generated token or the administrator app token.
|
||
|
||
### `GET /api/integrations/home-assistant/devices`
|
||
|
||
Returns `Device[]`.
|
||
|
||
### `POST /api/integrations/home-assistant/devices/{id}/command`
|
||
|
||
Accepts `DeviceCommand`. A direct command is rejected if the device belongs to a disabled thermostat zone; re-enable the zone for normal HA/controller ownership or use the administrator technical device endpoint deliberately.
|
||
|
||
### `GET /api/integrations/home-assistant/snapshot`
|
||
|
||
Returns `devices`, `groups`, `control_plan` and `control_plan_revision` in one restricted request. The bundled Home Assistant coordinator uses this endpoint to reduce its normal refresh from three HTTP requests to one. The existing restricted devices/groups/control-plan endpoints remain available.
|
||
|
||
### `GET /api/integrations/home-assistant/control-plan`
|
||
|
||
Same payload as administrator `GET /api/control-plan`.
|
||
|
||
### `GET /api/integrations/home-assistant/groups`
|
||
|
||
Returns an HA-oriented derived group list. Each object includes:
|
||
|
||
```text
|
||
id, name, zone_ids, zone_names,
|
||
power_enabled, effective_power,
|
||
mode, preset, house_mode,
|
||
zone_count, enabled_zones, active_zones, demanding_zones,
|
||
device_count, online_devices, current_temperature,
|
||
members[], next_events[]
|
||
```
|
||
|
||
Member rows include zone/device identity, configured/effective enable state, mode/preset, room/target temperature, demand, source, schedule and current manual/local ownership markers.
|
||
|
||
### `POST /api/integrations/home-assistant/groups/{id}/control`
|
||
|
||
Same body/semantics as normal group control.
|
||
|
||
### `POST /api/integrations/home-assistant/house/control`
|
||
|
||
Same `{ "mode": "cool|heat|off" }` semantics as administrator house mode.
|
||
|
||
### `POST /api/integrations/home-assistant/house/preset`
|
||
|
||
Same `{ "preset": "auto|comfort|sleep|away" }` semantics.
|
||
|
||
### `POST /api/integrations/home-assistant/house/power`
|
||
|
||
Same `{ "power": true|false }` semantics.
|
||
|
||
### `POST /api/integrations/home-assistant/zones/{id}/control`
|
||
|
||
Same `ZoneControlPatch` thermostat semantics as the normal zone control endpoint. The internal source is recorded as Home Assistant thermostat control.
|
||
|
||
---
|
||
|
||
## WebSocket
|
||
|
||
### Connection
|
||
|
||
Without administrator authentication:
|
||
|
||
```text
|
||
ws://HOST:8787/ws
|
||
```
|
||
|
||
When `GREE_CONTROLLER_APP_TOKEN` is configured:
|
||
|
||
```text
|
||
ws://HOST:8787/ws?token=APP_TOKEN
|
||
```
|
||
|
||
Generated restricted HA tokens are not WebSocket administrator tokens.
|
||
|
||
### Message envelope
|
||
|
||
Every server event uses:
|
||
|
||
```json
|
||
{
|
||
"event": "device.updated",
|
||
"timestamp": "2026-08-30T06:54:00Z",
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
The first frame is always `bootstrap` with the same payload as `GET /api/bootstrap`, including `control_plan` and `control_plan_revision`, unless bootstrap generation itself fails. If the generic event queue is overrun, the server sends another full `bootstrap` to resynchronize the client instead of silently continuing with missed state.
|
||
|
||
Common live events include:
|
||
|
||
```text
|
||
bootstrap
|
||
control_plan.updated
|
||
device.created
|
||
device.updated
|
||
device.deleted
|
||
devices.discovered
|
||
zone.created
|
||
zone.updated
|
||
zone.deleted
|
||
group.created
|
||
group.updated
|
||
group.deleted
|
||
schedule.created
|
||
schedule.updated
|
||
schedule.deleted
|
||
schedule.template_applied
|
||
automation.created
|
||
automation.updated
|
||
automation.deleted
|
||
flow.created
|
||
flow.updated
|
||
flow.deleted
|
||
settings.application.updated
|
||
settings.gree.updated
|
||
settings.history.updated
|
||
settings.influxdb.updated
|
||
settings.notifications.updated
|
||
settings.night.updated
|
||
settings.home_assistant.updated
|
||
settings.debug.updated
|
||
house.mode_changed
|
||
outdoor.updated
|
||
configuration.imported
|
||
api.request
|
||
gree.frame_received
|
||
gree.frame
|
||
log.created
|
||
```
|
||
|
||
`control_plan.updated` uses `data.revision` plus `data.plan`. Revisions increase only when the materialized plan changes semantically; `generated_at` alone does not create a new revision.
|
||
|
||
`zone.updated` keeps the existing full-zone payload. Regulator passes that only advance the internal `updated_at` heartbeat are deduplicated and do not emit a frame; any actual zone state/sensor/control change still emits the full snapshot. Device polling keeps `device.updated` heartbeats for live `last_seen` UI, but heartbeat-only fields do not invalidate `control-plan`.
|
||
|
||
In `0.13.2`, the bundled Web UI mirrors entity CRUD events directly into its local state. Schedule-template replacement performs a targeted schedule resync, configuration import performs a full bootstrap resync, and runtime automation/device-health changes publish their updated snapshots immediately.
|
||
|
||
In `0.13.3`, logical thermostat targets use `0.1 °C` precision consistently. Temporary Quick Thermostat accepts comma/dot decimal input in the bundled Web UI; hardware-specific GREE target rounding is unchanged.
|
||
|
||
In `0.13.4`, disabled-zone thermostat controls remain visually active because explicit temporary/manual thermostat actions are still allowed while zone automation is disabled. The UI shows this distinction directly on the thermostat card; HTTP and WebSocket contracts are unchanged.
|
||
|
||
In `0.13.5`, the same disabled-zone behavior is explained directly beside the zone enabled/disabled setting, so users see before changing it that disabling a zone stops automation but does not block manual thermostat, temporary thermostat or direct manual control. HTTP and WebSocket contracts remain unchanged.
|
||
|
||
In `0.13.6`, notification settings add `alert_types.sensor_discrepancy`, allowing GREE vs Home Assistant temperature-difference notifications to be disabled independently from thermostat/group control errors. The sensor fallback behavior itself is unchanged.
|
||
|
||
In `0.13.7`, the bootstrap payload also contains all eight redacted settings views. The bundled Web UI applies the same bootstrap mapper for initial HTTP load and WebSocket resynchronization, so a bootstrap frame no longer triggers eight additional settings GET requests.
|
||
|
||
Additional engine/integration events may be introduced without changing the envelope.
|
||
|
||
`api.request` data:
|
||
|
||
```json
|
||
{
|
||
"method": "GET",
|
||
"path": "/api/system/info",
|
||
"status": 200,
|
||
"duration_ms": 2
|
||
}
|
||
```
|
||
|
||
GREE debug events are emitted only when enabled and are intended for diagnostics, not as a stable protocol API.
|
||
|
||
---
|
||
|
||
## Localization endpoints
|
||
|
||
Language files are public so the UI can localize before administrator authentication.
|
||
|
||
### `GET /lang/index.json`
|
||
|
||
Returns the generated catalog of embedded packs.
|
||
|
||
### `GET /lang/{code}.json`
|
||
|
||
Returns one embedded language pack, e.g.:
|
||
|
||
```text
|
||
GET /lang/en.json
|
||
GET /lang/pl.json
|
||
```
|
||
|
||
---
|
||
|
||
## Practical API examples
|
||
|
||
Assume:
|
||
|
||
```bash
|
||
BASE='http://127.0.0.1:8787'
|
||
AUTH='Authorization: Bearer APP_TOKEN'
|
||
```
|
||
|
||
Discover devices:
|
||
|
||
```bash
|
||
curl -X POST "$BASE/api/discovery" -H "$AUTH" -H 'Content-Type: application/json' \
|
||
-d '{"protocol_version":0,"passes":3}'
|
||
```
|
||
|
||
Directly poll a device:
|
||
|
||
```bash
|
||
curl -X POST "$BASE/api/devices/DEVICE_ID/poll" -H "$AUTH"
|
||
```
|
||
|
||
Set a zone target:
|
||
|
||
```bash
|
||
curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: application/json' \
|
||
-d '{"setpoint":22.5}'
|
||
```
|
||
|
||
Return zone to automatic scheduling:
|
||
|
||
```bash
|
||
curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: application/json' \
|
||
-d '{"preset":"auto","clear_local_thermostat_override":true}'
|
||
```
|
||
|
||
Turn the whole managed house off:
|
||
|
||
```bash
|
||
curl -X POST "$BASE/api/house/power" -H "$AUTH" -H 'Content-Type: application/json' \
|
||
-d '{"power":false}'
|
||
```
|
||
|
||
Read current ownership/desired-vs-actual state:
|
||
|
||
```bash
|
||
curl "$BASE/api/control-plan" -H "$AUTH"
|
||
```
|
||
|
||
Read 90 days of zone history:
|
||
|
||
```bash
|
||
curl "$BASE/api/history?scope=zones&zone_id=ZONE_ID&hours=2160" -H "$AUTH"
|
||
```
|
||
|
||
Create a restricted Home Assistant token:
|
||
|
||
```bash
|
||
curl -X POST "$BASE/api/access-tokens" -H "$AUTH" -H 'Content-Type: application/json' \
|
||
-d '{"name":"Home Assistant"}'
|
||
```
|
||
|
||
Use that token:
|
||
|
||
```bash
|
||
curl -H 'Authorization: Bearer gree_controller_RESTRICTED_TOKEN' \
|
||
"$BASE/api/integrations/home-assistant/control-plan"
|
||
```
|
||
|
||
|
||
|
||
## Visual Flow API
|
||
|
||
Flow is the source-of-truth representation for visual schedule/automation logic. `GET /api/flows` lists Flows; `GET /api/flows/:id`, `POST /api/flows`, `PUT /api/flows/:id` and `DELETE /api/flows/:id` manage them. A Flow payload contains `name`, `enabled`, optional `draft`, optional `description`, `nodes` and `edges`. `draft=true` is reserved for work-in-progress graphs: the backend forces them disabled and stores no generated schedules or automations.
|
||
|
||
Saving an executable Flow validates the DAG and compiles it atomically. If the editor receives HTTP 400 during normal save, it can offer to retry with `draft=true`; draft validation preserves the editable graph while allowing missing actions or unfinished semantic wiring. Drafts never execute, and converting an existing Flow to a draft atomically removes its previously generated outputs. A thermostat action driven only by one weekday block and one time-range block is emitted as a native schedule when its settings are schedule-compatible. More complex graphs are emitted as Flow-triggered automations. Generated schedules and automations expose `flow_id` / `flow_node_id`, use stable names in the form `flow-<stable-unique-id>`, and cannot be edited or deleted through their legacy endpoints; edit the owning Flow instead.
|
||
|
||
Condition blocks support weekday, time/date ranges, optional 5-field CRON, application Night mode, outdoor/device/zone temperatures, house/device/zone/group state, arbitrary Home Assistant state/numeric/attribute/availability sources, rolling mean/median, oscillation detection, and `shared_input` references. Stateful gate blocks include `stable_for`, `delay`, `state_duration` (`min_seconds`, optional `max_seconds`), `on_change` (`mode: result|value`) and `rate_limit` (`max_count`, `period_seconds`). `on_change` establishes a baseline on its first observation and does not fire immediately. `rate_limit` must feed an action directly and consumes quota only after a successful action execution. Shared inputs are stored in `home_assistant.flow_inputs` and resolve dynamically at evaluation time. Shared inputs store reusable value sources only. They never store a comparison operator or threshold. For comparison-capable source kinds, each `shared_input` Flow node defines its own `operator` and `value`. `PUT /api/settings/home-assistant` rejects shared-input configs that contain an `operator`, and rejects comparison `value` fields for comparison-capable source kinds. Logic blocks support AND, OR and NOT. Action blocks target thermostat zones, GREE devices, climate groups or a generic Home Assistant service. Direct GREE actions map to the existing `DeviceCommand` fields including fan, swing, quiet, turbo, light, air, xfan, health and sleep; ownership rules may suppress fields that would fight an active thermostat. Flow group actions use the existing group controller and support power, house/heat/cool mode, auto/comfort/sleep/away and the existing custom group target.
|
||
|
||
## Compressor protection queue
|
||
|
||
Runtime settings expose `compressor_protection_enabled` and `compressor_protection_seconds` (30–1800; default 180). While enabled, thermostat starts and Heat/Cool reversals that fall inside the protection window are represented on the owning zone by `compressor_pending_action`, `compressor_pending_since`, and `compressor_pending_until`.
|
||
|
||
- `POST /api/zones/:id/compressor-queue/cancel` cancels the currently pending compressor-protection task for one thermostat.
|
||
- `POST /api/compressor-queue/cancel-all` cancels all currently pending compressor-protection tasks.
|
||
|
||
Cancellation suppresses the same pending intent until a new explicit thermostat/group/house command re-arms it, a scheduled Temporary Quick Thermostat session takes ownership, or a different mode/target creates a new intent. Safety OFF commands are not delayed by compressor protection. Direct technical device commands remain immediate manual-control operations.
|
||
|
||
### Flow portability and diagnostics
|
||
|
||
`GET /api/flows/:id/export` returns a versioned `gree-controller-flow` JSON document containing only the source graph. Generated schedules/automations are not exported.
|
||
|
||
`POST /api/flows/import` accepts either that envelope or a direct Flow source payload, creates a new Flow ID/revision, and preserves draft state. Executable imports are validated and recompiled; draft imports remain disabled with no generated outputs.
|
||
|
||
`POST /api/flows/simulate` accepts `{ flow, flow_id?, at?, overrides?, log? }`. `at` is RFC3339. `overrides` maps Flow node IDs to simulated values. The endpoint validates and compiles the graph, evaluates every action and returns per-node traces plus `matched`, `would_execute` and `blocked_reason` (for example disabled zone/device, manual/local/temporary thermostat ownership, thermostat-output conflict or disabled Flow). It never mutates thermostat/device/group/schedule/automation state. Home Assistant read/attribute/parse failures evaluate safely as false and are visible in the trace instead of accidentally satisfying `NOT`/`neq` logic.
|
||
|
||
`GET /api/flows/:id/logs?limit=100` returns events associated with the Flow or its generated automations.
|
||
|
||
Existing Flow updates require `expected_revision`. A mismatched revision returns HTTP 409 to prevent stale editor tabs from overwriting newer graphs. Source Flow plus generated outputs are replaced atomically in one database transaction while configuration/automation/schedule/thermostat-cycle operations are serialized.
|
||
|
||
Additional Flow condition blocks are `house_mode`, `device_state`, `zone_state`, `group_state`, `night_mode`, `ha_attribute`, `ha_available`, `constant` and `shared_input`, alongside weekday/time/date, temperature, Home Assistant state/numeric and AND/OR/NOT blocks. `device_state` can inspect enabled/online/power/mode/fan/swing/quiet/turbo/light/air/xfan/health/sleep state. The editor ships 37 categorized editable templates covering comfort, energy, safety, night, reliability, Home Assistant heat-source coordination and advanced multi-branch logic. The template UI adds search, favorites/recent history, a graph preview and runtime requirement checks; shared inputs expose usage links and HA-backed inputs can be tested against live entity state.
|