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
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.
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. 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:
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.
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.
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.
discovery = Discovery(allow_loopback=True)
devices = await discovery.scan(wait_for=5)
Manually poll for updated device state.
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.
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.
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.
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.
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.
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.
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.
Request a muted power-on command where supported; wait for the reported firmware HID first.
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; 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).
device.target_humidity = 45
await device.push_state_update()
Set an air-to-water heating-water target only after receiving Celsius status.
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.
Turn the device off, then request a fresh status update.
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.
python -m gree_emulator --list-profiles
Run one simulated AC on loopback, with stable identity across restarts.
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.
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.
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).
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.
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.
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 for configuration, protocol differences, and model limits.
Device and protocol reference
- Known MIDs: current polling contracts and gateway observations.
- Known HIDs: firmware-string segments, matching rules, and sourced firmware codes.
- Cloud-only firmware findings: 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 or tcpdump to capture the network traffic can greatly assist in investigations.
Setup
This presumes you have python installed
pip install -r requirements.txt
Getting some basic information about your network
Linux / OSX
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 here if you can.
python gree.py --discovery > discovery_results.txt
Metadata
Release files for greeclimate 4.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| greeclimate-4.0.0.tar.gz | 126.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| greeclimate-4.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 208.7 kB
Release files / greeclimate-4.0.0.tar.gz
| Download URL | greeclimate-4.0.0.tar.gz |
|---|---|
| Size | 126.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2550805fc690a6605f973f04efb9badc34e04030789d8fc0ed14097443488c25
|
|
BLAKE2b-256 checksum How to use checksums |
ac6d6ee7dab04991f165c46e4cf16b57e6d98d8033bfd876c8a98d3bfd6c59ed
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logRelease files / greeclimate-4.0.0-py3-none-any.whl
| Download URL | greeclimate-4.0.0-py3-none-any.whl |
|---|---|
| Size | 81.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d573b1bf4a1ac84e3a5aaec113a596cf2d45145101efb59ad2eb686322c85e3a
|
|
BLAKE2b-256 checksum How to use checksums |
e0c39afc95f2ffe440e15cd46bd1ce4f969ddcc89e96a19e003e268b80f670c2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logSecurity for greeclimate 4.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for greeclimate-4.0.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.