Skip to content
This repository was archived by the owner on Jun 7, 2019. It is now read-only.
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 220 additions & 30 deletions flux.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#!/usr/bin/env python
# Smart Bulb Flux Bulb Control With Bluez
# Author: Tony DiCola
# Author: (Flux Bulb modifications) Jeremy Plichta
#
# Author: (Added lots of functionality) Eric Schiesser
# This script will cycle a Flux Bulb Bluetooth Low Energy light bulb
# through a rainbow of different hues.
# through a rainbow of different hues if no options are specified.
# It will also change the color of the bulb to a specified color
# or change the brightness of the warm-white LEDs (see --help option).
#
# Dependencies:
# - You must install the pexpect library, typically with 'sudo pip install pexpect'.
Expand All @@ -15,27 +18,121 @@
import math
import sys
import time
import argparse
import re

import pexpect

# Parse input range for hue values
def parseNumRange(string):
m = re.match(r'(\d+\.?\d*)(?:-(\d+\.?\d*))?$', string)
# ^ (or use .split('-'). anyway you like.)
if not m:
raise argparse.ArgumentTypeError("'" + string + "' is not a range of numbers. Expected forms like '0-1' or '0.2-0.5'.")
start = float(m.group(1))
end = float(m.group(2))
if start < 0 or start > 1 or end < 0 or end > 1 or end < start:
raise argparse.ArgumentTypeError("'" + string + "' is not a range of numbers from 0 to 1.")
return (start, end)

# Parse a color value (-r -g -b options) from 0-1 range to the 0-255 range
def parseColorValue(string):
try:
color = float(string)
except:
raise argparse.ArgumentTypeError("'" + string + "' is not a number.")
color255 = int(color*255)
if color255 > 255 or color255 < 0:
raise argparse.ArgumentTypeError("'" + string + "' is not between 0 and 1")
colorhex = hex(color255)
return color255

# Parse an RGB value (-c option) into a list of (r, g, b) values (each 0-255)
def parseRGB(string):
r,g,b = string.split(',')
r = parseColorValue(r)
g = parseColorValue(g)
b = parseColorValue(b)
return (r, g, b)

# This checks the hex input of the -x/--rgbhex option and simply returns the input if valid
def parseRGBhex(string):
m = re.match(r'^(.{2})(.{2})(.{2})$', string)
if not m:
raise argparse.ArgumentTypeError("'" + string + "' is not a 3-byte hex string like 00eeff.")
try:
r = int(m.group(1),16)
g = int(m.group(2),16)
b = int(m.group(3),16)
except:
raise argparse.ArgumentTypeError("'" + string + "' is not a 3-byte hex string like 00eeff.")
return string

# Parse input arguments
parser = argparse.ArgumentParser(description='Control a Flux BLE Bulb')
parser.add_argument('mac',help='Bluetooth MAC address in format xx:xx:xx:xx:xx:xx',
type=str)
parser.add_argument('-e','--huerange',help='hue range (between 0.0 and 1.0) in format 0.0-1.0',type=parseNumRange)
parser.add_argument('-c','--color',help='change to color given by RGB list, like ' + "'0.5, 0.5, 0.5' (overrides -r -g -b)",metavar='R,G,B',type=parseRGB)
parser.add_argument('-r','--red',help='red value from 0 to 1',type=parseColorValue,metavar='R')
parser.add_argument('-g','--green',help='green value from 0 to 1',type=parseColorValue,metavar='G')
parser.add_argument('-b','--blue',help='blue value from 0 to 1',type=parseColorValue,metavar='B')
parser.add_argument('-w','--white',help='white value from 0 to 1',type=parseColorValue,metavar='W')
parser.add_argument('-a','--addmode',help='''switch addmode on or off.
If a color is not specified, the current value in the bulb will remain the same.''',action="store_true")
parser.add_argument('-x','--rgbhex',help='specify RGB color with hex string (overrides -c -r -g -b)',metavar='RRGGBB',type=parseRGBhex)
args = parser.parse_args()

# Check to see which mode we are in.
# If the RGB values are supplied, we need to send one command
# If the warm-white value is supplied, we need to send a slightly different command
# If we want to send both, we need to send a different command entirely

if (args.red is not None or
args.green is not None or
args.blue is not None or
args.color is not None or
args.rgbhex is not None):
setcolor = True
else:
setcolor = False

if args.white is not None:
setwhite = True
else:
setwhite = False

if (setwhite and setcolor) or args.addmode:
setmode = 3
elif setwhite and not setcolor:
setmode = 2
elif (not setwhite) and setcolor:
setmode = 1
else:
setmode = 0

# Configuration values.
HUE_RANGE = (0.0, 1.0) # Tuple with the minimum and maximum hue values for a
if args.huerange is not None:
HUE_RANGE = args.huerange
else:
HUE_RANGE = (0.0, 1.0)
# Tuple with the minimum and maximum hue values for a
# cycle. Stick with 0 to 1 to cover all hues.
SATURATION = 1.0 # Color saturation for hues (1 is full color).
VALUE = 1.0 # Color value for hues (1 is full value).
CYCLE_SEC = 5.0 # Amount of time for a full cycle of hues to complete.
CYCLE_SEC = 20.0 # Amount of time for a full cycle of hues to complete.
SLEEP_SEC = 0.01 # Amount of time to sleep between loop iterations.


# Get bulb address from command parameters.
if len(sys.argv) != 2:
print 'Error must specify bulb address as parameter!'
print 'Usage: sudo python colorific.py <bulb address>'
print 'Example: sudo python colorific.py 5C:31:3E:F2:16:13'
sys.exit(1)
bulb = sys.argv[1]

#if len(sys.argv) < 2:
# print 'Error must specify bulb address as parameter!'
# print 'Usage: sudo python colorific.py <bulb address>'
# print 'Example: sudo python colorific.py 5C:31:3E:F2:16:13'
# sys.exit(1)
#if len(sys.argv) = 3:
#bulb = sys.argv[1]
bulb = args.mac
# Run gatttool interactively.
gatt = pexpect.spawn('gatttool -I')

Expand All @@ -47,25 +144,118 @@
hue_min, hue_max = HUE_RANGE
hue = hue_min

# Enter main loop.
print 'Press Ctrl-C to quit.'
last = time.time()
while True:
# Get amount of time elapsed since last update, then compute hue delta.
now = time.time()
hue_delta = (now-last)/CYCLE_SEC*(hue_max-hue_min)
hue += hue_delta
# If hue exceeds the maximum wrap back around to start from the minimum.
if hue > hue_max:
hue = hue_min+math.modf(hue)[0]
# Compute 24-bit RGB color based on HSV values.
r, g, b = map(lambda x: int(x*255.0), colorsys.hsv_to_rgb(hue, SATURATION,
VALUE))
# Set light color by sending color change packet over BLE.
# 56RRGGBB00f0aa
line = 'char-write-cmd 0x002e 56{0:02X}{1:02X}{2:02X}00f0aa'.format(r, g, b)
# Check if we are setting the color manually or running the loop
# setmode = 0 --> loop through hues
# setmode = 1 --> set color only
# setmode = 2 --> set white only
# setmode = 3 --> set both warm-white and color

# set color
if setmode == 1:
if args.red is not None:
r = args.red
else:
r = 0
if args.green is not None:
g = args.green
else:
g = 0
if args.blue is not None:
b = args.blue
else:
b = 0
if args.color is not None:
r, g, b = args.color
if args.rgbhex is None:
line = 'char-write-cmd 0x002e 56{0:02X}{1:02X}{2:02X}00f0aa'.format(r, g, b)
else:
line = 'char-write-cmd 0x002e 56{0}00f0aa'.format(args.rgbhex)
print line
gatt.sendline(line)
# Wait a short period of time and setup for the next loop iteration.
time.sleep(SLEEP_SEC)
last = now
gatt.sendline('disconnect')

# set warm-white
elif setmode == 2:
w = args.white
line = 'char-write-cmd 0x002e 56000000{0:02X}0faa'.format(w)
print line
gatt.sendline(line)
time.sleep(SLEEP_SEC)
gatt.sendline('disconnect')

# set colors and warm-white by writing to their respective characteristics
elif setmode == 3:
# Characteristic handles for each color + warm-white
cmds = {'red': '0x0025',
'green': '0x0028',
'blue': '0x002b',
'white': '0x0031'};
colors = dict()
if args.red is not None:
colors['red'] = args.red
elif not args.addmode:
colors['red'] = 0
if args.green is not None:
colors['green'] = args.green
elif not args.addmode:
colors['green'] = 0
if args.blue is not None:
colors['blue'] = args.blue
elif not args.addmode:
colors['blue'] = 0
if args.white is not None:
colors['white'] = args.white
elif not args.addmode:
colors['white'] = 0
if args.color is not None:
r, g, b = args.color
colors['red'] = r
colors['blue'] = b
colors['green'] = g
if args.rgbhex is not None:
m = re.match(r'^(.{2})(.{2})(.{2})$', args.rgbhex)
colors['red'] = int(m.group(1),16)
colors['green'] = int(m.group(2),16)
colors['blue'] = int(m.group(3),16)
for key in colors:
line = 'char-write-cmd {0} {1:02X}'.format(cmds[key], colors[key])
print line
gatt.sendline(line)
time.sleep(SLEEP_SEC)
time.sleep(SLEEP_SEC)
gatt.sendline('disconnect')

# Loop through colors
elif setmode == 0:
# Run main loop
print 'Press Ctrl-C to quit.'
last = time.time()
try:
while True:
# Get amount of time elapsed since last update, then compute hue delta.
now = time.time()
hue_delta = (now-last)/CYCLE_SEC*(hue_max-hue_min)
hue += hue_delta
# If hue exceeds the maximum wrap back around to start from the minimum.
if hue > hue_max:
hue = hue_min+math.modf(hue)[0]
# Compute 24-bit RGB color based on HSV values.
r, g, b = map(lambda x: int(x*255.0), colorsys.hsv_to_rgb(hue, SATURATION,
VALUE))
# Set light color by sending color change packet over BLE.
# 56RRGGBB00f0aa
# If the RGB options are specified, change the light to the specified color
line = 'char-write-cmd 0x002e 56{0:02X}{1:02X}{2:02X}00f0aa'.format(r, g, b)
print line
gatt.sendline(line)
# Wait a short period of time and setup for the next loop iteration.
time.sleep(SLEEP_SEC)
last = now
except KeyboardInterrupt:
# turn off the light and disconnect on exit
line = 'char-write-cmd 0x002e 5600000000f0aa'
time.sleep(SLEEP_SEC)
gatt.sendline(line)
time.sleep(SLEEP_SEC)
gatt.sendline('disconnect')