Bathroom occupancy monitor

Don't you hate it when you get up from your desk at work to go to the bathroom, only to find out that all the stalls are occupied? I did, so I made a website that shows the current status of each stall. It normally looks something like this:

To make this possible, I installed a 433 MHz door/window sensor on each stall door. Somewhere nearby I put an ESP8266 module with a 433 MHz receiver board. Each time a stall door is locked or unlocked, the module gets a signal from the sensor and passes it on to a Firebase cloud function that saves the current state and timestamp in a database. Finally there's a website that reads the database and displays the current status for everyone to see.

Even though the sensors I used are normally meant to detect when a door (or window) is opened, what I really wanted to detect in this case is whether the doors are locked. I achieved that using a zip tie and a neodymium magnet attached to the door lock:

What's nice about these sensors is that they're cheap, require no modification, and will run on a single AAA battery for many months. One thing to keep in mind is that it's important that the sensors send a signal every time they detect a state change in any direction, not just when they detect that the door/window has just been opened. Not all of the sensors on the market do that and sometimes it's not clear from the description.

The ESP8266 board I used was a Wemos D1 mini clone, here's what it looks like with the 433 MHz receiver board:

The rest is software, the ESP8266 is running an Arduino sketch and the web part uses Firebase. You can see the whole thing here. The cloud function part isn't strictly necessary, the ESP8266 could write to the Firebase database directly, but it was much easier for me to do it this way.

Right now I'm only using the website to know if the bathroom is occupied, but it might be interesting to gather some statistics, such as the average time people spend in the bathroom or how likely it is to be occupied depending on time of day.


Darkroom enlarger timer

I have recently started making traditional prints of my analog photos and it is a lot of fun in itself, but naturally I am also treating it as an excuse to play with some electronics. I have previously described a simple timer I made for measuring how much time each print spends in the developer and fixer trays. Today I'd like to present my solution for the more important type of darkroom timer: the one that controls the exposure time on the enlarger. Here's what my setup looks like:

The enlarger lamp is connected through a reprogrammed Sonoff S20 wifi smart plug. It runs a simple HTTP server written in MicroPython. It responds to three commands: "on", "off" and "expose". The last one takes a duration in milliseconds and switches the lamp on for that time.

The second component is a smartphone app written in Flutter, so it should in theory run on both Android an iOS, but I have only tested on Android. It lets the user specify the exposure time, either directly or using a simple test strip mode. In test strip mode, exposures are made in such a way that if you cover a larger part of the paper before each exposure, the resulting exposure times of each part will be increasing in configured fractions of a stop.

Finally there's a footswitch, which is just a pedal converted into a USB keyboard using a Digispark (which is an ATtiny85 board in the shape of a USB plug that can be programmed with Arduino). Whenever the pedal is pressed, the Digispark sends an "Enter" keystroke and the smartphone app reacts as if the "START" button was tapped and starts the next exposure.

The code for all three components is available here. To run the Python code on the smart plug, first it needs to be flashed with MicroPython firmware. The smartphone app is pretty basic right now and doesn't have fancy features like dry down compensation, saving dodge/burn programs or any split-grade automation. Another useful feature would be to have the safelight connected through another smart plug and turn it off when the enlarger lamp is turned on for focusing.

Oh, and even though the app's interface is all red, it's still probably not safe for photographic paper, so it's best to cover the phone's screen when the paper is out.


Darkroom tray timer

Two kinds of timers are used in a darkroom when making prints. One for controlling the exposure time on the enlarger and one for measuring the time the print spends in the developer, stop bath and fixer trays. Arguably the second kind is not as critical as the first, as any clock that displays seconds can be used for that purpose. Nevertheless I've made such a timer and I'm using it regularly when making prints. Here's what it looks like:

