Skip to content

Instantly share code, notes, and snippets.

@wsoyka
Last active July 15, 2019 00:24
Show Gist options
  • Save wsoyka/4b516384d0a6a88c9ba731d104dc71c7 to your computer and use it in GitHub Desktop.
Save wsoyka/4b516384d0a6a88c9ba731d104dc71c7 to your computer and use it in GitHub Desktop.
yeelight python cli script
import argparse
import struct
import time
from yeelight import Bulb
##################################################################################
##################################################################################
## Easily run actions on your Yeelights ##
## (e.g. when your Home Automation solution doesn't properly support Yeelights) ##
## This script is based on: https://gitlab.com/stavros/python-yeelight/ ##
## Thanks to stavros for building the interface to the yeelights! ##
## To use you first have to install the plugin via pip3 install yeelight ##
##################################################################################
##################################################################################
"""
TODO
support multiple bulbs
expose duration of change
"""
#####################################################################
# If you want to extend this script: most arg keys are only set in #
# ordered_args, irgnore the mirrored ones, they are not set. #
#####################################################################
# get order of args to run in actions in order, kudos to
# https://stackoverflow.com/a/9028031 and
# https://stackoverflow.com/a/8632404
def make_custom_action(customval):
class CustomAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if not 'ordered_args' in namespace:
setattr(namespace, 'ordered_args', [])
previous = namespace.ordered_args
# decide if we want passed args or set custom ones
if (customval is 'use_passed'):
previous.append((self.dest, values))
else:
previous.append((self.dest, customval))
setattr(namespace, 'ordered_args', previous)
if hasattr(namespace, self.dest):
delattr(namespace, self.dest)
return CustomAction
parser = argparse.ArgumentParser(
description='Control Yeelights with CLI arguments.\nBased on https://gitlab.com/stavros/python-yeelight.\n' +
'Commands order will be recognized and the same command can be executed multiple times in one run.',
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('--address', '-a', dest="addr", default='10.0.0.66', metavar='XXX.XXX.XXX.XXX',
help="IP adress of Yeelight (HAS TO BE IN DEVELOPMENT/LAN MODE!)")
parser.add_argument('--delay', '-d', dest="delay", type=float, default=0.25,
help="delay between each command in seconds. setting to 0 can and probably will lead do commands "
+ "not being recognized by lamp")
parser.add_argument('--on', '-on', dest="on",
action=make_custom_action(True), nargs=0, help="turn lamp on")
parser.add_argument('--off', '-off', dest="on",
action=make_custom_action(False), nargs=0, help="turn lamp off")
parser.add_argument('--toggle', dest="toggle",
action=make_custom_action(None), nargs=0, help="toggle lamp")
parser.add_argument('--restart', '-re', dest="restart", action=make_custom_action(None), nargs=0,
help="toggle lamp twice")
parser.add_argument('--brightness', '-bright', dest="bright", type=float, metavar="PERCENT",
action=make_custom_action('use_passed'), nargs='?', help="set brightness to 0-100")
parser.add_argument('--color-temperature', '-ct', dest="ct", type=int, action=make_custom_action('use_passed'),
nargs='?', metavar="KELVIN", help="set color temperature of lamp")
parser.add_argument('--hue-color-temperature', '-hct', dest="hct", type=int, action=make_custom_action('use_passed'),
nargs='?', metavar="HUECT",
help="set color temperature of Yeelight based on color temperature of Phillips Hue light "
+"(different than normal color temperature!)")
parser.add_argument('--hex-color', '-hc', dest="hc", type=str, action=make_custom_action('use_passed'), nargs='?',
metavar="FFFFFF", help="set color of lamp based on hex")
# parser.add_argument('--red', '-r', dest="red", type=int, metavar="RED")
# parser.add_argument('--green', '-g', nargs='?',dest="green", type=int, metavar="GREEN")
# parser.add_argument('--blue', '-b', nargs='?', dest="blue", type=int, metavar="BLUE")
args = parser.parse_args()
def check_rgb(r, g, b):
return 0 <= r <= 255 and 0 <= g <= 255 and 0 <= b <= 255
# check if any of the given values where set
def any_set(list):
for i in list:
if i is not None:
return True
return False
def hue_ct_to_ct(huect):
return 1000000 / huect
def on(bool):
if bool:
print("turning lamp on")
bulb.turn_on()
else:
print("turning lamp off")
bulb.turn_off()
def toggle():
print("toggle")
bulb.toggle()
def restart():
bulb.toggle()
time.sleep(0.25)
bulb.toggle()
def bright(b):
if 0 <= b <= 100:
print("set bright to: {0}".format(b))
bulb.set_brightness(b)
else:
print("bright has to be between 0 and 100")
# make this hex based for simpler parsing
"""#if args.red is not None or args.blue is not None or args.green is not None:
def rgb(red,green,blue):
if check_rgb(red, green, blue):
print("set rgb to: {0},{1},{2}".format(red,green,blue))
bulb.set_rgb(red, blue, green)
else:
# if any_set([args.red, args.green, args.blue]):
parser.error('"All 3 RGB channels have to be set.')
"""
def ct(color_temp):
print("set color temperature to: {0}".format(color_temp))
bulb.set_color_temp(color_temp)
def hct(hue_color_temp):
new_ct = hue_ct_to_ct(hue_color_temp)
print("setting ct from huect: {0}".format(new_ct))
bulb.set_color_temp(new_ct)
def hc(hex_color):
red, green, blue = struct.unpack('BBB', bytes.fromhex(hex_color))
print("setting color to rgb: ({0},{1},{2}) (hex: {3})".format(
red, green, blue, hex_color))
bulb.set_rgb(red, blue, green)
# Set HSV value.
# bulb.set_hsv(153, 100, 50)
# Set hue and saturation, but keep value (brightness) the same.
# bulb.set_hsv(320, 100)
# Save this setting as default.
# bulb.set_default()
if hasattr(args, 'ordered_args'):
print(args)
# discard original args as they are all unset
orig_args = args # keep a backup
# now use ordered args as main args
args = args.ordered_args
bulb = Bulb(orig_args.addr, effect="smooth", duration=1000)
# interpret variables as method names to not have huge if else tree
for method_name, value in args:
# print("key {0} value {1}".format(method_name,value))
# find method by str name
possibles = globals().copy()
possibles.update(locals())
method = possibles.get(method_name)
# print(method)
if not method:
print("couldnt find function: {0}".format(method))
if value is None:
method()
else:
method(value)
time.sleep(orig_args.delay)
else:
parser.error("Specify at least one arg")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment