HomeKit is finally out in the wild, bringing Siri voice control to several consumer smart home devices.
Unfortunately, I mean a literal handful — everything you’ve already bought is probably not compatible. However, the protocol has already been reworked and an open source emulator for the HomeKit API is available: or in simple terms, you can now create «fake» HomeKit devices and have Siri control them like any other official HomeKit accessory.
Today we’re going to create a Wi-Fi controlled light source and control it with Siri. Here’s a demo.
Here’s what you need:
- Raspberry Pi (I used RPi2, there is a slight difference in Node versions to install given the updated ARM architecture — see notes later).
- MQTT broker installed on Raspberry Pi. See Installing Mosquitto on the Pi in my OpenHAB Guide Part 2, It doesn’t need to be installed specifically on the Pi — you can even use an MQTT cloud server, but since we need a Pi for this tutorial anyway, it’s handy.
- NodeMCU v2 (Arduino compatible)
- Non-pixel LEDs (I would recommend 4 pixels for testing, then you can add an external power supply and add as many as you like)
Installing the HomeKit Bridge
We are going to install a NodeJS application called HAP-NodeJS on Raspberry Pi: this will form a bridge between HomeKit requests and Wi-Fi devices. We will now set up this bridge with one accessory, but you can add as many as you like.
I’m actually installing this on my existing home server running OpenHAB — I’m hoping to link them together later, but for now I know they can coexist on the same Raspberry Pi. If you do the same, make a backup of your current Pi SD card just in case. If things go wrong, you can restore this.
Start with a full refresh from a terminal or SSH session. .
sudo apt-get update
sudo apt-get upgrade
You may need to do this twice if it’s been a while.
Now install a few basic packages that we will need:
sudo apt-get установить npm git-core libnss-mdns libavahi-compat-libdnssd-dev
Next, we are going to install the latest version of NodeJS. You might be tempted to do this with apt-get but don’t do it — this version is really old and won’t work. Instead, visit nodejs.org , navigate to the directory download/release/latest-v5.x.0/ and check which link is for the latest version. You are looking for linux-armv7l for Raspberry Pi 2 or linuxarmv6l for original RPi models. Then, modifying the URLs and directory names as necessary, download and install using the following commands.
wget https://nodejs.org/download/release/latest-v5.x.0/node-v5.5.0-linux-armv7l.tar.gz
tar -xvf node-v5.5.0-linux-armv7l.tar.gz
cd node-v5.5.0-linux-armv7l
sudo cp -R * / usr / local
Confirm by typing
версия узла
And you should see v5.5 (or whatever the latest you downloaded).
Next, we have a few Node modules to install.
sudo npm install -g npm
sudo npm install -g node-gyp
In this first command, we are actually using the Node Package Manager (npm) to install the newer version. Clever!
Now to download the HomeKit emulator called HAP-NodeJS :
git clone https://github.com/KhaosT/HAP-NodeJS.git
cd HAP-NodeJS
нпм перестроить
sudo npm install node-persist
sudo npm установить srp
At this point, I ran into this error: » #error This node/NAN/v8 version requires a C++11 compiler «. If this happens to you, install a more recent C++ compiler with the commands:
sudo apt-get установить gcc-4.8 g ++ - 4.8
Возможности обновления sudo --install / usr / bin / gccgcc / usr / bin / gcc-4.6 20
Возможности обновления sudo --install / usr / bin / gcc gcc /usr/bin/gcc-4.8 50
Возможности обновления sudo --install / usr / bin / g ++ g ++ /usr/bin/g++-4.6 20
Возможности обновления sudo --install / usr / bin / g ++ g ++ /usr/bin/g++-4.8 50
Now you shouldn’t have any problems. Continue to run these commands, one by one:
sudo npm установить srp
sudo npm install mdns --unsafe-perm
sudo npm install debug
sudo npm install ed25519 --unsafe-perm
sudo npm install curve25519 --unsafe-perm
This should be everything. Try starting the emulator with:
узел Core.js
If you get messages that it can’t find such-and-such a module, simply enter command sudo npm install and add the name of the module that was missing. Assuming all is well, you should see a few warnings and your HomeKit bridge should work. Here’s what success looks like:

It is immediately clear that a set of 6 fake devices has already been created. We’ll use them as a starting point for our own Wi-Fi indicator later, but for now, we’ll just use them for testing purposes. You can also see more debug information if you start the server with:
DEBUG = * узел Core.js
Now navigate to an Apple device capable of working with Siri. Curiously, Apple doesn’t provide a stock HomeKit app other than registered developers, so download the free Elgato Eve app, a HomeKit management app that lets you add (even non-Elgato) devices to your HomeKit network.
The first time you launch the app, you will need to name your home, walk through it, and walk through it. Then select Add Accessory. Ignore the message about being close to it!

Next, you will be prompted to find a unique «HomeKit Installation Code». Ignore this and click «Add to [название вашего дома]».
It will also tell you that the device is not certified. Actually it is not. Go ahead anyway. When you get to the screen asking for a passcode…