As you can see it is operated with a foot switch and has no display. Instead it beeps when it's time to move the print to the next tray. Each press of the switch triggers the start of the next timer: first it measures 60 seconds for the developer, then 10 seconds for the stop bath, then 60 seconds for the fixer and finally 120 seconds for the wash (I'm using RC paper). After that it goes back to the first timer. A sequence of short beeps at the start confirms which timer we're currently on.

The case was designed in Fusion 360 and 3D printed in PETG. Inside there's an ATtiny85 chip, a piezo buzzer and a CR2032 battery. The code running on the ATtiny85 can be found here and a schematic of the connections is shown below. When the timer is not active it goes into deep sleep so the battery should hopefully last a long time. One thing to keep in mind when programming the ATtiny85 with Arduino is that not every core supports the tone() function, used to make sound with the buzzer. I'm using this one.

Stay tuned for the next episode in which I show my solution for the enlarger timer.


Bluetooth trackball with all the electronics inside the ball

For a while now I've been fascinated with the idea of making a trackball-type input device in which the ball itself would be the entire device. Unlike a regular trackball, where the ball is just a ball and registering its motion and communicating with the computer is done by external sensors and chips, here everything would live inside the ball. And now I have finally done it. Here's a video:

And here's how I did it. For obvious reasons, the device couldn't use USB, it had to be Bluetooth. For registering the rotation of the ball, a combination of an accelerometer and a gyroscope seemed like a good way of ensuring smooth results. I ended up using the following components:

I designed the ball itself in Fusion 360 and then 3D printed it. It has a diameter of 10 centimeters and consists of three parts, two halves of the ball and an inside part that holds the electronics and the battery. The three parts screw together to form the ball. I also made a stand on which the ball can be rolled, but that's just to make it easier to use as a trackball, the stand is optional and the device works when held in the air as well.

Here's what it looks like inside (the MPU-6050 is under the battery):

For the software part, I was pleasantly surprised when I found out that the MPU-6050 chip can do the sensor fusion magic (combining the outputs of the accelerometer and the gyro) for us and just give us the 3D orientation in the form of a quaternion. What's left then for the Arduino to do is to read the current orientation, calculate the difference between the previous orientation and the current one, translate it to X and Y mouse cursor offsets and send the appropriate commands to the Bluetooth chip that will pass them on to the computer.

Here's the Arduino code.

You may wonder how the ball knows which direction the cursor should go when it's rotated in a certain direction. The answer is of course that it doesn't, so the initial mapping of the directions might not be correct. But it does know which way is up (because it has an accelerometer), so the directions can be calibrated by rotating the ball around the vertical axis.

While I personally consider this project a great success, there's always room for improvement. For example, the trackball would be a little more practical if it had the ability to click, not just to move the cursor. I explored the idea of detecting taps on the ball using the accelerometer, but found it hard to eliminate false positives. This area needs some additional work.

An obvious shortcoming of the current solution is that to switch the device on or off, I have to unscrew the two halves and connect or disconnect the battery. Ideally there would be some way of doing that without disassembling the ball, a magnetic switch of some sort or maybe the device could always be on and just go to sleep when it's not used for a while (like regular wireless mice do).

On a related note it would be really cool if we also didn't have to open the ball to change the battery. Seems like a good use case for wireless charging.

If I ever do a next version of this, I will probably try to use a different Bluetooth board, one that's not discontinued (and perhaps can do Bluetooth LE).


Time tracking wifi cube

There are many apps and websites for tracking time spent on projects and I'm sure they work well. But I like physical objects so I made a gadget - a cube that you can flip to a different face to indicate that you're now working on some project or task. Each face corresponds to a different project and one of the faces means you don't want to track time anymore. The idea is of course not original, there are similar commercial products available, but hey, this one's mine.

I had two design goals: first, I wanted the gadget to be standalone and not need a companion app on a phone or a computer. So it needs to have wifi and talk directly to the Internet. Second, I wanted the cube to last a long time on battery, at least a few months. So I had to learn a bit about how deep sleep modes work on microcontrollers.

