adding Hue light control
Co-authored-by: Alex Dimmock <AtomicAlexD@users.noreply.github.com>
This commit is contained in:
@@ -10,6 +10,9 @@ build/
|
|||||||
dist/
|
dist/
|
||||||
wheels/
|
wheels/
|
||||||
*.egg-info
|
*.egg-info
|
||||||
|
.cache*
|
||||||
|
*.log
|
||||||
|
|
||||||
# Virtual environments
|
# Virtual environments
|
||||||
.venv
|
.venv
|
||||||
|
.env
|
||||||
@@ -14,29 +14,90 @@ CRITICAL: When calling tools, use COMPACT JSON with NO SPACES:
|
|||||||
{
|
{
|
||||||
"name": "get_weather",
|
"name": "get_weather",
|
||||||
"description": "Get current weather for a location",
|
"description": "Get current weather for a location",
|
||||||
|
"parameters"[{"city":"string"}],
|
||||||
"examples": [
|
"examples": [
|
||||||
{
|
{"tool":"get_weather","parameters":{"city":"London"}},
|
||||||
"input": {"tool":"get_weather","parameters":{"city":"New York"}},
|
{"tool":"get_weather","parameters":{"city":"Kettering"}},
|
||||||
"output": {"temperature": 22, "condition": "partly cloudy", "humidity": 65}
|
{"tool":"get_weather","parameters":{"city":"Peterborough"}},
|
||||||
},
|
|
||||||
{
|
|
||||||
"input": {"tool":"get_weather","parameters":{"city":"London"}},
|
|
||||||
"output": {"temperature": 18, "condition": "rainy", "humidity": 80}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "find_folder",
|
"name": "find_folder",
|
||||||
"description": "Find any folder that matches the name provided on your machine or an optional directory",
|
"description": "Find any folder that matches the name provided on your machine or an optional directory",
|
||||||
|
"parameters"[{"folder_name":"string"}],
|
||||||
"examples": [
|
"examples": [
|
||||||
{
|
{"tool":"find_folder","parameters":{"folder_name":"devin"}},
|
||||||
"input": {"tool":"find_folder","parameters":{"folder_name":"devin"}},
|
{"tool":"find_folder","parameters":{"folder_name":"winutils"}},
|
||||||
},
|
{"tool":"find_folder","parameters":{"folder_name":"Dygma"}},
|
||||||
{
|
|
||||||
"input": {"tool":"find_folder","parameters":{"folder_name":"winutils"}},
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}'''
|
},
|
||||||
|
{
|
||||||
|
"name": "turn_on_light",
|
||||||
|
"description": "Turn on any light in the house by name",
|
||||||
|
"parameters"[{"light_name":"string"}],
|
||||||
|
"examples": [
|
||||||
|
{"tool":"turn_on_light","parameters":{"light_name":"Monkey"}},
|
||||||
|
{"tool":"turn_on_light","parameters":{"light_name":"Bedside"}},
|
||||||
|
{"tool":"turn_on_light","parameters":{"light_name":"Bookshelf"}},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "turn_off_light",
|
||||||
|
"description": "Turn off any light in the house by name",
|
||||||
|
"parameters"[{"light_name":"string"}],
|
||||||
|
"examples": [
|
||||||
|
{"tool":"turn_off_light","parameters":{"light_name":"Monkey"}},
|
||||||
|
{"tool":"turn_off_light","parameters":{"light_name":"Bedside"}},
|
||||||
|
{"tool":"turn_off_light","parameters":{"light_name":"Bookshelf"}},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "set_light_brightness",
|
||||||
|
"description": "Set the brightness level of any light in the house by name",
|
||||||
|
"parameters": [
|
||||||
|
{"light_name": "string"},
|
||||||
|
{"brightness": "integer (1-100)"}
|
||||||
|
],
|
||||||
|
"examples": [
|
||||||
|
{"tool": "set_light_brightness", "parameters": {"light_name": "Monkey", "brightness": 25}},
|
||||||
|
{"tool": "set_light_brightness", "parameters": {"light_name": "Bedside", "brightness": 50}},
|
||||||
|
{"tool": "set_light_brightness", "parameters": {"light_name": "Bookshelf", "brightness": 75}},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "turn_on_room",
|
||||||
|
"description": "Turn on any room of lights in the house by name",
|
||||||
|
"parameters"[{"room_name":"string"}],
|
||||||
|
"examples": [
|
||||||
|
{"tool":"turn_on_room","parameters":{"room_name":"Office"}},
|
||||||
|
{"tool":"turn_on_room","parameters":{"room_name":"Bedroom"}},
|
||||||
|
{"tool":"turn_on_room","parameters":{"room_name":"Lounge"}},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "turn_off_room",
|
||||||
|
"description": "Turn off any room of lights in the house by name",
|
||||||
|
"parameters"[{"room_name":"string"}],
|
||||||
|
"examples": [
|
||||||
|
{"tool":"turn_off_room","parameters":{"room_name":"Kitchen"}},
|
||||||
|
{"tool":"turn_off_room","parameters":{"room_name":"Lounge"}},
|
||||||
|
{"tool":"turn_off_room","parameters":{"room_name":"Office"}},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "set_room_brightness",
|
||||||
|
"description": "Set the brightness level of any room of lights in the house by name",
|
||||||
|
"parameters": [
|
||||||
|
{"room_name": "string"},
|
||||||
|
{"brightness": "integer (1-100)"}
|
||||||
|
],
|
||||||
|
"examples": [
|
||||||
|
{"tool": "set_room_brightness", "parameters": {"room_name": "Office", "brightness": 25}},
|
||||||
|
{"tool": "set_room_brightness", "parameters": {"room_name": "Bedroom", "brightness": 50}},
|
||||||
|
{"tool": "set_room_brightness", "parameters": {"room_name": "Kitchen", "brightness": 75}},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
# Tool config
|
# Tool config
|
||||||
# WMO Weather interpretation codes mapping
|
# WMO Weather interpretation codes mapping
|
||||||
@@ -70,3 +131,26 @@ WEATHER_CODE_MAP = {
|
|||||||
96: "Thunderstorm with slight hail",
|
96: "Thunderstorm with slight hail",
|
||||||
99: "Thunderstorm with heavy hail"
|
99: "Thunderstorm with heavy hail"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LIGHT_NAMES = {
|
||||||
|
'monkey': '9b128205-0a98-46b5-9234-2bd0be9fd009',
|
||||||
|
'bench': 'b0fae364-78ed-4f99-86d0-6c65cbf792cd',
|
||||||
|
"bedside": '945298e2-96b6-4c61-a506-1691bf7d7989',
|
||||||
|
'bookshelf': '96d805e6-f39a-4e5f-9bde-3a42cbadfc6c',
|
||||||
|
'lounge 1': '3ffcd59a-a19d-4066-af35-b5f45a2cf946',
|
||||||
|
'lounge 2': 'facf3d02-f88d-482c-acb1-f9a4ed519356',
|
||||||
|
'kitchen 1': 'badef93d-10ab-437d-9fc2-a09181a08fae',
|
||||||
|
'kitchen 2': 'f15db9c5-0b67-49a6-8687-f20d768048b7',
|
||||||
|
'kitchen 3': '47be084d-9e35-4108-aa4d-8da8f13e7d42',
|
||||||
|
'kitchen 4': 'be408308-b14e-4bc3-9065-68055fd74b68',
|
||||||
|
'shelf': '296ff923-da19-43a5-b1b2-7de25b227469',
|
||||||
|
'cupboards': 'fe27cf93-68e2-47f2-a39e-d9bc4650263b',
|
||||||
|
'office white': 'a1e0f26b-90b8-4044-98a3-58ed6a0f84b0',
|
||||||
|
}
|
||||||
|
ROOM_NAMES = {
|
||||||
|
'office': 'bb0856ac-81d9-439a-83dc-8703c90574ba',
|
||||||
|
'bedroom': '621fea30-f8b6-4de9-a347-1b4436321398',
|
||||||
|
'lounge': '3dc9aab6-6379-4fa4-8e96-aae94fa692cf',
|
||||||
|
'Kitchen': 'eaa524bc-edb6-4fd8-89b7-7cfc563ed7f1',
|
||||||
|
'graveyard': 'aec3c969-581f-45c4-8f8f-b9407ee8caa3',
|
||||||
|
}
|
||||||
@@ -11,8 +11,6 @@ from llm_interface import llm
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
#TODO: add context for llm calls so we have history and can chain messages
|
|
||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
class RichConsoleHandler(logging.StreamHandler):
|
class RichConsoleHandler(logging.StreamHandler):
|
||||||
@@ -45,9 +43,10 @@ def main(logger):
|
|||||||
logger.info(f"Response equals original: {handled_response == llm_reply}")
|
logger.info(f"Response equals original: {handled_response == llm_reply}")
|
||||||
|
|
||||||
if handled_response != llm_reply:
|
if handled_response != llm_reply:
|
||||||
output = language_model.tool_response(prompt=f'Make this lovely markdown, use fun emojis {handled_response}')
|
output = language_model.tool_response(prompt=f'''Your original Request was: {llm_prompt},
|
||||||
#TODO: Make sure to pass the history into the this so that if we ask additional questions it has context
|
This is the tool reply: {handled_response}
|
||||||
# For example get weather AND suggest clothing based on weather.
|
Make a markdown format reply, using fun emojis.
|
||||||
|
''')
|
||||||
elif handled_response == llm_reply:
|
elif handled_response == llm_reply:
|
||||||
output = handled_response
|
output = handled_response
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ dependencies = [
|
|||||||
"openmeteo-requests>=1.7.2",
|
"openmeteo-requests>=1.7.2",
|
||||||
"pydantic>=2.11.7",
|
"pydantic>=2.11.7",
|
||||||
"pytest>=8.4.1",
|
"pytest>=8.4.1",
|
||||||
|
"python-dotenv>=1.1.1",
|
||||||
"requests>=2.32.5",
|
"requests>=2.32.5",
|
||||||
"requests-cache>=1.2.1",
|
"requests-cache>=1.2.1",
|
||||||
"retry-requests>=2.0.0",
|
"retry-requests>=2.0.0",
|
||||||
|
|||||||
@@ -7,45 +7,64 @@ import config
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import dotenv
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
|
# Suppress SSL warnings (this is fine for local network)
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
class ToolExecutor:
|
class ToolExecutor:
|
||||||
def __init__(self, logger):
|
def __init__(self, logger):
|
||||||
self.tools = {
|
self.available_tools = {
|
||||||
"get_weather": self.get_weather,
|
"get_weather": self.get_weather,
|
||||||
"find_folder": self.find_folder
|
"find_folder": self.find_folder,
|
||||||
|
"turn_on_light": self.turn_on_light,
|
||||||
|
"turn_off_light": self.turn_off_light,
|
||||||
|
"set_light_brightness": self.set_light_brightness,
|
||||||
|
"turn_on_room": self.turn_on_room,
|
||||||
|
"turn_off_room": self.turn_off_room,
|
||||||
|
"set_room_brightness": self.set_room_brightness
|
||||||
}
|
}
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
|
self.bridge_ip = '192.168.0.152'
|
||||||
|
self.app_key = os.getenv("USERNAME")
|
||||||
|
self.base_url = f'https://{self.bridge_ip}/clip/v2/resource'
|
||||||
|
self.session = requests.Session()
|
||||||
|
self.session.verify = False # For local network devices
|
||||||
|
|
||||||
def process_llm_response(self, llm_output):
|
|
||||||
|
def process_llm_response(self, llm_output):
|
||||||
if llm_output.startswith('{"tool":'):
|
if llm_output.startswith('{"tool":'):
|
||||||
# Parse the tool call from LLM response
|
# Parse the tool call from LLM response
|
||||||
llm_output = json.loads(llm_output)
|
llm_output = json.loads(llm_output)
|
||||||
|
|
||||||
self.logger.info(f"Parsed LLM output: {llm_output}")
|
self.logger.info(f"Parsed LLM output: {llm_output}")
|
||||||
tool_name = llm_output.get("tool",'no tool')
|
tool_name = llm_output.get("tool",'no tool')
|
||||||
parameters = llm_output.get("parameters", 'no parameters')
|
parameters = llm_output.get("parameters", 'no parameters')
|
||||||
self.logger.info(f'parsed tool: {tool_name}, parsed parameters: {parameters}')
|
self.logger.info(f'parsed tool: {tool_name}, parsed parameters: {parameters}')
|
||||||
|
|
||||||
# Execute the actual function
|
# Execute the actual function
|
||||||
result = self.tools[tool_name](**parameters)
|
result = self.available_tools[tool_name](**parameters)
|
||||||
|
|
||||||
# Return result for LLM to use
|
# Return result for LLM to use
|
||||||
return result
|
return result
|
||||||
return llm_output
|
return llm_output
|
||||||
|
|
||||||
def get_weather(self, city: str, units: str = "celsius"):
|
def get_weather(self, city: str, units: str = "celsius"):
|
||||||
"""Get current weather for a location using Open-Meteo API with openmeteo-requests library."""
|
"""Get current weather for a location using Open-Meteo API with openmeteo-requests library."""
|
||||||
|
|
||||||
self.logger.info(f'get weather called, city = {city}')
|
self.logger.info(f'get weather called, city = {city}')
|
||||||
if not city:
|
if not city:
|
||||||
raise ValueError("City parameter is required")
|
raise ValueError("City parameter is required")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Setup the Open-Meteo API client with cache and retry on error
|
# Setup the Open-Meteo API client with cache and retry on error
|
||||||
cache_session = requests_cache.CachedSession('.cache', expire_after=3600)
|
cache_session = requests_cache.CachedSession('.cache', expire_after=3600)
|
||||||
retry_session = retry(cache_session, retries=5, backoff_factor=0.2)
|
retry_session = retry(cache_session, retries=5, backoff_factor=0.2)
|
||||||
openmeteo = openmeteo_requests.Client(session=retry_session)
|
openmeteo = openmeteo_requests.Client(session=retry_session)
|
||||||
|
|
||||||
# Step 1: Get coordinates for the city using geocoding API
|
# Step 1: Get coordinates for the city using geocoding API
|
||||||
geocoding_url = "https://geocoding-api.open-meteo.com/v1/search"
|
geocoding_url = "https://geocoding-api.open-meteo.com/v1/search"
|
||||||
geocoding_params = {
|
geocoding_params = {
|
||||||
@@ -54,33 +73,33 @@ class ToolExecutor:
|
|||||||
"language": "en",
|
"language": "en",
|
||||||
"format": "json"
|
"format": "json"
|
||||||
}
|
}
|
||||||
|
|
||||||
geo_response = requests.get(geocoding_url, params=geocoding_params, timeout=10)
|
geo_response = requests.get(geocoding_url, params=geocoding_params, timeout=10)
|
||||||
geo_response.raise_for_status()
|
geo_response.raise_for_status()
|
||||||
geo_data = geo_response.json()
|
geo_data = geo_response.json()
|
||||||
self.logger.info(f'Geo data for city: {geo_data}')
|
self.logger.info(f'Geo data for city: {geo_data}')
|
||||||
|
|
||||||
if not geo_data.get("results"):
|
if not geo_data.get("results"):
|
||||||
raise Exception(f"City '{city}' not found")
|
raise Exception(f"City '{city}' not found")
|
||||||
|
|
||||||
location = geo_data["results"][0]
|
location = geo_data["results"][0]
|
||||||
latitude = location["latitude"]
|
latitude = location["latitude"]
|
||||||
longitude = location["longitude"]
|
longitude = location["longitude"]
|
||||||
city_name = location["name"]
|
city_name = location["name"]
|
||||||
country = location.get("country", "Unknown")
|
country = location.get("country", "Unknown")
|
||||||
|
|
||||||
# Step 2: Get weather data using openmeteo-requests
|
# Step 2: Get weather data using openmeteo-requests
|
||||||
url = "https://api.open-meteo.com/v1/forecast"
|
url = "https://api.open-meteo.com/v1/forecast"
|
||||||
|
|
||||||
# Convert temperature units for the API
|
# Convert temperature units for the API
|
||||||
temperature_unit = "celsius" if units == "celsius" else "fahrenheit"
|
temperature_unit = "celsius" if units == "celsius" else "fahrenheit"
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
"latitude": latitude,
|
"latitude": latitude,
|
||||||
"longitude": longitude,
|
"longitude": longitude,
|
||||||
"current": [
|
"current": [
|
||||||
"temperature_2m",
|
"temperature_2m",
|
||||||
"relative_humidity_2m",
|
"relative_humidity_2m",
|
||||||
"weather_code",
|
"weather_code",
|
||||||
"surface_pressure",
|
"surface_pressure",
|
||||||
"wind_speed_10m"
|
"wind_speed_10m"
|
||||||
@@ -88,7 +107,7 @@ class ToolExecutor:
|
|||||||
"temperature_unit": temperature_unit,
|
"temperature_unit": temperature_unit,
|
||||||
"wind_speed_unit": "kmh"
|
"wind_speed_unit": "kmh"
|
||||||
}
|
}
|
||||||
|
|
||||||
responses = openmeteo.weather_api(url, params=params)
|
responses = openmeteo.weather_api(url, params=params)
|
||||||
response = responses[0]
|
response = responses[0]
|
||||||
self.logger.info(f'Weather API response: {response}')
|
self.logger.info(f'Weather API response: {response}')
|
||||||
@@ -96,16 +115,16 @@ class ToolExecutor:
|
|||||||
# Get current weather data
|
# Get current weather data
|
||||||
current = response.Current()
|
current = response.Current()
|
||||||
self.logger.info(f'Current weather data: {current}')
|
self.logger.info(f'Current weather data: {current}')
|
||||||
|
|
||||||
# Extract values using the library's methods
|
# Extract values using the library's methods
|
||||||
temperature = current.Variables(0).Value() # temperature_2m
|
temperature = current.Variables(0).Value() # temperature_2m
|
||||||
humidity = current.Variables(1).Value() # relative_humidity_2m
|
humidity = current.Variables(1).Value() # relative_humidity_2m
|
||||||
weather_code = int(current.Variables(2).Value()) # weather_code
|
weather_code = int(current.Variables(2).Value()) # weather_code
|
||||||
pressure = current.Variables(3).Value() # surface_pressure
|
pressure = current.Variables(3).Value() # surface_pressure
|
||||||
wind_speed = current.Variables(4).Value() # wind_speed_10m
|
wind_speed = current.Variables(4).Value() # wind_speed_10m
|
||||||
|
|
||||||
condition = config.WEATHER_CODE_MAP.get(weather_code, "Unknown")
|
condition = config.WEATHER_CODE_MAP.get(weather_code, "Unknown")
|
||||||
|
|
||||||
|
|
||||||
json_reply = {
|
json_reply = {
|
||||||
"temperature": temperature,
|
"temperature": temperature,
|
||||||
@@ -120,41 +139,84 @@ class ToolExecutor:
|
|||||||
}
|
}
|
||||||
self.logger.info(f"Weather data result: {json_reply}")
|
self.logger.info(f"Weather data result: {json_reply}")
|
||||||
return json_reply
|
return json_reply
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"Weather lookup failed: {str(e)}")
|
raise Exception(f"Weather lookup failed: {str(e)}")
|
||||||
|
|
||||||
def find_folder(self, folder_name: str, search_path: str = "/home/"):
|
def find_folder(self, folder_name: str, search_path: str = "/home/"):
|
||||||
"""Search for folders with a specific name on the PC."""
|
"""Search for folders with a specific name on the PC."""
|
||||||
|
|
||||||
self.logger.info(f'find_folder called, folder_name = {folder_name}, search_path = {search_path}')
|
self.logger.info(f'find_folder called, folder_name = {folder_name}, search_path = {search_path}')
|
||||||
|
|
||||||
if not folder_name:
|
if not folder_name:
|
||||||
raise ValueError("folder_name parameter is required")
|
raise ValueError("folder_name parameter is required")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
found_folders = []
|
found_folders = []
|
||||||
# Use find command to search for directories
|
# Use find command to search for directories
|
||||||
cmd = ['find', search_path, '-type', 'd', '-name', folder_name]
|
cmd = ['find', search_path, '-type', 'd', '-name', folder_name]
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||||
self.logger.warning(result)
|
self.logger.info(result)
|
||||||
if result.stdout:
|
if result.stdout:
|
||||||
found_folders = [path.strip() for path in result.stdout.split('\n') if path.strip()]
|
found_folders = [path.strip() for path in result.stdout.split('\n') if path.strip()]
|
||||||
|
|
||||||
# Limit results to first 50 to avoid overwhelming output
|
# Limit results to first 50 to avoid overwhelming output
|
||||||
found_folders = found_folders[:50]
|
found_folders = found_folders[:50]
|
||||||
|
|
||||||
json_reply = {
|
json_reply = {
|
||||||
"folder_name": folder_name,
|
"folder_name": folder_name,
|
||||||
"search_path": search_path,
|
"search_path": search_path,
|
||||||
"found_folders": found_folders,
|
"found_folders": found_folders,
|
||||||
"count": len(found_folders)
|
"count": len(found_folders)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.logger.info(f"Folder search result: {json_reply}")
|
self.logger.info(f"Folder search result: {json_reply}")
|
||||||
return json_reply
|
return json_reply
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
raise Exception("Folder search timed out after 30 seconds")
|
raise Exception("Folder search timed out after 30 seconds")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"Folder search failed: {str(e)}")
|
raise Exception(f"Folder search failed: {str(e)}")
|
||||||
|
|
||||||
|
def tell_hue(self, endpoint, payload):
|
||||||
|
headers = {"hue-application-key": self.app_key}
|
||||||
|
api_path = self.base_url+endpoint
|
||||||
|
return self.session.put(api_path, json=payload, headers=headers, timeout=10)
|
||||||
|
|
||||||
|
def control_light(self, light_name, on_state=True, brightness=None):
|
||||||
|
"""Control a light"""
|
||||||
|
endpoint = f'/light/{config.LIGHT_NAMES[light_name.lower()]}'
|
||||||
|
data = {"on": {"on": on_state}}
|
||||||
|
if brightness is not None:
|
||||||
|
data["dimming"] = {
|
||||||
|
"brightness": brightness
|
||||||
|
}
|
||||||
|
return self.tell_hue(endpoint,data)
|
||||||
|
|
||||||
|
def control_room(self, room_name, on_state=True, brightness=None):
|
||||||
|
"""Control a light"""
|
||||||
|
endpoint = f'/grouped_light/{config.ROOM_NAMES[room_name.lower()]}'
|
||||||
|
data = {"on": {"on": on_state}}
|
||||||
|
if brightness is not None:
|
||||||
|
data["dimming"] = {
|
||||||
|
"brightness": brightness
|
||||||
|
}
|
||||||
|
return self.tell_hue(endpoint,data)
|
||||||
|
|
||||||
|
def turn_on_light(self, light_name):
|
||||||
|
return self.control_light(light_name)
|
||||||
|
|
||||||
|
def turn_off_light(self, light_name):
|
||||||
|
return self.control_light(light_name, on_state=False)
|
||||||
|
|
||||||
|
def set_light_brightness(self, light_name, brightness):
|
||||||
|
return self.control_light(light_name, on_state=True, brightness=brightness)
|
||||||
|
|
||||||
|
def turn_on_room(self, room_name):
|
||||||
|
return self.control_room(room_name)
|
||||||
|
|
||||||
|
def turn_off_room(self, room_name):
|
||||||
|
return self.control_room(room_name, on_state=False)
|
||||||
|
|
||||||
|
def set_room_brightness(self, room_name, brightness):
|
||||||
|
return self.control_room(room_name, on_state=True, brightness=brightness)
|
||||||
@@ -116,6 +116,7 @@ dependencies = [
|
|||||||
{ name = "openmeteo-requests" },
|
{ name = "openmeteo-requests" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
|
{ name = "python-dotenv" },
|
||||||
{ name = "requests" },
|
{ name = "requests" },
|
||||||
{ name = "requests-cache" },
|
{ name = "requests-cache" },
|
||||||
{ name = "retry-requests" },
|
{ name = "retry-requests" },
|
||||||
@@ -130,6 +131,7 @@ requires-dist = [
|
|||||||
{ name = "openmeteo-requests", specifier = ">=1.7.2" },
|
{ name = "openmeteo-requests", specifier = ">=1.7.2" },
|
||||||
{ name = "pydantic", specifier = ">=2.11.7" },
|
{ name = "pydantic", specifier = ">=2.11.7" },
|
||||||
{ name = "pytest", specifier = ">=8.4.1" },
|
{ name = "pytest", specifier = ">=8.4.1" },
|
||||||
|
{ name = "python-dotenv", specifier = ">=1.1.1" },
|
||||||
{ name = "requests", specifier = ">=2.32.5" },
|
{ name = "requests", specifier = ">=2.32.5" },
|
||||||
{ name = "requests-cache", specifier = ">=1.2.1" },
|
{ name = "requests-cache", specifier = ">=1.2.1" },
|
||||||
{ name = "retry-requests", specifier = ">=2.0.0" },
|
{ name = "retry-requests", specifier = ">=2.0.0" },
|
||||||
@@ -382,6 +384,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" },
|
{ url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dotenv"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "qh3"
|
name = "qh3"
|
||||||
version = "1.5.4"
|
version = "1.5.4"
|
||||||
|
|||||||
Reference in New Issue
Block a user