Feather-based Hue lighting controllerCreated by Dave Astels
Last updated on 2018-04-17 114644 PM UTC
23444455556
71221212123
2527293033
Guide Contents
Guide ContentsOverviewParts
Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500DS3231 Precision RTC FeatherWing - RTC Add-on For Feather BoardsFeatherWing OLED - 128x32 OLED Add-on For All Feather BoardsPhoto cell (CdS photoresistor)PIR (motion) sensorBreadboard-friendly RGB Smart NeoPixel - Pack of 4FeatherWing Proto - Prototyping Add-on For All Feather BoardsFeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
Custom Sensor WingCodeTalking to HUE
Finding your HUE BridgeLight groupsControlling lights
Getting the WeatherUsing Adafruit IOKeys and suchPutting it all togetherDownloads
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 2 of 33
OverviewThe Philips HUE lighting system is very cool Wifi control of your lighting full color lighting etc You simply replace yourregular bulbs with HUE bulbs and gain the an immediate improvement by having switched to LED lighting Open up theHUE app on your smartphone and you can have very precise control of onoff brightness and color (with color bulbs)of individual bulbs
Thats great but is having to dig out your phone is as bad as having to walk to the wall switch other than not have toget out of bed to turn lights onoff And who carries their phone when going for a midnight snack Using an voiceassistant like Amazon Echo (which I use) helps but you still have to proactively do something and you have to bewithin earshot of the device Wouldnt it be nice if the lighting was controlled for you turning on and off automatically Thats what well do in the project
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 3 of 33
PartsFormats
For this project I decided to use boards in the Feather ecosystem Because it was going to need WiFi connectivityinteract with the HUE system I chose the Feather M0 WiFi It also needed to know what time it was (well see whylater) so I chose the DS3231 RTC featherwing To display some status output I added an OLED featherwing I make useof the A and C buttons on the OLED wing to provide a manual override of lighting for testing These can stack up (withthe OLED and sensor wings on top) with the right headers and a FeatherWing Doubler or Tripler I used the tripler inmy build since I had one sitting around unused
To round it out I built a custom feather wing with the sensors I needed and a NeoPixel for a status indicator
Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
$3495IN STOCK
ADD TO CART
DS3231 Precision RTC FeatherWing - RTC Add-on ForFeather Boards
$1395IN STOCK
ADD TO CART
FeatherWing OLED - 128x32 OLED Add-on For All FeatherBoards
$1495IN STOCK
ADD TO CART
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 4 of 33
Photo cell (CdS photoresistor)
$095IN STOCK
ADD TO CART
PIR (motion) sensor
$995IN STOCK
ADD TO CART
Breadboard-friendly RGB Smart NeoPixel - Pack of 4
$795IN STOCK
ADD TO CART
FeatherWing Proto - Prototyping Add-on For All FeatherBoards
$495IN STOCK
ADD TO CART
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 5 of 33
FeatherWing Tripler Mini Kit - Prototyping Add-on ForFeathers
$850IN STOCK
ADD TO CART
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 6 of 33
Custom Sensor WingThe off the shelf FeatherWings provide the computing power wifi timekeeping and status display (which isnt reallyneeded but is nice while working on the software) Whats missing is inputs to the system Specifically knowing whensomeone is in the room and how lightdark it is in the room I use this combined with the time of day to figure out howto control the lights
For motion sensing I use a basic PIR For light measurement I use a simple CdS photocell in a voltage divider
There are already excellent learning guides on photoresisters and PIRs so I wont redo them here
Notice that Im also using a NeoPixel This Ill use as an at a glance status indicator since the wifi Feather M0 doesnthave a Dotstar Even if it did the Dotstar could easily be covered depending on how the boards are stacked
As usual I started with the lower profile components the NeoPixel breakout and the photocell voltage divider Keep inmind that the photocell has to be clear of the PIR and able to see the ambient light
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 7 of 33
I replaced the header pins on the PIR with longer ones that would reach past the capacitors and into the holes on theproto wing
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 8 of 33
The PIR fits neatly between the photoresistor and neopixel
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 9 of 33
For this one-off unit I did simple point to point wiring largely on the underside of the board
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 10 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 11 of 33
CodeThere are several functional areas in the code and Ill go through each individually
The code here has been edited for clarify The actual code in the repository has more error checking and diagnosticoutput
You can find the full code listing here
Written by Dave AstelsMIT license check LICENSE for more informationAll text above must be included in any redistribution
include ltSPIhgtinclude ltWirehgtinclude ltAdafruit_GFXhgtinclude ltAdafruit_SSD1306hgtinclude ltRTClibhgtinclude ltWiFi101hgtinclude ltArduinoJsonhgtinclude ltAdafruit_NeoPixelhgtinclude Adafruit_MQTThinclude Adafruit_MQTT_Clienth
include secretsh
define BUTTON_A 9define BUTTON_B 6define BUTTON_C 5define MOTION_PIN 10define LIGHT_PIN A1define NEOPIXEL_PIN A5
define ROOM_ID 4
define OLED_RESET 4Adafruit_SSD1306 display(OLED_RESET)
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(1 NEOPIXEL_PIN NEO_GRB + NEO_KHZ800)
RTC_DS3231 rtc
WiFiClient client
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
DynamicJsonBuffer jsonBuffer(8500)
const char hue_ip = NULL
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 12 of 33
uint8_t light_numbers = NULLboolean last_motion = false
DateTime sunrise = NULLDateTime sunset = NULL
hardcoded day startend times
DateTime wakeup = DateTime(0 0 0 8 0 0)DateTime bedtime = DateTime(0 0 0 23 30 0)
boolean need_sunrise_sunset_times = false
define TRACE 1
void init_log()ifdef TRACE Serialbegin(9600) while (Serial) Serialprintln(Starting)endif
void log(const char msg)ifdef TRACE Serialprint(msg)endif
void log(const int i)ifdef TRACE Serialprint(i)endif
void logln(const char msg)ifdef TRACE Serialprintln(msg)endif
const char fetch_hue_ip() logln(Getting HUE IP) displayprintln(Getting HUE IP) if (clientconnectSSL(wwwmeethuecom 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 13 of 33
logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() logln(status) displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(Getting HUE IP) logln(HEADER ERROR) displayprintln(HEADER ERROR) return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return NULL
return strdup(root[0][F(internalipaddress)])
boolean fetch_sunrise_sunset(long sunrise long sunset) logln(Contacting DarkSky) displayprintln(Contacting DarkSky) if (clientconnectSSL(apidarkskynet 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return false
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 14 of 33
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() logln(HEADER ERROR) displayprintln(HEADER ERROR) return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset() long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
uint8_t lights_for_group(const char group_number)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 15 of 33
logln(Finding lights) displayprintln(Finding lights) if (clientconnect(hue_ip 80)) displayprintln(COULD NOT CONNECT) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(HEADER ERROR) return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) displayprintln(JSON PARSE ERROR) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 16 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
23444455556
71221212123
2527293033
Guide Contents
Guide ContentsOverviewParts
Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500DS3231 Precision RTC FeatherWing - RTC Add-on For Feather BoardsFeatherWing OLED - 128x32 OLED Add-on For All Feather BoardsPhoto cell (CdS photoresistor)PIR (motion) sensorBreadboard-friendly RGB Smart NeoPixel - Pack of 4FeatherWing Proto - Prototyping Add-on For All Feather BoardsFeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
Custom Sensor WingCodeTalking to HUE
Finding your HUE BridgeLight groupsControlling lights
Getting the WeatherUsing Adafruit IOKeys and suchPutting it all togetherDownloads
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 2 of 33
OverviewThe Philips HUE lighting system is very cool Wifi control of your lighting full color lighting etc You simply replace yourregular bulbs with HUE bulbs and gain the an immediate improvement by having switched to LED lighting Open up theHUE app on your smartphone and you can have very precise control of onoff brightness and color (with color bulbs)of individual bulbs
Thats great but is having to dig out your phone is as bad as having to walk to the wall switch other than not have toget out of bed to turn lights onoff And who carries their phone when going for a midnight snack Using an voiceassistant like Amazon Echo (which I use) helps but you still have to proactively do something and you have to bewithin earshot of the device Wouldnt it be nice if the lighting was controlled for you turning on and off automatically Thats what well do in the project
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 3 of 33
PartsFormats
For this project I decided to use boards in the Feather ecosystem Because it was going to need WiFi connectivityinteract with the HUE system I chose the Feather M0 WiFi It also needed to know what time it was (well see whylater) so I chose the DS3231 RTC featherwing To display some status output I added an OLED featherwing I make useof the A and C buttons on the OLED wing to provide a manual override of lighting for testing These can stack up (withthe OLED and sensor wings on top) with the right headers and a FeatherWing Doubler or Tripler I used the tripler inmy build since I had one sitting around unused
To round it out I built a custom feather wing with the sensors I needed and a NeoPixel for a status indicator
Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
$3495IN STOCK
ADD TO CART
DS3231 Precision RTC FeatherWing - RTC Add-on ForFeather Boards
$1395IN STOCK
ADD TO CART
FeatherWing OLED - 128x32 OLED Add-on For All FeatherBoards
$1495IN STOCK
ADD TO CART
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 4 of 33
Photo cell (CdS photoresistor)
$095IN STOCK
ADD TO CART
PIR (motion) sensor
$995IN STOCK
ADD TO CART
Breadboard-friendly RGB Smart NeoPixel - Pack of 4
$795IN STOCK
ADD TO CART
FeatherWing Proto - Prototyping Add-on For All FeatherBoards
$495IN STOCK
ADD TO CART
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 5 of 33
FeatherWing Tripler Mini Kit - Prototyping Add-on ForFeathers
$850IN STOCK
ADD TO CART
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 6 of 33
Custom Sensor WingThe off the shelf FeatherWings provide the computing power wifi timekeeping and status display (which isnt reallyneeded but is nice while working on the software) Whats missing is inputs to the system Specifically knowing whensomeone is in the room and how lightdark it is in the room I use this combined with the time of day to figure out howto control the lights
For motion sensing I use a basic PIR For light measurement I use a simple CdS photocell in a voltage divider
There are already excellent learning guides on photoresisters and PIRs so I wont redo them here
Notice that Im also using a NeoPixel This Ill use as an at a glance status indicator since the wifi Feather M0 doesnthave a Dotstar Even if it did the Dotstar could easily be covered depending on how the boards are stacked
As usual I started with the lower profile components the NeoPixel breakout and the photocell voltage divider Keep inmind that the photocell has to be clear of the PIR and able to see the ambient light
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 7 of 33
I replaced the header pins on the PIR with longer ones that would reach past the capacitors and into the holes on theproto wing
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 8 of 33
The PIR fits neatly between the photoresistor and neopixel
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 9 of 33
For this one-off unit I did simple point to point wiring largely on the underside of the board
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 10 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 11 of 33
CodeThere are several functional areas in the code and Ill go through each individually
The code here has been edited for clarify The actual code in the repository has more error checking and diagnosticoutput
You can find the full code listing here
Written by Dave AstelsMIT license check LICENSE for more informationAll text above must be included in any redistribution
include ltSPIhgtinclude ltWirehgtinclude ltAdafruit_GFXhgtinclude ltAdafruit_SSD1306hgtinclude ltRTClibhgtinclude ltWiFi101hgtinclude ltArduinoJsonhgtinclude ltAdafruit_NeoPixelhgtinclude Adafruit_MQTThinclude Adafruit_MQTT_Clienth
include secretsh
define BUTTON_A 9define BUTTON_B 6define BUTTON_C 5define MOTION_PIN 10define LIGHT_PIN A1define NEOPIXEL_PIN A5
define ROOM_ID 4
define OLED_RESET 4Adafruit_SSD1306 display(OLED_RESET)
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(1 NEOPIXEL_PIN NEO_GRB + NEO_KHZ800)
RTC_DS3231 rtc
WiFiClient client
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
DynamicJsonBuffer jsonBuffer(8500)
const char hue_ip = NULL
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 12 of 33
uint8_t light_numbers = NULLboolean last_motion = false
DateTime sunrise = NULLDateTime sunset = NULL
hardcoded day startend times
DateTime wakeup = DateTime(0 0 0 8 0 0)DateTime bedtime = DateTime(0 0 0 23 30 0)
boolean need_sunrise_sunset_times = false
define TRACE 1
void init_log()ifdef TRACE Serialbegin(9600) while (Serial) Serialprintln(Starting)endif
void log(const char msg)ifdef TRACE Serialprint(msg)endif
void log(const int i)ifdef TRACE Serialprint(i)endif
void logln(const char msg)ifdef TRACE Serialprintln(msg)endif
const char fetch_hue_ip() logln(Getting HUE IP) displayprintln(Getting HUE IP) if (clientconnectSSL(wwwmeethuecom 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 13 of 33
logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() logln(status) displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(Getting HUE IP) logln(HEADER ERROR) displayprintln(HEADER ERROR) return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return NULL
return strdup(root[0][F(internalipaddress)])
boolean fetch_sunrise_sunset(long sunrise long sunset) logln(Contacting DarkSky) displayprintln(Contacting DarkSky) if (clientconnectSSL(apidarkskynet 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return false
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 14 of 33
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() logln(HEADER ERROR) displayprintln(HEADER ERROR) return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset() long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
uint8_t lights_for_group(const char group_number)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 15 of 33
logln(Finding lights) displayprintln(Finding lights) if (clientconnect(hue_ip 80)) displayprintln(COULD NOT CONNECT) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(HEADER ERROR) return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) displayprintln(JSON PARSE ERROR) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 16 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
OverviewThe Philips HUE lighting system is very cool Wifi control of your lighting full color lighting etc You simply replace yourregular bulbs with HUE bulbs and gain the an immediate improvement by having switched to LED lighting Open up theHUE app on your smartphone and you can have very precise control of onoff brightness and color (with color bulbs)of individual bulbs
Thats great but is having to dig out your phone is as bad as having to walk to the wall switch other than not have toget out of bed to turn lights onoff And who carries their phone when going for a midnight snack Using an voiceassistant like Amazon Echo (which I use) helps but you still have to proactively do something and you have to bewithin earshot of the device Wouldnt it be nice if the lighting was controlled for you turning on and off automatically Thats what well do in the project
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 3 of 33
PartsFormats
For this project I decided to use boards in the Feather ecosystem Because it was going to need WiFi connectivityinteract with the HUE system I chose the Feather M0 WiFi It also needed to know what time it was (well see whylater) so I chose the DS3231 RTC featherwing To display some status output I added an OLED featherwing I make useof the A and C buttons on the OLED wing to provide a manual override of lighting for testing These can stack up (withthe OLED and sensor wings on top) with the right headers and a FeatherWing Doubler or Tripler I used the tripler inmy build since I had one sitting around unused
To round it out I built a custom feather wing with the sensors I needed and a NeoPixel for a status indicator
Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
$3495IN STOCK
ADD TO CART
DS3231 Precision RTC FeatherWing - RTC Add-on ForFeather Boards
$1395IN STOCK
ADD TO CART
FeatherWing OLED - 128x32 OLED Add-on For All FeatherBoards
$1495IN STOCK
ADD TO CART
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 4 of 33
Photo cell (CdS photoresistor)
$095IN STOCK
ADD TO CART
PIR (motion) sensor
$995IN STOCK
ADD TO CART
Breadboard-friendly RGB Smart NeoPixel - Pack of 4
$795IN STOCK
ADD TO CART
FeatherWing Proto - Prototyping Add-on For All FeatherBoards
$495IN STOCK
ADD TO CART
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 5 of 33
FeatherWing Tripler Mini Kit - Prototyping Add-on ForFeathers
$850IN STOCK
ADD TO CART
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 6 of 33
Custom Sensor WingThe off the shelf FeatherWings provide the computing power wifi timekeeping and status display (which isnt reallyneeded but is nice while working on the software) Whats missing is inputs to the system Specifically knowing whensomeone is in the room and how lightdark it is in the room I use this combined with the time of day to figure out howto control the lights
For motion sensing I use a basic PIR For light measurement I use a simple CdS photocell in a voltage divider
There are already excellent learning guides on photoresisters and PIRs so I wont redo them here
Notice that Im also using a NeoPixel This Ill use as an at a glance status indicator since the wifi Feather M0 doesnthave a Dotstar Even if it did the Dotstar could easily be covered depending on how the boards are stacked
As usual I started with the lower profile components the NeoPixel breakout and the photocell voltage divider Keep inmind that the photocell has to be clear of the PIR and able to see the ambient light
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 7 of 33
I replaced the header pins on the PIR with longer ones that would reach past the capacitors and into the holes on theproto wing
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 8 of 33
The PIR fits neatly between the photoresistor and neopixel
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 9 of 33
For this one-off unit I did simple point to point wiring largely on the underside of the board
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 10 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 11 of 33
CodeThere are several functional areas in the code and Ill go through each individually
The code here has been edited for clarify The actual code in the repository has more error checking and diagnosticoutput
You can find the full code listing here
Written by Dave AstelsMIT license check LICENSE for more informationAll text above must be included in any redistribution
include ltSPIhgtinclude ltWirehgtinclude ltAdafruit_GFXhgtinclude ltAdafruit_SSD1306hgtinclude ltRTClibhgtinclude ltWiFi101hgtinclude ltArduinoJsonhgtinclude ltAdafruit_NeoPixelhgtinclude Adafruit_MQTThinclude Adafruit_MQTT_Clienth
include secretsh
define BUTTON_A 9define BUTTON_B 6define BUTTON_C 5define MOTION_PIN 10define LIGHT_PIN A1define NEOPIXEL_PIN A5
define ROOM_ID 4
define OLED_RESET 4Adafruit_SSD1306 display(OLED_RESET)
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(1 NEOPIXEL_PIN NEO_GRB + NEO_KHZ800)
RTC_DS3231 rtc
WiFiClient client
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
DynamicJsonBuffer jsonBuffer(8500)
const char hue_ip = NULL
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 12 of 33
uint8_t light_numbers = NULLboolean last_motion = false
DateTime sunrise = NULLDateTime sunset = NULL
hardcoded day startend times
DateTime wakeup = DateTime(0 0 0 8 0 0)DateTime bedtime = DateTime(0 0 0 23 30 0)
boolean need_sunrise_sunset_times = false
define TRACE 1
void init_log()ifdef TRACE Serialbegin(9600) while (Serial) Serialprintln(Starting)endif
void log(const char msg)ifdef TRACE Serialprint(msg)endif
void log(const int i)ifdef TRACE Serialprint(i)endif
void logln(const char msg)ifdef TRACE Serialprintln(msg)endif
const char fetch_hue_ip() logln(Getting HUE IP) displayprintln(Getting HUE IP) if (clientconnectSSL(wwwmeethuecom 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 13 of 33
logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() logln(status) displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(Getting HUE IP) logln(HEADER ERROR) displayprintln(HEADER ERROR) return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return NULL
return strdup(root[0][F(internalipaddress)])
boolean fetch_sunrise_sunset(long sunrise long sunset) logln(Contacting DarkSky) displayprintln(Contacting DarkSky) if (clientconnectSSL(apidarkskynet 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return false
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 14 of 33
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() logln(HEADER ERROR) displayprintln(HEADER ERROR) return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset() long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
uint8_t lights_for_group(const char group_number)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 15 of 33
logln(Finding lights) displayprintln(Finding lights) if (clientconnect(hue_ip 80)) displayprintln(COULD NOT CONNECT) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(HEADER ERROR) return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) displayprintln(JSON PARSE ERROR) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 16 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
PartsFormats
For this project I decided to use boards in the Feather ecosystem Because it was going to need WiFi connectivityinteract with the HUE system I chose the Feather M0 WiFi It also needed to know what time it was (well see whylater) so I chose the DS3231 RTC featherwing To display some status output I added an OLED featherwing I make useof the A and C buttons on the OLED wing to provide a manual override of lighting for testing These can stack up (withthe OLED and sensor wings on top) with the right headers and a FeatherWing Doubler or Tripler I used the tripler inmy build since I had one sitting around unused
To round it out I built a custom feather wing with the sensors I needed and a NeoPixel for a status indicator
Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
$3495IN STOCK
ADD TO CART
DS3231 Precision RTC FeatherWing - RTC Add-on ForFeather Boards
$1395IN STOCK
ADD TO CART
FeatherWing OLED - 128x32 OLED Add-on For All FeatherBoards
$1495IN STOCK
ADD TO CART
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 4 of 33
Photo cell (CdS photoresistor)
$095IN STOCK
ADD TO CART
PIR (motion) sensor
$995IN STOCK
ADD TO CART
Breadboard-friendly RGB Smart NeoPixel - Pack of 4
$795IN STOCK
ADD TO CART
FeatherWing Proto - Prototyping Add-on For All FeatherBoards
$495IN STOCK
ADD TO CART
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 5 of 33
FeatherWing Tripler Mini Kit - Prototyping Add-on ForFeathers
$850IN STOCK
ADD TO CART
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 6 of 33
Custom Sensor WingThe off the shelf FeatherWings provide the computing power wifi timekeeping and status display (which isnt reallyneeded but is nice while working on the software) Whats missing is inputs to the system Specifically knowing whensomeone is in the room and how lightdark it is in the room I use this combined with the time of day to figure out howto control the lights
For motion sensing I use a basic PIR For light measurement I use a simple CdS photocell in a voltage divider
There are already excellent learning guides on photoresisters and PIRs so I wont redo them here
Notice that Im also using a NeoPixel This Ill use as an at a glance status indicator since the wifi Feather M0 doesnthave a Dotstar Even if it did the Dotstar could easily be covered depending on how the boards are stacked
As usual I started with the lower profile components the NeoPixel breakout and the photocell voltage divider Keep inmind that the photocell has to be clear of the PIR and able to see the ambient light
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 7 of 33
I replaced the header pins on the PIR with longer ones that would reach past the capacitors and into the holes on theproto wing
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 8 of 33
The PIR fits neatly between the photoresistor and neopixel
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 9 of 33
For this one-off unit I did simple point to point wiring largely on the underside of the board
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 10 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 11 of 33
CodeThere are several functional areas in the code and Ill go through each individually
The code here has been edited for clarify The actual code in the repository has more error checking and diagnosticoutput
You can find the full code listing here
Written by Dave AstelsMIT license check LICENSE for more informationAll text above must be included in any redistribution
include ltSPIhgtinclude ltWirehgtinclude ltAdafruit_GFXhgtinclude ltAdafruit_SSD1306hgtinclude ltRTClibhgtinclude ltWiFi101hgtinclude ltArduinoJsonhgtinclude ltAdafruit_NeoPixelhgtinclude Adafruit_MQTThinclude Adafruit_MQTT_Clienth
include secretsh
define BUTTON_A 9define BUTTON_B 6define BUTTON_C 5define MOTION_PIN 10define LIGHT_PIN A1define NEOPIXEL_PIN A5
define ROOM_ID 4
define OLED_RESET 4Adafruit_SSD1306 display(OLED_RESET)
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(1 NEOPIXEL_PIN NEO_GRB + NEO_KHZ800)
RTC_DS3231 rtc
WiFiClient client
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
DynamicJsonBuffer jsonBuffer(8500)
const char hue_ip = NULL
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 12 of 33
uint8_t light_numbers = NULLboolean last_motion = false
DateTime sunrise = NULLDateTime sunset = NULL
hardcoded day startend times
DateTime wakeup = DateTime(0 0 0 8 0 0)DateTime bedtime = DateTime(0 0 0 23 30 0)
boolean need_sunrise_sunset_times = false
define TRACE 1
void init_log()ifdef TRACE Serialbegin(9600) while (Serial) Serialprintln(Starting)endif
void log(const char msg)ifdef TRACE Serialprint(msg)endif
void log(const int i)ifdef TRACE Serialprint(i)endif
void logln(const char msg)ifdef TRACE Serialprintln(msg)endif
const char fetch_hue_ip() logln(Getting HUE IP) displayprintln(Getting HUE IP) if (clientconnectSSL(wwwmeethuecom 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 13 of 33
logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() logln(status) displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(Getting HUE IP) logln(HEADER ERROR) displayprintln(HEADER ERROR) return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return NULL
return strdup(root[0][F(internalipaddress)])
boolean fetch_sunrise_sunset(long sunrise long sunset) logln(Contacting DarkSky) displayprintln(Contacting DarkSky) if (clientconnectSSL(apidarkskynet 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return false
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 14 of 33
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() logln(HEADER ERROR) displayprintln(HEADER ERROR) return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset() long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
uint8_t lights_for_group(const char group_number)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 15 of 33
logln(Finding lights) displayprintln(Finding lights) if (clientconnect(hue_ip 80)) displayprintln(COULD NOT CONNECT) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(HEADER ERROR) return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) displayprintln(JSON PARSE ERROR) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 16 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
Photo cell (CdS photoresistor)
$095IN STOCK
ADD TO CART
PIR (motion) sensor
$995IN STOCK
ADD TO CART
Breadboard-friendly RGB Smart NeoPixel - Pack of 4
$795IN STOCK
ADD TO CART
FeatherWing Proto - Prototyping Add-on For All FeatherBoards
$495IN STOCK
ADD TO CART
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 5 of 33
FeatherWing Tripler Mini Kit - Prototyping Add-on ForFeathers
$850IN STOCK
ADD TO CART
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 6 of 33
Custom Sensor WingThe off the shelf FeatherWings provide the computing power wifi timekeeping and status display (which isnt reallyneeded but is nice while working on the software) Whats missing is inputs to the system Specifically knowing whensomeone is in the room and how lightdark it is in the room I use this combined with the time of day to figure out howto control the lights
For motion sensing I use a basic PIR For light measurement I use a simple CdS photocell in a voltage divider
There are already excellent learning guides on photoresisters and PIRs so I wont redo them here
Notice that Im also using a NeoPixel This Ill use as an at a glance status indicator since the wifi Feather M0 doesnthave a Dotstar Even if it did the Dotstar could easily be covered depending on how the boards are stacked
As usual I started with the lower profile components the NeoPixel breakout and the photocell voltage divider Keep inmind that the photocell has to be clear of the PIR and able to see the ambient light
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 7 of 33
I replaced the header pins on the PIR with longer ones that would reach past the capacitors and into the holes on theproto wing
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 8 of 33
The PIR fits neatly between the photoresistor and neopixel
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 9 of 33
For this one-off unit I did simple point to point wiring largely on the underside of the board
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 10 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 11 of 33
CodeThere are several functional areas in the code and Ill go through each individually
The code here has been edited for clarify The actual code in the repository has more error checking and diagnosticoutput
You can find the full code listing here
Written by Dave AstelsMIT license check LICENSE for more informationAll text above must be included in any redistribution
include ltSPIhgtinclude ltWirehgtinclude ltAdafruit_GFXhgtinclude ltAdafruit_SSD1306hgtinclude ltRTClibhgtinclude ltWiFi101hgtinclude ltArduinoJsonhgtinclude ltAdafruit_NeoPixelhgtinclude Adafruit_MQTThinclude Adafruit_MQTT_Clienth
include secretsh
define BUTTON_A 9define BUTTON_B 6define BUTTON_C 5define MOTION_PIN 10define LIGHT_PIN A1define NEOPIXEL_PIN A5
define ROOM_ID 4
define OLED_RESET 4Adafruit_SSD1306 display(OLED_RESET)
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(1 NEOPIXEL_PIN NEO_GRB + NEO_KHZ800)
RTC_DS3231 rtc
WiFiClient client
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
DynamicJsonBuffer jsonBuffer(8500)
const char hue_ip = NULL
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 12 of 33
uint8_t light_numbers = NULLboolean last_motion = false
DateTime sunrise = NULLDateTime sunset = NULL
hardcoded day startend times
DateTime wakeup = DateTime(0 0 0 8 0 0)DateTime bedtime = DateTime(0 0 0 23 30 0)
boolean need_sunrise_sunset_times = false
define TRACE 1
void init_log()ifdef TRACE Serialbegin(9600) while (Serial) Serialprintln(Starting)endif
void log(const char msg)ifdef TRACE Serialprint(msg)endif
void log(const int i)ifdef TRACE Serialprint(i)endif
void logln(const char msg)ifdef TRACE Serialprintln(msg)endif
const char fetch_hue_ip() logln(Getting HUE IP) displayprintln(Getting HUE IP) if (clientconnectSSL(wwwmeethuecom 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 13 of 33
logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() logln(status) displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(Getting HUE IP) logln(HEADER ERROR) displayprintln(HEADER ERROR) return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return NULL
return strdup(root[0][F(internalipaddress)])
boolean fetch_sunrise_sunset(long sunrise long sunset) logln(Contacting DarkSky) displayprintln(Contacting DarkSky) if (clientconnectSSL(apidarkskynet 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return false
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 14 of 33
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() logln(HEADER ERROR) displayprintln(HEADER ERROR) return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset() long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
uint8_t lights_for_group(const char group_number)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 15 of 33
logln(Finding lights) displayprintln(Finding lights) if (clientconnect(hue_ip 80)) displayprintln(COULD NOT CONNECT) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(HEADER ERROR) return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) displayprintln(JSON PARSE ERROR) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 16 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
FeatherWing Tripler Mini Kit - Prototyping Add-on ForFeathers
$850IN STOCK
ADD TO CART
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 6 of 33
Custom Sensor WingThe off the shelf FeatherWings provide the computing power wifi timekeeping and status display (which isnt reallyneeded but is nice while working on the software) Whats missing is inputs to the system Specifically knowing whensomeone is in the room and how lightdark it is in the room I use this combined with the time of day to figure out howto control the lights
For motion sensing I use a basic PIR For light measurement I use a simple CdS photocell in a voltage divider
There are already excellent learning guides on photoresisters and PIRs so I wont redo them here
Notice that Im also using a NeoPixel This Ill use as an at a glance status indicator since the wifi Feather M0 doesnthave a Dotstar Even if it did the Dotstar could easily be covered depending on how the boards are stacked
As usual I started with the lower profile components the NeoPixel breakout and the photocell voltage divider Keep inmind that the photocell has to be clear of the PIR and able to see the ambient light
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 7 of 33
I replaced the header pins on the PIR with longer ones that would reach past the capacitors and into the holes on theproto wing
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 8 of 33
The PIR fits neatly between the photoresistor and neopixel
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 9 of 33
For this one-off unit I did simple point to point wiring largely on the underside of the board
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 10 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 11 of 33
CodeThere are several functional areas in the code and Ill go through each individually
The code here has been edited for clarify The actual code in the repository has more error checking and diagnosticoutput
You can find the full code listing here
Written by Dave AstelsMIT license check LICENSE for more informationAll text above must be included in any redistribution
include ltSPIhgtinclude ltWirehgtinclude ltAdafruit_GFXhgtinclude ltAdafruit_SSD1306hgtinclude ltRTClibhgtinclude ltWiFi101hgtinclude ltArduinoJsonhgtinclude ltAdafruit_NeoPixelhgtinclude Adafruit_MQTThinclude Adafruit_MQTT_Clienth
include secretsh
define BUTTON_A 9define BUTTON_B 6define BUTTON_C 5define MOTION_PIN 10define LIGHT_PIN A1define NEOPIXEL_PIN A5
define ROOM_ID 4
define OLED_RESET 4Adafruit_SSD1306 display(OLED_RESET)
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(1 NEOPIXEL_PIN NEO_GRB + NEO_KHZ800)
RTC_DS3231 rtc
WiFiClient client
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
DynamicJsonBuffer jsonBuffer(8500)
const char hue_ip = NULL
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 12 of 33
uint8_t light_numbers = NULLboolean last_motion = false
DateTime sunrise = NULLDateTime sunset = NULL
hardcoded day startend times
DateTime wakeup = DateTime(0 0 0 8 0 0)DateTime bedtime = DateTime(0 0 0 23 30 0)
boolean need_sunrise_sunset_times = false
define TRACE 1
void init_log()ifdef TRACE Serialbegin(9600) while (Serial) Serialprintln(Starting)endif
void log(const char msg)ifdef TRACE Serialprint(msg)endif
void log(const int i)ifdef TRACE Serialprint(i)endif
void logln(const char msg)ifdef TRACE Serialprintln(msg)endif
const char fetch_hue_ip() logln(Getting HUE IP) displayprintln(Getting HUE IP) if (clientconnectSSL(wwwmeethuecom 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 13 of 33
logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() logln(status) displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(Getting HUE IP) logln(HEADER ERROR) displayprintln(HEADER ERROR) return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return NULL
return strdup(root[0][F(internalipaddress)])
boolean fetch_sunrise_sunset(long sunrise long sunset) logln(Contacting DarkSky) displayprintln(Contacting DarkSky) if (clientconnectSSL(apidarkskynet 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return false
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 14 of 33
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() logln(HEADER ERROR) displayprintln(HEADER ERROR) return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset() long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
uint8_t lights_for_group(const char group_number)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 15 of 33
logln(Finding lights) displayprintln(Finding lights) if (clientconnect(hue_ip 80)) displayprintln(COULD NOT CONNECT) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(HEADER ERROR) return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) displayprintln(JSON PARSE ERROR) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 16 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
Custom Sensor WingThe off the shelf FeatherWings provide the computing power wifi timekeeping and status display (which isnt reallyneeded but is nice while working on the software) Whats missing is inputs to the system Specifically knowing whensomeone is in the room and how lightdark it is in the room I use this combined with the time of day to figure out howto control the lights
For motion sensing I use a basic PIR For light measurement I use a simple CdS photocell in a voltage divider
There are already excellent learning guides on photoresisters and PIRs so I wont redo them here
Notice that Im also using a NeoPixel This Ill use as an at a glance status indicator since the wifi Feather M0 doesnthave a Dotstar Even if it did the Dotstar could easily be covered depending on how the boards are stacked
As usual I started with the lower profile components the NeoPixel breakout and the photocell voltage divider Keep inmind that the photocell has to be clear of the PIR and able to see the ambient light
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 7 of 33
I replaced the header pins on the PIR with longer ones that would reach past the capacitors and into the holes on theproto wing
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 8 of 33
The PIR fits neatly between the photoresistor and neopixel
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 9 of 33
For this one-off unit I did simple point to point wiring largely on the underside of the board
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 10 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 11 of 33
CodeThere are several functional areas in the code and Ill go through each individually
The code here has been edited for clarify The actual code in the repository has more error checking and diagnosticoutput
You can find the full code listing here
Written by Dave AstelsMIT license check LICENSE for more informationAll text above must be included in any redistribution
include ltSPIhgtinclude ltWirehgtinclude ltAdafruit_GFXhgtinclude ltAdafruit_SSD1306hgtinclude ltRTClibhgtinclude ltWiFi101hgtinclude ltArduinoJsonhgtinclude ltAdafruit_NeoPixelhgtinclude Adafruit_MQTThinclude Adafruit_MQTT_Clienth
include secretsh
define BUTTON_A 9define BUTTON_B 6define BUTTON_C 5define MOTION_PIN 10define LIGHT_PIN A1define NEOPIXEL_PIN A5
define ROOM_ID 4
define OLED_RESET 4Adafruit_SSD1306 display(OLED_RESET)
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(1 NEOPIXEL_PIN NEO_GRB + NEO_KHZ800)
RTC_DS3231 rtc
WiFiClient client
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
DynamicJsonBuffer jsonBuffer(8500)
const char hue_ip = NULL
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 12 of 33
uint8_t light_numbers = NULLboolean last_motion = false
DateTime sunrise = NULLDateTime sunset = NULL
hardcoded day startend times
DateTime wakeup = DateTime(0 0 0 8 0 0)DateTime bedtime = DateTime(0 0 0 23 30 0)
boolean need_sunrise_sunset_times = false
define TRACE 1
void init_log()ifdef TRACE Serialbegin(9600) while (Serial) Serialprintln(Starting)endif
void log(const char msg)ifdef TRACE Serialprint(msg)endif
void log(const int i)ifdef TRACE Serialprint(i)endif
void logln(const char msg)ifdef TRACE Serialprintln(msg)endif
const char fetch_hue_ip() logln(Getting HUE IP) displayprintln(Getting HUE IP) if (clientconnectSSL(wwwmeethuecom 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 13 of 33
logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() logln(status) displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(Getting HUE IP) logln(HEADER ERROR) displayprintln(HEADER ERROR) return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return NULL
return strdup(root[0][F(internalipaddress)])
boolean fetch_sunrise_sunset(long sunrise long sunset) logln(Contacting DarkSky) displayprintln(Contacting DarkSky) if (clientconnectSSL(apidarkskynet 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return false
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 14 of 33
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() logln(HEADER ERROR) displayprintln(HEADER ERROR) return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset() long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
uint8_t lights_for_group(const char group_number)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 15 of 33
logln(Finding lights) displayprintln(Finding lights) if (clientconnect(hue_ip 80)) displayprintln(COULD NOT CONNECT) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(HEADER ERROR) return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) displayprintln(JSON PARSE ERROR) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 16 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
I replaced the header pins on the PIR with longer ones that would reach past the capacitors and into the holes on theproto wing
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 8 of 33
The PIR fits neatly between the photoresistor and neopixel
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 9 of 33
For this one-off unit I did simple point to point wiring largely on the underside of the board
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 10 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 11 of 33
CodeThere are several functional areas in the code and Ill go through each individually
The code here has been edited for clarify The actual code in the repository has more error checking and diagnosticoutput
You can find the full code listing here
Written by Dave AstelsMIT license check LICENSE for more informationAll text above must be included in any redistribution
include ltSPIhgtinclude ltWirehgtinclude ltAdafruit_GFXhgtinclude ltAdafruit_SSD1306hgtinclude ltRTClibhgtinclude ltWiFi101hgtinclude ltArduinoJsonhgtinclude ltAdafruit_NeoPixelhgtinclude Adafruit_MQTThinclude Adafruit_MQTT_Clienth
include secretsh
define BUTTON_A 9define BUTTON_B 6define BUTTON_C 5define MOTION_PIN 10define LIGHT_PIN A1define NEOPIXEL_PIN A5
define ROOM_ID 4
define OLED_RESET 4Adafruit_SSD1306 display(OLED_RESET)
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(1 NEOPIXEL_PIN NEO_GRB + NEO_KHZ800)
RTC_DS3231 rtc
WiFiClient client
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
DynamicJsonBuffer jsonBuffer(8500)
const char hue_ip = NULL
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 12 of 33
uint8_t light_numbers = NULLboolean last_motion = false
DateTime sunrise = NULLDateTime sunset = NULL
hardcoded day startend times
DateTime wakeup = DateTime(0 0 0 8 0 0)DateTime bedtime = DateTime(0 0 0 23 30 0)
boolean need_sunrise_sunset_times = false
define TRACE 1
void init_log()ifdef TRACE Serialbegin(9600) while (Serial) Serialprintln(Starting)endif
void log(const char msg)ifdef TRACE Serialprint(msg)endif
void log(const int i)ifdef TRACE Serialprint(i)endif
void logln(const char msg)ifdef TRACE Serialprintln(msg)endif
const char fetch_hue_ip() logln(Getting HUE IP) displayprintln(Getting HUE IP) if (clientconnectSSL(wwwmeethuecom 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 13 of 33
logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() logln(status) displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(Getting HUE IP) logln(HEADER ERROR) displayprintln(HEADER ERROR) return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return NULL
return strdup(root[0][F(internalipaddress)])
boolean fetch_sunrise_sunset(long sunrise long sunset) logln(Contacting DarkSky) displayprintln(Contacting DarkSky) if (clientconnectSSL(apidarkskynet 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return false
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 14 of 33
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() logln(HEADER ERROR) displayprintln(HEADER ERROR) return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset() long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
uint8_t lights_for_group(const char group_number)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 15 of 33
logln(Finding lights) displayprintln(Finding lights) if (clientconnect(hue_ip 80)) displayprintln(COULD NOT CONNECT) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(HEADER ERROR) return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) displayprintln(JSON PARSE ERROR) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 16 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
The PIR fits neatly between the photoresistor and neopixel
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 9 of 33
For this one-off unit I did simple point to point wiring largely on the underside of the board
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 10 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 11 of 33
CodeThere are several functional areas in the code and Ill go through each individually
The code here has been edited for clarify The actual code in the repository has more error checking and diagnosticoutput
You can find the full code listing here
Written by Dave AstelsMIT license check LICENSE for more informationAll text above must be included in any redistribution
include ltSPIhgtinclude ltWirehgtinclude ltAdafruit_GFXhgtinclude ltAdafruit_SSD1306hgtinclude ltRTClibhgtinclude ltWiFi101hgtinclude ltArduinoJsonhgtinclude ltAdafruit_NeoPixelhgtinclude Adafruit_MQTThinclude Adafruit_MQTT_Clienth
include secretsh
define BUTTON_A 9define BUTTON_B 6define BUTTON_C 5define MOTION_PIN 10define LIGHT_PIN A1define NEOPIXEL_PIN A5
define ROOM_ID 4
define OLED_RESET 4Adafruit_SSD1306 display(OLED_RESET)
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(1 NEOPIXEL_PIN NEO_GRB + NEO_KHZ800)
RTC_DS3231 rtc
WiFiClient client
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
DynamicJsonBuffer jsonBuffer(8500)
const char hue_ip = NULL
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 12 of 33
uint8_t light_numbers = NULLboolean last_motion = false
DateTime sunrise = NULLDateTime sunset = NULL
hardcoded day startend times
DateTime wakeup = DateTime(0 0 0 8 0 0)DateTime bedtime = DateTime(0 0 0 23 30 0)
boolean need_sunrise_sunset_times = false
define TRACE 1
void init_log()ifdef TRACE Serialbegin(9600) while (Serial) Serialprintln(Starting)endif
void log(const char msg)ifdef TRACE Serialprint(msg)endif
void log(const int i)ifdef TRACE Serialprint(i)endif
void logln(const char msg)ifdef TRACE Serialprintln(msg)endif
const char fetch_hue_ip() logln(Getting HUE IP) displayprintln(Getting HUE IP) if (clientconnectSSL(wwwmeethuecom 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 13 of 33
logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() logln(status) displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(Getting HUE IP) logln(HEADER ERROR) displayprintln(HEADER ERROR) return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return NULL
return strdup(root[0][F(internalipaddress)])
boolean fetch_sunrise_sunset(long sunrise long sunset) logln(Contacting DarkSky) displayprintln(Contacting DarkSky) if (clientconnectSSL(apidarkskynet 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return false
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 14 of 33
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() logln(HEADER ERROR) displayprintln(HEADER ERROR) return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset() long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
uint8_t lights_for_group(const char group_number)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 15 of 33
logln(Finding lights) displayprintln(Finding lights) if (clientconnect(hue_ip 80)) displayprintln(COULD NOT CONNECT) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(HEADER ERROR) return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) displayprintln(JSON PARSE ERROR) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 16 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
For this one-off unit I did simple point to point wiring largely on the underside of the board
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 10 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 11 of 33
CodeThere are several functional areas in the code and Ill go through each individually
The code here has been edited for clarify The actual code in the repository has more error checking and diagnosticoutput
You can find the full code listing here
Written by Dave AstelsMIT license check LICENSE for more informationAll text above must be included in any redistribution
include ltSPIhgtinclude ltWirehgtinclude ltAdafruit_GFXhgtinclude ltAdafruit_SSD1306hgtinclude ltRTClibhgtinclude ltWiFi101hgtinclude ltArduinoJsonhgtinclude ltAdafruit_NeoPixelhgtinclude Adafruit_MQTThinclude Adafruit_MQTT_Clienth
include secretsh
define BUTTON_A 9define BUTTON_B 6define BUTTON_C 5define MOTION_PIN 10define LIGHT_PIN A1define NEOPIXEL_PIN A5
define ROOM_ID 4
define OLED_RESET 4Adafruit_SSD1306 display(OLED_RESET)
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(1 NEOPIXEL_PIN NEO_GRB + NEO_KHZ800)
RTC_DS3231 rtc
WiFiClient client
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
DynamicJsonBuffer jsonBuffer(8500)
const char hue_ip = NULL
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 12 of 33
uint8_t light_numbers = NULLboolean last_motion = false
DateTime sunrise = NULLDateTime sunset = NULL
hardcoded day startend times
DateTime wakeup = DateTime(0 0 0 8 0 0)DateTime bedtime = DateTime(0 0 0 23 30 0)
boolean need_sunrise_sunset_times = false
define TRACE 1
void init_log()ifdef TRACE Serialbegin(9600) while (Serial) Serialprintln(Starting)endif
void log(const char msg)ifdef TRACE Serialprint(msg)endif
void log(const int i)ifdef TRACE Serialprint(i)endif
void logln(const char msg)ifdef TRACE Serialprintln(msg)endif
const char fetch_hue_ip() logln(Getting HUE IP) displayprintln(Getting HUE IP) if (clientconnectSSL(wwwmeethuecom 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 13 of 33
logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() logln(status) displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(Getting HUE IP) logln(HEADER ERROR) displayprintln(HEADER ERROR) return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return NULL
return strdup(root[0][F(internalipaddress)])
boolean fetch_sunrise_sunset(long sunrise long sunset) logln(Contacting DarkSky) displayprintln(Contacting DarkSky) if (clientconnectSSL(apidarkskynet 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return false
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 14 of 33
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() logln(HEADER ERROR) displayprintln(HEADER ERROR) return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset() long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
uint8_t lights_for_group(const char group_number)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 15 of 33
logln(Finding lights) displayprintln(Finding lights) if (clientconnect(hue_ip 80)) displayprintln(COULD NOT CONNECT) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(HEADER ERROR) return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) displayprintln(JSON PARSE ERROR) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 16 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 11 of 33
CodeThere are several functional areas in the code and Ill go through each individually
The code here has been edited for clarify The actual code in the repository has more error checking and diagnosticoutput
You can find the full code listing here
Written by Dave AstelsMIT license check LICENSE for more informationAll text above must be included in any redistribution
include ltSPIhgtinclude ltWirehgtinclude ltAdafruit_GFXhgtinclude ltAdafruit_SSD1306hgtinclude ltRTClibhgtinclude ltWiFi101hgtinclude ltArduinoJsonhgtinclude ltAdafruit_NeoPixelhgtinclude Adafruit_MQTThinclude Adafruit_MQTT_Clienth
include secretsh
define BUTTON_A 9define BUTTON_B 6define BUTTON_C 5define MOTION_PIN 10define LIGHT_PIN A1define NEOPIXEL_PIN A5
define ROOM_ID 4
define OLED_RESET 4Adafruit_SSD1306 display(OLED_RESET)
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(1 NEOPIXEL_PIN NEO_GRB + NEO_KHZ800)
RTC_DS3231 rtc
WiFiClient client
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
DynamicJsonBuffer jsonBuffer(8500)
const char hue_ip = NULL
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 12 of 33
uint8_t light_numbers = NULLboolean last_motion = false
DateTime sunrise = NULLDateTime sunset = NULL
hardcoded day startend times
DateTime wakeup = DateTime(0 0 0 8 0 0)DateTime bedtime = DateTime(0 0 0 23 30 0)
boolean need_sunrise_sunset_times = false
define TRACE 1
void init_log()ifdef TRACE Serialbegin(9600) while (Serial) Serialprintln(Starting)endif
void log(const char msg)ifdef TRACE Serialprint(msg)endif
void log(const int i)ifdef TRACE Serialprint(i)endif
void logln(const char msg)ifdef TRACE Serialprintln(msg)endif
const char fetch_hue_ip() logln(Getting HUE IP) displayprintln(Getting HUE IP) if (clientconnectSSL(wwwmeethuecom 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 13 of 33
logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() logln(status) displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(Getting HUE IP) logln(HEADER ERROR) displayprintln(HEADER ERROR) return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return NULL
return strdup(root[0][F(internalipaddress)])
boolean fetch_sunrise_sunset(long sunrise long sunset) logln(Contacting DarkSky) displayprintln(Contacting DarkSky) if (clientconnectSSL(apidarkskynet 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return false
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 14 of 33
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() logln(HEADER ERROR) displayprintln(HEADER ERROR) return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset() long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
uint8_t lights_for_group(const char group_number)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 15 of 33
logln(Finding lights) displayprintln(Finding lights) if (clientconnect(hue_ip 80)) displayprintln(COULD NOT CONNECT) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(HEADER ERROR) return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) displayprintln(JSON PARSE ERROR) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 16 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
CodeThere are several functional areas in the code and Ill go through each individually
The code here has been edited for clarify The actual code in the repository has more error checking and diagnosticoutput
You can find the full code listing here
Written by Dave AstelsMIT license check LICENSE for more informationAll text above must be included in any redistribution
include ltSPIhgtinclude ltWirehgtinclude ltAdafruit_GFXhgtinclude ltAdafruit_SSD1306hgtinclude ltRTClibhgtinclude ltWiFi101hgtinclude ltArduinoJsonhgtinclude ltAdafruit_NeoPixelhgtinclude Adafruit_MQTThinclude Adafruit_MQTT_Clienth
include secretsh
define BUTTON_A 9define BUTTON_B 6define BUTTON_C 5define MOTION_PIN 10define LIGHT_PIN A1define NEOPIXEL_PIN A5
define ROOM_ID 4
define OLED_RESET 4Adafruit_SSD1306 display(OLED_RESET)
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(1 NEOPIXEL_PIN NEO_GRB + NEO_KHZ800)
RTC_DS3231 rtc
WiFiClient client
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
DynamicJsonBuffer jsonBuffer(8500)
const char hue_ip = NULL
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 12 of 33
uint8_t light_numbers = NULLboolean last_motion = false
DateTime sunrise = NULLDateTime sunset = NULL
hardcoded day startend times
DateTime wakeup = DateTime(0 0 0 8 0 0)DateTime bedtime = DateTime(0 0 0 23 30 0)
boolean need_sunrise_sunset_times = false
define TRACE 1
void init_log()ifdef TRACE Serialbegin(9600) while (Serial) Serialprintln(Starting)endif
void log(const char msg)ifdef TRACE Serialprint(msg)endif
void log(const int i)ifdef TRACE Serialprint(i)endif
void logln(const char msg)ifdef TRACE Serialprintln(msg)endif
const char fetch_hue_ip() logln(Getting HUE IP) displayprintln(Getting HUE IP) if (clientconnectSSL(wwwmeethuecom 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 13 of 33
logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() logln(status) displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(Getting HUE IP) logln(HEADER ERROR) displayprintln(HEADER ERROR) return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return NULL
return strdup(root[0][F(internalipaddress)])
boolean fetch_sunrise_sunset(long sunrise long sunset) logln(Contacting DarkSky) displayprintln(Contacting DarkSky) if (clientconnectSSL(apidarkskynet 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return false
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 14 of 33
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() logln(HEADER ERROR) displayprintln(HEADER ERROR) return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset() long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
uint8_t lights_for_group(const char group_number)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 15 of 33
logln(Finding lights) displayprintln(Finding lights) if (clientconnect(hue_ip 80)) displayprintln(COULD NOT CONNECT) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(HEADER ERROR) return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) displayprintln(JSON PARSE ERROR) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 16 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
uint8_t light_numbers = NULLboolean last_motion = false
DateTime sunrise = NULLDateTime sunset = NULL
hardcoded day startend times
DateTime wakeup = DateTime(0 0 0 8 0 0)DateTime bedtime = DateTime(0 0 0 23 30 0)
boolean need_sunrise_sunset_times = false
define TRACE 1
void init_log()ifdef TRACE Serialbegin(9600) while (Serial) Serialprintln(Starting)endif
void log(const char msg)ifdef TRACE Serialprint(msg)endif
void log(const int i)ifdef TRACE Serialprint(i)endif
void logln(const char msg)ifdef TRACE Serialprintln(msg)endif
const char fetch_hue_ip() logln(Getting HUE IP) displayprintln(Getting HUE IP) if (clientconnectSSL(wwwmeethuecom 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 13 of 33
logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() logln(status) displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(Getting HUE IP) logln(HEADER ERROR) displayprintln(HEADER ERROR) return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return NULL
return strdup(root[0][F(internalipaddress)])
boolean fetch_sunrise_sunset(long sunrise long sunset) logln(Contacting DarkSky) displayprintln(Contacting DarkSky) if (clientconnectSSL(apidarkskynet 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return false
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 14 of 33
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() logln(HEADER ERROR) displayprintln(HEADER ERROR) return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset() long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
uint8_t lights_for_group(const char group_number)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 15 of 33
logln(Finding lights) displayprintln(Finding lights) if (clientconnect(hue_ip 80)) displayprintln(COULD NOT CONNECT) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(HEADER ERROR) return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) displayprintln(JSON PARSE ERROR) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 16 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() logln(status) displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(Getting HUE IP) logln(HEADER ERROR) displayprintln(HEADER ERROR) return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return NULL
return strdup(root[0][F(internalipaddress)])
boolean fetch_sunrise_sunset(long sunrise long sunset) logln(Contacting DarkSky) displayprintln(Contacting DarkSky) if (clientconnectSSL(apidarkskynet 443)) logln(COULD NOT CONNECT) displayprintln(COULD NOT CONNECT) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() logln(CONNECTION ERROR) displayprintln(CONNECTION ERROR) return false
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 14 of 33
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() logln(HEADER ERROR) displayprintln(HEADER ERROR) return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset() long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
uint8_t lights_for_group(const char group_number)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 15 of 33
logln(Finding lights) displayprintln(Finding lights) if (clientconnect(hue_ip 80)) displayprintln(COULD NOT CONNECT) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(HEADER ERROR) return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) displayprintln(JSON PARSE ERROR) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 16 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() logln(HEADER ERROR) displayprintln(HEADER ERROR) return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) logln(JSON PARSE ERROR) displayprintln(JSON PARSE ERROR) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset() long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
uint8_t lights_for_group(const char group_number)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 15 of 33
logln(Finding lights) displayprintln(Finding lights) if (clientconnect(hue_ip 80)) displayprintln(COULD NOT CONNECT) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(HEADER ERROR) return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) displayprintln(JSON PARSE ERROR) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 16 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
logln(Finding lights) displayprintln(Finding lights) if (clientconnect(hue_ip 80)) displayprintln(COULD NOT CONNECT) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() displayprintln(CONNECTION ERROR) return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() displayprintln(status) return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() displayprintln(HEADER ERROR) return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) displayprintln(JSON PARSE ERROR) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 16 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
log(Turning light ) log(light_number) logln(on_off on off)
log(PUT api) log(HUE_USER) log(lights) log(light_number) logln(state HTTP11)
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 17 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
void MQTT_connect() if (mqttconnected()) return while (mqttconnect() = 0) connect will return 0 for connected mqttdisconnect() delay(5000) wait 5 seconds
void setup() init_log() pinMode(BUTTON_A INPUT_PULLUP) pinMode(BUTTON_B INPUT_PULLUP) pinMode(BUTTON_C INPUT_PULLUP) pinMode(MOTION_PIN INPUT_PULLUP) displaybegin(SSD1306_SWITCHCAPVCC 0x3C) initialize with the I2C addr 0x3C (for the 128x32) displaydisplay() delay(2000) displaysetTextSize(1) displaysetTextColor(WHITE) WiFisetPins(8742)
attempt to connect to WiFi network int status = WL_IDLE_STATUS
displayprint(WIFI_SSID) while (status = WL_CONNECTED) displayprint() delay(500) status = WiFibegin(WIFI_SSID WIFI_PASS)
if ( rtcbegin()) while (1)
if (rtclostPower()) rtcadjust(DateTime(F(__DATE__) F(__TIME__)))
Clear the buffer displayclearDisplay() displaysetCursor(0 0)
hue_ip = fetch_hue_ip() if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID) if (light_numbers == NULL)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 18 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
if (light_numbers == NULL) while (true) pixelbegin() pixelsetPixelColor(0 0 0 0) pixelshow()
if (update_sunrise_sunset()) sunrise = new DateTime(0 0 0 7 0 0) sunset = new DateTime(0 0 0 16 30 0)
long ping_time = 0void ping_if_time(DateTime now) if (nowsecondstime() gt= ping_time) ping_time = nowsecondstime() + 250 if (mqttping()) logln(No MQTT ping) mqttdisconnect()
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 19 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
if (motion_started) logln(Publishing motion start) if (motion_feedpublish((const char)started)) logln(n MQTT motion publish failedn) if (photocell_feedpublish(light_level)) logln(n MQTT photocell publish failedn) pixelsetPixelColor(0 16 0 0) else if (motion_ended) if (motion_feedpublish((const char)ended)) logln(n MQTT motion publish failedn) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) if (control_feedpublish((const char)on)) logln(n MQTT control publish failedn) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) if (control_feedpublish((const char)off)) logln(nMQTT control publish failedn) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 20 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
Talking to HUE
Finding your HUE Bridge
Im using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing
Be sure thatyour WiFi modules firmware and SSL certificates are up to date See the Adafruit Feather M0 WiFi withATWINC1500 tutorial for details
To use the HUE restful interface to control lighting you first have to find out what to talk to
It starts by making a secure connection to wwwmeethuecom doing a GET from apinupnp If the result has a 200status the response body is parsed and the internalipaddress member is extracted Thats the IP on the local networkof the HUE bridge device
To access the API on your HUE bridge device youll need a user See the HUE Getting Started guide for details
Light groups
const char fetch_hue_ip() if (clientconnectSSL(wwwmeethuecom 443)) return NULL clientprintln(GET apinupnp HTTP11) clientprintln(Host wwwmeethuecom) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonArrayamp root = jsonBufferparseArray(client) clientstop()
if (rootsuccess()) return NULL
return strdup(root[0][F(internalipaddress)])
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 21 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
In addition to managing individual lights HUE allows you group lights The idea behind this is to group lights intorooms to allow you to control all the lights in a room at once from the hue smartphone app
While everything about your local HUE installation is discoverable through the RESTful API Ive simplified things a bitby hardcoding the group id that the device should control In a full installation each module would need to have itsroomgroup id set somehow In my final vision the name of the room the module is located in would be assigned froma central controller Thats beyond the scope of this guide though
The above code is from setup() It gets the IP of the local HUE bridge and then asks for the lights in the room thatsbeen set
Notice that I use the first (ie the 0th) element in the array of light numbers to stash the number of lights for lateriteration
hue_ip = fetch_hue_ip()if (hue_ip == NULL) while (true) light_numbers = lights_for_group(ROOM_ID)if (light_numbers == NULL) while (true)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 22 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
Controlling lights
Once the list of lights is in hand they can be controlled
uint8_t lights_for_group(const char group_number) if (clientconnect(hue_ip 80)) return NULL
clientprint(GET api) clientprint(HUE_USER) clientprint(groups) clientprint(group_number) clientprintln( HTTP11)
clientprint(Host ) clientprintln(hue_ip) clientprintln(Connection close) if (clientprintln()) clientstop() return NULL
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return NULL
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return NULL
JsonObjectamp group = jsonBufferparseObject(client) clientstop()
if (groupsuccess()) return NULL
JsonArrayamp lights = group[lights] uint8_t light_numbers = (uint8_t)malloc(lightssize() + 1) light_numbers[0] = (uint8_t)lightssize() for (uint i = 0 i lt lightssize() i++) light_numbers[i+1] = (uint8_t)atoi((const char )lights[i]) return light_numbers
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 23 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
To keep things simple this just fires amp forgets assuming the lights will get set appropriately You can close thefeedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights
void update_light(uint8_t light_number boolean on_off uint8_t brightness) if (clientconnect(hue_ip 80)) return
char content[32] sprintf(content onsbrid on_off true false brightness)
clientprint(PUT api) clientprint(HUE_USER) clientprint(lights) clientprint(light_number) clientprintln(state HTTP11)
clientprint(Host ) clientprintln(hue_ip)
clientprintln(Connection close)
clientprint(Content-Type ) clientprintln(applicationjson) clientprintln(User-Agent FeatherM0Sender) clientprint(Content-Length ) clientprintln(strlen(content)) clientprintln()
clientprintln(content) clientstop()
void update_all_lights(uint8_t light_numbers boolean on_off uint8_t brightness) if (light_numbers = NULL) uint8_t num_lights = light_numbers[0] for (int i = 0 i lt num_lights i++) update_light(light_numbers[i+1] on_off brightness)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 24 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
Getting the WeatherOk we dont really care about the weather I use DarkSky to fetch the weather data for the day but ignore everythingother than the sunrise and sunset times Using that information along with the time of day from the RTC the systemcan determine if its day or night
boolean fetch_sunrise_sunset(long sunrise long sunset) if (clientconnectSSL(apidarkskynet 443)) return false
clientprint(GET forecast) clientprint(DARKSKY_KEY) clientprint(429837-812497units=caampexclude=currentlyminutelyhourlyalertsflagsamplanguage=en) clientprintln( HTTP11)
clientprint(Host ) clientprintln(apidarkskynet) clientprintln(Connection close) if (clientprintln()) clientstop() return false
char status[32] = 0 clientreadBytesUntil(r status sizeof(status)) if (strcmp(status HTTP11 200 OK) = 0) clientstop() return false
char endOfHeaders[] = rnrn if (clientfind(endOfHeaders)) clientstop() return false
JsonObjectamp root = jsonBufferparseObject(client) clientstop()
if (rootsuccess()) return false
JsonObjectamp data = root[daily][data][0] long start_of_day = data[time] long raw_sunrise_time = data[sunriseTime] long raw_sunset_time = data[sunsetTime]
sunrise = raw_sunrise_time - start_of_day sunset = raw_sunset_time - start_of_day
return true
boolean update_sunrise_sunset()
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 25 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
The Darksky api takes url arguments for the location units as well as data items to exclude In this case I have itexclude as much as possible to minimize the size of the response body
Note that fetch_sunrise_sunset returns times in seconds be relative to the start of the day That then gets converted toDateTime objects for use later
long sunrise_seconds sunset_seconds if (fetch_sunrise_sunset(ampsunrise_seconds ampsunset_seconds)) return false if (sunrise) delete sunrise sunrise = new DateTime(0 0 0 sunrise_seconds 3600 (sunrise_seconds 60) 60 0)
if (sunset) delete sunset sunset = new DateTime(0 0 0 sunset_seconds 3600 (sunset_seconds 60) 60 0)
return true
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 26 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
Using Adafruit IOFor fun I decided to log some things to AdafruitIO
motion startendlight level when motion is detectedwhen lights are turned onoff
Feeds are created in the preamble
We make use of those feeds in loop() For example
Setup MQTTdefine AIO_SERVER ioadafruitcomdefine AIO_SERVERPORT 1883const char MQTT_SERVER[] PROGMEM = AIO_SERVERconst char MQTT_USERNAME[] PROGMEM = AIO_USERconst char MQTT_PASSWORD[] PROGMEM = AIO_KEY
Adafruit_MQTT_Client mqtt(ampclient AIO_SERVER AIO_SERVERPORT AIO_USER AIO_KEY)Adafruit_MQTT_Publish photocell_feed(ampmqtt AIO_USER feedshue-controllerhue-photocell)Adafruit_MQTT_Publish motion_feed(ampmqtt AIO_USER feedshue-controllerhue-motion)Adafruit_MQTT_Publish control_feed(ampmqtt AIO_USER feedshue-controllerhue-control)
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) else if (motion_ended) motion_feedpublish((const char)ended))
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 27 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 28 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
Keys and suchYou should never ever put keys and other confidential information in code I have all mine in environment variablesand I use a shell script to put them in a header field that gets included If you use this approach just be careful to addthat header file to gitignore
This will result in a file that looks like
Set any env vars needed to build
rm secretshecho define WIFI_PASS $WIFI_PASSWORD gtgt secretshecho define WIFI_SSID $WIFI_SSID gtgt secretshecho define HUE_USER $HUE_USER gtgt secretshecho define DARKSKY_KEY $DARKSKY_KEY gtgt secretshecho define AIO_USER $AIO_USER gtgt secretshecho define AIO_KEY $AIO_KEY gtgt secretshecho gtgt secretsh
define WIFI_PASS define WIFI_SSID define HUE_USER define DARKSKY_KEY define AIO_USER define AIO_KEY
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 29 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
Putting it all together
boolean is_between(DateTime now DateTime start DateTime end) now gt start || now gt end if (now-gthour() lt start-gthour()) return false if (now-gthour() == start-gthour() ampamp now-gtminute() lt start-gtminute()) return false if (now-gthour() gt end-gthour()) return false if (now-gthour() == end-gthour() ampamp now-gtminute() gt end-gtminute()) return false return true
void loop() displayclearDisplay() displaysetCursor(0 0) DateTime now = rtcnow() ping_if_time(now) MQTT_connect()
boolean is_motion = digitalRead(MOTION_PIN) int32_t light_level = analogRead(LIGHT_PIN) char buf[22] sprintf(buf d02d02d 02d02d02d nowyear() nowmonth() nowday() nowhour() nowminute() nowsecond())
if (nowhour() == 0) if (need_sunrise_sunset_times) if (update_sunrise_sunset()) while (true) need_sunrise_sunset_times = false else need_sunrise_sunset_times = true boolean motion_started = is_motion ampamp last_motion boolean motion_ended = is_motion ampamp last_motion last_motion = is_motion
if (motion_started) motion_feedpublish((const char)started)) photocell_feedpublish(light_level)) pixelsetPixelColor(0 16 0 0) else if (motion_ended) motion_feedpublish((const char)ended)) pixelsetPixelColor(0 0 0 0) pixelshow() displayprintln(buf) if (is_motion) displayprint( ) else displayprint(No) displayprint( Motion Light )
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 30 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
This starts by reading the motion detector and light sensor Then if weve passed midnight we update the sunrise andsunset times for the new day A flag is used to note that theyve been updated so that it will only update once per day(ie the first time the hour is 0 Once the hour passes 0 the flag is reset for the next day
Next motion startend is detected It looks for a change in the state of the PIR To do this it tracks the previouslyobserved state When the new state differs theres a state change
If motion just started or ended it posts to the motion feed on AdafruitIO and changes the color of the NeoPixelappropriately If motion started it also posts the light reading to the light level feed
Relevant information is also displayed on the OLED
The crux of the process is at the end of loop() This is the code that decides if the lights should be turned on or off
Lights are turned on if
the on button is pushed ormotion has started and
its rather dark in the room orits dark outside
Additionally if the current time is between the (for now) hardcoded get up and got to bed times the lights are turnedon at full brightness otherwise (ie you should be asleep but are up for a snack or drink) they turn on at 10brightness
Lights are turned off if
the off button is pushed ormotion has stopped
In any other case the lights are not changed As you can see the trigger for changing the lights is the push of one ofthe control buttons or a change in motion state
displayprint( Motion Light ) displayprintln(light_level) displayprint(is_between(ampnow sunrise sunset) Light Dark) displayprintln( out now) displayprint(You should be ) displayprintln(is_between(ampnow ampwakeup ampbedtime) awake asleep) displaydisplay()
if (digitalRead(BUTTON_A) || (motion_started ampamp (light_level lt 50 || is_between(ampnow sunrise sunset)))) control_feedpublish((const char)on)) update_all_lights(light_numbers true is_between(ampnow ampwakeup ampbedtime) 100 10) if (digitalRead(BUTTON_C) || motion_ended) control_feedpublish((const char)off)) update_all_lights(light_numbers false 0) delay(400)
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 31 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
copy Adafruit Industries httpslearnadafruitcomfeather-hue-lighting-controller Page 32 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33
Downloads
Download the code
httpsadafruitAEJ
copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33