{"id":51290556,"url":"https://github.com/jeeyo/esp32-ir-ac-thermostat","last_synced_at":"2026-06-30T09:38:55.649Z","repository":{"id":353623757,"uuid":"1189531066","full_name":"jeeyo/esp32-ir-ac-thermostat","owner":"jeeyo","description":"ESPHome-compatible thermostat using only IR signals for non-smart air conditioners","archived":false,"fork":false,"pushed_at":"2026-05-02T06:55:23.000Z","size":380,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-02T08:35:20.791Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jeeyo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-23T12:17:46.000Z","updated_at":"2026-05-02T06:51:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jeeyo/esp32-ir-ac-thermostat","commit_stats":null,"previous_names":["jeeyo/esp32-ir-remote","jeeyo/esp32-ir-ac-thermostat"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/jeeyo/esp32-ir-ac-thermostat","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeeyo%2Fesp32-ir-ac-thermostat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeeyo%2Fesp32-ir-ac-thermostat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeeyo%2Fesp32-ir-ac-thermostat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeeyo%2Fesp32-ir-ac-thermostat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jeeyo","download_url":"https://codeload.github.com/jeeyo/esp32-ir-ac-thermostat/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeeyo%2Fesp32-ir-ac-thermostat/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34961549,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-30T02:00:05.919Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-30T09:38:55.025Z","updated_at":"2026-06-30T09:38:55.643Z","avatar_url":"https://github.com/jeeyo.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ESP32 IR Remote — Smart AC Controller\n\nESPHome firmware for M5StickC-Plus that controls an AC unit via IR with acoustic beep confirmation, and acts as a standalone smart thermostat using the onboard temperature sensor. Works with any AC that responds to an IR remote and emits a confirmation beep. Exposed as a full `climate` entity in Home Assistant — no cloud, no subscription.\n\n## Features\n\n- **Smart Thermostat** — maintains a target temperature by toggling the AC on/off; hysteresis and compressor-protection timers built in\n- **Acoustic Confirmation** — listens for the AC's confirmation beep after each IR command; retries up to 3× before reporting failure\n- **Passive Monitoring** — detects beeps from a physical remote and syncs Home Assistant state automatically\n- **IR Learning** — capture raw IR codes from your original remote directly on the device\n- **Beep Calibration** — sweep 1–8 kHz to find your AC's exact beep frequency and amplitude\n- **Temperature / Humidity / Pressure** — onboard ENV HAT sensors exposed to Home Assistant\n- **On-device Display** — shows AC state, thermostat mode + setpoint, current temperature, and status\n\n## Hardware\n\n### Bill of Materials\n\n| Item | SKU / Notes |\n|------|-------------|\n| M5StickC-Plus | ESP32-PICO, built-in display + PDM mic |\n| M5Stack IR Unit | U002 — IR TX + demodulating RX in one Grove module |\n| M5Stack ENV III HAT | SHT30 temp/humidity + QMP6988 pressure — **required for thermostat** |\n\nThe ENV III HAT attaches directly to the M5StickC-Plus HAT port (no wiring). The IR Unit connects via the Grove port.\n\n### Wiring: M5Stack IR Unit → Grove Port\n\n```\nIR Unit (Grove)    M5StickC-Plus\n───────────────    ─────────────\n  Yellow (TX) ──── GPIO32 (Grove pin 1)\n  White  (RX) ──── GPIO33 (Grove pin 2)\n  Red   (5V)  ──── 5V\n  Black (GND) ──── GND\n```\n\n---\n\n## Quick Start — Flash Pre-built Firmware\n\nNo toolchain needed. Download and flash in 2 minutes.\n\n1. Download the latest `ac-remote.bin` from [GitHub Releases](https://github.com/jeeyo/esp32-ir-ac-thermostat/releases/latest)\n2. Open [ESPHome Web Installer](https://web.esphome.io/) in Chrome or Edge\n3. Click **Install** → select the `.bin` file → connect your M5StickC-Plus via USB-C\n4. On first boot, the device exposes a WiFi network named **AC-Remote-Fallback** (password `fallback123`). Connect to it with your phone; a captive portal opens where you enter your home WiFi credentials\n5. Once the device joins your network, open Home Assistant → **Settings → Devices \u0026 Services → Add Integration → ESPHome**\n6. Enter the device IP or hostname `ac-remote.local`. On first adoption, HA generates an API encryption key and stores it\n\n\u003e After adopting, the device shows up with all entities. You'll need to learn your IR codes before the thermostat can actually control the AC — see [Learn IR Codes](#learn-ir-codes) below.\n\u003e\n\u003e The pre-built firmware does not hard-code WiFi, API encryption, or OTA credentials — those are configured post-install via the captive portal and Home Assistant. If you want to bake them in at build time, see [Setup from Source](#setup-from-source).\n\n---\n\n## Setup from Source\n\nBuild your own firmware with WiFi / API / OTA credentials baked in. **You do not need to clone this repo** — a small wrapper YAML pulls everything from GitHub.\n\nYou only create two files: `secrets.yaml` and `my-ac-remote.yaml`.\n\n### 1. Install ESPHome\n\n```bash\npip install esphome\n```\n\n### 2. Pick a release tag\n\nBrowse [Releases](https://github.com/jeeyo/esp32-ir-ac-thermostat/releases) and pick the version you want to build (e.g. `v0.1.0`). Use the same tag in **both** places below so the upstream YAML and the bundled `beep_detector` component come from the same commit.\n\n### 3. Create `secrets.yaml`\n\n```yaml\nwifi_ssid: \"YourWiFi\"\nwifi_password: \"YourWiFiPassword\"\napi_encryption_key: \"base64-32-bytes\"\nota_password: \"your-ota-password\"\n```\n\nGenerate the API key:\n\n```bash\npython3 -c \"import secrets, base64; print(base64.b64encode(secrets.token_bytes(32)).decode())\"\n```\n\n`secrets.yaml` should stay out of version control — add it to `.gitignore` if you keep your wrapper config in a repo.\n\n### 4. Create `my-ac-remote.yaml`\n\nThis is the only config file you build with. It pulls `ac-remote.yaml` and the `beep_detector` component straight from GitHub at the tag you picked, and layers your secrets on top.\n\n```yaml\nsubstitutions:\n  # Tell the upstream config to fetch beep_detector from GitHub at the same tag.\n  beep_detector_source: github://jeeyo/esp32-ir-ac-thermostat@v0.1.0\n  ota_password: !secret ota_password\n\npackages:\n  upstream:\n    url: https://github.com/jeeyo/esp32-ir-ac-thermostat\n    ref: v0.1.0\n    files: [ac-remote.yaml]\n    refresh: 1d\n\nwifi:\n  ssid: !secret wifi_ssid\n  password: !secret wifi_password\n\napi:\n  encryption:\n    key: !secret api_encryption_key\n```\n\nThe wrapper:\n\n- **`packages.upstream`** pulls `ac-remote.yaml` from GitHub at the chosen tag.\n- **`substitutions.beep_detector_source`** redirects the upstream's local-path component reference to GitHub, so no clone is needed.\n- **`substitutions.ota_password`** fills in the OTA password (the upstream defaults to empty).\n- **`wifi`** / **`api`** are dict-merged with the upstream blocks: your station credentials and API encryption key get added without removing the captive-portal AP fallback.\n\n\u003e Bump the tag in **both** `beep_detector_source` and `packages.upstream.ref` together when you want to upgrade.\n\n### 5. Build and flash\n\nUSB:\n\n```bash\nesphome run my-ac-remote.yaml\n```\n\nOTA (subsequent updates, no cable needed):\n\n```bash\nesphome run my-ac-remote.yaml --device ac-remote.local\n```\n\nESPHome downloads the upstream YAML and the `beep_detector` source on first build and caches them; pass `--cache off` if you ever need to force a re-fetch.\n\n### Developer / contributor build\n\nIf you're modifying this repo itself, clone it and build `ac-remote.yaml` directly — the substitutions default to the local `components/` path, so no wrapper is needed. Drop `wifi:` / `api:` / `ota_password:` overrides into a sibling `secrets.yaml` and patch the YAML, or create a wrapper that points `packages.upstream: !include ac-remote.yaml` at the local file.\n\n---\n\n## Learn IR Codes\n\nThe firmware ships with **placeholder IR codes**. You must replace them with codes learned from your actual AC remote before the AC will respond.\n\n1. Short-press **Button B** — display shows `IR LEARN`\n2. Point your AC remote at the IR Unit and press the **power ON** button\n3. Open ESPHome logs (`esphome logs ac-remote.yaml`) and copy the `raw:` array\n4. Paste it into `ac-remote.yaml` under `send_ac_on_attempt` → `remote_transmitter.transmit_raw: code:`\n5. Repeat for the **power OFF** button → paste into `send_ac_off_attempt`\n6. Press **Button B** again to exit IR Learn mode\n7. Reflash: `esphome run ac-remote.yaml`\n\n---\n\n## Calibrate Beep Detection\n\nIf the thermostat never confirms commands (check `binary_sensor.ac_beep_confirmed`), the beep detector needs calibrating for your specific AC unit and room.\n\n1. Long-press **Button A** for 3 seconds — display shows `CALIBRATE`\n2. Use the physical remote to trigger your AC so it beeps\n3. Wait for the 10-second calibration window to complete\n4. Open logs and note:\n   - `Peak frequency` → update `target_frequency` in `beep_detector:` block\n   - `Suggested amplitude_min` / `amplitude_max` → update those fields\n5. Reflash with updated values\n\n---\n\n## Adopting in Home Assistant\n\n### Step 1 — Add the ESPHome Integration\n\n1. **Settings → Devices \u0026 Services → Add Integration → ESPHome**\n2. Enter `ac-remote.local` (or device IP)\n3. Enter the API encryption key from your `secrets.yaml`\n4. The device appears with ~12 entities across sensors, switches, binary sensors, and a climate entity\n\n### Step 2 — Add a Thermostat Card to Lovelace\n\nPaste this into a dashboard card (YAML mode):\n\n```yaml\ntype: thermostat\nentity: climate.ac_thermostat\nname: AC Thermostat\n```\n\nOr use the visual card editor: **Add Card → Thermostat → Entity: climate.ac_thermostat**.\n\nThe card lets you set target temperature and switch between `off` and `cool` modes. The device handles the rest.\n\n### Step 3 — Example Automations\n\n**Away mode — raise setpoint when nobody is home:**\n\n```yaml\nautomation:\n  - alias: \"AC away mode\"\n    trigger:\n      - platform: state\n        entity_id: group.household\n        to: not_home\n    action:\n      - service: climate.set_temperature\n        target:\n          entity_id: climate.ac_thermostat\n        data:\n          temperature: 28\n          hvac_mode: cool\n```\n\n**Night schedule — lower setpoint at bedtime:**\n\n```yaml\nautomation:\n  - alias: \"AC night cooling\"\n    trigger:\n      - platform: time\n        at: \"22:00:00\"\n    action:\n      - service: climate.set_temperature\n        target:\n          entity_id: climate.ac_thermostat\n        data:\n          temperature: 22\n          hvac_mode: cool\n\n  - alias: \"AC morning off\"\n    trigger:\n      - platform: time\n        at: \"07:00:00\"\n    action:\n      - service: climate.set_hvac_mode\n        target:\n          entity_id: climate.ac_thermostat\n        data:\n          hvac_mode: \"off\"\n```\n\n**Alert on IR failure:**\n\n```yaml\nautomation:\n  - alias: \"AC command failed alert\"\n    trigger:\n      - platform: state\n        entity_id: binary_sensor.ac_command_failed\n        to: \"on\"\n    action:\n      - service: notify.mobile_app\n        data:\n          message: \"AC IR remote failed after 3 attempts — check device placement\"\n```\n\n### Step 4 — Full Entity Reference\n\n| Entity | Type | Description |\n|--------|------|-------------|\n| `climate.ac_thermostat` | Climate | Thermostat — set target temp and mode (off/cool) |\n| `number.cool_deadband` | Number | Tuning: °C above setpoint before cooling engages (default 0.5) |\n| `number.cool_overrun` | Number | Tuning: °C below setpoint before cooling disengages (default 0.5) |\n| `switch.ac_power` | Switch | Direct AC toggle (bypasses thermostat) |\n| `button.re_sync_ac_state` | Button | Flip the tracked AC state **without sending IR** — use when HA and the real AC have drifted out of sync |\n| `sensor.temperature` | Sensor | Room temperature from ENV HAT (°C) |\n| `sensor.humidity` | Sensor | Relative humidity (%) |\n| `sensor.pressure` | Sensor | Barometric pressure (hPa) |\n| `sensor.battery_level` | Sensor | M5StickC-Plus battery charge (%) from AXP192 |\n| `binary_sensor.ac_beep_confirmed` | Binary Sensor | Last IR command was acoustically confirmed |\n| `binary_sensor.ac_command_failed` | Binary Sensor | Last command failed after 3 retries |\n| `sensor.beep_frequency` | Sensor | Calibration: detected peak frequency (Hz) |\n| `sensor.beep_amplitude` | Sensor | Calibration: detected peak amplitude |\n| `text_sensor.last_action` | Text Sensor | Human-readable status of last action |\n\n---\n\n## Button Controls\n\n| Button | Action | Function |\n|--------|--------|----------|\n| Button A | Short press | Toggle AC on/off (normal mode only) |\n| Button A | Long press 3s | Enter beep calibration mode |\n| Button B | Short press | Toggle IR learn mode |\n\nThermostat setpoint is adjusted via Home Assistant, not the device buttons.\n\n---\n\n## How It Works\n\n### Thermostat Control\n\nThe `climate.ac_thermostat` entity runs a bang-bang controller with a configurable hysteresis band (default 1.0 °C total: 0.5 °C deadband + 0.5 °C overrun):\n\n- When room temp exceeds `target + cool_deadband` → sends AC ON via IR (with beep confirmation)\n- When room temp drops below `target − cool_overrun` → sends AC OFF via IR\n- Compressor protection: minimum 3-minute run time, 5-minute off time before cycling\n- If 3 consecutive IR commands fail to get a beep confirmation, the thermostat forces itself to `off` mode and alerts via `binary_sensor.ac_command_failed`\n\nTune the band live in HA via `number.cool_deadband` and `number.cool_overrun` — settings persist across reboots. Tighter bands hit the setpoint more precisely but may cycle the compressor more often (the min-run/off timers remain a hard floor).\n\n\u003e **Important:** The thermostat sets the AC on/off, but cannot change the AC's internal setpoint. Leave the AC's own remote set to a cold temperature (e.g. 18 °C) so the thermostat's \"on\" bursts actually cool the room.\n\n### Command Flow\n\n1. HA (thermostat or switch) triggers IR send\n2. IR raw code transmitted via M5Stack IR Unit (GPIO32)\n3. PDM microphone listens for confirmation beep (5-second window)\n4. Beep detected within amplitude window → command confirmed, state updated\n5. No beep → retry (up to 3 attempts, 1 second apart)\n6. After 3 failures → `command_failed` sensor activates, thermostat disables itself\n\n### Passive Monitoring\n\nThe microphone runs continuously. A beep that wasn't triggered by the device itself means someone used the physical remote — the device toggles the HA switch state to stay in sync.\n\n### Amplitude Window\n\nMultiple identical AC units in adjacent rooms produce the same beep frequency. The `amplitude_min` / `amplitude_max` window ensures only the nearby unit's beep (at expected volume) triggers detection. Calibration finds the right window for your installation.\n\n---\n\n## Troubleshooting\n\n**Thermostat not switching the AC:**\n- Check that IR codes have been learned (placeholders do nothing)\n- Verify `binary_sensor.ac_beep_confirmed` — if always off, run calibration\n- Confirm the AC's own internal setpoint is low enough (e.g. 18 °C)\n\n**No beep detected / always unconfirmed:**\n- Run calibration (Button A long press) to find correct frequency\n- Check amplitude values in logs — may need a wider `amplitude_min/max` window\n- Ensure the ENV HAT is not blocking the M5 microphone port\n\n**False triggers from adjacent rooms:**\n- Narrow the amplitude window (tighter `amplitude_min` / `amplitude_max`)\n- Position the device closer to your target AC unit\n\n**IR codes don't work:**\n- Verify with a phone camera that the IR LED flashes when a command is sent\n- Try learning codes from a closer distance to the IR receiver\n- Some AC protocols are long — the receiver buffer is 10 KB which handles 100+ pulses\n\n**Temperature reading seems off:**\n- The SHT30 on the ENV HAT can read 1–2 °C high due to heat from the M5 body\n- Apply a fixed offset in the YAML under `env_temperature` sensor: add `filters: - offset: -1.5`\n- Calibrate against a reference thermometer after running the device for 30 minutes\n\n**Display not working:**\n- AXP192 must initialise before the display — check I2C connection at GPIO21/22\n- Verify the ENV HAT is seated correctly (it shares the I2C bus)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeeyo%2Fesp32-ir-ac-thermostat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjeeyo%2Fesp32-ir-ac-thermostat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeeyo%2Fesp32-ir-ac-thermostat/lists"}