Closets! We all have, and they all need lights. But is it ever convenient to attach little magnet and switches to the doors? How much time do you want to spend with fiddly little adjustments? Does adding little white plastic bumps on the doors make them look more attractive? And is it ever possible to run the stretches of wiring and make it vanish?
The Bluetooth-enabled Infineon DPS310, mounted on the "Sensor Hub Nano" solves these problems. It's fast and precise enough to constantly measure the indoor air pressure. Normally, the pressure only varies a little bit. But when the door is opened, there's an easily detected spike in the air pressure.
The basic steps to writing the automation are to first gather and analyze sample data. This will in turn drive the CPU and network requirements for the final app.
The first step is to collect some data. I wrote a simple C# app to initialize the Sensor Hub and pull out pressure data. A few buttons and controls let me perform A/B tests ("door stays closed" and "door is opened") and write the data to a CSV (coma-separated value) file which I could analyze in Excel.
Here's some typical raw data when the door stays closed. Each line is a single test run containing 16 samples of pressure data. The "mess" of data is instructive: it shows that the overall variation is fairly small.
A great second step in any analysis is to normalize the data. For each data row of 16 samples, determine the amount by which each sample varies from the mean. The data, by the way, is in millibars.
The deviation data is much easier to deal with! The maximum deviation from the mean data is less than 0.015
We can look at a similar graph of data when we open the door. The raw data looks like this:
The normalized "door opened" data is also much easier to understand. Note that a typical reading is less than the average; that's because the pressure goes up significantly during a door open event.
The normalized data shows that the maximum peak when the door is close is a little over 0.01 mb, but the smallest peak when the door is opened is .1 mb. That's an almost 10x difference. A delta like that is easy to automate!
We also learn from examining the data that groups of 16 samples easily determine open versus closed. It's good to know that we don't have to look at much data to get a great sense of "door open" versus "door closed"
"Edge" computing is the latest buzz-word in IOT. It really just means that the more analysis we can do close to the sensors, the more reliable and faster the automation will be. In this case, we can see that the math involved in distinguishing an opening door from a non-opening door is small. We can simply keep a circular buffer of the 16 most recent values and constantly recompute the average. Because almost any processor is fast enough to handle this math, our cloud interactions becomes one of monitoring (and possibly control) only. This is great because we don't want the closet light to fail just because we're out of internet range, and we don't want to prevent people from using this automation in remote, non-internet areas.
The basic algorithm is pretty simple. Firstly, there's an edge case to handle for the times when we haven't seen 16 samples worth of data. The edge case happens very infrequently when the system reboots. We get 16 samples within a second, and so handling this case really just means that we have to first fill up our buffer and then start the real work.
Now we get some choices. The simplest data structure is to just have an circular buffer with an index pointer, plus the current average value. Each time we get a new sample we:
- Compare the sample to the average. This gives us the "door open" signal
- Remove the current value at the index from the average
- Replace the current value at the index with the new sample
- Increment the index and wrap around if needed
Two other basic algorithms were tested. The data was run through a fast Fourier transform (FFT) on the grounds that the frequency response of the data should show a strong spike.
Graphing the results of some FFT calculations shows some promise as you can see in this typical graph.
Using an FFT was rejected on two grounds. Firstly, although it's easy to "eyeball" a difference between the opening door even and the door stays closed event, it's much harder to program the distinction. Secondly, performing an FFT is mathematically complex and prevents us from using a low-end processor.
I also used the Azure Machine Learning Studio to take the data and make an automatically generated algorithm to understand the data. Although this was successful, the Azure Machine Learning Studio can only be used on Azure. The algorithms can't be moved down to the edge device. We obviously can't have each closet light constantly beaming data up to Azure for analysis every second!
There's a slight improvement we can make. Instead of keeping track of the average, we can keep track of the sum, and as an integer, not a float. We need to keep 3 digits of precision, so the values look like "1002540". The new set of steps is then
- Convert each new sample to an integer by multiplying by 1000
- Compare the new sample times 16 with the sum of values
- Subtract the old sample in the buffer at the index from the sum
- Add the new sample to the sum
- Replace the old sample value at the index with the new sample
- Increment the index and wrap around to zero as needed
The advantages here is that floating point numbers are notorious for slowly drifting -- we might get in a situation where the average value we're tracking steadily increases or decreases in a way that isn't detectable until the system has been in operation for a long time.
Alternatively, we can recalculate the average each time; that's an alternate way to prevent the number from drifting. A great real-world example the problem of allowing floats to drift over time is the Vancouver Stock exchange. Between January 1982 and November 1983, the stock exchange overall value dropped from 1000 to 524.811. The exchange value was then corrected and the value jumped to 1098.892 -- slightly more than double!
For this project, I used the beLight lamp kit from TI; these are nice and small but bright lamps that with an RGB+White control -- this gives you a good rang of colors to choose from. Any other lamp can, of course, be controled.
When I program my IOT devices, I want a language that has a really simple programming model for handling Bluetooth devices. A language like C# would work except that controlling a device like the DPS310 or the beLight requires lots of code to do simple things. Languages like Python have low-level support for Bluetooth, but have real usability issues. Let's consider a device name, for example. The Python examples I've seen tell you to run a different program to determine the Bluetooth address of your device, which you then hard-code into your Python app.
Being a programmer, my solution was to add Bluetooth capability to my existing BASIC-programmable calculator. In BC BASIC, there's a simple API where the user can pick a device from a list. That same list lets the user rename the device, so the next time they pick it, it can have a nice name instead of an address.
Similarly, I add new Bluetooth devices to the list of known Bluetooth devices constantly. Adding the device also includes updating the extensive documentation so there's less guesswork about exactly how to control any Bluetooth device.
The end result is an always-updated, always-simple IOT building kit! And that's why I made my program in BASIC and not another language.
Detect door opening using pressure changesPlain text
REM REM Turn on a light when the door opens REM CLS GREEN PRINT "PRESSURE READING" REM REM All of the Pressure data. Arrays in REM BASIC start at 1. REM DIM Pressure() PressureIndex = 1 TotalPressureValues = 0 REM REM All of the Light variables. We just drive REM the first beLight the we see. REM allLights = Bluetooth.DevicesName("beLight*") oneLight = allLights Light = oneLight.As("beLight") REM 0=off otherwise is time.AsTimeInSeconds LightTime = 0 REM REM Let the user pick the OPressure Sensor. REM bt = Bluetooth.PickDevicesRfcommName (“IFX_NANOHUB”) REM Functions are altitude, pressure, temperature dps310 = bt.As (“DPS310”, "", “OnPressureChanged”, "") REM REM The FOREVER statement will loop forever., REM FOREVER REM REM The Pressure-change callback. Will update REM the Pressure array (etc), call the function REM that says if the door is opened, and REM will call the light update function. REM FUNCTION OnPressureChanged (dps310, value) Screen.ClearLine (4) PRINT "PRESSURE=",value GLOBAL TotalPressureValues GLOBAL Pressure GLOBAL PressureIndex Pressure[PressureIndex] = value PressureIndex = PressureIndex + 1 IF (PressureIndex > 16) THEN PressureIndex = 1 TotalPressureValues = TotalPressureValues + 1 REM Handle the startup edge case. If there REM aren't enough data points, just REM accept it and don't try to do the REM calculations. IF (TotalPressureValues < 16) THEN RETURN open = CalculateIsDoorOpen (Pressure, value) Screen.ClearLine (5) PRINT "OPEN=", open HandleDoor(open) END FUNCTION CalculateIsDoorOpen(data, latest) mean = data.Mean delta = Math.Abs (mean - latest) retval = 0 IF (delta > 0.05) THEN retval = 1 RETURN retval END REM does one of two actions REM if the light is off, and should be on, turn it on REM if the light is on, and should be off, turn it off. FUNCTION HandleDoor(door) GLOBAL LightTime GLOBAL Light now = DateTime.GetNow() unix = now.AsTotalSeconds delta = unix - LightTime Screen.ClearLine (6) PRINT "Door", door, LightTime, delta IF (door = 1 AND LightTime = 0) LightTime = unix Light.SetColor (0, 0, 255, 50) Screen.ClearLine(7) PRINT "ON" END IF IF ((door = 0 AND LightTime <> 0) AND delta > 5) LightTime = 0 Light.SetColor (0, 0, 0, 0) Screen.ClearLine(7) PRINT "OFF" END IF END
Did you replicate this project? Share it!I made one
Love this project? Think it could be improved? Tell us what you think!