TRIGGERcmd
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    • Register
    • Login

    Python script to change my wifi color LED light bulb state

    Scheduled Pinned Locked Moved Windows
    1 Posts 1 Posters 571 Views 1 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • RussR Offline
      Russ
      last edited by Russ

      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 on
      

      Set the color to red:

      python3 c:\tools\tuyabulb.py --id abcdefghijklmnop123456 --key "123456!@ABCcdefh" --ip 192.168.86.25 red
      

      Set the brightness to 60 percent:

      python3 c:\tools\tuyabulb.py --id abcdefghijklmnop123456 --key "123456!@ABCcdefh" --ip 192.168.86.25 60
      

      Here'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"
       }
      

      Russell VanderMey

      1 Reply Last reply Reply Quote 0

      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
      • First post
        Last post