| name | home-assistant |
| description | Controls smart home devices and reads sensor data via Home Assistant |
| version | 1.0.0 |
| author | system |
| tools | ["ha_get_state","ha_list_entities","ha_call_service","ha_get_history","ha_get_logbook","ha_get_home_status","ha_get_camera_snapshot","ha_fire_event","ha_render_template","ha_list_services"] |
| dependencies | [] |
When to Use
- User asks about home sensor readings (temperature, humidity, motion, doors)
- User wants to control lights, switches, fans, covers, or thermostats
- User asks for historical sensor data or trends
- User asks for a home status overview
Entity IDs
Home Assistant entities follow the format domain.name but names are often non-obvious.
CRITICAL: NEVER guess entity IDs. Always use ha_list_entities first to discover actual IDs, or use ha_get_home_status to get all readings at once. Entity IDs frequently don't match what you'd expect (e.g. bathroom temperature might be sensor.temperature, not sensor.bathroom_temperature).
Common domains:
sensor — numeric readings (temperature, humidity, power)
binary_sensor — on/off states (motion, door, window)
light — controllable lights (supports brightness, color)
switch — on/off switches
climate — thermostats / HVAC
fan — fans with speed control
cover — garage doors, blinds, shutters
Common Services
Device control (uses entity_id)
| Domain | Service | Use |
|---|
| light | turn_on | Turn on (optional: brightness 0-255, color_name, rgb_color) |
| light | turn_off | Turn off |
| light | toggle | Toggle on/off |
| switch | turn_on | Turn on |
| switch | turn_off | Turn off |
| climate | set_temperature | Set target temp (service_data: {temperature: 72}) |
| climate | set_hvac_mode | Set mode (heat, cool, auto, off) |
| fan | turn_on | Turn on (optional: percentage) |
| cover | open_cover | Open garage door / blinds |
| cover | close_cover | Close garage door / blinds |
Global services (omit entity_id)
| Domain | Service | Example service_data |
|---|
| notify | mobile_app_ | {"message": "motion detected", "title": "Alert"} |
| notify | persistent_notification | {"message": "...", "title": "..."} |
| tts | speak | {"entity_id": "tts.<provider>", "media_player_entity_id": "media_player.living_room", "message": "hello"} — entity_id is the TTS service entity (tts.*), NOT the speaker. Find providers with ha_list_entities(domain="tts"). If you only have media_player.play_media, use that instead. |
| scene | turn_on | Pass entity_id: "scene.movie_time" to activate |
| scene | create | {"scene_id": "guest_mode", "entities": {"light.kitchen": {"state": "on", "brightness": 180}}} |
| script | turn_on | entity_id: "script.bedtime", or omit entity_id and pass {"variables": {...}} for parameterized scripts |
| automation | trigger | entity_id: "automation.morning_routine" |
| homeassistant | reload_config_entry | For reloading an integration |
Speakers & TTS — finding the right entities
HA has two kinds of entities involved in voice output:
- TTS provider (
tts.*) — the engine that converts text to audio. Examples: tts.chatterbox, tts.piper, tts.google_translate_en_com, tts.openai_tts. Discover with ha_list_entities(domain="tts"). On this system, prefer tts.chatterbox — it's the configured/tuned provider. Only fall back to another if chatterbox isn't present.
- Speaker / media player (
media_player.*) — where the audio plays. Home Assistant Voice PE devices may register as media_player.home_assistant_voice_<id> and/or assist_satellite.* — check both domains.
To speak on a speaker:
ha_call_service(
domain: "tts", service: "speak",
service_data: {
"entity_id": "tts.piper", # TTS provider
"media_player_entity_id": "media_player.living_room", # speaker
"message": "Dinner is ready"
}
)
If TTS isn't configured, fall back to media_player.play_media with a URL, or use notify.mobile_app_<device> to send a push instead.
Area/device targeting (use target param)
ha_call_service(domain: "light", service: "turn_off", target: {"area_id": "kitchen"})
ha_call_service(domain: "light", service: "turn_on", target: {"entity_id": ["light.a", "light.b"]}, service_data: {"brightness": 128})
Tool Selection
- Reading one entity:
ha_get_state — fast, cached
- Finding entities:
ha_list_entities — discover what's available; area filter uses HA's real area registry (device→area inheritance respected), falls back to friendly-name matching
- Controlling devices / services:
ha_call_service — lights, scenes, scripts, TTS, notify, automation triggering, etc.
- Trends/history:
ha_get_history — min/max/avg over time (numeric sensors) or state changes (non-numeric)
- Activity log:
ha_get_logbook — timestamped state changes (location history, door events, etc.)
- Full overview:
ha_get_home_status — snapshot of all active devices
- Camera frame:
ha_get_camera_snapshot — fetches JPEG to workspace
- Complex state questions:
ha_render_template — one call answers what would take 20 ha_get_states
- Trigger automations via bus:
ha_fire_event — emit custom events that automations can listen for
Templates (ha_render_template)
When a question would take many ha_get_state calls, use one template instead:
# "Which motion sensors are active right now?"
template: "{{ states | selectattr('attributes.device_class','equalto','motion') | selectattr('state','equalto','on') | map(attribute='entity_id') | list | tojson }}"
# "Are all the downstairs lights off?"
template: "{{ area_entities('downstairs') | select('match','^light\\.') | map('states') | select('equalto','on') | list | length == 0 }}"
# "What's the average temperature across all thermostats?"
template: "{{ states.climate | map(attribute='attributes.current_temperature') | select('number') | sum / states.climate | list | length }}"
# "Is anyone home?" (person domain)
template: "{{ states.person | selectattr('state','equalto','home') | map(attribute='attributes.friendly_name') | list | tojson }}"
Return values that are JSON-parseable are auto-parsed; otherwise raw string is returned.
Camera Snapshots
To grab a still frame from an HA camera (Reolink, generic MJPEG, etc.), use ha_get_camera_snapshot — one tool, one call:
ha_get_camera_snapshot(entity_id: "camera.garage")
It authenticates with HA, fetches the JPEG, saves it to selfies_{your_slug}/{entity}_{timestamp}.jpg, and returns the path. Do NOT use ha_call_service with camera.snapshot — that writes into HA's container filesystem and the file is unreachable from your workspace.
Save location rules: the default (selfies_{your_slug}/) is almost always correct. Only override save_path when you have a clear reason to share the snapshot with another Choom — in that case use choom_commons/. NEVER save snapshots to sibling_journal/ — that folder is text-only and append-only (you can't delete entries). If you try, the tool will redirect to your personal folder and warn you.
Once saved, the returned path works everywhere:
- Analyze it:
analyze_image(image_path: "<path>", prompt: "...")
- Send to phone:
send_notification(message: "...", file_paths: ["<path>"]) — images display inline in Signal.
- Display in GUI: happens automatically via the
file_created event; chat UI renders the image inline.
Use ha_list_entities with domain: "camera" first if you don't know the entity_id.
PTZ / camera presets — HA does NOT have a generic "camera.ptz" service
This is the #1 place Chooms hallucinate service names (camera.available_ptz_presets, ptz.list_presets, onvif.ptz_preset — none of these exist). Actual PTZ control in HA depends on the camera's integration. Two common patterns:
- Preset selector entities (Reolink, ONVIF, most modern integrations): presets are exposed as
select.<camera>_ptz_preset entities. List options with ha_get_state(entity_id="select.<camera>_ptz_preset") — attributes.options is the preset names. Move to one via:
ha_call_service(domain="select", service="select_option",
entity_id="select.<camera>_ptz_preset",
service_data={"option": "<preset name>"})
- Button entities (some integrations): each preset is a
button.<camera>_preset_<name> entity. Press with ha_call_service(domain="button", service="press", entity_id="button.<preset>").
Discovery workflow when you're unsure what services/entities a device exposes:
ha_list_entities() with no filter, grep the result for the camera name — look for select.*, button.*, or integration-specific entities (e.g. reolink.*) related to the camera.
ha_list_services(domain="<integration>") (e.g. domain: "reolink", domain: "onvif") for integration-specific PTZ services.
ha_list_services(domain: "camera") to see what the camera.* domain actually supports — almost never has native PTZ.
- If the camera's state has
attributes.pan / attributes.tilt but no preset entities, the integration only supports live PTZ streaming, not preset recall — report that to the user instead of guessing.
Never call camera.*_ptz_*, ptz.*, or onvif.ptz_* services without first confirming they exist via ha_list_services. If HA returns 400: Bad Request, the most common cause is a non-existent service name — discovery, not retry.
Case sensitivity & settling: select.select_option is case-sensitive on the option value — but the handler auto-corrects common case mismatches ("driveway" → "Driveway") by looking up attributes.options on the entity. When the target looks like a PTZ preset (entity_id contains "ptz" or "preset"), the handler also waits ~6 seconds after the service call so the camera has time to physically move before you take a snapshot. Don't take a snapshot immediately yourself — just wait for ha_call_service to return, then call ha_get_camera_snapshot.
The preset select reads back as "unknown" — that is NORMAL, not a failure: Reolink (and most ONVIF) PTZ preset selects are write-only. Selecting an option commands the camera to move, but HA never reads the active preset back, so ha_get_state on the select entity (and the HOME ENVIRONMENT summary) shows unknown even when the camera is sitting exactly where you sent it. When your select.select_option call returns success: true with moved_to: "<preset>", the move worked — the camera went where you sent it. Do NOT re-select the preset, and do NOT call ha_get_state afterward to "confirm" the position (it will always say unknown and send you in a loop). Trust the success result; to see the view, call ha_get_camera_snapshot.
Important
- NEVER guess entity IDs — always call
ha_list_entities or ha_get_home_status first to find actual IDs
- If
ha_get_state returns a 404 error, the entity_id is wrong — use ha_list_entities to find the correct one
- Entity states can be
unavailable or unknown — do not interpret these as readings
- For lights, brightness is 0-255 in the HA API (not a percentage)
- Always confirm destructive actions with the user (e.g. opening garage door)