Building a Smart Air Pressure Sensor with Espruino and Angular
Why did I need such air pressure sensor? A few weeks ago, Ariella Eliassaf, Avi Aminov and I took on a challenge and tried to build a robot that plays the trumpet in a weekend makers hackathon called Geekcon. We had some initial success with getting sound out of the trumpet, but had some difficulties obtaining repeatable results. 🎺
This is not our first time in Geekcon — last year we built the Chrome T-Rex game in Real Life, and later were invited to display it in the Chrome Dev Summit. Thus, I was really pumped when I found that the Chrome Dev Summit invited us to present our project again this year: add a MIDI interface to the robot and let the attendees control the robot through the Web MIDI API. 🎶
In a nutshell, the Web MIDI API allows you to control musical instruments connected to your device. It works on both desktop and Android — meaning, I could plug any MIDI device through USB and control it directly from the web. You can receive input from a piano keyboard, or send notes to different kind of synthesizers. You can see it in action in this nice demo by Sam Dutton:
So we were invited to present the project, great news! However, this also means we need to get the project to work and actually play the trumpet and a reliable and reproducible manner. And this is also where the Air Pressure sensor comes into play: in order to get reproducible results, we need to be able to measure the parameters that affect the sound, including the air pressure that we have in the system.
Measuring Air Pressure
First, let me describe how our robot works: We have an air pump that is connected to a chamber. The top of the chamber has a small hole drilled in it, and two water-filled latex lips are pressed to it. We press these latex lips against the mouthpiece of the trumpet, and as they pressured air flows through them , it creates a vibration that produces the sound. 🔊
Our goal is to measure the air pressure inside the chamber. The chamber has to be sealed (otherwise, we won’t be able to build the air pressure inside), so we looked for a wireless sensor solution. Fortunately, as you will shortly see, it was pretty easy to build one from scratch, just using off the shelf component.
After a quick online search, I found a sensor that seemed accurate enough and was available as an easy-to-use breakout board:
SparkFun Pressure Sensor Breakout - MS5803-14BA - SEN-12909 - SparkFun Electronics
This is the MS5803-14BA Pressure Sensor Breakout, a high resolution pressure sensor with both an I2C and SPI interface…
While not cheap, it seemed to be accurate enough for our needs, and had an standard I²C interface, meaning it should be simple to connect. Also, being sold by SparkFun assured that it’d come with working demo code and a detailed hookup guide.
After sorting out the sensor, we needed some way to capture the data from the sensor and to send it wirelessly, so we could plot a graph that shows the air pressure changes in real time. I had an old ng-beacon board lying around, so I quickly put together a prototype:
As you can see, connecting it to the sensor was very simple: I²C only requires four wires, two for data and two for power.
The code I ended up with was pretty short — apart from some boilerplate code to set up the Bluetooth radio, this was what the initialization code looked like:
Basically, resetting the sensors, and then asking the Bluetooth Radio to call the
startSensor method whenever someone connects to our device, and the
stopSensor method when they disconnect. The reason for that is to save power — we don’t need to run the measurements if nobody is looking at them!
Next, the logic for starting/stopping the sensor:
Basically, we just set a timer that will fire every 50ms and call the
readSensor function. So now we are ready to reveal where the real magic happens:
We call the
sensor.read() method, asking for 12 bits precision (4096 = 2¹²), the highest precision supported by the sensor. This sends a command to the sensor (through I²C), then returns a promise that resolves with the response from the sensor. The response contains the measured air pressure and we also the current air temperature as a bonus.
We convert these values to integers and wrap them in byte arrays (2 bytes for the temperature, 4 bytes for the air pressure), and then ask the Bluetooth radio to send them down the wire by calling the
updateServices method. If you are not familiar with Bluetooth Low Energy, you can read my introduction to it. In a nutshell, Services are containers for Charactersitics. Characteristics are simply variables that can send values between devices.
We store the measured temperature in Characteristic
0x2a63 , and the air pressure in Characteristic
0x2a6d, both inside Service
0x181a. These numbers may look arbitrary by they are actually defined by the Bluetooth SIG as Environmental Sensing Service. In other words, they are part of the standard.
The low power consumption of this chip, and the fact that I’m not reading from the sensor most of the time (when no one is connected) allows it to happily run for weeks in a single CR2032 battery, meaning I can just drop it inside the air chamber and connect to it as needed.
Visualizing the Data
The hardware part is done, and now we just need to connect to it and visualize the data. For testing, I used the nRF Connect app, an Android app that is very useful when working with Bluetooth Low Energy devices. When we implemented the code for the hardware above, we followed the standards, so nRF Connect can actually decode and display the pressure and temperature values:
The reported air pressure is 100702.4 Pascal or about 100.7 Kilo-Pascal (kPA), which is roughly equivalent to the the barometric pressure (1 bar = 100kPa).
At this point, we can already measure the air pressure and send it to another device over the air. The next step would be to plot it in a nice graph, so we need to create our own application. We will use Web Bluetooth, an API that allows us to communicate with Bluetooth Low Energy devices directly from the web. It is available in Chrome on most of the platforms (Windows, Mac, Linux, Android, and Chrome OS), which means we can build the entire solution with just web technologies.
I then quickly created a new Angular app, added Angular Material (nowadays it’s as simple as typing
ng add @angular/material. I love the times we live in!), and went on to implementing the logic for connecting and talking to the sensor:
I won’t go into much detail explaining the code, but here is a quick summary: We start by scanning for the sensor device (lines 19–21), connecting to it (line 22), then requesting the
0x181a Service (line 23) and the two Characteristics we defined in it (lines 24–25). Then we listen for new values on these two Characteristics (lines 26–29). Whenever a new value comes in, the code extracts the value from characteristic and converts it to the appropriate units, Celcius and Pascal (lines 31–32), and then it emits an object containing these values (lines 33–36).
The above code abstracts away all the communication with the hardware — our application simply has to call the
connect() method, and then subscribe to the
readings$ Observable, and it starts receiving values from the sensor. All the Web Bluetooth API specifics are hidden in the above code!
Now we just need to put the final piece of the puzzle: plotting these values in a graph. We will use the Smoothie library for that, the same one I used when I wanted to visualize my brain waves.
We create an Angular component with just a Canvas in it:
and the code that gets that Observable with the pressure data and adds every incoming value to the graph:
The code inside
ngAfterViewInit connects the chart to our canvas, and then inside
ngOnInit we create a new Time Series, subscribe the the pressure data (which gets the objects we emitted in
PressureSensorService, and adds every incoming value to the time series.
You can find the final code in the GitHub repo.
We did it!
Isn’t this amazing? I love the web! 😍