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" }
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login