User avatar
Stephanie
Posts: 13
Joined: Tue Jun 17, 2014 12:03 pm
Location: Ontario, Canada
Contact: Website

Controlling a BeeWi SmartLite

Sun Aug 09, 2015 1:25 pm

I recently came across some "BeeWi SmartLite" bulbs at the local home-improvement shop. They are controlled through Bluetooth, and they can do white and RGB colours. The price was right so I picked up a couple and got to work trying to figure out how to control them with my Raspberry Pi.

The result is I've written a Python script that allows command-line control over the bulbs. They can be turned on and off, the brightness can be controlled, they can be set to colour or white mode, and the colour temperature of the white can be controlled.

I have also figured out how to set their internal timers and toggle their 'disco' mode but haven't bothered putting that in my script. For the timers, I have more faith in just setting cron jobs on the Pi to control things. Eg. come winter, I'm liking the thought of a light in my bedroom coming on when it's time for me to get up.

Finally, I've put in a status function to grab the bulb's current settings. It's mostly just for fun but can be useful in debugging.

Here's some examples of code output:

Code: Select all

pi@mcpi ~ $ sudo beewibulb
Correct usage is "[sudo] beewibulb <device address> <command> [argument]"
       <device address> in the format XX:XX:XX:XX:XX:XX
       Commands:  toggle          - toggle light on / off
                  on              - turn light on
                  off             - turn light off
                  bright          - set brightness to 100%
                  medium          - set brightness to  60%
                  dim             - set brightness to  10%
                  colour RRGGBB   - set bulb to colour RRGGBB
                  white           - set bulb to white mode
                  warm            - set white to warm CCT
                  cool            - set white to cool CCT

Code: Select all

pi@mcpi ~ $ sudo beewibulb D0:39:72:XX:XX:XX status
bulb name   = Zone 1 Bulb
firmware    = V2.2 R141224
raw status  = 01 b2 00 00 ff
bulbIsOn    = True
bulbIsWhite = True
brightness  = 100%
white tone  = cool
And here is the full Python script:

Code: Select all

#!/usr/bin/env python2.7

#	A python script to control the BeeWi 'BBL207 - Smart LED Color Bulb'
#	BeeWi website: http://www.bee-wi.com/en.cfm
#	Tested & working with this bulb in particular: http://www.bee-wi.com/bbl207,us,4,BBL207-A1.cfm
#
#	by Stephanie Maks
#
#	Figured out with help from -- and thanks to -- the following sources:
#		http://stackoverflow.com/questions/24853597/ble-gatttool-cannot-connect-even-though-device-is-discoverable-with-hcitool-lesc
#		http://mike.saunby.net/2013/04/raspberry-pi-and-ti-cc2541-sensortag.html
#		https://github.com/sandeepmistry/node-yeelight-blue
#
#	note: this needs to be called with 'sudo' or by root in order to work with some of the hci commands

import os
import sys
import socket
from subprocess import call, check_output
import pexpect
import time

def cycleHCI() :
	# maybe a useless time waster but it makes sure our hci0 is starting fresh and clean
	# nope in fact we need to call this before each time we do hci or gatt stuff or it doesn't work
	call(['hciconfig', 'hci0', 'down'])
	time.sleep(0.1)
	call(['hciconfig', 'hci0', 'up'])
	time.sleep(0.1)

def getSettings(mac) :
	# read the settings characteristic (read handle is 0x0024)
	# command return value = 'Characteristic value/descriptor: 00 b2 ff 00 00'
	# function returns settings as array of five integers
	cycleHCI();
	settings_array = [0,0,0,0,0]
	raw_input=check_output(['gatttool', '-i', 'hci0', '-b', mac, '--char-read', '--handle=0x0024']);
	if ':' in raw_input:
		raw_list=raw_input.split(':')
		raw_data=raw_list[1]
		raw_data=raw_data.strip()
		octet_list=raw_data.split(' ')
		if len(octet_list) > 4 :
			for i in range(0, 5) :
				j = int(octet_list[i], 16)
				settings_array[i] = j
	return settings_array

