← 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!
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:
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!
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 thelevel
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>
hasProperty<Level>
withValue<x>
greater thanValue<75>
, setThing<HS100>
Property<On>
toValue<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:
- The level property is
readOnly
as the user can’t set the value of the sensor, only read it. - We’re using
tornado.ioloop.PeriodicCallback
to periodically update the level value. The library methodnotify_of_external_update
is called on ourlevel
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.
If light level over 70Lux, turn on downstairs lights
That’s it. Done!
Here are the logs for the last 24 hours
Light levels and fairy lights status
I, Pete Taylour, shall be writing up some projects.
I'm a full-stack software dev from London Brighton.