feather-based hue lighting controller - adafruit … hue lighting controller created by dave astels...

33
Feather-based Hue lighting controller Created by Dave Astels Last updated on 2018-04-17 11:46:44 PM UTC

Upload: dothuan

Post on 08-Jun-2018

217 views

Category:

Documents


0 download

TRANSCRIPT

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

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

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads

Downloads

Download the code

httpsadafruitAEJ

copy Adafruit Industries Last Updated 2018-04-17 114643 PM UTC Page 33 of 33

  • Guide Contents
  • Overview
  • Parts
    • Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500
    • DS3231 Precision RTC FeatherWing - RTC Add-on For Feather Boards
    • FeatherWing OLED - 128x32 OLED Add-on For All Feather Boards
    • Photo cell (CdS photoresistor)
    • PIR (motion) sensor
    • Breadboard-friendly RGB Smart NeoPixel - Pack of 4
    • FeatherWing Proto - Prototyping Add-on For All Feather Boards
    • FeatherWing Tripler Mini Kit - Prototyping Add-on For Feathers
      • Custom Sensor Wing
      • Code
      • Talking to HUE
        • Finding your HUE Bridge
        • Light groups
        • Controlling lights
          • Getting the Weather
          • Using Adafruit IO
          • Keys and such
          • Putting it all together
          • Downloads