Here's a video that shows the cube in action. (The laptop is just there to show that time reporting works, as I said the cube talks to the Internet directly.)

These are the parts that I used to make this happen:

  • ESP8266 board (Wemos D1 mini) for wifi
  • MPU-6050 accelerometer to check which side the cube is flipped to
  • SW-18010P vibration sensor for detecting motion
  • ATtiny85 chip for watching the vibration sensor and waking up the wifi chip
  • MCP1826 voltage regulator with a shutdown pin that made it easy to switch the wifi chip on and off

The idea here is that most of the time the ESP8266 chip is off and the ATtiny85 chip is in deep sleep, using almost no power from the battery. It is set to wake up when the state of one of its pins changes - a pin connected to the vibration sensor. Then the ATtiny85 chip enables the voltage regulator, waking up the ESP8266 chip, which reads the orientation of the cube from the accelerometer and makes a HTTP request to report it to a time tracking service. When it's done it signals the ATtiny85 chip on another pin. The ATtiny85 then disables the voltage regulator and goes back to sleep, waiting for another interrupt on the vibration sensor pin. An LED indicates when the chips are awake.

Here's a schematic of the connections. You may notice an extra button, I will explain its purpose in a moment.

And here's the code: the part running on the ATtiny85 is done in Arduino and the ESP8266 part in MicroPython.

I used Toggl for tracking time, but any service with a reasonable API could be used, the cube just makes HTTP requests.

Since the cube is woken up by motion, we have to consider what happens when you put it in a bag and take it with you. We don't want it to wake up constantly and deplete the battery. So here's how it works. The ESP8266 chip reads the accelerometer and waits for the readings to settle before making the HTTP request and signaling the ATtiny85 to go to sleep. So if the cube keeps moving, that signal never comes. And the ATtiny85 chip has a 30 second timeout. If it doesn't receive the signal before the timer runs out, it switches to "travel mode". Which means it goes back to sleep, but now it ignores the vibration sensor - it will only be woken up when the button is pressed. When it's pressed, it goes back to normal mode. That way we still get the long battery life, but we can take the cube with us.

Currently pressing the button is not very convenient as the cube needs to be opened up to access it.

Another improvement that comes to mind is making it work with multiple wifi networks. Currently it only remembers one, which makes it hard to use the same cube at, say, home and work.


Google Assistant support for IKEA TRÅDFRI lights

Update (2018-04-21): IKEA has rolled out official support for Google Assistant, so if you just want your lights to work with Google Home, you don't need any of this. If you're interested in the technical side, you may still find this post useful.

Earlier this year I got some IKEA smart lights from their TRÅDFRI line. They can be controlled by a remote and also through a smartphone app, if you have IKEA's gateway. Around the same time I also got a Google Home and naturally I wanted them all to work together. IKEA is planning to add official support for Google Assistant very soon, but in the meantime I rolled out my own. So while all this may be obsolete soon, I still wanted to publish it in the hope that it might be useful for someone (perhaps for adding Assistant support to some other smart lighting system without official support). Below I try to provide a step-by-step description of how I got it to work, but if you just want to see the code, here it is.

Let's get started. We're going to need a computer on the same network as the IKEA gateway (or it must be able to reach it though some clever routing). I used a Raspberry Pi, but any computer running Linux will do. This computer needs to have its 443 port accessible from the Internet, so if it's behind a router, you need to configure port forwarding. We're also going to need a domain name that will point to our Linux box and a proper SSL/TLS certificate for that domain. I'm not going to go into details about these, but you can easily get a certificate for free at Let's Encrypt. Below I'm using home.example.com as the domain name, obviously you should change it to your actual domain wherever it appears.

The general architecture of the solution is as follows. We're going to create a Smart Home App on Actions on Google and deploy it in testing mode. Then when we talk to the Google Assistant (on Google Home or a phone or a smartwatch), Google's cloud magic will make a call to our server, asking what lights are available or telling it to turn a certain light on or off. Then our app will talk to the IKEA gateway, which in turn talks to the light bulbs themselves. Using the Actions SDK instead of IFTTT means we will get to use more natural language commands without defining each phrase manually.

Google's documentation likes to give all the examples in node.js, but the app can by anything as long as it speaks the proper API over HTTP. I wrote mine in Python using Django and deployed it using nginx and uwsgi, but any other way of deploying a Django app would also work.

Let's get the code (below I assume we're running as the pi user, change to your actual user and group if they're different):
sudo mkdir /var/www/home.example.com
sudo chown pi:pi /var/www/home.example.com
cd /var/www/home.example.com/
git clone https://github.com/jfedor2/ikea-tradfri-google-assistant.git .
One more thing we're going to need is the coap-client tool, compiled with DTLS (encryption) support. It's what we're going to use to talk to the IKEA gateway. I'm going to borrow the instructions for compiling it from here:
sudo apt-get install build-essential autoconf automake libtool
git clone --recursive https://github.com/obgm/libcoap.git
cd libcoap
git checkout dtls
git submodule update --init --recursive
./configure --disable-documentation --disable-shared
sudo make install
Once we have the coap-client tool installed, there's a one-time step that we need to perform to set up an identity that we'll use to authenticate to the IKEA gateway (in other words, a login and password). For that we will need to know the gateway's IP address on the local network and the security code that's printed on the bottom of the gateway. Also we need to come up with an identity (username) that we'll use. This can be anything, e.g. "foobar". With those in hand we run the following command (substituting the appropriate values):
coap-client -m post -u Client_identity -k 'security code' -e '{"9090":"chosen username"}' "coaps://IP address of your IKEA gateway:5684/15011/9063"
The gateway should reply with a pre-shared key that we'll use for authentication. Write that down somewhere.

