Skip to content

MQTT ​

Homecast publishes real-time device state to MQTT and accepts commands. Compatible with Home Assistant, Node-RED, and any MQTT client.

Connecting ​

SettingValue
Hostmqtt.homecast.cloud
Port8883 (TLS) or 1883
Username(leave blank)
PasswordYour API access token (hc_...)

Create an access token in Settings → API Access, or in the MQTT Browser → Connection Details.

Example ​

bash
# Subscribe to all topics
mosquitto_sub -h mqtt.homecast.cloud -p 8883 \
  --cafile /etc/ssl/cert.pem \
  -u "" -P "hc_your_token_here" \
  -t "homecast/#" -v

# Turn off a light
mosquitto_pub -h mqtt.homecast.cloud -p 8883 \
  --cafile /etc/ssl/cert.pem \
  -u "" -P "hc_your_token_here" \
  -t "homecast/my-home-a1b2/living-room-c3d4/desk-lamp-e5f6/set" \
  -m '{"on": false}'

Topics ​

Follows the Zigbee2MQTT convention: the base topic is the device state, /set is for commands.

Device state ​

homecast/{home}/{room}/{accessory}

Retained JSON with all characteristics, sorted alphabetically:

json
{"brightness": 80, "color_temp": 200, "on": true}

Device commands ​

homecast/{home}/{room}/{accessory}/set

Publish a JSON object with the characteristics to change:

json
{"on": false}
{"brightness": 50, "color_temp": 300}

Availability ​

homecast/{home}/{room}/{accessory}/availability

Retained string: "online" or "offline".

Device info ​

homecast/{home}/{room}/{accessory}/info

Retained JSON describing what the accessory is, rather than its state. The state topic says an air conditioner has speed 100; this says that speed belongs to a heater_cooler service, that hvac_mode accepts 0, 1 and 2, and who made it:

json
{
  "category": "Other",
  "manufacturer": "Powrmatic",
  "model": "Default-Model",
  "keys": {
    "cool_target": {"service": "heater_cooler", "writable": true, "min": 16, "max": 31, "step": 1},
    "hvac_mode":   {"service": "heater_cooler", "writable": true, "min": 0, "max": 2, "step": 1, "valid": [0, 1, 2]},
    "speed":       {"service": "heater_cooler", "writable": true, "min": 0, "max": 100, "step": 1}
  }
}

Only keys the state topic publishes appear under keys. Any field may be missing — min, max, step and valid are there only when the accessory declares them.

Service groups ​

Groups control multiple devices at once (e.g., "All Kitchen Lights"):

homecast/{home}/{room}/{group}              # group state
homecast/{home}/{room}/{group}/set          # control all devices in group
homecast/{home}/{room}/{group}/members      # JSON array of member slugs

The default room ​

Apple Home keeps a default room that it doesn't list alongside the rooms you created, but accessories can sit in it. It publishes as default, slugged like any other room:

homecast/{home}/default-{hex}/{accessory}

Accessories with no room ​

A virtual accessory belongs to the home rather than to any part of it, so it has no room segment at all. Everything else about it is unchanged:

homecast/{home}/{accessory}                 # device state
homecast/{home}/{accessory}/set             # commands
homecast/{home}/{accessory}/availability    # online/offline

A group whose members are all roomless publishes the same way.

Scenes ​

homecast/{home}/scene/{scene}/execute

Publish any payload to trigger the scene.

Home status ​

homecast/{home}/status

Retained: "online" or "offline". Updated via MQTT Last Will and Testament.

Home Assistant auto-discovery ​

homeassistant/{component}/homecast_{id}/config

Retained JSON with HA discovery payload. Published automatically when MQTT is enabled. Devices appear in Home Assistant without manual configuration.

Virtual accessories are the exception: a Switch is discovered like any other switch, but the other types have no Home Assistant equivalent to be discovered as. Subscribe to their topics directly, or use the Home Assistant integration.

Slugs ​

Topic segments use the format {name}-{first 4 hex of UUID}:

  • county-hall-2d10 (home)
  • kitchen-dfee (room)
  • default-1910 (the default room — named default rather than after itself, whose name Apple localizes, but slugged the same way)
  • desk-lamp-e5f6 (accessory)
  • kitchen-lights-182f (service group)

Slugs are stable — renaming a device in HomeKit doesn't change the slug.

Editions ​

FeatureCommunityCloud
Custom MQTT brokersConnect Mac app to your brokerStore in DB, cloud bridge connects
Homecast Broker (mqtt.homecast.cloud)Not availablePer-home toggle in Settings
MQTT BrowserAt homecast.cloud/mqttAlso at mqtt.homecast.cloud
HA auto-discoveryPublished by Mac appPublished by cloud bridge
Service group supportVia Mac appVia cloud bridge

MQTT Browser ​

MQTT Browser showing live topic tree

Browse live device state and publish commands at mqtt.homecast.cloud or homecast.cloud/mqtt.

Features:

  • Auto-connects with your Homecast session
  • Filter by home and room
  • Visual controls: toggles for booleans, sliders for brightness/color
  • Raw JSON editor for advanced use
  • Publish history
  • Service group support with member list
  • Connection details dialog with access token management

Enabling MQTT ​

  1. Go to Settings → Account → enable Developer Mode
  2. Go to Settings → Homes → click your home
  3. Toggle Homecast Broker on
  4. (Optional) Add custom brokers below

MQTT setup in Home settings

Characteristic names ​

MQTT uses simplified names for HomeKit characteristics:

MQTT nameHomeKit characteristic
onPower state
brightnessBrightness (0–100)
color_tempColor temperature (50–500)
hueHue (0–360)
saturationSaturation (0–100)
current_tempCurrent temperature
heat_targetHeating threshold
cool_targetCooling threshold
positionCurrent position (blinds, 0–100)
targetTarget position
lockedLock state
lock_targetLock target
motionMotion detected
contactContact sensor state
speedFan rotation speed
volumeSpeaker volume
muteMute state
batteryBattery level
activeActive state
alarm_stateSecurity system state
alarm_targetSecurity system target
hvac_modeHeater/cooler target mode: 0 auto, 1 heat, 2 cool. /set also takes "auto", "heat", "cool"
hvac_stateHeater/cooler current state: 0 inactive, 1 idle, 2 heating, 3 cooling
swing_modeHeater/cooler swing: 0 off, 1 on
target_tempThermostat target temperature
thermostat_modeThermostat target mode: 0 off, 1 heat, 2 cool, 3 auto
thermostat_stateThermostat current state: 0 off, 1 heating, 2 cooling
relative_humidityCurrent relative humidity
position_stateCovering motion: 0 closing, 1 opening, 2 stopped
obstructionObstruction detected
low_batteryLow battery status

An air conditioner is a heater/cooler, not a thermostat: HomeKit gives the two different mode numbering, which is why they publish under different names.

Virtual accessories ​

Virtual accessories publish and accept commands like any other device. They have no room, so they sit directly under the home (see Accessories with no room).

Each carries one key, and what writing to it means depends on the type:

TypeAPI typeMQTT keyPublishing to /set
Switchinput_booleanontrue / false
Modeinput_selectmodeselects that option
Countercountercountsets the count — it does not add to it
Numberinput_numbernumbersets the value
Textinput_texttextsets the text
Date & timeinput_datetimedatetimesets it, same format the dashboard stores
Timertimertimer"active" starts it; anything else cancels it
bash
# Switch a mode
mosquitto_pub -t "homecast/my-home-a1b2/home-mode-80d9/set" -m '{"mode": "Away"}'

# Start a timer
mosquitto_pub -t "homecast/my-home-a1b2/porch-timer-2c12/set" -m '{"timer": "active"}'

Timers ​

A timer's state is idle, active or paused. On its own that only says whether it is running, so it publishes the countdown alongside:

KeyMeaning
timer_started_atwhen the current run started
timer_ends_atwhen it will run out
timer_duration_mshow long it runs for
timer_finished_atwhen it last ran out — stays until the next start
json
{"timer": "active", "timer_started_at": 1754563200000, "timer_ends_at": 1754563500000, "timer_duration_ms": 300000}

Times are absolute (epoch milliseconds) rather than a remaining span, because a retained message is read long after it was published — work out the remaining time from timer_ends_at at the moment you need it.

timer_finished_at is there because an idle timer is otherwise indistinguishable from one that has never run. Cancelling a timer does not set it: a cancelled timer never finished.