Select to enter the code manually and enter the following:
031-45-154
This can be found/changed in the file light_accessory.js but more on that later. Add this accessory to your default room, name it fake light and keep going through the dialog boxes to select an icon, etc.
Finally, switch back to the SSH session where you have HAP-NodeJS running. You may have already seen the message “Are we online?” is an Elgato application polling light status. Open Siri and tell her, «Turn on the fake light,» then try turning it off again. Hopefully you will see some debug messages from HAP-NodeJS to show that it has received the commands.
Мы находимся? Нет.
Включение света!
Выключить свет!
Fantastic, this first step is finished. Now we need some real light before we go back to set up the bridge again.
Creating Wi-Fi Light
The hardware side of this step is surprisingly simple if we start with four neo-pixels since we can power them directly from the NodeMCU dev board and its USB connection. If you have a longer bar, don’t worry — we have determined this in the software, so the rest simply won’t turn on.
Connect the red power cable from the Neopixel core to the VIN pin, the blue ground to GND, and the green signal cable to the pin labeled D2 on the NodeMCU. Be very careful with polarity: if you mix up ground and VIN, you will send a powerful pulse through your board and destroy it in the process.
If your Arduino environment is not yet set up to work with the ESP8266, go ahead and follow the guide in my ESP8266: The Arduino Killer guide and then come back after you’ve confirmed it works. Install these additional libraries:
- lmroy’s PubSubClient
- Non-Apixels Adafruit
The code we’re using is a modification of Github user Aditya Tannu — I’ve removed unnecessary over-the-air update features, added some missing HSV features, and made it easier to create more lights by just changing one variable If you don’t see the code embedded below, you will find it in this list.
|
#include <ESP8266 WiFi.h> |
|
#include <WiFiClient.h> |
|
#include <ESP8266mDNS.h> |
|
#include <WiFiUdp.h> |
|
#include <PubSubClient.h> |
|
#include <Adafruit_NeoPixel.h> |
|
|
|
#define PIN four |
|
Adafruit_NeoPixel strip = Adafruit_NeoPixel(fourPIN, NEO_GRB + NEO_KHZ800); |
|
|
|
const char*ssid= «….«; |
|
const char*password= «…«; |
|
const char*host= «office light«; // the name of your fixture, and the base channel to listen to |
|
IPAddress MQTTserver(192, 168, one, 99); |
|
|
|
/* NO NEED TO CHANGE BENEATH THIS LINE */ |
|
int hue = 0; |
|
float brightness= 0.0; |
|
float saturation = 0.0; |
|
|
|
#define BUFFER_SIZE 100 |
|
|
|
WiFiClient wclient; |
|
PubSubClient client(wclient, MQTTserver); |
|
|
|
|
|
void callback(const MQTT::Publish& pub) { |
|
|
|
uint16_t i, j; |
|
|
|
currentValues(); |
|
String myMessage = String(pub.payload_string()); |
|
// handle message arrived |
|
Serial.print(pub.topic()); |
|
Serial.print(« => «); |
|
String myTopic = String(pub.topic()); |
|
|
|
|
|
if(myTopic == host) |
|
{ |
|
|
|
Serial.println(pub.payload_string()); |
|
|
|
if(pub.payload_string() == «on«) |
|
{ |
|
|
|
// use this to reset parameters if you want them to always come on bright white. |
|
//hue = 0; |
|
brightness= 1.0; |
|
//saturation = 0.0; |
|
|
|
for(i=0; inumPixels(); i++) { |
|
strip.setPixelColor(i, HSV Color(hue,saturation,brightness)); |
|
} |
|
strip.show(); |
|
|
|
} |
|
else |
|
{ |
|
//hue = 0; |
|
brightness= 0.0; |
|
//saturation = 0.0; |
|
|
|
for(i=0; inumPixels(); i++) { |
|
strip.setPixelColor(i, HSV Color(hue,saturation,brightness)); |
|
} |
|
strip.show(); |
|
} |
|
|
|
} |
|
|
|
else if(myTopic == host+(String)«/brightness«) |
|
{ // Brightness up to 100 |
|
Serial.println(pub.payload_string()); |
|
brightness = (myMessage.toFloat())/100; |
|
for(i=0; inumPixels(); i++) { |
|
strip.setPixelColor(i, HSV Color(hue,saturation,brightness)); |
|
} |
|
strip.show(); |
|
|
|
} |
|
else if(myTopic == host+(String)«/hue«) |
|
{ // Hue value 0-360 |
|
Serial.println(pub.payload_string()); |
|
hue = myMessage.toInt(); |
|
for(i=0; inumPixels(); i++) { |
|
strip.setPixelColor(i, HSV Color(hue,saturation,brightness)); |
|
} |
|
strip.show(); |
|
|
|
} |
|
else if(myTopic == host+(String)«/saturation«) |
|
{ // Saturation value at 0-100 |
|
Serial.println(pub.payload_string()); |
|
saturation = (myMessage.toFloat())/100; |
|
for(i=0; inumPixels(); i++) { |
|
strip.setPixelColor(i, HSV Color(hue,saturation,brightness)); |
|
} |
|
strip.show(); |
|
} |
|
currentValues(); |
|
} |
|
|
|
void setup() { |
|
Serial.begin(115200); |
|
Serial.println(«Booting«); |
|
WiFi.mode(WIFI_STA); |
|
WiFi.begin(ssid, password); |
|
while (wifi.waitForConnectResult() != WL_CONNECTED) { |
|
Serial.println(«Connection failed! Rebooting…«); |
|
delay(5000); |
|
ESP.restart(); |
|
} |
|
|
|
Serial.println(«Ready«); |
|
Serial.print(«IP address: «); |
|
Serial.println(wifi.local IP()); |
|
|
|
// MQTT callback |
|
client.set_callback(callback); |
|
|
|
strip.begin(); |
|
strip.show(); // Initialize all pixels to ‘off’ |
|
|
|
} |
|
|
|
void loop() { |
|
if (wifi.status() == WL_CONNECTED) { |
|
if (!client.connected()) { |
|
if (client.connect(«ESP8266: Fountain«)) { |
|
client.publish(«outTopic«,(String)«hello world, I’m «+host); |
|
client.subscribe(host+(string)«/#«); |
|
} |
|
} |
|
|
|
if (client.connected()) |
|
client.loop(); |
|
} |
|
|
|
} |
|
|
|
// Convert Hue/Saturation/Brightness values to a packed 32-bit RBG color. |
|
// hue must be a float value between 0 and 360 |
|
// saturation must be a float value between 0 and 1 |
|
// brightness must be a float value between 0 and 1 |
|
uint32_t HSV Color(float h, float s, float v) { |
|
|
|
h = constraint(h, 0, 360); |
|
s= constraint(s, 0, one); |
|
v= constraint(v, 0, one); |
|
|
|
int i, b, p, q, t; |
|
float f; |
|
|
|
h /= 60.0; // sector 0 to 5 |
|
i = floor(h); |
|
f = h — i; // factorial part of h |
|
|
|
b=v* 255; |
|
p = v * ( one -s)* 255; |
|
q = v * ( one -s*f)* 255; |
|
t = v * ( one -s*( one — f ) ) * 255; |
|
|
|
switch( i ) { |
|
case 0: |
|
return strip.color(b, t, p); |
|
case one: |
|
return strip.color(q, b, p); |
|
case 2: |
|
return strip.color(p, b, t); |
|
case 3: |
|
return strip.color(p, q, b); |
|
case four: |
|
return strip.color(t, p, b); |
|
default: |
|
return strip.color(b, p, q); |
|
} |
|
} |
|
|
|
void currentValues(){ |
|
Serial.println(««); |
|
Serial.println(«current state«); |
|
Serial.print(«Hue (0-360):«); |
|
Serial.println(hue); |
|
Serial.print(«Saturation (0-100in, 0-1):«); |
|
Serial.println(saturation*100); |
|
Serial.print(«Brightness (0-100):«); |
|
Serial.println(brightness*100); |
|
Serial.println(««); |
|
} |
|
|
Update the following lines with your own network information and a unique name for each instrument (host) you create.
const char * ssid = "....";
const char * password = "...";
const char * host = "officelight";
IP-адрес MQTTserver (192, 168, 1, 99);
The IP address of this device is automatically obtained via DHCP — it doesn’t matter if it changes as we are connecting to the same MQTT server every time.
At the moment we are only using 4 Neopixels, but you can increase the number later if you feed them from an external source. Download the code and let’s check it out — use your favorite MQTT client to send commands (change the hostname in the following instructions if you have changed it) .
- You can send it to root office channel to enable it. Send any other value to this channel to disable it.
- You can send a number from 0 to 360 to the office light/tint to change the color. We’re using the HSV color space, so 0 and 360 are red, 120 are green, and 240 are blue.
- You are sending a percentage value for brightness (0-100, not including the % character).
- The same for saturation. A value of 100 will be fully saturated (i.e., a solid color) and zero will be pure white, regardless of the specified hue.
Once you have verified that the MQTT powered light fixture is working, continue.
Setting up a new HomeKit accessory
Return to your Raspberry Pi and close the HAP-NodeJS application if you haven’t already. Go to directory / accessories . To make this easier, you can directly download the code already associated with the «officelight» fixture by typing the following:
wget https://gist.githubusercontent.com/jamesabruce/a6607fa9d93e41042fee/raw/12e4fd1d1c2624e7540ba5e17c3e79bc6bdec5fd/Officelight_accessory.js
This is essentially a duplicate of the standard light accessory with the variable names changed (again, adapted from Adisan’s work, simplified for ease of use). Here’s what you need to know to create your own accessories based on this.