def getDeviceInfo(mac) :
	deviceName = ''
	deviceVers = ''
	raw_input=check_output(['gatttool', '-i', 'hci0', '-b', mac, '--char-read', '--handle=0x0003']);
	if ':' in raw_input:
		raw_list=raw_input.split(':')
		raw_data=raw_list[1]
		raw_data=raw_data.strip()
		octet_list=raw_data.split(' ')
		for octet in octet_list :
			j = int(octet, 16)
			if j > 31 and j < 127 : deviceName += str(unichr(j))
	raw_input=check_output(['gatttool', '-i', 'hci0', '-b', mac, '--char-read', '--handle=0x0018']);
	if ':' in raw_input:
		raw_list=raw_input.split(':')
		raw_data=raw_list[1]
		raw_data=raw_data.strip()
		octet_list=raw_data.split(' ')
		for octet in octet_list :
			j = int(octet, 16)
			if j > 31 and j < 127 : deviceVers += str(unichr(j))
	cycleHCI()
	return (deviceName, deviceVers)

def	writeCommandToBulb(mac, the_command) :
	# settings commands are sent to the 0x0021 characteristic
	# all commands are prefixed with 0x55 (85) and suffixed with 0x0d 0x0a (CR LF)
	if the_command != '' :
		the_command = '55' + the_command + '0D0A'
		cycleHCI()
		hd = check_output(['/usr/local/bin/hcitool', '-i', 'hci0', 'lecc', mac])
		ha = hd.split(' ');
		connectionHandle=0
		if ha[0] == 'Connection' and ha[1] == 'handle' : connectionHandle = ha[2]
		if connectionHandle > 0 :
			bulb = pexpect.spawn('gatttool -i hci0 -b ' + mac + ' -I')
			bulb.expect('\[LE\]>', timeout=60)
			bulb.sendline('connect')
			bulb.expect('\[LE\]>', timeout=60)
			bulb.sendline('char-write-cmd 0x0021 ' + the_command)
			bulb.expect('\[LE\]>', timeout=60)
			bulb.sendline('disconnect')
			bulb.expect('\[LE\]>', timeout=60)
			bulb.sendline('exit')
			bulb.expect(pexpect.EOF, timeout=60)
			call(['hcitool', '-i', 'hci0', 'ledc', connectionHandle])
		cycleHCI()

def setBrightness(mac, value) :
	# brightness command is 12 [02 to 0B] (18, [2 to 11])
	if value == 'low' :
		writeCommandToBulb(mac, '1202')
	elif value == 'med' :
		writeCommandToBulb(mac, '1207')
	else :
		writeCommandToBulb(mac, '120B')

def setBulbWhiteTemp(mac, tone) :
	# brightness command is 11 [02 to 0B] (17, [2 to 11])
	if tone == 'warm' :
		writeCommandToBulb(mac, '110B')
	elif tone == 'cool' :
		writeCommandToBulb(mac, '1102')

def setBulbWhite(mac) :
	# set to white command is 14 FF FF FF (20, -128, -128, -128)
	writeCommandToBulb(mac, '14FFFFFF')

def setBulbColour(mac, colour) :
	# colour command is 13 RR GG BB (19, Red, Green, Blue)
	value = '13' + colour
	writeCommandToBulb(mac, value)

def switchBulbOn(mac) :
	# on command is 10 01 (16, 1)
	writeCommandToBulb(mac, '1001')

def switchBulbOff(mac) :
	# off command is 10 00 (16, 0)
	writeCommandToBulb(mac, '1000')

def printHelp() :
	print 'Correct usage is "[sudo] beewibulb <device address> <command> [argument]"'
	print '       <device address> in the format XX:XX:XX:XX:XX:XX'
	print '       Commands:  toggle          - toggle light on / off'
	print '                  on              - turn light on'
	print '                  off             - turn light off'
	print '                  bright          - set brightness to 100%'
	print '                  medium          - set brightness to  60%'
	print '                  dim             - set brightness to  10%'
	print '                  colour RRGGBB   - set bulb to colour RRGGBB'
	print '                  white           - set bulb to white mode'
	print '                  warm            - set white to warm CCT'
	print '                  cool            - set white to cool CCT'

