A React app for Philips Hue lights

In my previous post I showed how a Philips Hue bridge can be integrated with Home Assistant for controlling lights with automation. The Philips Hue bridge has an easy-to-use REST API so you can make your own integration on any platform that supports http. In this post I will describe how I have created a React web application for controlling my lights via the Philips Hue api.

About Philips Hue

See my previous post for more information on the Philips Hue system:

Philips Hue and Home Assistant

About React

React is a popular open-source JavaScript library (originally from Facebook) that lets you create responsive html/JavaScript user interfaces with structured and composable components. React’s rendering is very efficient as the React engine uses a virtual DOM for calculating what parts in the real DOM that need to be updated. React supports development with modern JavaScript (ES6 and JSX) and you normally use tools like webpack and babel for transpiling ES6 into a ES5 JavaScript bundle when you deploy your application so that the code can be executed on all modern browsers in an easy way.

Create-react-app

React and modern JavaScript is great, but setting up a proper transpiling- and bundling environment with webpack and babel from scratch is not a trivial task. To get started with React and modern JavaScript a tool like create-react-app can be used:

https://github.com/facebookincubator/create-react-app

It will set up a React template so you can get started immediately with your code. You can eject out of the create-react-app environment with yarn eject (it is a one-way exit, though). This will replace the initial development environment with webpack- and babel-configurations that you can continue to extend upon.

Styling and components

In the example app, I have added material-ui for styling and some ready-to-use UI-components like a toogle switches and sliders.

The Philips Hue api

The Philips Hue bridge has a RESTful api that can be accessed with http-requests:

https://developers.meethue.com/documentation/getting-started

If you have the IP-address of the Hue bridge, you can start the CLIP API Debugger in a browser on the same network:

http://<IP_ADDRESS_OF_HUE_BRIDGE>/debug/clip.html

To get started, you need to send a Post request to the Hue bridge for generating a new username token:

hue_get_new_usertoken.png

Press the button on the Hue bridge, and then press Post with a message body as above (replace the devicetype property with anything you like). This will return a new username token for you:

hue_new_user_response.png

This token can then be used for accessing the API of your Hue bridge. For example, to get information on all lights in your system, send a GET request to a URL that includes the username after api/:

http://<IP_ADDRES_OF_HUE_BRIDGE>/api/5Bg8MEr2C3GKldw4SYsAEp8BLiJ6GK41dZnUABA5/lights

In the CLIP API Debugger:

hue_get_all_lights.png

The GET request returns a JSON object with a dictionary (key-value pairs) for all your lights.

Similarly, you can get a list of all the Hue groups in your system with:

http://<IP_ADDRES_OF_HUE_BRIDGE>/api/5Bg8MEr2C3GKldw4SYsAEp8BLiJ6GK41dZnUABA5/groups

To change the state or the brightness of a light, you send a PUT request with a body. For example, to turn on the light with id “6” and set the brightness to 150:

hue_change_light_state.png

My example app, as described below, uses a GET request to get the on/of states and brightness for all the lights in a Hue system. When you use the toggle switches and sliders in the GUI, it will send PUT requests for updating the state of the lights.

The example app

If you have a Hue system, you can try out my React app from GitHub. The app fetches all lights from the Hue bridge and let’s you toggle the lights on/off and change the brightness of each light via a slider. The app polls for state changes from the bridge every 5 seconds, so if you use another app to for changing the lights states, it will be reflected in this web app’s GUI within 5 seconds.

Building and running the application

Download and install git if you don’t have it on your system already.

Download and install yarn if you don’t have it on your system already.

Get the code for the application:

git clone https://github.com/LarsBergqvist/philips_hue_react_app.git

Check (via your router) what IP-address your Philips Hue bridge has been assigned on your local network, e.g. 192.168.1.232.

Create a new user-token with the CLIP API Debugger on the bridge, see https://developers.meethue.com/documentation/getting-started

Modify philips_hue_react_app/src/config.json with the IP-address and the new user-token. For example:

{
  "apiUrl": "http://192.168.1.232",
  "username":"5Bg8MEr2C3GKldw4SYsAEp8BLiJ6GK41dZnUABA5"
}

Install the dependencies, build and start the app:

cd philips_hue_react_app
yarn install
yarn start

The application should start on localhost with port 3000:

philips_react_app_wide

The code

The main part of the application is a React component called LightsView. It fetches Philips Hue state data before the component is mounted (and also every 5 seconds via a timer).


import React, { Component } from 'react';
import LightItem from './LightItem';
import Config from './config';
class LightsView extends Component {
constructor(props) {
super(props);
this.requestFailed = false;
this.data = null;
this.onToggleLight = this.onToggleLight.bind(this);
this.fetchData = this.fetchData.bind(this);
this.onBrightnessChanged = this.onBrightnessChanged.bind(this);
setInterval(this.fetchData,5000);
}
componentWillMount() {
this.fetchData();
}
getUrlWithUsername() {
return Config.apiUrl + '/api/' + Config.username + '/lights';
}
fetchData() {
let url = this.getUrlWithUsername();
fetch(url)
.then(response => {
if (!response.ok) {
throw Error('Network request failed');
}
return response;
})
.then(d => d.json())
.then(d => {
this.data = d;
this.requestFailed = false;
this.setState({newData:new Date()});
}, () => {
this.requestFailed = true;
this.setState({newData:new Date()});
})
}
changeState(id, bodyData) {
let url = this.getUrlWithUsername() + '/' + id + '/state';
fetch(url, { method: 'PUT', body: bodyData })
.then(response => {
if (!response.ok) {
throw Error('Network request failed');
}
return response;
})
.then(d => d.json())
.then(d => {
this.requestFailed = false;
this.fetchData();
}, () => {
this.requestFailed = true;
})
}
onToggleLight(id, isOn) {
let bodyData = '{"on":' + !isOn + '}';
this.changeState(id, bodyData);
}
onBrightnessChanged(id, newValue) {
let bodyData = '{"bri":' + newValue + '}';
this.changeState(id, bodyData);
}
render() {
if (this.requestFailed) {
let url = this.getUrlWithUsername();
return <p className='warning'>Could not fetch from {url}</p>
}
if (!this.data) {
return <p>Loading…</p>;
}
if (this.data[0] !== undefined) {
return <p className='warning'>{this.data[0].error.description}</p>;
}
let data = this.data;
let lightItems = [];
let toggleHandler = this.onToggleLight;
let brightnessHandler = this.onBrightnessChanged;
Object.keys(data).forEach(function(id,index) {
let item = data[id];
let light = <LightItem key={id} id={id} name={data[id].name}
isOn={item.state.on} bri={item.state.bri}
reachable={item.state.reachable}
onToggleLight={toggleHandler}
onBrightnessChanged={brightnessHandler}/>
lightItems.push(light);
});
return (
<div align='center' style={{maxWidth:950,margin: '20px auto 0'}}>
{lightItems}
</div>
);
}
}
export default LightsView;

view raw

LightsView.js

hosted with ❤ by GitHub

The LightsView component renders an array of LightItem components (one item for each Philips Hue light found). Callback methods defined in LightsView are sent as props arguments for each LightItem. When a light is toggled on/off in the GUI or when a brightness slider changes its value, this callback is called on the LightsView component and the LightsView makes a PUT request with new states for the corresponding Hue light to the Hue bridge.

A LightItem component only does rendering and routes callbacks to its parent component (via props-properties sent to the LightItem from its parent), so it can be written as one single render method:


import React from 'react';
import Toggle from 'material-ui/Toggle';
import Slider from 'material-ui/Slider';
const LightItem = (props) => (
<div className='items'>
<div className='item toggle'>
<Toggle
toggled={props.isOn}
label={props.name}
onToggle={() => props.onToggleLight(props.id,props.isOn)}
disabled={!props.reachable}
/>
{props.reachable ? '' : <div className='warning'>not reachable</div>}
</div>
{props.reachable ?
<div className='item slider'>
<Slider
min={0}
max={255}
step={1}
value={props.bri}
onChange={(event,newValue) => props.onBrightnessChanged(props.id,newValue)}
/>
</div> : '' }
</div>
);
export default LightItem;

view raw

LightItem.js

hosted with ❤ by GitHub

If a Hue light returns reachable=false as state, it means that the light can not be contacted by the Hue bridge (this happens if the light bulb has been physically turned off e.g.). In that case, the LightItem renders a text ‘not reachable’.

Going further

Feel free to build upon this initial sketch. Perhaps adding a view for displaying and controlling Hue groups? If you have Philips Hue lights with color (which I don’t), add a GUI for modifying the color of the lights.

https://github.com/LarsBergqvist/philips_hue_react_app

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s