Metadata-Version: 2.4
Name: gree-rc-encoder
Version: 0.1.2
Summary: Encode and decode Gree air-conditioner IR remote control signals
Author-email: Alexey Cluster <cluster@cluster.wtf>
License: GPL-3.0-or-later
Project-URL: Homepage, https://github.com/ClusterM/gree-rc-encoder
Project-URL: Repository, https://github.com/ClusterM/gree-rc-encoder
Project-URL: Issues, https://github.com/ClusterM/gree-rc-encoder/issues
Project-URL: PyPI, https://pypi.org/project/gree-rc-encoder/
Keywords: gree,ir,remote,air-conditioner,lirc
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Home Automation
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Dynamic: license-file

# gree-rc-encoder

Python library to encode and decode Gree air-conditioner IR remote-control
signals (pulse timings ↔ high-level state).

- **GitHub:** https://github.com/ClusterM/gree-rc-encoder
- **PyPI:** https://pypi.org/project/gree-rc-encoder/
- **License:** [GPLv3](LICENSE)

## Install

From [PyPI](https://pypi.org/project/gree-rc-encoder/):

```bash
pip install gree-rc-encoder
```

From a local checkout (editable, with test deps):

```bash
pip install -e ".[dev]"
```

## Quick start

```python
from gree_rc import (
    GreeState,
    Mode,
    FanSpeed,
    encode_control,
    encode_temperature,
    decode,
    to_ir_ctl,
)

state = GreeState(
    is_on=True,
    mode=Mode.COOL,
    temperature=24,
    fan_speed=FanSpeed.AUTO,
    light_on=True,
)

timings = encode_control(state)       # list[int] durations in microseconds
print(to_ir_ctl(timings))             # "+9000 -4500 +655 ..." for ir-ctl

result = decode(timings)
assert result.state.temperature == 24

# I Feel: remote sensor reading (sent periodically while ifeel_mode is on)
temp_frame = encode_temperature(27)
assert decode(temp_frame).temperature == 27
```

## API overview

| Function | Purpose |
|----------|---------|
| `encode_control(state)` | `GreeState` → 559 timing values |
| `decode_control(timings)` | timings / ir-ctl string → `GreeState` |
| `encode_temperature(temp)` | I Feel ambient °C (0–255) → 35 timing values |
| `decode_temperature(timings)` | → int °C |
| `decode(timings)` | auto-detect control / temperature / combined → `DecodeResult` |
| `to_ir_ctl` / `from_ir_ctl` | convert to/from ir-ctl pulse strings |
| `encode_gree` / `parse_gree` | nibble-level frame without IR timings |

The library performs no I/O and does not print; raise `GreeEncodeError`,
`GreeDecodeError`, or `GreeValidationError` on failure.

## GreeState fields

Fields marked **model-dependent** exist only on some units (the remote protocol
still carries the bits; unsupported hardware ignores them). Per the manual:
`wifi_on` / `wifi_reset`, `cold_plasma`, `uvc_lamp`, `soft_sound`, `fast_cool`,
`start_auto_clean`, `fresh_air`.

| Field | Meaning |
|-------|---------|
| `is_on` | Unit power on/off |
| `mode` | `cool` / `dry` / `fan_only` / `heat` / `auto` |
| `temperature` | Setpoint, 16–31.5 in 0.5 steps |
| `units` | `"C"` or `"F"` |
| `fan_speed` | `auto`, `speed_1`…`speed_5`, `silent`, `turbo` |
| `x_fan` | X-FAN: after off in Cool/Dry, indoor fan keeps running briefly to dry the evaporator. Not available in Auto/Fan/Heat. Defaults off after the unit is powered. |
| `cold_plasma` | **Model-dependent.** Cold Plasma health function. Requires `is_on`. |
| `uvc_lamp` | **Model-dependent.** UVC lamp health function. Requires `is_on`. Both may be on together. |
| `humidity_level_set` | Manual humidity control via the Humidity button (Cool/Dry only). |
| `humidity_level` | Target humidity % when set manually (`None` if unused). Cool: 40–80; Dry: 30–70; multiples of 5. |
| `humidity_level_auto` | Humidity button “auto” mode — meaning depends on mode: in **Cool**, smart dehumidification (**Ao**); in **Dry**, continuous dehumidification (**Co**). Mutually exclusive with `humidity_level_set`. |
| `vertical_swing_mode` | Vertical louvre: off, full swing, or fixed positions 1–5 |
| `horizontal_swing_mode` | Horizontal louvre: off, full/double swing, positions, or `pos_1_5` |
| `swing_started` | **Poorly understood** (empirical). Changes when automatic movement of vertical or horizontal swing is turned on or off (louvre moving, not fixed). Value is the result of that last toggle: `True` ≈ some swing was started, `False` ≈ some swing was stopped. |
| `ifeel_mode` | I Feel: remote sends ambient-temperature frames from its built-in sensor (see Protocol notes). |
| `timer_set` | Timer armed (`True` iff `timer_mode` is not `disabled`) |
| `timer_mode` | `disabled` / `off` (turn off later) / `on` (turn on later) |
| `timer_hours_set` | Timer duration in hours (0.5 steps) |
| `timer_off_left_mins` / `timer_on_left_mins` | Remaining minutes for off/on timers (wire fields) |
| `wifi_on` | **Model-dependent.** Wi-Fi module enabled |
| `wifi_reset` | **Model-dependent.** **One-shot**: `True` only in a single frame; clears Wi-Fi settings so the network can be reconfigured |
| `soft_sound` | **Model-dependent.** Manual name: *Volume control of IDU Buzzer* — quieter indoor-unit beeps |
| `power_down_level` | *Power limiting*: `0` = off, `1` = level 1, `2` = level 2 (stricter than 1). Cleared when the remote turns the unit off. With two indoor units, the outdoor unit follows the stricter limit. Independent of `energy_saving`. |
| `energy_saving` | Energy-saving (**SE** on the remote): Cool only; requires `fan_speed=auto` and `sleep_mode_level=0`. The unit applies a factory energy-saving setpoint — the encoded `temperature` is still sent but ignored. Independent of `power_down_level`. |
| `light_on` / `light_auto` | Display light on; auto dimming (`light_auto` requires `light_on`) |
| `fresh_air` | **Model-dependent.** Manual name: *two-way ventilation*; fan speed follows the AC / fan button |
| `sleep_mode_level` | `0` = cancel; `1`/`2` = preset sleep curves; `3` = DIY hourly schedule (see Protocol notes; schedule payload not fully implemented) |
| `display_indoor_temperature` / `display_indoor_humidity` | What the indoor display shows (mutually exclusive) |
| `reset_clean_reminder` | **One-shot**: toggle/reset the filter clean-reminder function |
| `fast_cool` | **Model-dependent.** Fast cool (Cool only): temporary 25°C or 16°C boost for ~20 minutes, then restore previous state |
| `start_auto_clean` | **Model-dependent.** **One-shot**: start auto clean; unit must be off |

## Hardware demo

Real receive/transmit via `ir-ctl` lives in
[`examples/ir_ctl_demo.py`](examples/ir_ctl_demo.py):

```bash
python3 examples/ir_ctl_demo.py receive
python3 examples/ir_ctl_demo.py send --dry-run
python3 examples/ir_ctl_demo.py temperature --dry-run
```

## Tests

```bash
pytest
```

## Protocol notes

### Control frame

A typical control frame is four subsignals (139 timings each) separated by
~40 ms spaces. Each subsignal carries 16 nibbles (LSB-first bits), a `0,1,0`
delimiter with a 20 ms space in the middle, a **marker** nibble, and a CRC
nibble.

Markers identify the logical signal index (0..4). Captures may include more
than four subsignals and may list them in any order; the decoder places each
subsignal into a fixed 5-slot array by marker before parsing `GreeState`.

### I Feel (ambient temperature)

When **I Feel** is enabled (`GreeState.ifeel_mode`), the remote periodically
sends a short 35-sample frame with a different leading pulse. That frame
carries the reading from the remote’s built-in temperature sensor so the
indoor unit can hold the setpoint at the remote’s location rather than only
at the AC.

### Sleep mode level 3

`sleep_mode_level` values 1–2 are fixed sleep curves. Level **3** lets the
user program an automatic temperature change every hour for up to eight
hours. That schedule needs an extra subsignal (logical slot 4 / ~699-sample
capture). This library decodes the level flag and will accept/arrange the
extra subsignal by marker, but **encoding and full parsing of the eight
hourly setpoints is not implemented yet**.
