Metadata-Version: 2.4
Name: greeclimate
Version: 4.0.0
Summary: Discover, connect and control Gree based minisplit systems
Home-page: https://github.com/cmroche/greeclimate
Author: Clifford Roche
Author-email: 
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Requires-Dist: ifaddr~=0.2.0
Requires-Dist: pycryptodome~=3.24
Dynamic: author
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license-file
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

![Python package](https://github.com/cmroche/greeclimate/workflows/Python%20package/badge.svg)

## Gree Climate

Discover, connect and control Gree based mini-split systems.

**greeclimate** is a ***fully async*** Python3 based package for controlling Gree based ACs and heat pumps. Gree is a common brand for mini-split systems and is licensed and resold under many product names. This module should work for any device that also works with the Gree+ app, but has been tested on

- Proklima mini-splits units
- Trane mini-split heat pump (4TXK38)

_If you have tested and know of others systems that work, please fork and submit a PR with the make and model_

**Based on the following work**

- [Gree Remote by tomikaa87](https://github.com/tomikaa87/gree-remote)

## Getting the package

The easiest way to grab **greeclimate** is through PyPI
`pip3 install greeclimate`

## Use Gree Climate

Discover devices on the LAN and bind immediately after scanning. Use `create_device()` for exact-MID selection of an `AirConditionerDevice`, `DehumidifierDevice`, `AirToWaterDevice` (`9300` only), or neutral `Device`.

```python
import asyncio

from greeclimate.device_factory import create_device
from greeclimate.discovery import Discovery


async def main():
    discovery = Discovery()
    try:
        for device_info in await discovery.scan(wait_for=5):
            device = create_device(device_info)
            try:
                await device.bind()
                print(device_info.name, device_info.ip)
            finally:
                device.close()
    finally:
        discovery.close()


asyncio.run(main())
```

The following snippets run inside `main()`; device-control snippets go after binding and before closing `device`.

### Migrating typed controls

Version 4.0.0 establishes the compatibility boundary for the changes below and the [firmware-profile constructor migration](docs/HIDS.md#firmware-code-traits-and-limitations). Version 3.7.0 shipped these breaking changes under a minor version and has been yanked from PyPI.

`Device` now owns transport, binding, status polling, and raw properties only. Replace `Device(device_info)` with `create_device(device_info)` for typed controls selected from a registered exact MID. Selection is fixed at construction: later MID/model metadata changes or a reported firmware HID never change the family.

For a known appliance whose MID is missing or unregistered, construct its family directly:

```python
from greeclimate.air_conditioner import AirConditionerDevice
from greeclimate.dehumidifier import DehumidifierDevice
from greeclimate.air_to_water import AirToWaterDevice

device = AirConditionerDevice(device_info)  # Or DehumidifierDevice / AirToWaterDevice.
```

Unknown MIDs produce a neutral `Device` and default to identity-only `["hid"]` polling. Direct AC instances with an unknown MID poll `Pow`, `Mod`, `SetTem`, `TemUn`, `TemRec`, and `TemSen`; direct dehumidifiers use their own humidity/control baseline. Known MID polling lists are preserved even for untyped families. See [MID polling contracts](docs/MIDS.md).

The AC control examples below require an `AirConditionerDevice`; the humidity example requires a `DehumidifierDevice`, and the air-to-water example requires an `AirToWaterDevice`. Optional controls still depend on the appliance. Neutral devices retain the raw API example below.


Scan a known IP address instead of broadcasting on the LAN.

```python
from ipaddress import IPv4Address

devices = await discovery.scan(
    wait_for=5, target_addresses=[IPv4Address("192.0.2.10")]
)
```

Discover a device listening on UDP port 7000, including loopback addresses.

```python
discovery = Discovery(allow_loopback=True)
devices = await discovery.scan(wait_for=5)
```

Manually poll for updated device state.

```python
await device.update_state()
```

Request exact status columns and access raw protocol properties by name (or a string-valued enum).
Explicit columns are sent unchanged as one query, without adding `hid`; raw writes do not validate appliance support, but typed ATW instances still enforce their send-time command guards.
The exact name `self` is reserved and rejected before staging a property or sending a status query.

```python
await device.update_state(["Pow", "Lig"])
# Read the cache after a DATA reply arrives.
print(device.get_property("Pow"))
device.set_property("Lig", 0)
await device.push_state_update()
```

For an AC, cool to 25°C and send updates to the device.

```python
from greeclimate.device import Mode, TemperatureUnits

device.power = True
device.mode = Mode.Cool
device.temperature_units = TemperatureUnits.C
device.target_temperature = 25
await device.push_state_update()
```

Set a Fahrenheit target by selecting units before setting the temperature.

```python
from greeclimate.device import TemperatureUnits

device.temperature_units = TemperatureUnits.F
device.target_temperature = 77
await device.push_state_update()
```

Set fan speed and swing positions.

```python
from greeclimate.device import FanSpeed, HorizontalSwing, VerticalSwing

device.fan_speed = FanSpeed.Auto
device.horizontal_swing = HorizontalSwing.FullSwing
device.vertical_swing = VerticalSwing.FullSwing
await device.push_state_update()
```

Enable optional features where supported by the device.

```python
device.light = True
device.xfan = True
device.quiet = True
await device.push_state_update()
```

Use capability evidence to offer an optional AC control without treating unknown as unsupported.

```python
if device.capabilities["quiet"] is True:
    device.quiet = True
    await device.push_state_update()
```

`capabilities` is a fresh, immutable 32-key snapshot: `True` means declared protocol support, `False` means outside the typed family's API or explicitly excluded by firmware, and `None` means not established. Neutral devices report all `None`. Reading it sends no probe; it neither gates setters nor guarantees physical effects or available readings. See [evidence and precedence](docs/MIDS.md#capability-evidence).

Request a muted power-on command where supported; wait for the reported firmware HID first.

```python
from greeclimate.network import Response

hid_received = asyncio.Event()

def on_data(data):
    if data.get("hid"):
        hid_received.set()

device.add_handler(Response.DATA, on_data)
try:
    await device.request_version()
    await asyncio.wait_for(hid_received.wait(), timeout=5)
finally:
    device.remove_handler(Response.DATA, on_data)

device.buzzer = False
device.power = True
await device.push_state_update()
```

`buzzer` is a local preference: changing it alone sends nothing. Buzzer fields are sent only when explicitly enabled by a matching [firmware-code profile](docs/HIDS.md#firmware-code-traits-and-limitations); absent, unknown or unsupported profiles add none. Recognized fields do not guarantee physical muting.

Set a `DehumidifierDevice`'s target relative humidity (not an AC dry-mode setpoint).

```python
device.target_humidity = 45
await device.push_state_update()
```

Set an air-to-water heating-water target only after receiving Celsius status.

```python
from greeclimate.air_to_water import AirToWaterDevice
from greeclimate.network import Response

if not isinstance(device, AirToWaterDevice):
    raise TypeError("This example requires an air-to-water heat pump")
celsius_received = asyncio.Event()

def on_atw_data(data):
    if device.temperature_units == 0:
        celsius_received.set()

device.add_handler(Response.DATA, on_atw_data)
try:
    await device.update_state()
    await asyncio.wait_for(celsius_received.wait(), timeout=5)
finally:
    device.remove_handler(Response.DATA, on_atw_data)

device.heating_water_target_temperature_c = 35
await device.push_state_update()
```

ATW default polling preserves the `9300` preset plus sensor fields in deduplicated order, using queries of at most 20 columns to fit the firmware's encrypted request limit. Explicit `update_state(columns)` remains one exact query and must fit the device limits. Mode changes require a **received off** power state; sending or staging power-off alone is not enough, and mode plus power-on must be separate commands. Target writes require **received Celsius** and are revalidated at send time. Cooling resolves to wire value 5 for firmware code `362001066763`; explicit `cooling_mode_value=0`, `5`, or `None` overrides automatic resolution (`None` disables cooling). See [ATW controls](docs/MIDS.md#air-to-water-controls).

Turn the device off, then request a fresh status update.

```python
device.power = False
await device.push_state_update()
await device.update_state()
```

Automatic discovery uses IPv4 subnet broadcasts via `ifaddr`, skipping IPv6, loopback, and non-loopback `/31` and `/32` networks. `allow_loopback=True` adds loopback host addresses; non-empty `target_addresses` bypasses interface enumeration, while `None` or `[]` uses it.

Legacy `bcast_ifaces` (`scan`) and `broadcastAddrs` (`search_devices`) keywords and positional calls remain supported; combining an alias with non-`None` `target_addresses` raises `ValueError`.

## Use Gree Emulator

Run these commands from the repository root with Python 3.10+ and dependencies installed; each server runs until Ctrl-C.

List supported simulated profiles and their exact MID values.

```bash
python -m gree_emulator --list-profiles
```

Run one simulated AC on loopback, with stable identity across restarts.

```bash
python -m gree_emulator --profile sim-w04n-v17 --mid 10001 --seed demo --listen 127.0.0.1
```

Run three independently controlled simulated ACs on the same endpoint.

```bash
python -m gree_emulator --profile sim-w04n-v17 --mid 10001 --count 3 --seed demo --listen 127.0.0.1
```

Run the supplied mixed-device configuration, including gateways and child devices.

```bash
python -m gree_emulator --config examples/emulator-devices.json
```

Run the simulated air-to-water heat pump (the `9301` and `9302` profiles remain raw-client families).

```bash
python -m gree_emulator --profile sim-w06a-p3-v6.04-9300 --mid 9300 --seed demo --listen 127.0.0.1
```

Expose the simulated ACs to LAN discovery on UDP port 7000, on a trusted test network only.

```bash
python -m gree_emulator --profile sim-w04n-v17 --mid 10001 --count 3 --seed demo --listen 0.0.0.0 --port 7000
```

Capture packet logs on stderr while keeping startup announcements on stdout; scrub identities, addresses, and bind exchanges before sharing.

```bash
python -m gree_emulator --profile sim-w04n-v17 --mid 10001 --seed demo --listen 127.0.0.1 2> emulator-packets.log
```

These are simulated appliances, not complete firmware emulations. Unsupported command fields reject the entire batch; check a fresh status poll rather than treating a successful send as accepted control. See the [emulator contracts](docs/MIDS.md#emulator-contracts) for configuration, protocol differences, and model limits.

## Device and protocol reference

- [Known MIDs](docs/MIDS.md): current polling contracts and gateway observations.
- [Known HIDs](docs/HIDS.md): firmware-string segments, matching rules, and sourced firmware codes.
- [Cloud-only firmware findings](docs/CLOUD_ONLY_FIRMWARE.md): inspected LAN/cloud boundaries, not upgrade recommendations.

## Debugging

Maybe the reason you're here is that you're working with Home Assistant and your device isn't being detected.

There are a few tools to help investigate the various compatibility problems that Gree based devices present.

Below is a series of tests, please run them and use their output in issue reports. Additionally using [Wireshark](https://www.wireshark.org) or tcpdump to capture the network traffic can greatly assist in investigations.

### Setup

This presumes you have python installed

```bash
pip install -r requirements.txt
```

### Getting some basic information about your network
#### Linux / OSX
```bash
sudo route -n
sudo ifconfig
```
#### Windows command line
```
route print -4
ipconfig
```

### Running the discovery tests

First test is to check the response of devices when trying to discovery them, writes the results to **discovery_results.txt**. Use [Wireshark](https://www.wireshark.org) here if you can.

```bash
python gree.py --discovery > discovery_results.txt
```