if __name__=='__main__' :
	if os.geteuid() != 0 :
		print 'WARNING: This script may not work correctly without sudo / root. Sorry.'
	if len(sys.argv) < 3 :
		printHelp()
	else :
		bulbIsOn = False
		bulbIsWhite = False
		device_address = sys.argv[1]
		command = sys.argv[2]
		command = command.lower()
		extra = ''
		error = ''
		if len(sys.argv) == 4 : extra = sys.argv[3]
		extra = extra.lower()
		#
		# address shortcuts
		if device_address == 'pp' : device_address = 'D0:39:72:XX:XX:XX'
		if device_address == 'z1' : device_address = 'D0:39:72:XX:XX:XX'
		if device_address == 'z4' : device_address = 'D0:39:72:XX:XX:XX'
		#
		if len(device_address) != 17 :
			print 'ERROR: device address must be in the format NN:NN:NN:NN:NN:NN'
			exit()
		settings = getSettings(device_address)
		if (settings[0] % 2) == 1 : bulbIsOn = True
		if (settings[1] & 15) > 0 : bulbIsWhite = True
		if command == 'colour' and extra == 'ffffff' : command = 'white'
		if 'stat' in command :
			name, version = getDeviceInfo(device_address)
			status = '%02x %02x %02x %02x %02x' % (settings[0], settings[1], settings[2], settings[3], settings[4])
			colour = '%02x%02x%02x' % (settings[2], settings[3], settings[4])
			brightness = (((settings[1] & 240) >> 4) - 1) * 10
			print 'bulb name   = ' + name
			print 'firmware    = ' + version
			print 'raw status  = ' + status
			print 'bulbIsOn    = ' + str(bulbIsOn)
			print 'bulbIsWhite = ' + str(bulbIsWhite)
			print 'brightness  = ' + str(brightness) + '%'
			if bulbIsWhite :
				cct = (settings[1] & 15)
				if cct == 2 : cct = 'cool'
				if cct == 11 : cct = 'warm'
				print 'white tone  = ' + cct
			else :
				print 'colour      = ' + colour
		elif command == 'on' :
			if not bulbIsOn : switchBulbOn(device_address)
		elif command == 'off' :
			if bulbIsOn : switchBulbOff(device_address)
		elif command == 'toggle' :
			if bulbIsOn :
				switchBulbOff(device_address)
			else :
				switchBulbOn(device_address)
		elif bulbIsOn :
			if command == 'bright' :
				setBrightness(device_address, 'high')
			elif command == 'dim' : 
				setBrightness(device_address, 'low')
			elif command == 'medium' :
				setBrightness(device_address, 'med')
			elif command == 'warm' or command == 'cool' :
				if bulbIsWhite : setBulbWhiteTemp(device_address, command)
			elif command == 'white' :
				if not bulbIsWhite : setBulbWhite(device_address)
			elif command == 'colour' :
				if len(extra) == 6 :
					setBulbColour(device_address, extra)
				elif extra == '' :
					extra = '%02x%02x%02x' % (settings[2], settings[3], settings[4])
					setBulbColour(device_address, extra)
				else :
					error = 'ERROR: colour command requires a 6-digit colour argument, in hex. eg. 6633CC'
			else :
				printHelp()
		else :
			error = 'off'
		if error != '' :
			if error == 'off' : error = 'ERROR: Turn the bulb on before trying to change the settings.'
			print error
	exit()
BeeWi have some other Bluetooth goodies (power outlet, temperature sensor) but I haven't seen them for sale. If I ever find them I'll try and incorporate them into the same or a similar script.

Cheers!

daviessm
Posts: 1
Joined: Thu Sep 03, 2015 4:31 pm

Re: Controlling a BeeWi SmartLite

Thu Sep 03, 2015 4:51 pm

I'm looking to do something similar with their temperature sensor so I'm interested to know how you discovered how to control the light. Is there any public documentation?

degerrit
Posts: 2
Joined: Fri Oct 23, 2015 10:43 am

Re: Controlling a BeeWi SmartLite

Fri Oct 23, 2015 11:08 am

Awesome job, Stephanie!
Also works with BeeWi light BBL125A1 on x86_64 which I'm using with DomotiGa.

Strange thing is though, the hcitool connect/disconnect [lecc/ledc] seems unnecessary on my system (openSUSE 13.1/x86_64) - in fact, the script doesn't work at all (fails silently) if I leave that in - looks like gatttool makes its own connection and doesn't use the handle provided by hcitool. Same for the cycleHCI() precaution you've made.

Here's a diff for my version. Since I don't have a Pi, yet, can't really say if these changes are for x86, for the BBL125A1, or for my version of BlueZ tools.

Code: Select all

--- /usr/local/bin/beewibulb.orig       2015-10-23 12:54:29.603981000 +0200
+++ /usr/local/bin/beewibulb_GH_publish 2015-10-23 13:00:21.876615000 +0200
@@ -3,6 +3,7 @@
 #   A python script to control the BeeWi 'BBL207 - Smart LED Color Bulb'
 #   BeeWi website: http://www.bee-wi.com/en.cfm
 #   Tested & working with this bulb in particular: http://www.bee-wi.com/bbl207,us,4,BBL207-A1.cfm
+#   Modified for BeeWi light BBL125A1 on openSUSE 13.1/x86_64 by Gerrit Hannaert, 23/10/2015
 #
 #   by Stephanie Maks
 #
@@ -10,9 +11,12 @@
 #      http://stackoverflow.com/questions/24853597/ble-gatttool-cannot-connect-even-though-device-is-discoverable-with-hcitool-lesc
 #      http://mike.saunby.net/2013/04/raspberry-pi-and-ti-cc2541-sensortag.html
 #      https://github.com/sandeepmistry/node-yeelight-blue
+#      https://www.nowsecure.com/blog/2014/02/07/bluetooth-packet-capture-on-android-4-4/
 #
 #   note: this needs to be called with 'sudo' or by root in order to work with some of the hci commands

+debug=0
+
 import os
 import sys
 import socket
@@ -75,24 +79,22 @@
    # all commands are prefixed with 0x55 (85) and suffixed with 0x0d 0x0a (CR LF)
    if the_command != '' :
       the_command = '55' + the_command + '0D0A'
-      cycleHCI()
-      hd = check_output(['/usr/local/bin/hcitool', '-i', 'hci0', 'lecc', mac])
-      ha = hd.split(' ');
-      connectionHandle=0
-      if ha[0] == 'Connection' and ha[1] == 'handle' : connectionHandle = ha[2]
-      if connectionHandle > 0 :
+      #cycleHCI()
+      if debug == 1 : print '(connect) gatttool -i hci0 -b ' + mac + ' -I'
          bulb = pexpect.spawn('gatttool -i hci0 -b ' + mac + ' -I')
          bulb.expect('\[LE\]>', timeout=60)
          bulb.sendline('connect')
          bulb.expect('\[LE\]>', timeout=60)
+      # maybe also expect "Connection successful" ?
+      if debug == 1 : print '(command) char-write-cmd 0x0021 ' + the_command
          bulb.sendline('char-write-cmd 0x0021 ' + the_command)
          bulb.expect('\[LE\]>', timeout=60)
+      if debug == 1 : print '(command) disconnect'
          bulb.sendline('disconnect')
          bulb.expect('\[LE\]>', timeout=60)
          bulb.sendline('exit')
          bulb.expect(pexpect.EOF, timeout=60)
-         call(['hcitool', '-i', 'hci0', 'ledc', connectionHandle])
-      cycleHCI()
+      #cycleHCI()

 def setBrightness(mac, value) :
    # brightness command is 12 [02 to 0B] (18, [2 to 11])

degerrit
Posts: 2
Joined: Fri Oct 23, 2015 10:43 am

Re: Controlling a BeeWi SmartLite

Sat Oct 24, 2015 11:50 am

daviessm wrote:I'm looking to do something similar with their temperature sensor so I'm interested to know how you discovered how to control the light. Is there any public documentation?
Hi there. I couldn't find any documentation, but what I did (before finding the issue I described below) is to trace/sniff/capture Bluetooth communication from the vendor's app. Was initially thinking of an adapter (Bluefruit LE Sniffer?) but I then worked with an old rooted Nexus 7 tablet and the Developer option to enable "Bluetooth HCI snoop" (copied the log file off using a File Manager app not adb). Easy & free! Just remember to write down when you pressed what button, so you can correlate the Bluetooth data to your actions.

See : https://www.nowsecure.com/blog/2014/02/ ... droid-4-4/

vekfra
Posts: 4
Joined: Sat Mar 12, 2016 3:57 pm

Sat Mar 12, 2016 6:40 pm

daviessm wrote:
I'm looking to do something similar with their temperature sensor so I'm interested to know how you discovered how to control the light. Is there any public documentation?
Created a new post regarding the BeeWi Smart Temperature & Humidity Sensor BBW200-A1
viewtopic.php?f=37&t=139848
Was able to read the actual values :D

Ruler
Posts: 1
Joined: Wed Apr 06, 2016 7:56 pm

Re: Controlling a BeeWi SmartLite

Wed Apr 06, 2016 8:04 pm

I'm able to use your script, but the only thing that works is to get the status from the bulb via the status parameter at the end. When I try to set a value or toggle the bulb I get this error:

Code: Select all

Traceback (most recent call last):
  File "bulbs.py", line 198, in <module>
    switchBulbOff(device_address)
  File "bulbs.py", line 128, in switchBulbOff
    writeCommandToBulb(mac, '1000')
  File "bulbs.py", line 79, in writeCommandToBulb
    hd = check_output(['/usr/local/bin/hcitool', '-i', 'hci0', 'lecc', mac])
  File "/usr/lib/python2.7/subprocess.py", line 566, in check_output
    process = Popen(stdout=PIPE, *popenargs, **kwargs)
  File "/usr/lib/python2.7/subprocess.py", line 710, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1335, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory
And I don't know what file is meant and missing. Thanks for clearing my path ;)

User avatar
Douglas6
Posts: 4008
Joined: Sat Mar 16, 2013 5:34 am
Location: Chicago, IL

Re: Controlling a BeeWi SmartLite

Thu Apr 07, 2016 12:03 am

It could be hcitool. On my recent Jessie Raspbian, it's at '/usr/bin/hcitool', not '/usr/local/bin/hcitool'.

Dragafe
Posts: 2
Joined: Sun Mar 20, 2016 7:33 pm

Re: Controlling a BeeWi SmartLite

Fri Jul 29, 2016 11:19 pm

Hi ! I try to use this script, it work but juste if I want 'status' ... When I try to do other thing (like toggle or on/off) it doen't work. I don't know and I don't lean python again...
Please, help me !

Thank you.

houkun1230
Posts: 6
Joined: Tue Aug 02, 2016 12:57 am

Re: Controlling a BeeWi SmartLite

Tue Aug 09, 2016 6:01 pm

Dragafe wrote:Hi ! I try to use this script, it work but juste if I want 'status' ... When I try to do other thing (like toggle or on/off) it doen't work. I don't know and I don't lean python again...
Please, help me !

Thank you.
I meet the same problem, have you solved it? Do you know how to solve it? Thank you

vulture_g7
Posts: 9
Joined: Tue Jan 05, 2016 7:45 am

Re: Controlling a BeeWi SmartLite

Mon Jan 30, 2017 11:07 am

@Stephanie

Resurrecting the thread here, could you please post the additional commands for timers control and colour cycle modes?

Thx!

Return to “Automation, sensing and robotics”

Who is online

Users browsing this forum: No registered users and 10 guests