Let's get back to our app and actually make it work. First, let's create a Python virtual environment for the app so that we can install the necessary modules locally:
cd /var/www/home.example.com/
virtualenv .virtualenv
. .virtualenv/bin/activate
pip install -r requirements.txt
Then we need to edit the Django settings file. You can either edit /var/www/home.example.com/tradfri/settings.py or leave that file unchanged and create a new file, /var/www/home.example.com/tradfri/local_settings.py, with just the settings that need changing locally. (That way it's easier to keep settings.py under version control.) You need to provide the following settings:
SECRET_KEY = 'insert a long string of random characters here that you will keep private'
ALLOWED_HOSTS = ['home.example.com']
COAP_CLIENT = '/usr/local/bin/coap-client' # or wherever you installed it
IKEA_IDENTITY = 'the username we chose above'
IKEA_PRE_SHARED_KEY = 'the pre-shared key that the gateway provided'
GATEWAY_ADDRESS = 'IP address of your IKEA gateway'
Depending on your local network configuration, it may also be useful to add the local IP address of your Linux computer to ALLOWED_HOSTS (if you're using port forwarding on the router, accessing the server through the proper domain name might not work from inside the network):
ALLOWED_HOSTS = ['home.example.com', '192.168.0.x']
Then, create the necessary tables in the database (run the following commands in the Python virtual environment created above - run the activate command before if you haven't already in this shell):
./manage.py migrate
And let's create a user that we will use for OAuth2 account linking:
./manage.py createsuperuser
Choose whatever username and password you want and remember them.

In the extras directory, I provided example configuration files for nginx and uwsgi, home.example.com.conf and home.example.com.ini. Edit them with your specific settings and put them in /etc/nginx/sites-enabled/ and /etc/uwsgi/apps-enabled/, respectively.

After updating the configuration files, restart uwsgi and nginx (install them first if necessary):
sudo apt-get install nginx uwsgi-plugin-python
sudo service uwsgi restart
sudo service nginx restart
Whenever you change something in the Django app, you need to restart uwsgi.

In the extras directory there's also an actions.json file, that we will use to tell Google where to look for our app. But first we need to create a project for it.

Go to the Actions on Google console. Click "Add/import project", choose a name and click "create project". In the "Add actions to your app" section, click on "Actions SDK". It will give you an example command that looks something like this:
gactions update --action_package PACKAGE_NAME --project smartlights-abc12
smartlights-abc12 is your project ID, which you will need in a moment.

Update the /var/www/home.example.com/extras/action.json file with your actual domain name, download the gactions command-line tool and run it, substituting the actual project ID of course (you may have to provide a path to the tool if it's not in your PATH):
gactions test --action_package /var/www/home.example.com/extras/action.json --project smartlights-abc12
This will put your app in testing mode and make it accessible in the Google Home app on your phone (you have to be using the same Google account). Google says an app only works in testing mode for 3 days, but I found that in reality it works indefinitely.

In the Google Actions console you probably also need to provide some text and graphics for your app. It doesn't matter what you put there (no-one else is going to see it).

Now all that is left is to set up OAuth2 for account linking. We need to configure it on both our app and the Actions on Google console. Go to https://home.example.com/oauth2/applications/ or http://192.168.0.x/oauth2/applications/ (substituting the correct address of course). Log in using the user and password you created with createsuperuser. Click "New Application". Put some name in (I used "google"), note the client ID and client secret (you're going to need them in a moment), choose "client type": "confidential", "authorization grant type": "authorization code" and put https://oauth-redirect.googleusercontent.com/r/smartlights-abc12 in "redirect uris" (substituting your project ID from above). Click "Save". Now go to the Actions on Google console and in your project go to "Account linking". Choose "Authorization code" under "Grant type" as before. Under "Client information", paste the client ID and client secret that you saw in our app's web interface a moment ago. Then put https://home.example.com/oauth2/authorize/ under "Authorization URL" and https://home.example.com/oauth2/token/ under "Token URL". Click "save".

We're almost done. Open the Google Home app on your phone. Open the hamburger menu and tap "Home control". Tap the plus icon in the bottom right corner. This should give you a long list of devices that are officially supported by the Google Assistant, but also our humble app should be there on the very top, with its name prefixed with "[test]". Tapping it should take you to a login form on our app. Login with the user and password you created with createsuperuser a few minutes ago and on the next screen confirm that you want to give Google access. (You may have to disable wifi on your phone and use the cellular network connection for this step if https://home.example.com/ doesn't work from inside your local network.)

Boom, that's it. You should get a list of your IKEA lights and be able to control them with Google Assistant.

Update (2017-12-04): With version 1.2.42 of the IKEA gateway firmware, the authentication scheme changed. The code and this post have been updated accordingly.


External notification light for your phone

Among other interesting things, Android 4.3 introduced a proper way of accessing notifications from an app. I used this API to make an external notification light for my phone:

It uses Adafruit's Trinket, a Bluetooth serial board from dx.com to talk to the phone and it's powered via USB. Here's a diagram of the connections:

The Arduino sketch that's running on the Trinket is very simple, it listens on the serial line that's connected to the Bluetooth board and turns the LED on and off depending on what character is received. You can see the sketch here. On Android side, the code is also pretty simple, there's an intent filter in the manifest to register a listener that gets called whenever a notification is posted or dismissed. In the listener we check what notifications are active and if they requested the notification light to be turned on (your phone might not even have a notification light, but the information is still there). Then we connect to the Trinket via Bluetooth and tell it to turn the LED on or off. You can see the code here. There is no UI and the Bluetooth address is hardwired.

There is no special permission in the manifest to let the app access notifications, instead you grant access via a checkbox in the security section of your phone's settings:

As usual, there's room for improvement. For example if the phone fails to connect to the device via Bluetooth (because it's out of range or powered off), it should probably try again in some time. Also, we could have an RGB LED and send color and timing information, instead of just on/off state, to better replicate the behavior of the notification light.