Searching the Internet lead me to a few cheap LED strips from China (thank you Aliexpress) for under 1€/meter. The strip include 30 LEDs, the Bluetooth controller and a 5v power supply (an USB connector). As you can expect there was no documentation about the product - just a link to download the Android App (Happy Lightning).
A few days later I find another deal (this time on Amazon). The package include 10 meters of strip (30 LEDs/meter), Bluetooth-RF controller, RF remote control and 12v power supply. It sounded very professional, but there was not documentation either about the product - just the same link to the Android App.
So I decided to investigate how to control these strips using Google Assistant, a Raspberry Pi (RPI) and my programming skills.
Finally I have developed an API-REST (over Python) to control the LED strip with a RPI. Also IFTTT is used to create the Google Assistant <-> API requests communication.
- Acknowledgements And Resources
- Disclaimer
- 1. Intro and Setup
- 2. Sniffing Bluetooth packets and reverse engineering the commands
- 3. Creating an API-REST service
- 4. Google Assistant integration with the API-REST
The information of this guide is based on the tutorials from:
- Led strip with Bluetooth Control
- Server/API
- Google Assistant
- Intructables : Google Assistant implementation - I
- Programminghistorian : Google Assistant implementation - II
In theory, it is impossible to brick your RPI or strips using this method, but I don't take responsibility for any damage caused.
If you find any mistakes in this tutorial, please submit a PR 👍🏻
The basic set-up for the project is:
- A LED strip with a Bluetooth controller.
- A computer with ADB drivers installed.
- A mobile phone with Android (with the LED strip app and the nRF Connect app).
- An USB type C cable (or similar).
- General knowledges of Wireshark and packet sniffing
- A RPI with Raspbian installed.
- An IDE (Atom or similar).
- General knowledges of Python and API operation.
- A Google account and an IFTTT account.
A little bit of information about how a Bluetooth Low Energy device work:
In Bluetooth Low Energy, devices can perform one of two roles. A device can be either a “Central” (in this example, your phone) or a “Peripheral” (and respectively, the bulb).
Bluetooth devices have services that correspond to one function of the device. Each service exposes variables/properties called characteristics. The characteristics represent one parameter of the service, which can be read, written or both. For example, in the Battery service above, it will have a Battery Level Characteristic, a read-only value containing 1-byte value between 0 and 100, reporting percentage of battery remaining in the device. A temperature service can have one characteristic for the temperature, and another one for the humidity, both are read-only. A smart bulb service can have one characteristic for the on/off switch (writing 0 turns it off, 1 on), and another characteristic for the brightness (0 to 100 or so), which can be either write-only or read+write.
Peripherals periodically advertise which services they have (usually once per second or so). Centrals (such as your phone) see those advertisements, and can initiate a connection with any peripheral around them, and start reading/writing from the characteristics exposed by its services. Each service and characteristic is identified by a unique 16-bit or 128-bit number, such as
ff05
(16bit) or00000000–0000–1000–8000–00805F9B34FB
(128bit). 16bits are reserved for standard services and characteristics, such as the Battery Level service mentioned before, and are defined by the Bluetooth SIG Group. Nevertheless, many consumer devices like to use them for their own purposes.
- Uri Shaked/Medium.com
Some services and characteristics (not all are available) of my Bluetooth Controller are:
- 0x1800 (Generic Access)
- 0x2A00 (Device Name)
- 0x2A01 (Appearance)
- 0x2A02 (Peripheral Privacy Flag)
- 0x1801 (Generic Attribute)
- 0x2A05 (Service Changed)
- 0x180A (Generic Information)
- 0x2A29 (Manufacturer Name String) --> Beken
- 0x2A24 (Model Number String)
- 0x2A25 (Serial Number String)
- 0x2A26 (Firmware Revision String)
- 0x2A27 (Hardware Revision String)
- 0x2A28 (Software Revision String)
- 0x2A23 (System ID)
- 0x2A2A (IEEE 11073-20601 Regulatory Certification)
- 0x2A50 (PnP ID)
- 0xFFD5 (Unknown Service) --> Led control service
- 0xFFDA (Unknown Characteristic)
- 0xFFD9 (Unknown Characteristic) -> LED control characteristic
- 0xFFD0 (Unknown Service)
- 0xFFD4 (Unknown Characteristic)
- 0xFFD1 (Unknown Characteristic)
- To enable Developer Options, open the
Settings screen
, scroll down to the bottom, and tapAbout phone or About tablet
. Scroll down to the bottom of the About screen and find the Build number. Tap theBuild number
field seven times to enable Developer Options. Tap a few times and you’ll see a toast notification with a countdown that reads “You are now X steps way from being a developer.” - Tap the Back button and you’ll see the
Developer options
menu just above the “About Phone” section in Settings. This menu is now enabled on your device—you won’t have to repeat this process again unless you perform a factory reset. - To enable USB Debugging, you’ll need to jump into the Developer options menu, scroll down to the Debugging section, and toggle the “USB Debugging” slider.
- Also in the Developer options screen select
Enable Bluetooth HCI snoop log
. The log file is now enabled. - Now activate the Bluetooth and use the Control App of the Strips (turn on/off a few times, change the color, etc).
- To retrieve and analyze the .log, download and unzip ADB on your computer.
- Open a
Powershell
terminal with superuser control (Right-click
the PowerShell shortcut in your taskbar or Start Menu, or on your Desktop and selectRun as Administrator
to open a PowerShell window that runs with admin privileges). - Move to the directory where you unzip the file (with the
cd
command) and run:
./adb.exe shell dumpsys bluetooth_manager
./adb.exe bugreport > BUG_REPORT.txt
Probably you have to accept the conection in yout mobile phone (to grant permission to the PC).
- After a few minutes a .zip file will be created in the directory, unzip it and move to the directory
\data\misc\bluetooth
there you can find the .log file. - Open the file with Wireshark and filter the captured messages (the src and dst field are good choices)
- Now you have to look for changes on the Hexadecimal message. In my case, for this Bluetooth controller:
Commom Part | Value |
---|---|
02 03 00 0e 00 0a 00 04 00 12 28 00 | 56 00 ED FF 00 f0 AA |
02 03 00 0e 00 0a 00 04 00 12 28 00 | 56 RR GG BB 00 f0 AA |
With Wireshark you can see that those messages use the characteristic 0xFFD9 (associated to 0xFFD5 service). Now changing the marked values (RR, GG, BB) set an specific color (you can test this with the nRF Connect app).
A few codes for this specific controller are:
Action | Value |
---|---|
Switch ON | CC 23 33 |
Switch OFF | CC 24 33 |
Set COLOR | 56 RR GG BB 00 f0 AA |
After obtaining the commands you need to configure Bluepy and Flask.
First you need to import the library
from bluepy.btle import UUID, Peripheral, Scanner, DefaultDelegate
And create a class to control the BLE devices
class DeviceControl:
def __init__(self, mac_addr):
# Init LED STRIP with white color
self.R = 255
self.G = 255
self.B = 255
self.mac_addr = mac_addr
self.p = Peripheral(self.mac_addr)
self.s = self.p.getServiceByUUID(LED_SERVICES[4])
self.ch_W = self.s.getCharacteristics(LED_CHARACTERISTICS[0])[0]
self.ch_W.write(LedStripMessages.color_message(self.R, self.G, self.B))
def turn_on(self):
self.ch_W.write(LedStripMessages.on_message())
self.p.disconnect()
return ""
def turn_off(self):
self.ch_W.write(LedStripMessages.off_message())
self.p.disconnect()
First you need to import the library
import struct, time, flask
Define a server
# Flask Web App definition
app = flask.Flask(__name__)
app.config["DEBUG"] = True
And create a class to define the API
class API():
@app.route('/led/mesa/on', methods=['POST'])
def turn_on_1():
dc = DeviceControl(MAC_ADDR[0]);
dc1 = DeviceControl(MAC_ADDR[1]);
dc2 = DeviceControl(MAC_ADDR[2]);
dc.turn_on()
dc1.turn_on()
dc2.turn_on()
return ""
@app.route('/led/techo/on', methods=['POST'])
def turn_on_2():
dc = DeviceControl(MAC_ADDR[3]);
dc.turn_on()
return ""
@app.route('/led/all/on', methods=['POST'])
def turn_on_3():
dc = DeviceControl(MAC_ADDR[0]);
dc1 = DeviceControl(MAC_ADDR[1]);
dc2 = DeviceControl(MAC_ADDR[2]);
dc3 = DeviceControl(MAC_ADDR[3]);
dc.turn_on()
dc1.turn_on()
dc2.turn_on()
dc3.turn_on()
return ""
@app.route('/led/mesa/off', methods=['POST'])
def turn_off_1():
dc = DeviceControl(MAC_ADDR[0]);
dc1 = DeviceControl(MAC_ADDR[1]);
dc2 = DeviceControl(MAC_ADDR[2]);
dc.turn_off()
dc1.turn_off()
dc2.turn_off()
return ""
@app.route('/led/techo/off', methods=['POST'])
def turn_off_2():
dc = DeviceControl(MAC_ADDR[3]);
dc.turn_off()
return ""
@app.route('/led/all/off', methods=['POST'])
def turn_off_3():
dc = DeviceControl(MAC_ADDR[0]);
dc1 = DeviceControl(MAC_ADDR[1]);
dc2 = DeviceControl(MAC_ADDR[2]);
dc3 = DeviceControl(MAC_ADDR[3]);
dc.turn_off()
dc1.turn_off()
dc2.turn_off()
dc3.turn_off()
return ""
And finally run the server
app.run(host='0.0.0.0')
After defining the API-REST methods, you need to configure a Static IP on your RPI, configure a DDNS service and configure Port-forwarding on your router's firewall.
In addition to that, you need to set the IFTTT services. IFTTT refers to:
If This Then That (commonly known as IFTTT, /ɪft/) is a web-based service that allows users to create chains of conditional statements triggered by changes that occur within other web services such as Gmail, Facebook, Telegram, Instagram, Pinterest or Google Assistant.
- Wikipedia
- You need to edit the /etc/dhcpcd.conf file
sudo nano /etc/dhcpcd.conf
- Inside the file uncomment and edit the following lines (you need to indicate an IP within the range of your network, the IP of your router and your favorite DNS provider's IP)
# Example static IP configuration:
interface eth0
static ip_address=192.168.0.10/24
static routers=192.168.0.1
static domain_name_servers=192.168.0.1 8.8.8.8 fd51:42f8:caae:d92e::1
- Finally save the file (CTRL+o) and reboot the System
sudo reboot
- Go to the DuckDNS website and sign up with your Google account (or other via).
- The web will redirect you to the administration panel, you need to enter a name for your domain and press "add domain". The domain and you public IP will be added below.
- Now go to DuckDNS/install and select your Operating System (OS) and the domain you want to configure. In this case, we are going the use the RPI to run the DDNS (but as I mention before you can use OpenWRT or other OS).
- After choosing a domain, a step by step guide will be deployed. In the case of the RPI:
If your linux install is running a crontab, then you can use a cron job to keep updated. We can see this with
ps -ef | grep cr[o]n
If this returns nothing - then go and read up how to install cron for your distribution of linux. also confirm that you have curl installed, test this by attempting to run curl
curl
If this returns a command not found like error - then find out how to install curl for your distribution. otherwise lets get started and make a directory to put your files in, move into it and make our main script
mkdir duckdns
cd duckdns
sudo nano duck.sh
Now copy this text and put it into the file. The example below is for the domain XXXX. If you want the configuration for a different domain, use the drop down box above. You can pass a comma separated (no spaces) list of domains, you can if you need to hard code an IP (best not to - leave it blank and we detect your remote ip).
echo url="https://www.duckdns.org/update?domains=XXXX&token=YOUR_TOKEN&ip=" | curl -k -o ~/duckdns/duck.log -K -
Now save the file (CTRL+o and CTRL+x). This script will make a https request and log the output in the file duck.log. Now make the duck.sh file executeable
sudo chmod 700 duck.sh
Next we will be using the cron process to make the script get run every 5 minutes
crontab -e
Copy this text and paste it at the bottom of the crontab
*/5 * * * * ~/duckdns/duck.sh >/dev/null 2>&1
Now save the file (CTRL+o then CTRL+x)lets test the script
./duck.sh
This should simply return to a prompt. We can also see if the last attempt was successful (OK or bad KO)
cat duck.log
If it is KO check your Token and Domain are correct in the duck.sh script.
- DuckDNS.org
- Go to the web browser and type the private IP of your router (i.e 192.168.0.1)
- Inside the router administration panel you have to find your firewall/port-forwards setting
- And create a rule allowing connection from the WAN to the LAN on the external port 80 (http) and destination the RPI static IP on the server port (generally 5000).
- Go to IFTTT website and create a new account.
- Click create a new Applet.
- In the "If this" rectangle click add and search for Google Assistant, select it.
- Choose "Say a simple phrase" as your trigger (you can choose other option depending of the project you are working on).
- Now choose the phrase and other variations. For example "Turn my lamp on". And the response of the assistant. After that click "Create Trigger" and proceed.
- In the "Then that" rectangle click add and search for Webhooks, select it.
- Now select "Make a web request" and set "Method" to POST, "Content Type" should be text/plain and "Body" can be left blank. Finally set the URL to
http://ipaddressgoeshere/methodyouwanttocall
- Create your action and choose Finish.