Skip to content

Instantly share code, notes, and snippets.

@ristomatti
Last active March 9, 2025 00:04
Show Gist options
  • Save ristomatti/bc15496ee252f243c69aee5783edf077 to your computer and use it in GitHub Desktop.
Save ristomatti/bc15496ee252f243c69aee5783edf077 to your computer and use it in GitHub Desktop.
Rofi / terminal scripts for Home Assistant
#!/usr/bin/env bash
#
# Rofi / terminal interface to interact with Home Assistant LLM conversation API.
#
# Requirements
# - xh: https://github.com/ducaale/xh
# - jq: https://stedolan.github.io/jq/
# - rofi: https://github.com/davatorium/rofi
#
# Resources
# - REST API: https://developers.home-assistant.io/docs/api/rest/
#
# Copyright (c) 2025 Ristomatti Airo <[email protected]>
#
# This work is licensed under the terms of the MIT license.
# For a copy, see <https://opensource.org/licenses/MIT>.
set -Eeuo pipefail
trap cleanup SIGINT SIGTERM ERR EXIT
# Log file (optional)
LOG_FILE="/tmp/hass-assist.log"
function cleanup() {
trap - SIGINT SIGTERM ERR EXIT
}
cd "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
source hass-utils.sh
utils::init_environment
messages=
function rofi_hass_assist() {
local conversation_id="${1:-}" response_text="${2:-}"
local input_text response_body
input_text="$(read_input "${response_text}")"
if [[ -z "$input_text" ]]; then
return 0
fi
if [[ -z "$messages" ]]; then
messages=$(printf '<span font_weight="ultrabold">&#62; %s</span>' "$input_text")
else
messages=$(printf '%s\n\n<span font_weight="ultrabold">&#62; %s</span>' "$messages" "$input_text")
fi
if [[ -n "$conversation_id" ]]; then
response_body=$(
utils::ha_post /api/conversation/process \
"language=${HA_ASSISTANT_LANG}" \
"agent_id=${HA_ASSISTANT_ID}" \
"conversation_id=${conversation_id}" \
"text=${input_text}"
)
else
response_body=$(
utils::ha_post /api/conversation/process \
"language=${HA_ASSISTANT_LANG}" \
"agent_id=${HA_ASSISTANT_ID}" \
"text=${input_text}"
)
fi
conversation_id=$(jq --raw-output '.conversation_id' <<< "$response_body")
response_text=$(jq --raw-output '.response.speech.plain.speech' <<< "$response_body")
if [[ -n "$response_text" ]]; then
if utils::is_terminal; then
echo -e "${response_text}\n"
else
messages=$(printf '%s\n%s' "$messages" "$response_text")
fi
fi
rofi_hass_assist "$conversation_id"
}
function read_input() {
local response_text="${1:-}"
if utils::is_terminal; then
read -p "> " -r input_text
echo "$input_text"
elif [[ -n "$messages" ]]; then
rofi -dmenu -i -p "Request" -markup -l 0 -mesg "$(echo -ne "${messages}")"
else
rofi -dmenu -i -p "Request" -l 0
fi
}
rofi_hass_assist
#!/usr/bin/env bash
#
# Rofi / terminal interface to activate Home Assistant scenes.
#
# Requirements
# - xh: https://github.com/ducaale/xh
# - jq: https://stedolan.github.io/jq/
# - rofi: https://github.com/davatorium/rofi
# - fzf: https://github.com/junegunn/fzf
#
# Resources
# - REST API: https://developers.home-assistant.io/docs/api/rest/
#
# Copyright (c) 2025 Ristomatti Airo <[email protected]>
#
# This work is licensed under the terms of the MIT license.
# For a copy, see <https://opensource.org/licenses/MIT>.
set -Eeuo pipefail
trap cleanup SIGINT SIGTERM ERR EXIT
# Log file (optional)
LOG_FILE="/tmp/hass-scene.log"
function cleanup() {
trap - SIGINT SIGTERM ERR EXIT
}
cd "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
source hass-utils.sh
function rofi_hass_scene() {
local entity_id="${1:-}"
utils::init_environment
if [[ -n "${entity_id}" ]]; then
activate_scene "${entity_id}"
else
activate_scene "$(select_scene)"
fi
}
function activate_scene() {
local entity_id="${1}"
if [[ ! "${entity_id}" == "null" ]] && [[ -n "${entity_id}" ]]; then
utils::log "Activating scene: ${entity_id}"
utils::ha_post "/api/services/scene/turn_on" "entity_id=${entity_id}" &> /dev/null &
fi
}
function select_scene() {
local scenes entity_id
scenes=$(
utils::ha_get "/api/states" | \
jq '[ .[] | select(.entity_id | contains("scene."))
| { entity_id, friendly_name: .attributes.friendly_name }
] | sort_by(.friendly_name)'
)
if ! utils::is_terminal; then
rofi_scene_picker "${scenes}"
else
fzf_scene_picker "${scenes}"
fi
}
function rofi_scene_picker() {
local scenes="${1}" scene_names friendly_name
scene_names=$(
jq -r 'map(.friendly_name) | sort[]' <<< "${scenes}"
)
friendly_name=$(
rofi \
-dmenu \
-normal-window \
-sync -i \
-p "Scene" <<< "${scene_names}"
)
jq --raw-output --arg name "$friendly_name" '
.[] | select(.friendly_name == $name) | .entity_id
' <<< "${scenes}"
}
function fzf_scene_picker() {
local scenes="${1}" scene_headers scene_rows
scene_headers="Friendly name;Entity ID"
scene_rows=$(
jq -r '.[] | [.friendly_name, .entity_id] | join(";")' <<< "${scenes}"
)
column -t -s ";" <<< "$(printf "%s\n%s" "${scene_headers}" "${scene_rows}")" | \
fzf \
--with-nth 1,2 \
--header-lines 1 \
--delimiter ' +' \
--height 15 \
--reverse \
--bind 'enter:become(echo {2})'
}
rofi_hass_scene "$@"
#
# Utility functions for Home Assistant scripts.
#
# Copyright (c) 2025 Ristomatti Airo <[email protected]>
#
# This work is licensed under the terms of the MIT license.
# For a copy, see <https://opensource.org/licenses/MIT>.
# Read and validate environment variables
function utils::init_environment() {
if [ -z "${HA_BASE_URL:-}" ] || [ -z "${HA_API_TOKEN:-}" ]; then
utils::source_dotenv
fi
utils::validate_env_vars
}
# Read environment variables from .env, if it exists.
function utils::source_dotenv() {
if [[ -f .env ]]; then
set -a; source .env; set +a
else
utils::die "Error: .env file not found"
fi
}
# Validate required environment variables are set.
function utils::validate_env_vars() {
if [[ -z "$HA_BASE_URL" ]] ; then
utils::die "Error: Environment variable HA_BASE_URL not set"
elif [[ -z "$HA_API_TOKEN" ]]; then
utils::die "Error: Environment variable HA_API_TOKEN not set"
fi
}
# Send GET request to Home Assistant API
function utils::ha_get() {
local uri="$1"
xh --auth-type bearer --auth "$HA_API_TOKEN" get "${HA_BASE_URL}${uri}"
}
# Send POST request to Home Assistant API
function utils::ha_post() {
local uri="$1"; shift
local -a body=( "$@" )
xh --ignore-stdin --auth-type bearer --auth "$HA_API_TOKEN" post "${HA_BASE_URL}${uri}" "${body[@]}"
}
# Log and print message
function utils::log() {
local -r message="${1:-}"
if [[ -n "$LOG_FILE" ]]; then
echo >&2 -e "$message" >> "$LOG_FILE"
fi
echo "$message" 1>&2
}
function utils::is_terminal() {
test -t 0
}
# Log error message and exit.
function utils::die() {
local error_msg="$1"
local code=${2:-1} # default exit status 1
if [ -n "$error_msg" ]; then
utils::log "$error_msg"
fi
exit "$code"
}
@ristomatti
Copy link
Author

Example .env file:

#
# Example .env file. Store in the same directory as the scripts.
#

# Home Assistant base URL
HA_BASE_URL="http://home-assistant-host:8123"

# Home Assistant API token
HA_API_TOKEN="********"

# Home Assistant conversation assistant
HA_ASSISTANT_ID="conversation.chatgpt"
HA_ASSISTANT_LANG="en"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment