Python script to change my wifi color LED light bulb state
-
My PC talks to my wifi light bulb directly, not via Tuya's cloud API.
I can say, "Alexa, bulb 1 red" or "Alexa bulb 1 off" etc.
I can also say, "Use @TRIGGERcmd to turn bulb 1 blue" using the MCP tool with ChatGPT.
Example commands:
Turn the bulb on:
python3 c:\tools\tuyabulb.py --id abcdefghijklmnop123456 --key "123456!@ABCcdefh" --ip 192.168.86.25 onSet the color to red:
python3 c:\tools\tuyabulb.py --id abcdefghijklmnop123456 --key "123456!@ABCcdefh" --ip 192.168.86.25 redSet the brightness to 60 percent:
python3 c:\tools\tuyabulb.py --id abcdefghijklmnop123456 --key "123456!@ABCcdefh" --ip 192.168.86.25 60Here's the script:
#!/usr/bin/env python3 import argparse import ipaddress import platform import subprocess import sys import tinytuya # Predefined color presets (RGB values) COLOR_PRESETS = { "red": (255, 0, 0), "green": (0, 255, 0), "blue": (0, 0, 255), "white": (255, 255, 255), "yellow": (255, 255, 0), "cyan": (0, 255, 255), "magenta": (255, 0, 255), "orange": (255, 165, 0), "purple": (128, 0, 128), "pink": (255, 192, 203), } def ping_host(ip: str, timeout: int = 1) -> bool: """Ping a host to check if it's reachable. Returns True if ping succeeds.""" param = "-n" if platform.system().lower() == "windows" else "-c" timeout_param = "-w" if platform.system().lower() == "windows" else "-W" timeout_value = str(timeout * 1000) if platform.system().lower() == "windows" else str(timeout) command = ["ping", param, "1", timeout_param, timeout_value, ip] try: result = subprocess.run(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=timeout + 1) return result.returncode == 0 except (subprocess.TimeoutExpired, Exception): return False def make_device(device_id: str, ip: str, local_key: str, version: str | None): # BulbDevice works for most Tuya bulbs. If yours isn't a "bulb" type, use Device instead. d = tinytuya.BulbDevice(device_id, ip, local_key) d.set_socketPersistent(True) # keep socket open for reliability d.set_socketTimeout(5) # seconds # Many Tuya WiFi bulbs are 3.3. Some are 3.1. If you're unsure, try 3.3 first. if version: d.set_version(float(version)) else: d.set_version(3.3) return d def main(): p = argparse.ArgumentParser(description="Local control of a Tuya bulb via tinytuya (no cloud).") p.add_argument("--id", required=True, help="Tuya device id") p.add_argument("--ip", help="Bulb IP address on your LAN") p.add_argument("--subnet", help="Subnet to scan (e.g. 192.168.1.0/24)") p.add_argument("--start-from", help="IP address to start scanning from (only with --subnet)") p.add_argument("--key", required=True, help="Tuya localKey (16+ chars)") p.add_argument("--ver", default=None, help="Protocol version (e.g. 3.3 or 3.1). Default: 3.3") p.add_argument("cmd", help="Command: off, on, status, color name (red, green, etc.), or brightness 0-100") args = p.parse_args() # Determine command type cmd_lower = args.cmd.lower() if cmd_lower in ["off", "on", "status"]: cmd_type = cmd_lower elif cmd_lower in COLOR_PRESETS: cmd_type = "color" preset_color = cmd_lower elif args.cmd.isdigit(): brightness_val = int(args.cmd) if 0 <= brightness_val <= 100: cmd_type = "brightness" else: p.error("Brightness must be between 0-100") else: p.error(f"Invalid command: {args.cmd}. Use: off, on, status, color name, or brightness (0-100)") # Validate that either --ip or --subnet is provided if not args.ip and not args.subnet: p.error("Either --ip or --subnet must be specified") if args.ip and args.subnet: p.error("Cannot specify both --ip and --subnet") if args.start_from and not args.subnet: p.error("--start-from can only be used with --subnet") # Determine which IPs to try if args.subnet: try: network = ipaddress.ip_network(args.subnet, strict=False) all_ips = [str(ip) for ip in network.hosts()] # Filter to start from specified IP if provided if args.start_from: start_ip = ipaddress.ip_address(args.start_from) # Verify start IP is in the subnet if start_ip not in network: print(f"ERROR: Start IP {args.start_from} is not in subnet {args.subnet}", file=sys.stderr) return 1 # Filter to IPs >= start_from ips_to_try = [ip for ip in all_ips if ipaddress.ip_address(ip) >= start_ip] print(f"Scanning subnet {args.subnet} from {args.start_from} ({len(ips_to_try)} hosts)...", file=sys.stderr) else: ips_to_try = all_ips print(f"Scanning subnet {args.subnet} ({len(ips_to_try)} hosts)...", file=sys.stderr) except ValueError as e: print(f"ERROR: Invalid subnet or IP format: {e}", file=sys.stderr) return 1 else: ips_to_try = [args.ip] # Try each IP until one works last_error = None for ip in ips_to_try: if args.subnet: print(f"Trying {ip}...", file=sys.stderr) # Quick ping check to skip unreachable hosts if not ping_host(ip): continue dev = make_device(args.id, ip, args.key, args.ver) dev = make_device(args.id, ip, args.key, args.ver) try: if cmd_type == "off": r = dev.turn_off() elif cmd_type == "on": r = dev.turn_on() elif cmd_type == "brightness": r = dev.set_brightness_percentage(brightness_val) elif cmd_type == "color": # Get RGB values from preset rgb = COLOR_PRESETS[preset_color] # Set the color r = dev.set_colour(rgb[0], rgb[1], rgb[2]) else: r = dev.status() # Check if the device responded with an errora if isinstance(r, dict) and r.get("Error"): if args.subnet: # During subnet scan, continue to next IP on error last_error = r.get("Error") continue else: # For direct IP, print error and exit print(r) return 2 # Success! Print result and exit if args.subnet: print(f"SUCCESS: Device found at {ip}", file=sys.stderr) print(r) return 0 except Exception as e: last_error = e if not args.subnet: print(f"ERROR: {e}", file=sys.stderr) return 1 # For subnet scan, continue to next IP continue finally: try: dev.set_socketPersistent(False) except Exception: pass # If we get here with subnet scan, none of the IPs worked if args.subnet: print(f"ERROR: Device not found in subnet {args.subnet}. Last error: {last_error}", file=sys.stderr) return 1 if __name__ == "__main__": raise SystemExit(main())This is my commands.json entry:
{ "trigger": "Tuya Bulb 1", "command": "python3 c:\\tools\\tuyabulb.py --id abcdefghijklmnop123456 --key \"123456!@ABCcdefh\" --ip 192.168.86.25", "offCommand": "", "ground": "foreground", "voice": "bulb 1", "voiceReply": "", "allowParams": "true", "mcpToolDescription": "Controls the state of light bulb 1. Parameters are: on, off, red, green, blue, white, yellow, cyan, magenta, orange, purple, pink, or brightness percentage from 0 to 100" }