| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- import requests
- import json
- from typing import Dict, Optional
- import time
- import logging
- logger = logging.getLogger(__name__)
- class LEDController:
- def __init__(self, ip_address: Optional[str] = None):
- self.ip_address = ip_address
- def _get_base_url(self) -> str:
- """Get base URL for WLED JSON API"""
- if not self.ip_address:
- raise ValueError("No WLED IP configured")
- return f"http://{self.ip_address}/json"
- def set_ip(self, ip_address: str) -> None:
- """Update the WLED IP address"""
- self.ip_address = ip_address
- def _send_command(self, state_params: Dict = None) -> Dict:
- """Send command to WLED and return status"""
- try:
- url = self._get_base_url()
-
- # First check current state
- response = requests.get(f"{url}/state", timeout=2)
- response.raise_for_status()
- current_state = response.json()
-
- # If WLED is off and we're trying to set something, turn it on first
- if not current_state.get('on', False) and state_params and 'on' not in state_params:
- # Turn on power first
- requests.post(f"{url}/state", json={"on": True}, timeout=2)
-
- # Now send the actual command if there are parameters
- if state_params:
- response = requests.post(f"{url}/state", json=state_params, timeout=2)
- response.raise_for_status()
- response = requests.get(f"{url}/state", timeout=2)
- response.raise_for_status()
- current_state = response.json()
-
- preset_id = current_state.get('ps', -1)
- playlist_id = current_state.get('pl', -1)
- # Use True as default since WLED is typically on when responding
- is_on = current_state.get('on', True)
-
- return {
- "connected": True,
- "is_on": is_on,
- "preset_id": preset_id,
- "playlist_id": playlist_id,
- "brightness": current_state.get('bri', 0),
- "message": "WLED is ON" if is_on else "WLED is OFF"
- }
- except ValueError as e:
- return {"connected": False, "message": str(e)}
- except requests.RequestException as e:
- return {"connected": False, "message": f"Cannot connect to WLED: {str(e)}"}
- except json.JSONDecodeError as e:
- return {"connected": False, "message": f"Error parsing WLED response: {str(e)}"}
- def check_wled_status(self) -> Dict:
- """Check WLED connection status and brightness"""
- return self._send_command()
- def set_brightness(self, value: int) -> Dict:
- """Set WLED brightness (0-255)"""
- if not 0 <= value <= 255:
- return {"connected": False, "message": "Brightness must be between 0 and 255"}
- return self._send_command({"bri": value})
- def set_power(self, state: int) -> Dict:
- """Set WLED power state (0=Off, 1=On, 2=Toggle)"""
- if state not in [0, 1, 2]:
- return {"connected": False, "message": "Power state must be 0 (Off), 1 (On), or 2 (Toggle)"}
- if state == 2:
- return self._send_command({"on": "t"}) # Toggle
- return self._send_command({"on": bool(state)})
- def _hex_to_rgb(self, hex_color: str) -> tuple:
- """Convert hex color string to RGB tuple"""
- hex_color = hex_color.lstrip('#')
- if len(hex_color) != 6:
- raise ValueError("Hex color must be 6 characters long (without #)")
- return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
- def set_color(self, r: int = None, g: int = None, b: int = None, w: int = None, hex: str = None) -> Dict:
- """Set WLED color using RGB(W) values or hex color code"""
- if hex is not None:
- try:
- r, g, b = self._hex_to_rgb(hex)
- except ValueError as e:
- return {"connected": False, "message": str(e)}
- # Prepare segment with color
- seg = {"col": [[r or 0, g or 0, b or 0]]}
- if w is not None:
- if not 0 <= w <= 255:
- return {"connected": False, "message": "White value must be between 0 and 255"}
- seg["col"][0].append(w)
- return self._send_command({"seg": [seg]})
- def set_effect(self, effect_index: int, speed: int = None, intensity: int = None,
- brightness: int = None, palette: int = None,
- # Primary color
- r: int = None, g: int = None, b: int = None, w: int = None, hex: str = None,
- # Secondary color
- r2: int = None, g2: int = None, b2: int = None, w2: int = None, hex2: str = None,
- # Transition
- transition: int = 0) -> Dict:
- """
- Set WLED effect with optional parameters
- Args:
- effect_index: Effect index (0-101)
- speed: Effect speed (0-255)
- intensity: Effect intensity (0-255)
- brightness: LED brightness (0-255)
- palette: FastLED palette index (0-46)
- r, g, b: Primary RGB color values (0-255)
- w: Primary White value for RGBW (0-255)
- hex: Primary hex color code (e.g., '#ff0000' or 'ff0000')
- r2, g2, b2: Secondary RGB color values (0-255)
- w2: Secondary White value for RGBW (0-255)
- hex2: Secondary hex color code
- transition: Duration of crossfade in 100ms units (e.g. 7 = 700ms). Default 0 for instant change.
- """
- try:
- effect_index = int(effect_index)
- except (ValueError, TypeError):
- return {"connected": False, "message": "Effect index must be a valid integer between 0 and 101"}
- if not 0 <= effect_index <= 101:
- return {"connected": False, "message": "Effect index must be between 0 and 101"}
- # Convert primary hex to RGB if provided
- if hex is not None:
- try:
- r, g, b = self._hex_to_rgb(hex)
- except ValueError as e:
- return {"connected": False, "message": f"Primary color: {str(e)}"}
- # Convert secondary hex to RGB if provided
- if hex2 is not None:
- try:
- r2, g2, b2 = self._hex_to_rgb(hex2)
- except ValueError as e:
- return {"connected": False, "message": f"Secondary color: {str(e)}"}
- # Build segment parameters
- seg = {"fx": effect_index}
-
- if speed is not None:
- if not 0 <= speed <= 255:
- return {"connected": False, "message": "Speed must be between 0 and 255"}
- seg["sx"] = speed
-
- if intensity is not None:
- if not 0 <= intensity <= 255:
- return {"connected": False, "message": "Intensity must be between 0 and 255"}
- seg["ix"] = intensity
- # Prepare colors array
- colors = []
-
- # Add primary color
- primary = [r or 0, g or 0, b or 0]
- if w is not None:
- if not 0 <= w <= 255:
- return {"connected": False, "message": "Primary white value must be between 0 and 255"}
- primary.append(w)
- colors.append(primary)
-
- # Add secondary color if any secondary color parameter is provided
- if any(x is not None for x in [r2, g2, b2, w2, hex2]):
- secondary = [r2 or 0, g2 or 0, b2 or 0]
- if w2 is not None:
- if not 0 <= w2 <= 255:
- return {"connected": False, "message": "Secondary white value must be between 0 and 255"}
- secondary.append(w2)
- colors.append(secondary)
- if colors:
- seg["col"] = colors
- if palette is not None:
- if not 0 <= palette <= 46:
- return {"connected": False, "message": "Palette index must be between 0 and 46"}
- seg["pal"] = palette
- # Combine with global parameters
- state = {"seg": [seg], "transition": transition}
- if brightness is not None:
- if not 0 <= brightness <= 255:
- return {"connected": False, "message": "Brightness must be between 0 and 255"}
- state["bri"] = brightness
- return self._send_command(state)
- def set_preset(self, preset_id: int) -> bool:
- preset_id = int(preset_id)
- # Send the command and get response
- response = self._send_command({"ps": preset_id})
- logger.debug(response)
- return response
- def effect_loading(led_controller: LEDController):
- res = led_controller.set_effect(47, hex='#ffa000', hex2='#000000', palette=0, speed=150, intensity=150)
- if res.get('is_on', False):
- return True
- else:
- return False
- def effect_idle(led_controller: LEDController):
- led_controller.set_preset(1)
- def effect_connected(led_controller: LEDController):
- res = led_controller.set_effect(0, hex='#08ff00', brightness=100)
- time.sleep(1)
- led_controller.set_effect(0, brightness=0) # Turn off
- time.sleep(0.5)
- res = led_controller.set_effect(0, hex='#08ff00', brightness=100)
- time.sleep(1)
- effect_idle(led_controller)
- if res.get('is_on', False):
- return True
- else:
- return False
- def effect_playing(led_controller: LEDController):
- led_controller.set_preset(2)
- def effect_error(led_controller: LEDController):
- res = led_controller.set_effect(0, hex='#ff0000', brightness=100)
|