The Internet of Things Not On The Internet

McDonalds!

There is no shortage of questionable products to connect you and your home to the internet.

Each brings another sketchy WiFi device connected to your network, and another set of microphones, each listening in your home for their special trigger word.

Rather than invite more digital strangers into our home, it feels more appropriate to keep a personal garden of digital things we’ve made.

In today’s post, we’ll build a tiny web application (and iPhone / Android app!) to control Christmas lights that only works when you’re home, and connected to your home network.

Tools of the Local Area Internet

React Native Christmas Lights

The Jetson Nano is my favorite platforms for building localized internet of things projects.

It’s an ARM64 device, with four built in USB ports, and was designed to do machine learning at the edge.

We’ll use it as a server for a Z-Wave USB Stick. Z-Wave is a protocol that exists to do home automation. Importantly for us, it operates separately from your WiFi.

In my case, I’ve hooked it up to 2 outdoor light switches, to turn on and off my Christmas lights.

The light switches come in a waterproof shielding, with a single manual button on them. You can press the button to enable pair mode, and also to manually turn the lights on and off.

Finally, there’s the React Native app running on my iPhone.

Normally, I wouldn’t attempt to build an iPhone app for such a small project.

But Expo has made running code on your phone a great experience. They automatically build and push to your phone via an app and the command line.

Building an API for Your Christmas Lights

The first thing we’ll want to do is check to make sure we’ve got the Z-Wave stick installed and running on our USB drive in the Jetson Nano.

Every time I work with Z-Wave, I forget details of how the Z-Wave network model works. Because of this, I like to open up an IPython session, and start playing around with the network:

import openzwave
from openzwave.option import ZWaveOption
from openzwave.network import ZWaveNetwork

# make sure these Z-wave commands get flushed with a time.sleep
# also, /dev/ttyACM0 is the name of my USB port for the Z-Stick

options = ZWaveOption('/dev/ttyACM0')
options.lock()

network = ZWaveNetwork(options)

# the next two lines start an IPython session in line
import IPython
IPython.embed()

From here, I start to remember how things work, and see that list(network.nodes.keys()) gives me a list of the nodes connected to the Z-Wave network. Because I’m in IPython, I can also tab through each object to see the functions available.

But how do you know what capabilities each Z-Wave node has?

Some Z-Wave devices have multiple switches built into them, each potentially with their own dimmer. To grab switches, we need to do a list(network.nodes[node].get_switches.keys()).

Putting it all together, we can then set state on each node with a set_switch function call:

node_list = list(network.nodes.keys())
for node in node_list:
    switches = list(network.nodes[node].get_switches().keys())
    if switches:
        if change['state'] == 'ON':
            network.nodes[node].set_switch(switches[0], True)
        else:
            network.nodes[node].set_switch(switches[0], False)

Now that I know how to effect change, I can build an API in Flask to serve up changes:

@app.route('/state', methods=['GET', 'POST'])
def get_state():
    if request.method == 'GET':
        node_list = list(network.nodes.keys())
        ON = False
        for node in node_list:
            switches = list(network.nodes[node].get_switches().keys())
            if switches:
                state = network.nodes[node].get_switch_state(switches[0])
                if state:
                    ON = True
                    continue
        if ON:
            return jsonify({'state': 'ON'})

        return jsonify({'state': 'OFF'})
    
    change = request.json

    node_list = list(network.nodes.keys())
    for node in node_list:
        switches = list(network.nodes[node].get_switches().keys())
        if switches:
            if change['state'] == 'ON':
                network.nodes[node].set_switch(switches[0], True)
            else:
                network.nodes[node].set_switch(switches[0], False)
    time.sleep(2)
    return jsonify(change)

I’ve added a time.sleep, because it can take a bit for the Z-Wave network to propagate state. Two seconds is probably too long to sleep for, but hey, it works, and I’m not in a hurry.

Building a React Native App with Expo

Again, building an app is usually too much work for such a small project. But it had been a while since I’d built an app, and I kept hearing about React Native, so I decided to give it a go.

I was surprised to be able to have a built version of my app on my phone in under an hour. There’s a new platform called Expo, which handles all of the heavy lifting usually associated with deploying an iPhone app.

Indeed, because my “app” was so small (literally a single button and a single state of ON or OFF), I really didn’t have much to do beyond the basic, example app:

import React, {useState, useEffect } from 'react';
import { StyleSheet, Text, View, Button, Alert } from 'react-native';

export default function App() {
  const [currentState, setCurrentState] = useState(0);

  const handlePress = e => {
    const newState = (currentState == 'ON') ? 'OFF' : 'ON'
    const data = {'state': newState}

    const requestOptions = {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(data)
    }
    fetch('http://192.168.1.44:8050/state', requestOptions)
      .then(response => response.json())
      .then(res => setCurrentState(res.state))
  }

  useEffect(() => {
    fetch('http://192.168.1.44:8050/state').then(res => res.json()).then(data => {
      setCurrentState(data.state);
    })
  }, []);

  return (
    <View style={styles.container}>
      <Text>Lights are currently {currentState} </Text>
      <Button title="Toggle Lights" onPress={handlePress} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Here, you can see the call to my Flask app’s API, on my local area network. I’ve set a static IP address for my Jetson Nano, and am running a Flask server on a non-standard port, 8050. I did this because I have other Flask apps running on my Nano.

When the app is opened, a GET request is made to the /state resource. It returns a JSON object, with the current state of the Z-Wave switches. If one is on, the network is considered ON.

From here, we can press a single button, to flip the state. This makes a POST request to the /state resource, with the desired new state.

I can build the app from my terminal to the Expo App on my phone via an expo build:ios. Once that’s done, I can open up the Expo app and control my Christmas lights.

Mission Accomplished!

Alright I’m sold, where’s the code?

Pressing the button

All of the code is open source, and lives on Github. I didn’t mention it, but there’s also a view in the Flask app you can visit in the browser to control the lights too. You can see it at the / url.

I encourage you to make your app look better than mine, and share the results.

Happy holidays!

Updated: