← All Posts

Making a Mozilla Webthing — A Lux Sensor — Part 3

August 07, 2019

This post follows on from Part 2

Code for this post: https://github.com/PTaylour/tsl2561-webthing

It works!

video of the lights turing on

You can see the fairy lights top left

Reading from the sensor

First, some python to read from the sensor. Using https://github.com/adafruit/Adafruit_TSL2561 to talk to the sensor:

# connect to the pi I2C interface
i2c = busio.I2C(board.SCL, board.SDA)
# connect to the sensor using the adafruit TSL2561 library
sensor = adafruit_tsl2561.TSL2561(i2c)

# this bit is pretty straight forward
def get_sensor_value:
    return sensor.lux or 0

That’s the easy bit done. Now, how do we make this in to an IoT device?

The Mozilla WebThings Gateway

I already have a Webthings Gateway running on a raspberry pi. (It’s sitting on top of the fridge).

Like Google Home and the like, the Mozilla Gateway let’s me control and monitor IoT devices in the house. Unlike Google Home-and-the-like, it’s open source and doesn’t require a connection to the public internet.

The Gateway automatically discovers devices that expose the Web Thing API.

There are two ways to do this:

screenshot from the mozilla website

Finding this out via the website: https://iot.mozilla.org/

Either by writing:

  • a Web Thing

or

  • an Adapter add-on

Adapter add-on

If you want to adapt an existing IoT device to work with the Gateway you’ll need write or use an Adaptor.

Adaptors are implemented as Gateway Addons so you install them wherever you’ve deployed your gateway.

The Gateway comes with a load of pre-installed, including one for the TPLink Devices including the HS100 plugs that all the side lights in our living room are plugged into.

That means, out of the box, I can use the Gateway UI to set up some rules to control the lights.

Like the gateway itself, addons are automatically kept up to date. Given this is a fast moving project in its early stages of development this is great! Bug fixes flowing in like magic!

gateway

The two on the left are HS100 devices. Spoiler, you can see the Light Sensor on the right.

What we need is a way to get the TSL2561 sensor to show up on the Gateway as an IoT device. Then, we can set up a rule along the lines of:

“if light level over 75Lux, turn on downstairs lights”

We could write a gateway-addon to do this, but there is an easier way: create a WebThing.

More info on implementing Adaptors: https://github.com/mozilla-iot/wiki/wiki/Adapter-API

Writing a WebThing

A WebThing is a server that exposes the Web Thing API for a device. The Gateway knows all about this API and uses it to detect a device’s capabilities and monitor and control it over the web.

Mozilla provide a bunch of WebThing libraries that provide the building blocks we’ll need:

  • a Thing for modelling an IoT device. We’ll need one of these for the sensor to show up in the gateway
  • a Property for modelling a property of an IoT devices. In our case this is the level of light detected by the sensor.
  • Value the actual value of a property

When we create a rule:

“if light level over 75Lux, turn on downstairs lights”

We’re actually saying is:

“if the Thing<LuxSensor> has Property<Level> with Value<x> greater than Value<75>, set Thing<HS100> Property<On> to Value<true>.”

The Thing: LuxSensor(Thing)

Full code for this post: https://github.com/PTaylour/tsl2561-webthing

Here is our Thing class. Two things of note that are specific to this device:

  1. The level property is readOnly as the user can’t set the value of the sensor, only read it.
  2. We’re using tornado.ioloop.PeriodicCallback to periodically update the level value. The library method notify_of_external_update is called on our level property to tell the gateway about any changes to it’s value.
class LuxSensor(Thing):
    """A lux sensor which updates its measurement every few seconds."""

    def __init__(self, poll_delay):
        Thing.__init__(
            self,
            "urn:dev:ops:tsl2561-lux-sensor",
            "TSL2561 Lux Sensor",
            ["MultiLevelSensor"],
            "A web connected light/lux sensor",
        )

        self.level = Value(0.0)
        self.add_property(
            Property(
                self,
                "level",
                self.level,
                metadata={
                    "@type": "LevelProperty",
                    "title": "Lux",
                    "type": "number",
                    "description": "The current light in lux",
                    "minimum": 0,
                    "maximum": 200,
                    "unit": "lux",
                    "readOnly": True,
                },
            )
        )

        self.timer = tornado.ioloop.PeriodicCallback(self.update_level, poll_delay)
        self.timer.start()

    def update_level(self):
        new_level = self.read_from_i2c()
        logging.debug("setting new light level: %s lux", new_level)
        self.level.notify_of_external_update(new_level)

    def cancel_update_level_task(self):
        self.timer.stop()

    @staticmethod
    def read_from_i2c():
        """Read from the raspberry pi input"""
        return sensor.lux or 0

Running a Server

The WebThings libraries also include a server implementation.

def start_server(port=8888, poll_delay=3.0):
    sensor = LuxSensor(poll_delay=poll_delay)
    server = WebThingServer(SingleThing(sensor), port=port)
    server.start()

On the pi I have systemd keeping this server alive on port 8888. See setup.py in the repo for more details.

Making a rule on the Gateway

As the server is running on the same pi as the gateway, my TSL2561 Lux Sensor Thing is discovered automatically.

Now, we just need to make a rule that uses it.

making a rule

If light level over 70Lux, turn on downstairs lights

That’s it. Done!

Here are the logs for the last 24 hours

gateway logs

Light levels and fairy lights status


© 2023