config tool enable/disable with comment

This commit is contained in:
Liam Pettigrew
2026-02-06 16:11:23 +11:00
parent 798b8eded4
commit 26dce967c4
7 changed files with 146 additions and 131 deletions
+1
View File
@@ -56,6 +56,7 @@ data/credentials.json
data/token.json
*.pem
*.key
.python_hue
# =============================================================================
# Local Data and Models
+3
View File
@@ -11,9 +11,12 @@ Usage:
"""
import os
import warnings
import yaml
from pathlib import Path
warnings.filterwarnings("ignore", message="Setting `pad_token_id` to `eos_token_id`")
# Load configuration
with open("./data/config.yml", "r") as f:
config = yaml.safe_load(f)
+58 -54
View File
@@ -2,6 +2,13 @@
# Copy this file to config.yml and update with your values.
# See README.md for detailed setup instructions for each integration.
# =============================================================================
# Tool Activation
# =============================================================================
# Each section below corresponds to an integration/tool. To disable a tool,
# comment out its entire section. Only configured (uncommented) tools are
# loaded on launch.
# =============================================================================
# General Settings
# =============================================================================
@@ -23,22 +30,22 @@ general:
# =============================================================================
# Register your app at https://developer.spotify.com/dashboard
# Required scopes: user-read-playback-state, user-modify-playback-state, user-read-currently-playing
spotify:
client_id: "your_spotify_client_id"
client_secret: "your_spotify_client_secret"
redirect_uri: "http://localhost:8888/callback"
# Device name as shown in Spotify Connect
device_id: "Your Speaker Name"
# Enable Pioneer AVR integration for audio output switching
# Set to false if not using an AVR
use_avr: false
# spotify:
# client_id: "your_spotify_client_id"
# client_secret: "your_spotify_client_secret"
# redirect_uri: "http://localhost:8888/callback"
# # Device name as shown in Spotify Connect
# device_id: "Your Speaker Name"
# # Enable Pioneer AVR integration for audio output switching
# # Set to false if not using an AVR
# use_avr: false
# =============================================================================
# Philips Hue Lighting
# =============================================================================
# Press the button on your Hue Bridge before first connection
philips:
hue_hub_ip: "192.168.1.100"
# philips:
# hue_hub_ip: "192.168.1.100"
# =============================================================================
# Home Assistant Integration
@@ -47,29 +54,26 @@ philips:
# 1. Go to your HA profile: http://your-ha:8123/profile
# 2. Scroll to "Long-Lived Access Tokens" and create a token
#
# IMPORTANT: This integration must be explicitly enabled. When enabled, it
# registers generic tool names like "turn_on" and "turn_off" which may conflict
# with other integrations (e.g., Philips Hue lighting). Only enable if you want
# Home Assistant to be your primary home automation controller.
home_assistant:
# Set to true to enable Home Assistant tools (disabled by default)
enabled: false
# URL to your Home Assistant instance
url: "http://192.168.1.50:8123"
# Long-lived access token from your HA profile
token: "your_long_lived_access_token"
# Request timeout in seconds
timeout: 10
# Map friendly names to entity IDs for easier voice control
# Keys are lowercase, values are full entity IDs
entity_aliases:
living room lights: "light.living_room"
bedroom lights: "light.bedroom"
front door: "lock.front_door"
garage: "cover.garage_door"
thermostat: "climate.main"
movie time: "scene.movie_time"
bedtime: "script.bedtime"
# NOTE: This registers generic tool names like "turn_on" and "turn_off" which
# may conflict with other integrations (e.g., Philips Hue lighting). Comment
# out this entire section if you don't use Home Assistant.
# home_assistant:
# # URL to your Home Assistant instance
# url: "http://192.168.1.50:8123"
# # Long-lived access token from your HA profile
# token: "your_long_lived_access_token"
# # Request timeout in seconds
# timeout: 10
# # Map friendly names to entity IDs for easier voice control
# # Keys are lowercase, values are full entity IDs
# entity_aliases:
# living room lights: "light.living_room"
# bedroom lights: "light.bedroom"
# front door: "lock.front_door"
# garage: "cover.garage_door"
# thermostat: "climate.main"
# movie time: "scene.movie_time"
# bedtime: "script.bedtime"
# =============================================================================
# Weather - Bureau of Meteorology (Australia)
@@ -88,20 +92,20 @@ bom:
# 1. Create credentials at https://console.cloud.google.com/
# 2. Enable Google Calendar API
# 3. Download OAuth 2.0 credentials JSON file
google:
# Path to OAuth client credentials file
cred_file: "./data/credentials.json"
# Path to store OAuth token (auto-generated on first auth)
token_file: "./data/token.json"
# google:
# # Path to OAuth client credentials file
# cred_file: "./data/credentials.json"
# # Path to store OAuth token (auto-generated on first auth)
# token_file: "./data/token.json"
# =============================================================================
# LG ThinQ (Smart Appliances)
# =============================================================================
# Get credentials from LG ThinQ developer portal
thinq:
access_token: "your_thinq_access_token"
country_code: "AU"
client_id: "your_thinq_client_id"
# thinq:
# access_token: "your_thinq_access_token"
# country_code: "AU"
# client_id: "your_thinq_client_id"
# =============================================================================
# Web Search - SearXNG
@@ -115,26 +119,26 @@ search:
# =============================================================================
# Find TV IP in TV settings: Network > Wi-Fi Connection > Advanced
# Find MAC address in TV settings: Network > Wi-Fi Connection > Advanced
webos:
ip_address: "192.168.1.101"
mac_address: "AA:BB:CC:DD:EE:FF"
# webos:
# ip_address: "192.168.1.101"
# mac_address: "AA:BB:CC:DD:EE:FF"
# =============================================================================
# Pioneer/Onkyo AVR (Audio Receiver)
# =============================================================================
# Uses eISCP protocol over network
# Default port is 60128 for most Pioneer/Onkyo receivers
pioneer:
avr_host: "192.168.1.102"
avr_port: 60128
# pioneer:
# avr_host: "192.168.1.102"
# avr_port: 60128
# =============================================================================
# Airtouch HVAC Control
# =============================================================================
# Map zone names to zone IDs (0-indexed)
# Zone IDs correspond to the order in your Airtouch system
airtouch:
living room: 0
bedroom: 1
office: 2
upstairs: 3
# airtouch:
# living room: 0
# bedroom: 1
# office: 2
# upstairs: 3
+49 -27
View File
@@ -2,36 +2,58 @@
Tools package for the voice assistant.
This package contains all the tools that can be used by the voice assistant.
All tools are automatically registered with the tool registry when this
package is imported.
Tools are conditionally loaded based on which integrations are configured
in data/config.yml. Commenting out or removing a config section disables
that tool entirely.
"""
# Import all tools to ensure they are registered with the tool registry
from . import spotify
from . import lighting
from . import weather_time
from . import google_calendar
from . import airtouch
from . import thinq
from . import webos
from . import search_web
from . import pioneer_avr
from . import home_assistant
import yaml
import logging
import importlib
logger = logging.getLogger(__name__)
# Load configuration to determine which tools to activate
try:
with open("./data/config.yml", "r") as f:
_config = yaml.safe_load(f) or {}
except FileNotFoundError:
_config = {}
# Mapping of tool module names to their required config key.
# A tool is only loaded if its config key is present in config.yml.
_TOOL_CONFIG_MAP = {
'spotify': 'spotify',
'lighting': 'philips',
'google_calendar': 'google',
'airtouch': 'airtouch',
'thinq': 'thinq',
'webos': 'webos',
'search_web': 'search',
'pioneer_avr': 'pioneer',
'home_assistant': 'home_assistant',
}
# Always load these tools (no config dependency)
_ALWAYS_LOAD = ['weather_time']
for _module_name in _ALWAYS_LOAD:
try:
importlib.import_module(f'.{_module_name}', package=__name__)
except Exception as e:
logger.error(f"Failed to load tool {_module_name}: {e}")
for _module_name, _config_key in _TOOL_CONFIG_MAP.items():
if _config_key in _config:
try:
importlib.import_module(f'.{_module_name}', package=__name__)
logger.info(f"Loaded tool: {_module_name}")
except Exception as e:
logger.error(f"Failed to load tool {_module_name}: {e}")
else:
logger.info(f"Skipping tool {_module_name} ('{_config_key}' not in config)")
# Import the tool registry
from .tool_registry import tool_registry, tool
__all__ = [
'tool_registry',
'tool',
'spotify',
'lighting',
'weather_time',
'google_calendar',
'airtouch',
'thinq',
'webos',
'search_web',
'pioneer_avr',
'home_assistant'
]
__all__ = ['tool_registry', 'tool']
+20 -36
View File
@@ -3,8 +3,9 @@
Connects to a Home Assistant instance via REST API for home automation control.
Requires a long-lived access token configured in data/config.yml.
This integration must be explicitly enabled in config.yml to prevent conflicts
with other home automation tools (e.g., Philips Hue lighting).
This module is only loaded when 'home_assistant' is present in config.yml.
Note: This registers generic tool names like "turn_on" and "turn_off" which
may conflict with other integrations (e.g., Philips Hue lighting).
"""
import requests
@@ -14,15 +15,9 @@ from typing import Optional
from .tool_registry import tool
# Load configuration
try:
with open("./data/config.yml", "r") as f:
config = yaml.safe_load(f)
HA_CONFIG = config.get('home_assistant', {})
except FileNotFoundError:
HA_CONFIG = {}
# Check if Home Assistant integration is enabled
HA_ENABLED = HA_CONFIG.get('enabled', False)
with open("./data/config.yml", "r") as f:
config = yaml.safe_load(f)
HA_CONFIG = config.get('home_assistant', {})
# Home Assistant connection settings
HA_URL = HA_CONFIG.get('url', 'http://localhost:8123')
@@ -30,17 +25,6 @@ HA_TOKEN = HA_CONFIG.get('token', '')
TIMEOUT = HA_CONFIG.get('timeout', 10)
def _noop_decorator(name=None, description=None, aliases=None):
"""No-op decorator when Home Assistant integration is disabled."""
def decorator(func):
return func
return decorator
# Use real tool decorator only when enabled
ha_tool = tool if HA_ENABLED else _noop_decorator
def _get_headers() -> dict:
"""Return authorization headers for Home Assistant API."""
return {
@@ -112,7 +96,7 @@ def _resolve_entity(name: str, domain: str = None) -> str:
return name
@ha_tool(
@tool(
name="turn_on",
description="Turn on a device, light, switch, or other Home Assistant entity",
aliases=["ha_turn_on", "switch_on", "turn_on_device"]
@@ -137,7 +121,7 @@ def turn_on(entity: str, brightness: Optional[int] = None) -> str:
return _call_service(domain, "turn_on", entity_id, data if data else None)
@ha_tool(
@tool(
name="turn_off",
description="Turn off a device, light, switch, or other Home Assistant entity",
aliases=["ha_turn_off", "switch_off", "turn_off_device"]
@@ -154,7 +138,7 @@ def turn_off(entity: str) -> str:
return _call_service(domain, "turn_off", entity_id)
@ha_tool(
@tool(
name="toggle",
description="Toggle a Home Assistant entity on or off",
aliases=["ha_toggle", "toggle_device"]
@@ -171,7 +155,7 @@ def toggle(entity: str) -> str:
return _call_service(domain, "toggle", entity_id)
@ha_tool(
@tool(
name="ha_set_brightness",
description="Set the brightness of a light in Home Assistant",
aliases=["ha_brightness", "ha_dim_light"]
@@ -192,7 +176,7 @@ def set_ha_brightness(entity: str, brightness: int) -> str:
return _call_service("light", "turn_on", entity_id, {"brightness": brightness_255})
@ha_tool(
@tool(
name="ha_set_color",
description="Set the color of a light in Home Assistant using color name or RGB",
aliases=["ha_color", "change_light_color"]
@@ -240,7 +224,7 @@ def set_color(entity: str, color: str) -> str:
return _call_service("light", "turn_on", entity_id, {"rgb_color": rgb})
@ha_tool(
@tool(
name="get_entity_state",
description="Get the current state of a Home Assistant entity",
aliases=["ha_state", "check_state", "is_on"]
@@ -275,7 +259,7 @@ def get_entity_state(entity: str) -> str:
return ", ".join(details)
@ha_tool(
@tool(
name="ha_service",
description="Call any Home Assistant service with custom data",
aliases=["call_service", "ha_call"]
@@ -303,7 +287,7 @@ def call_ha_service(domain: str, service: str, entity: str, data: Optional[str]
return _call_service(domain, service, entity_id, extra_data)
@ha_tool(
@tool(
name="ha_set_climate",
description="Set the temperature of a climate/thermostat entity in Home Assistant",
aliases=["ha_climate", "ha_thermostat"]
@@ -325,7 +309,7 @@ def set_climate(entity: str, temperature: float, hvac_mode: Optional[str] = None
return _call_service("climate", "set_temperature", entity_id, data)
@ha_tool(
@tool(
name="ha_lock",
description="Lock a lock entity in Home Assistant",
aliases=["lock_door"]
@@ -340,7 +324,7 @@ def lock(entity: str) -> str:
return _call_service("lock", "lock", entity_id)
@ha_tool(
@tool(
name="ha_unlock",
description="Unlock a lock entity in Home Assistant",
aliases=["unlock_door"]
@@ -355,7 +339,7 @@ def unlock(entity: str) -> str:
return _call_service("lock", "unlock", entity_id)
@ha_tool(
@tool(
name="ha_open_cover",
description="Open a cover/blind/garage in Home Assistant",
aliases=["ha_open", "open_blind", "open_garage"]
@@ -370,7 +354,7 @@ def open_cover(entity: str) -> str:
return _call_service("cover", "open_cover", entity_id)
@ha_tool(
@tool(
name="ha_close_cover",
description="Close a cover/blind/garage in Home Assistant",
aliases=["ha_close", "close_blind", "close_garage"]
@@ -385,7 +369,7 @@ def close_cover(entity: str) -> str:
return _call_service("cover", "close_cover", entity_id)
@ha_tool(
@tool(
name="ha_run_script",
description="Run a Home Assistant script or automation",
aliases=["ha_script", "run_automation"]
@@ -400,7 +384,7 @@ def run_script(script_name: str) -> str:
return _call_service("script", "turn_on", entity_id)
@ha_tool(
@tool(
name="ha_activate_scene",
description="Activate a Home Assistant scene",
aliases=["ha_scene", "set_scene"]
+1 -1
View File
@@ -10,7 +10,7 @@ from phue import Bridge
from .tool_registry import tool, tool_registry
b = Bridge(config['philips']['hue_hub_ip'])
b = Bridge(config['philips']['hue_hub_ip'], config_file_path="./data/.python_hue")
@tool(
+14 -13
View File
@@ -3,8 +3,11 @@ Weather and time information tool using the centralized tool registry.
"""
import yaml
with open("./data/config.yml", "r") as f:
config = yaml.safe_load(f)
try:
with open("./data/config.yml", "r") as f:
config = yaml.safe_load(f) or {}
except FileNotFoundError:
config = {}
from datetime import datetime
import re
@@ -75,14 +78,10 @@ def summarize_today_tomorrow(forecast_data, location):
def load_weather_config():
try:
return config['bom']
except Exception as e:
# Fallback to default values if config file is not available
return {
"host": "ftp.bom.gov.au",
"path": "/anon/gen/fwo/IDN11060.xml"
}
return config.get('bom', {
"host": "ftp.bom.gov.au",
"path": "/anon/gen/fwo/IDN11060.xml"
})
@tool(
@@ -90,18 +89,20 @@ def load_weather_config():
description="Get weather forecast for a location",
aliases=["weather", "forecast", "get_weather"]
)
def get_weather_forecast(location: str = config['bom']['default']) -> str:
def get_weather_forecast(location: str = None) -> str:
"""
Get weather forecast for the specified location.
Args:
location: The location for weather forecast
Returns:
Weather forecast summary
"""
# Load FTP configuration
ftp_config = load_weather_config()
if location is None:
location = ftp_config.get('default', 'Sydney')
# Connect to FTP and retrieve file into memory
ftp = FTP(ftp_config['host'])