npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

homebridge-ac-http

v1.0.13

Published

Homebridge plugin for any HTTP/REST-controlled air conditioner — IR blasters, ESP8266, ESP32, and any device with a REST API. Supports composed commands, templates, dual-axis swing, humidity, fan speed mapping, and custom headers.

Readme

homebridge-ac-http

Homebridge plugin for any HTTP/REST-controlled air conditioner. If your AC is controlled by an IR blaster, ESP8266, ESP32, Raspberry Pi, Tuya-local bridge, or any device with a REST API, this plugin exposes it as a native HomeKit Heater/Cooler accessory — supporting Homebridge 1.x and 2.0.

npm CI

Features

  • IR blaster / composed command — sends the full AC state (power + mode + temp + fan + swing) in one HTTP call, exactly how IR remotes work
  • Granular REST API — per-characteristic endpoints for devices that support individual commands
  • Templates — define endpoint config once per AC model, reuse across rooms with just a host change
  • Dual-axis swing — vertical (HomeKit SwingMode) and horizontal (linked Switch tile)
  • Stateless swing — IR toggle mode: fires every tap, no state polling needed
  • Fan speed mapping — any discrete speed names mapped to/from HomeKit 0–100% slider
  • Humidity sensor — optional currentRelativeHumidity linked to the AC tile
  • Custom HTTP headers — Bearer tokens, API keys, Basic auth
  • Setter debounce — prevents slider spam from flooding the AC controller
  • Configurable temperature range — override the default 16–30°C for your region
  • Localised tile labels — pick a language (en ja zh-CN zh-TW ko de fr es it pt nl) or set your own
  • All HTTP methods — GET, POST, PUT, PATCH, DELETE
  • Flexible response parsing — JSONPath extraction, bidirectional value maps

Compatibility

| Homebridge | Node.js | Status | |------------|---------|------------| | 1.6.x – 1.x | >= 18 | Supported | | 2.0.x | >= 22 | Supported |

What it looks like in HomeKit

Single AC tile (HeaterCooler service):

  • Power on/off
  • Mode selector: Auto / Heat / Cool
  • Target temperature (configurable range, 1°C steps)
  • Fan speed slider (0–100%, mapped to your AC's discrete speeds)
  • Vertical swing toggle

Additional tiles (only appear when configured):

  • [name] H-Swing — Switch tile for horizontal swing axis
  • Humidity sensor tile — shows current relative humidity

All tiles belong to the same accessory and appear together in the accessory detail view.

HomeKit Limitations

  • Fan-only and Dry modes have no HomeKit equivalent. Map them to Auto (0) via command.map.mode or setValueMap, or use a separate Switch accessory.
  • Horizontal swing cannot fit inside the HeaterCooler tile — it appears as a separate linked Switch.
  • Humidity appears as a separate linked Sensor tile, not inside the HeaterCooler panel.
  • Fixed-angle swing — HomeKit only shows on/off. Map the "on" state to your desired angle string via swingVertical.stateless + command.map.swingVertical.

Install

npm install -g homebridge-ac-http

Or install via the Homebridge UI plugin search.

Quick Start

Minimal config for an IR blaster:

{
  "platform": "AcHttpPlatform",
  "accessories": [
    {
      "name": "Living Room AC",
      "command": {
        "url": "http://192.168.1.10/api/send",
        "method": "POST",
        "body": "{\"power\":\"{active}\",\"mode\":\"{mode}\",\"temp\":{temperature},\"fan\":\"{fanSpeed}\",\"swing\":\"{swingVertical}\"}",
        "map": {
          "active":        { "0": "off",  "1": "on"                              },
          "mode":          { "0": "auto", "1": "heat", "2": "cool"               },
          "fanSpeed":      { "0": "auto", "20": "1",  "60": "3",  "100": "5"     },
          "swingVertical": { "0": "off",  "1": "on"                              }
        }
      },
      "swingVertical": { "stateless": true }
    }
  ]
}

How to Configure

Mode A: IR Blaster / Composed Command

For IR-controlled ACs, the blaster must receive the complete state in every call. Use the command block:

"command": {
  "url": "http://192.168.1.10/api/ir",
  "method": "POST",
  "body": "{\"power\":\"{active}\",\"mode\":\"{mode}\",\"temp\":{temperature},\"fan\":\"{fanSpeed}\",\"vswing\":\"{swingVertical}\",\"hswing\":\"{swingHorizontal}\"}",
  "map": {
    "active":          { "0": "off",  "1": "on"   },
    "mode":            { "0": "fan",  "1": "heat", "2": "cool" },
    "fanSpeed":        { "0": "auto", "20": "1",  "60": "3", "100": "5" },
    "swingVertical":   { "0": "off",  "1": "30deg" },
    "swingHorizontal": { "0": "off",  "1": "on"    }
  }
}

Body template placeholders:

| Placeholder | HomeKit source | |---------------------|-----------------------------------------| | {active} | Power on/off (mapped via map.active) | | {mode} | Target mode (mapped via map.mode) | | {temperature} | Target temperature (numeric, no map) | | {fanSpeed} | Fan speed % (threshold-mapped) | | {swingVertical} | Vertical swing 0/1 (mapped) | | {swingHorizontal} | Horizontal swing 0/1 (mapped) |

Fan speed map uses threshold matching: the map key is the minimum HomeKit % that triggers that speed. Key "0" = auto, "20" = speed 1 (for 20–39%), etc.

Numeric vs string in body: use {temperature} without quotes for a number, "{active}" with quotes for a string.

When command is configured, all SET operations use it. Individual characteristic set endpoints are ignored (but get endpoints still work for state polling).

What the plugin actually sends

When you change something in Home — say, switching to Cool mode at 24°C — the plugin builds the full AC state from memory and sends one HTTP request:

PUT http://192.168.1.10/api/ir
Content-Type: application/json

{
  "power": "on",
  "mode": "cool",
  "temp": 24,
  "fan": "auto",
  "vswing": "off",
  "hswing": "off"
}

A 200 OK with any body (or empty body) means success. Non-2xx responses are logged as errors in the Homebridge log.

What the plugin reads for state polling

If you configure stateUrl (or per-characteristic get endpoints), the plugin fetches the current state on a timer and updates HomeKit. Your device needs to return JSON that you can point a jsonPath at:

GET http://192.168.1.10/api/status

HTTP/1.1 200 OK
Content-Type: application/json

{
  "power": "on",
  "mode": "cool",
  "setpoint": 24,
  "room_temp": 26.5,
  "humidity": 62,
  "fan": "auto"
}

Then in config:

"stateUrl": "http://192.168.1.10/api/status",
"active":                      { "get": { "jsonPath": "$.power",    "valueMap": { "on": "1", "off": "0" } } },
"targetHeaterCoolerState":     { "get": { "jsonPath": "$.mode",     "valueMap": { "auto": "0", "heat": "1", "cool": "2" } } },
"currentTemperature":          { "get": { "jsonPath": "$.room_temp" } },
"currentRelativeHumidity":     { "get": { "jsonPath": "$.humidity"  } },
"coolingThresholdTemperature": { "get": { "jsonPath": "$.setpoint"  } }

jsonPath uses dot notation starting with $$.room_temp extracts the room_temp field, $.data.temp goes one level deeper, and so on.

Mode B: Granular REST API

For devices that accept individual property commands:

{
  "name": "Kitchen AC",
  "stateUrl": "http://192.168.1.20/api/status",
  "pollInterval": 30,
  "active": {
    "get": { "jsonPath": "$.power", "valueMap": { "ON": "1", "OFF": "0" } },
    "set": { "url": "http://192.168.1.20/api/power", "method": "POST", "body": "{\"value\":\"{value}\"}", "setValueMap": { "1": "ON", "0": "OFF" } }
  },
  "targetHeaterCoolerState": {
    "get": { "jsonPath": "$.mode", "valueMap": { "AUTO": "0", "HEAT": "1", "COOL": "2" } },
    "set": { "url": "http://192.168.1.20/api/mode", "method": "POST", "body": "{\"mode\":\"{value}\"}", "setValueMap": { "0": "AUTO", "1": "HEAT", "2": "COOL" } }
  },
  "currentTemperature":          { "get": { "jsonPath": "$.room_temp" } },
  "coolingThresholdTemperature": {
    "get": { "jsonPath": "$.setpoint" },
    "set": { "url": "http://192.168.1.20/api/setpoint", "method": "POST", "body": "{\"temp\":{value}}" }
  },
  "rotationSpeed": {
    "get": { "jsonPath": "$.fan", "valueMap": { "AUTO": "0", "LOW": "33", "HIGH": "100" } },
    "set": { "url": "http://192.168.1.20/api/fan", "method": "POST" },
    "fanSpeedMap": { "valueToPercent": { "auto": 0, "low": 33, "high": 100 } }
  }
}

Same AC Model in Multiple Rooms

If you have the same AC (or IR blaster) in several rooms, you only need to write the configuration once. Put the shared settings in templates under a name you choose, then each room's accessory just says which template to use and supplies its own IP address.

Anywhere you write {host} or {port} in a URL inside the template, the plugin fills it in from each accessory's host / port field.

{
  "platform": "AcHttpPlatform",
  "templates": [
    {
      "name": "my-ir-blaster",
      "command": {
        "url": "http://{host}/api/send",
        "method": "POST",
        "body": "{\"power\":\"{active}\",\"mode\":\"{mode}\",\"temp\":{temperature},\"fan\":\"{fanSpeed}\"}",
        "map": {
          "active": { "0": "off", "1": "on" },
          "mode":   { "0": "auto", "1": "heat", "2": "cool" },
          "fanSpeed": { "0": "auto", "20": "1", "60": "3", "100": "5" }
        }
      },
      "swingVertical": { "stateless": true },
      "minTemp": 16,
      "maxTemp": 30
    }
  ],
  "accessories": [
    { "name": "Living Room AC", "serial": "LR-001", "template": "my-ir-blaster", "host": "192.168.1.10" },
    { "name": "Bedroom AC",     "serial": "BR-001", "template": "my-ir-blaster", "host": "192.168.1.11" },
    { "name": "Office AC",      "serial": "OF-001", "template": "my-ir-blaster", "host": "192.168.1.12", "setterDelay": 500 }
  ]
}

Each accessory inherits everything from the template. You can override any individual field directly on the accessory — it takes priority over the template value.


## Full Config Reference

### Platform

| Field         | Type   | Default | Description |
|---------------|--------|---------|-------------|
| `language`    | string | `en`    | Language for tile labels. Options: `en` `ja` `zh-CN` `zh-TW` `ko` `de` `fr` `es` `it` `pt` `nl` |
| `templates`   | array  | —       | Shared AC model configs. Each entry needs a `name` field. |
| `accessories` | array  | —       | List of AC accessories. |

### Accessory / Template

| Field          | Type    | Default | Description |
|----------------|---------|---------|-------------|
| `name`         | string  | required | HomeKit display name. |
| `serial`       | string  | —        | Stable UUID seed. Strongly recommended. |
| `model`        | string  | —        | Shown in accessory info. |
| `template`     | string  | —        | Inherit from a named template. |
| `host`         | string  | —        | Replaces `{host}` in template URLs. |
| `port`         | integer | 80       | Replaces `{port}` in template URLs. |
| `stateUrl`     | string  | —        | Fallback GET URL for characteristics with no own `get.url`. |
| `pollInterval` | integer | 30       | State refresh interval in seconds. 0 = disabled. |
| `setterDelay`  | integer | 0        | Debounce ms for SET commands. Useful for sliders. |
| `minTemp`      | integer | 16       | Minimum HomeKit target temperature (°C). |
| `maxTemp`      | integer | 30       | Maximum HomeKit target temperature (°C). |
| `command`      | object  | —        | Composed command (IR mode). See above. |

### Endpoint Config (used in per-characteristic `get`/`set`)

| Field        | Type   | Default | Description |
|--------------|--------|---------|-------------|
| `url`        | string | required | Full URL. |
| `method`     | string | POST     | HTTP method: GET, POST, PUT, PATCH, DELETE. |
| `body`       | string | —        | Request body template. Use `{value}` as placeholder. |
| `jsonPath`   | string | —        | Dot-notation path to extract from JSON response, e.g. `$.data.temp`. |
| `valueMap`   | object | —        | Maps HTTP response string → HomeKit number string. Auto-reversed for SET. |
| `setValueMap`| object | —        | Maps HomeKit value → API string. Overrides reversed `valueMap`. |
| `headers`    | object | —        | Custom HTTP headers, e.g. `{ "Authorization": "Bearer token" }`. |
| `timeout`    | integer | 5000    | Request timeout in ms. |

### Swing Config (`swingVertical`, `swingHorizontal`)

| Field      | Type    | Default | Description |
|------------|---------|---------|-------------|
| `get`      | object  | —       | EndpointConfig for reading state. |
| `set`      | object  | —       | EndpointConfig for setting state (non-command mode). |
| `stateless`| boolean | false   | Momentary trigger: fires every tap, resets to OFF, no state polling needed. Use for IR toggles. |
| `modes`    | array   | —       | (Stateless only) Radio-button labels — one Switch tile per entry. |
| `label`    | string  | language default | Override the tile name suffix (e.g. `"スイング"`). |

## Local Testing

### Homebridge 1.x (existing installation)

```bash
npm install && npm run build
npm link
homebridge -D

Docker (both 1.x and 2.x)

npm run build
docker compose -f docker-compose.test.yml up
  • Homebridge 1.x UI: http://localhost:8581
  • Homebridge 2.x UI: http://localhost:8582

Add your plugin config via the Homebridge UI on first run.

Known Limitations

  • IR devices have no state feedback — the REST API must maintain its own state store.
  • Physical remote desync is unavoidable with IR.
  • Swing state tracked in memory — resets on Homebridge restart for stateless configs.
  • Fan-only and Dry modes: map to HomeKit Auto (0) via map.mode or document separately.