Building a Smart Air Pressure Sensor with Espruino and Angular

How to quickly develop custom IoT solutions using Web Technologies

In this post I am going to show you how I created a simple IoT device using just web technologies: JavaScript, Web Bluetooth and Angular. We will build an Air Pressure Sensor that can record the air pressure in display it in a graph in real time.

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. 🔊

Quick illustration of the system, not to scale

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.

The Sensor

After a quick online search, I found a sensor that seemed accurate enough and was available as an easy-to-use breakout board:

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.

The Hardware

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:

DBeacon on the left, sensor on the right

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 chip on this board has a Bluetooth radio built into it, and it also runs Espruino. Espruino is an small JavaScript engine that can run on embedded chips, which means I could program this chip in JavaScript, just like I did for the Chrome T-Rex game last year.

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 sensor in side the sealed air chamber, next to the air pump

Basically, that’s the gist of how the code that controls the hardware works. You can find the full source code here, including all the Bluetooth setup boilerplate the I skipped. If you want to dive deeper, you can also take a look at the source code for the MS5803 driver, that is the JavaScript code that actually speakers with the sensor.

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:

Temperature and Pressure as captured by the sensor. Also one missed call!

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.

This is what the Air Pressure graph looks like in the app when I repeatedly open / close the sealed air pressure

We did it!

We needed a custom solution for measuring the air pressure inside a sealed chamber, and in just few hours we managed to built it end-to-end: the sensor, the CPU with the Bluetooth connection, and a small application to display the results. We used off-the-shelve components (well, except for the ng-beacon, but you can easily replace it with a Pixl.js board that has the same chip in it and also includes an LCD display). We only needed to connect 4 wires, and write not-too-much JavaScript code to get a reliable solution that was tailored to our specific need.

Isn’t this amazing? I love the web! 😍


This is the 7th post in my Postober challenge — writing something new every single day throughout October.

I will tweet whenever I publish a new post, promise! ✍