Close
0%
0%

Raspberry Pi Hue/Nanoleaf Light Switch

Light control for rooms containing both Philips Hue and Nanoleaf lights using their respective REST APIs

Similar projects worth following
API calls from a tiny computer control Philips Hue and Nanoleaf lights! Modular software. Modular hardware. What more can one ask for?

The aim of this project is to end up with a handheld device with programmable settings for controlling Philips Hue and Nanoleaf lights, rather than having two separate remotes.

I was disappointed to find that there didn't seem to be an easy way to hack the Hue dimmer switch to control both brands of lights (despite it containing an ATMEGA256RFR2), so I decided to build my own.

I'll admit that it may be a bit heavy-handed to use a Raspberry Pi just to make a few network calls, but Python makes it a bit easier to prototype everything out. I may move this over to an ESP32 board in the future.

The Raspberry Pi runs some python code that takes care of menus, applications, input, and display. Originally, the "applications" were intended to just be one-off on/off toggles for lights in my home, but I quickly realized that these could be anything from editing files, controlling network connectivity, etc. The possibilities are endless :D

Hardware

- Raspberry Pi Zero W.

- A perma proto hat is used to connect some pushbuttons to act as GPIO inputs.

- A 128x32 Adafruit PiOLED display

Software

The software is written in Python and operates on a PyGame loop.

In general the code is split up as follows:

API Clients

API clients are implemented for both the Hue and Nanoleaf lights based on the documentation found here:

Philips Hue API Docs (Free account needed)

Nanoleaf API Docs

The clients aren't full implementations of the API. Only the parts relevant to this project are included.

Display Logic

I didn't want to develop directly on the Pi because I enjoy the facilities provided by an IDE. To make running on a desktop computer vs on the Pi easier, I abstracted out the rendering code to be platform independent. The only thing that differs is the code that either chooses to render text to a PyGame window, or to the OLED display. The PyGame loop doesn't know or care which one.

Input

The logical buttons that are understood by the program are defined in an enum. Like the display logic, there's an abstraction layer for the input. One implementation reads PyGame keyboard events and converts them to logical buttons, another interprets GPIO inputs.

Menus

The base menu class includes code to handle input and render up to 4 lines of text.

Menus can be nested. Leaves can be considered the "apps". For example, we might have one app that controls the lights in one room, one app that displays system info, etc.

The nested menu system operates on a stack. At any given point, the main loop peeks at the top of the stack, renders that menu, and uses that menu to interpret input.

When entering a menu or a leaf, that menu gets pushed onto the stack.

When going back, the top menu is popped.

  • 1 × Raspberry Pi Zero W
  • 1 × Adafruit Perma Proto Bonnet Mini Kit Used for prototyping the button interface for GPIO input
  • 1 × Adafruit PiOLED - 128x32 Mini OLED for Raspberry Pi

  • Fixing up MainLoop

    Tim Schonberger04/09/2020 at 15:10 0 comments

    Looking at the code, I felt like it was odd that the entire application was forever tied to a PyGame loop. For an actual game where elements need to update regularly, but for this application, the screen really only needs to update when input occurs. 

    Before, this is what was happening:

    A PyGame loop was running, constantly checking for input and rendering the state of the application. For running this on the desktop, this made sense, since PyGame was the easiest way to get a display and input working. However, on a Raspberry Pi, this loop is kind of wasteful since we're not running a window or using a keyboard.

    I decided to take another approach that looks less like a game loop.

    Instead, MainLoop became not a loop at all. All it really does now is set up the rendering, and then adds a callback to the input class. Whenever an input event occurs, it executes the callback when then triggers the draw function in the renderer. This way, the entire application isn't tied to any particular loop, and the screen only gets redrawn when an input event happens.

    This being said, there are still two separate implementations of the input class.

    RPi.GPIO luckily has the ability to set up callbacks. When a pin's state changes, it calls a callback that interprets the pin as a button, and then the input class calls the input callback in main loop and passes that button so the button can actually be handled.

    The PyGame input implementation still does operate on a loop to check for input events. It interprets the events into logical buttons, and then calls the input callback manually.

    This way, PyGame is only used when the application is run on the desktop, so when running on the Pi, we don't need to worry about a loop running constantly. Instead, we just block the application in the input class with

    input('\nPress any key to exit.\n')

View project log

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates