rawmeat.org

Code, craft, creating

Barometer build

2021-12-26 11 min read making code

I put together a barometer, built around Adafruit’s LPS25 breakout board, the Blue Pill microcontroller, and a small 16x2 character LCD panel. It was a learning project, and all of it was written in Rust.

The finished project is a tangle of wires on a breadboard, and the error handling is minimal, but I definitely have a barometer with an LCD readout.

You can get the code in my lps25_pressure_sensor_demo github repo.

project photo

The eventual goal is to incorporate the LPS25 breakout board into a larger project I have on the go, but I thought it would be useful to learn to use it on its own - and pick up a few more bits of knowledge along the way. Therefore I built this standalone project. I wanted to:

  • Use the LPS25 pressure sensor.
  • Learn to use the HD44780 LCD panel.
  • Learn how to share I2C devices on a bus with Rust.
  • Be able to re-use the setup to test other sensors.

The larger project this is eventually intended for already contains an e-ink display, LEDs, a CO2 sensor etc. and is starting to become quite complicated.

By doing this in a more self-contained project, I could get to a complete state faster, focus more on exactly what I wanted to learn, and build something that was immediately useful (if one considers an atmospheric pressure reading useful).

I had a lot of fun building this over the holidays.

The components

The microcontroller

I elected to use one of the Blue Pill boards I have in my drawer of components. The Blue Pill is a very affordable STM32 (ARM Cortex M3 by STMicroelectronics) microcontroller board. It’s easy to use, powerful, and cheap enough it can be embedded in the project. The CPU runs at up to 72MHz, it’s got 20KiB of RAM and 64KiB of Flash space.

If I didn’t have these at hand, I might have gone with a Raspberry Pi Pico, or something else based on the RP2040.

The STM32 family of microcontrollers are very well supported in Rust, which is what I wrote this project in. I’ve been trying to get more comfortable in Rust for a while, and this felt like another good project to practice with.

blue pill

You can read more about the Blue Pill on STM32-base’s Blue Pill page.

The programmer/debugger

The Blue Pill does not contain an on-board programmer or debugger. In order to program it, you need an external programmer that talks ARM’s SWD protocol. I use a clone of the ST-Link V2 USB dongle. I’ve also used the ST-Link to provide power.

Previously I’d used OpenOCD to program the Blue Pill, but my recent experience following the Knurling-rs projects showcased a much more ergonomic workflow which I wanted instead. The Knurling projects use the nRF52840 DK board rather than the Blue Pill, but it is equally possible to use the same convenient tooling with the Blue Pill.

By using probe-rs and its probe-run component to flash the program onto the microcontroller and to interact with it we can run programs on the microcontroller with just cargo run, which is really nice! In order for probe-rs to work, one must make sure the ST-Link V2 debugger runs the latest firmware, which I did with the STSW-LINK007 firmware upgrade application. It is a Java app which works well on Linux.

I started the project with Levi Pearson’s blue_pill_base git repo for working in Rust with the Blue Pill. This sets up the probe-rs integration and more and gives a nice and easy way to start the project out without setting up all the boilerplate from scratch.

The sensor

It wouldn’t be much of a barometer without a pressure sensor. The one I have is Adafruit’s LPS25 breakout board. It contains ST’s LPS25H MEMS pressure sensor. It measures absolute ambient pressure between 260-1260 hPa and can be read over I2C.

The lps25hb crate is the Rust driver for this sensor.

The display

The display is another thing I already had in my parts drawer. However, it’s not a part I’d used yet.

It’s an LCD panel displaying 16 characters on each of 2 rows and is addressed over I2C. It has a Hitachi HD44780 LCD controller, and an I2C backpack already soldered on. The device is sold by Handsontec as the I2C Serial Interface 1602 LCD Module. There are many compatible devices like this, sold under different names.

The IC on the backpack is a PCF8574T - an NXP version of a TI port expander chip. This IC lets the backpack control the 16 pins on the LCD panel with just the four needed for I2C (counting power and ground).

It would be interesting to talk to the port expander on its own at some point, but as I use the hd44780-driver crate to control the screen there is no need as it already includes the ability to use the I2C backpack. The driver is easy to use and made displaying the sensor readings very easy. The tricky part was generating the text to display - more on that later.

Wiring up the components

This diagram shows the microcontroller, sensor, display and how they’re connected.

wiring diagram

The programmer

This wiring diagram doesn’t show the ST-Link programmer, instead it shows how power is hooked up in the barometer build.

To replicate this build, first you need to connect up your programmer/debugger to the Blue Pill (see connecting your debugger for a pin-out diagram). For more information on the exact programmer I have used - look at the “ST-LINK/V2 Clone” section.

The I2C devices

Both the pressure sensor and LCD panel communicate over the I2C bus. That bus is made up of two wires, a clock wire and a data wire. The only tricky thing is that the labels on the pins are different on all three devices.

There are two I2C devices on the Blue Pill - I’m using the first of those. The lines of that is made up of SCL1 and SDA1, which are labeled PB6 and PB7 on the PCB. You can see the pinout diagram and chart on microcontrollerslab.com’s Blue Pill pinout - peripherals programming features article.

Adafruit has an article on how to connect the LPS25 on the LPS25 pressure sensor pinouts page on adafruit.com.

For the clock signal, there should be a connection between:

  • PB6 (SCL1) on the Blue Pill.
  • SCK on the LPS25.
  • SCL on the display.

For the I2C data line, there should be a connection between:

  • PB7 (SDA1) on the Blue Pill.
  • SDI on the LPS25.
  • SDA on the display board.

Power

All three devices we’re using are capable of operating at 5V. The Blue Pill can take 5V via USB or the pins marked 5V. The LPS25 is a 3.3V chip, but the Adafruit breakout board includes a voltage regulator that takes between 3 and 5V and safely converts it down. The display will only work fully at 5V - it will partially work at 3.3V, but the displayed text won’t be backlit and will be hard to read.

So you can totally choose to power this entirely at 5V. I chose to feed the pressure sensor 3.3V as the ST Link has a handy 3.3V output in addition to 5V, so it was very simple to do.

Adjust the display

Note that the display backpack has a potentiometer for contrast on the back. If you don’t see any text displayed - try adjusting that potentiometer.

Code

Crates used

A lot of the crates I need were brought in with the base repo that I based my code on. But, I did need a few more. The crates used over those to get the Blue Pill working are:

I2C setup

Easily the most difficult part of this project is actually setting up the microcontroller peripherals, clocks and the I2C bus. Luckily much of that was also done for us in the base repo we cloned.

It’s a fair bit of voodoo. Here’s what it looks like:

// Get access to the core peripherals from the cortex-m crate
let cp = cortex_m::Peripherals::take().unwrap();
// Get access to the device specific peripherals from the peripheral access crate
let dp = pac::Peripherals::take().unwrap();

// Take ownership over the raw flash and rcc devices and convert them into the corresponding
// HAL structs
let mut flash = dp.FLASH.constrain();
let mut rcc = dp.RCC.constrain();

// Freeze the configuration of all the clocks in the system and store the frozen frequencies in
// `clocks`
let clocks = rcc.cfgr.freeze(&mut flash.acr);
let mut delay = delay::Delay::new(cp.SYST, clocks);

// Acquire the GPIOB peripheral
let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);

// Set up I2C
let afio = dp.AFIO.constrain(&mut rcc.apb2);
let mut mapr = afio.mapr;
let mut apb = rcc.apb1;
let scl = gpiob.pb6.into_alternate_open_drain(&mut gpiob.crl);
let sda = gpiob.pb7.into_alternate_open_drain(&mut gpiob.crl);

let mode = i2c::Mode::Standard { frequency: 40.hz() };
let start_timeout_us: u32 = 10000;
let start_retries: u8 = 5;
let addr_timeout_us: u32 = 10000;
let data_timeout_us: u32 = 10000;

let i2c = i2c::BlockingI2c::i2c1(
    dp.I2C1,
    (scl, sda),
    &mut mapr,
    mode,
    clocks,
    &mut apb,
    start_timeout_us,
    start_retries,
    addr_timeout_us,
    data_timeout_us,
);

Shared bus

With rust’s ownership model, as soon as you pass the pins to the i2c1 device, and then the i2c device to the peripheral driver - you can no longer use those pins elsewhere. This is fundamental to how Rust operates, and how the HAL (Hardware Abstraction Layer) is designed.

Since I have both an I2C pressure sensor, and an I2C display, I need a way to re-use the I2C pins, and share the bus. The shared-bus create does exactly that. It manages the ownership and locking required, and lets me give proxy objects to the drivers that need the I2C bus. Using it is very simple - initialize it with the real I2C device, then acquire a proxy with .acquire_i2c().

let i2c_bus = shared_bus::BusManagerSimple::new(i2c);
i2c_bus.acquire_i2c()  // proxy ready to use.

Pressure sensor

The lps25hb crate makes taking pressure readings very simple. First initialize it:

// configure I2C interface for the LPS25HB driver.
let i2c_interface = I2cInterface::init(i2c_bus.acquire_i2c(), I2cAddress::SA0_VCC);
// create a new LPS25 instance with the I2C interface
let mut lps25hb = LPS25HB::new(i2c_interface);

lps25hb.sensor_on(true).unwrap();
// enable Block Data Update
lps25hb.bdu_enable(true).unwrap();
lps25hb.set_datarate(ODR::_1Hz).unwrap();

Then take a reading:

let press = lps25hb.read_pressure().unwrap();

You can also take a temperature reading with the sensor:

let temp = lps25hb.read_temperature().unwrap();

However, you can’t rely on that temperature reading. Right now, it’s 7C below what I expect to see when I read it. I think it’s just intended for coarse grained adjustments internal to the sensor itself. In fact, the data sheet doesn’t mention the temperature sensor as a feature.

LCD panel

The hd44780-driver crate is also very easy to use. First initialize the driver and device and set up some defaults for the display:

let mut lcd = HD44780::new_i2c(i2c_bus.acquire_i2c(), LCD_I2C_ADDRESS, &mut delay).unwrap();
lcd.reset(&mut delay).unwrap();
lcd.clear(&mut delay).unwrap();
lcd.set_display_mode(
    DisplayMode {
        display: Display::On,
        cursor_visibility: Cursor::Visible,
        cursor_blink: CursorBlink::On,
    },
    &mut delay
).unwrap();

Then writing to it is very easy:

lcd.write_str("First line", &mut delay).unwrap();
lcd.set_cursor_pos(40, &mut delay).unwrap();  // Move to 2nd row.
lcd.write_str("Second line", &mut delay).unwrap();

However, there is a wrinkle here. Formatting the string we’d like to write so that we can insert the pressure reading would usually be done with the format!() macro, but it’s not available!

In order to get it, we will need the alloc feature available, which we can get by adding the alloc-cortex-m crate. It needs a fair bit of setup though.

First we need to turn a feature on:

#![feature(alloc_error_handler)]

Import the required bits:

use core::alloc::Layout;
extern crate alloc;
use alloc_cortex_m::CortexMHeap;

Define the global allocator, and the alloc_error_handler.

#[global_allocator]
static ALLOCATOR: CortexMHeap = CortexMHeap::empty();

#[alloc_error_handler]
fn oom(_: Layout) -> ! {
    loop {}
}

Then at the start of the program set up the heap:

let start = cortex_m_rt::heap_start() as usize;
let size = 256; // in bytes
unsafe { ALLOCATOR.init(start, size) }

Finally, we have alloc support, and we can use the format! macro like we normally would:

let hpa_str = alloc::format!("{:.1} hPa", press);

Our writing to the LCD panel therefore actually looks like:

let hpa_str = alloc::format!("{:.1} hPa", press);
lcd.write_str(&hpa_str, &mut delay).unwrap();

The main loop

The code spends its time reading from the pressure sensor, writing to the LCD panel, then pausing for a while. A slightly shortened version looks like:

loop {
    let press = lps25hb.read_pressure().unwrap();
    let hpa_str = alloc::format!("{:.1} hPa", press);

    lcd.clear(&mut delay).unwrap();
    lcd.write_str(&hpa_str, &mut delay).unwrap();

    delay.delay_ms(2_000_u16);
}

You can read the code in my lps25_pressure_sensor_demo repo.

Running the code

The probe-rs tooling makes this so simple - there’s no need to start a separate debugger or anything. Just run it with:

cargo run --bin lps25_barometer --release

That flashes the code onto the device and returns logging to the console. It also shows you tracebacks if your code crashes or is aborted. It’s almost easy to forget the code is running on a separate device.

Summary

The crates to interact with the sensor and display are very simple to use, and using Rust for embedded programming has been a joy - especially with the probe-rs tooling. I will definitely carry on using Rust when writing embedded software like this.

There are some parts that are still a little arcane, like the hardware setup and finding out how to get formatting support, but nothing a little time with Google couldn’t solve.

I look forward to incorporating what I’ve learned here in my larger air quality sensor project, and to try out a few more I2C sensors.

Ear defender headphone build

2019-09-07 10 min read making

I work in a very noisy open plan office, which makes it hard to focus. To solve the problem, and still let me listen to some music, I made these noise blocking headphones. They’re built from heavy-duty Peltor ear defenders, usually worn in industrial settings to block harmful noise levels from damaging your hearing.

I’d like to show you how I made them, so that you can do the same. Here’s a picture of what they look like:

Finished headphones

3M Peltor X5 ear defenders have a 37dB noise reduction and are very effective at lowering the volume of the office hubbub. It doesn’t completely eliminate my louder co-worker’s talking however, it just makes it very faint - and without the usual background noise like the ventilation or computer hum. If I add in just a touch of white noise, or music then talking, or ringing phones no longer interrupt my concentration. It’s also nice not to have to switch between ear defenders and headphones if I want some music.

The office noise is so disruptive, and has been for so long, I’ve probably become quite sensitive to it. You might not feel the need for the white noise, but I find it very soothing, and it really helps with my focus. I tend to use one of Mynoise, which has nice natual scenery sounds or Noisli which is very straightforward.

Start of the project

I bought the ear defenders just to deal with the noise. They certainly work, and there are now several co-workers sporting this worksite chic.

As effective as they are, it only took a day before I realized I also wanted some white noise, or music. At an office, the ear defenders also send a bit of an antisocial message. I didn’t want to be quite so unapproachable as they make you look.

I needed to get hold of some headphone speaker drivers, wire them in, and to change the look of the ear defenders.

Prototype

The above photo isn’t how I started. I started by making a prototype. I wasn’t sure it was going to work, so I didn’t want to spend much money or invest time that might not pay off.

I bought the cheapest pair of headphones I could find on Amazon that I thought would work. At the time, that was a 7 GBP pair of red Phillips headphones. I put the innards from them into the ear defenders.

It worked great. I actually used the prototype at work for nearly a year. All I did was to cut out the motif from a Prometheus sticker, and use that to decorate the left ear piece.

I’m still using the prototype version on buses and flights - and they do a marvellous job of blocking out the rumble of the plane engines, or passengers being disruptive. They work so well, instead of re-using the ear defenders for the “real” version, I actually bought a second pair of Peltor X5s so that I can keep a pair at my desk, and a pair to use on the move.

So, if you want - you can just make the prototype version - they work just fine.

Disassembly

Here’s what the start of that prototyping looks like - I begun taking things apart:

Prototype

Prototyping really was easy - the ear defenders come apart with a bit (a lot) of force. The white plates come off, and under that is some thick noise isolating foam that is also removable.

The Phillips headphones came apart fairly easily as well. In order to not have to re-solder the wires to the driver, I broke apart the cups. The wires were quite fragile, and I ended up having to re-solder a few joints anyway.

If you’re a little more careful than I was, there is no need for any soldering at all in this project.

Prototype disassembled

I used a Dremel rotary tool with a cutting disc to cut down the plastic around the headphone speaker drivers, making sure to leave enough that I could jam the remains of the plastic plate into the foam to secure the drivers. That worked surprisingly well! You could also easily just use a file, or some pliers to shape the plastic.

I took them to work to test out, and I was very pleased with the result. It also encouraged my coworker to get a pair of ear defenders and start a very similar build.

At first I just put the cables under the ear pads, that worked. Then I cut a notch under the ear muff plates and had the cable coming out fairly sensibly. On the inside of the cup, I knotted the cable and stuck it in place with a blob of hot glue.

Here’s what the finished prototype looks like:

Finished Prototype

The “real” build.

Having used the first version for a year, that was pretty much a real build - it’s more accurate to call this a “version 2”.

There were a few issues with the prototype, so for version 2:

  1. I want them to sound good.
  2. I want the headphones to look better. I want them to look more like real headphones, and less like industrial ear defenders. I particularly want to hide the 3M logo.
  3. I want a more personalized logo.

The cheap Phillips headphone speaker drivers didn’t sound nearly as bad as you might imagine, but they still didn’t sound good. Before this build, and now at home, I have a pair of AKG 550s, so that is what I compare my very subjective “sounds good” judgement against.

So, I headed online and started looking for where one could buy headphone drivers. Most headphone speaker drivers were quite expensive, but I found some affordable ones on Aliexpress. The seller claimed the drivers had a good response over the audible spectrum, and was of a size I thought would fit into the cups nicely - 50mm wide. Having ordered them, they arrived promptly, well packaged and I could get started with the build.

In fact, thank you to Chitty’s Aliexpress store for great service, product and packaging.

Build Process

Parts list

Part Cost
Peltor X5 ear defenders 35.85
50mm headphone drivers 9.50
Big Boy body filler 6.40
3.5mm panel jacks (10-pack, you need 3) 6.99
Shielded cable (100m, you need 25cm) 23.12
30cm 3.5mm male-male cable 5.99
1.5m 3.5mm male-male cable* 5.59
Silver spray paint 8.99
Fancy masking tape - “Frog Tape” 4.90
Total £107.33

* 2-pack, 1.5m and 2.5m - I use the 1.5m at work, but want a longer cable if I should use them elsewhere.

So, not a cheap project! Especially when compared to the £42.85 for the prototype. However, I’ve got 99.75m shielded cable left, spare 3.5mm jacks, silver spray paint, body filler and fancy masking tape for many more projects.

I could definitely have saved money on the 3.5mm cables, but they had just the right look. I also didn’t need 100m of shielded cable, but I wanted to get a stock of the stuff to have for future projects.

Tools and consumables I already had

  • Soldering iron
  • Side cutters
  • Hobby scalpel
  • Multimeter
  • Dremel (and Dremel drill press, at the office makerspace)
  • Solder
  • Primer spray paint
  • Black ‘chalk board’ spray paint
  • Files
  • Sandpaper

Build steps

Empty out the ear defender cups

The black acoustic foam had to come out. I also cut some slots into the foam where the 3.5mm jacks and cables would need to go through when I put it back later.

Fill moulded details in ear defender cups

After removing the form, it was time to apply body filler to the headphone cups. I mixed it up, and smoothed it on.

I actually got this wrong twice. I mixed the bodyfiller wrong and it hardened too fast. The second time it didn’t harden at all. Mixing a small amount of bodyfiller can be quite hard, and you don’t have much time to work with it after it’s mixed. I had to spend a significant amount of time with a rasp to get the bodyfiller off. The third time worked fine.

It took a fair amount of filing and sanding to get the finish smooth. The result is worth it though.

Body Filler

Now, when you do this yourself - a few words of warning. The bodyfiller smells very strongly - and you should do this in a well-ventilated area. There’s warnings on the tin, they should definitely be respected. Should the filler not harden - don’t be tempted to put it in the oven to try to dry it out

  • that just makes the whole house smell of solvents and doesn’t work. It’s a bad idea, learn from my foolishness.

Painting

I did a test-paint after a small amount of body filler to make sure the paint would stick well. Here’s my little jig that holds the cup while I’m spray painting. It’s made from a clothes-hanger, and hooks over the nubs the headband connect to, and has a loop inside which presses against the inside of the cup.

Painting Jig

The paint stuck on well, so I sanded it back, applied the full coat of body filler - and then spray painted it with primer.

Primed

When the primer was dry, I gave the headphone cup a few coats of black paint I had around. It is a matte black blackboard paint which I’ve had plenty of fun with. The paint is still drying in this picture:

Painted

You can also see the second cup, with bodyfiller applied and (mostly) sanded, in the background.

Drilling the holes

I drilled three holes in the cups. Two holes in the left cup - one at the bottom for the connection from the music - and one on top to connect to the right cup. Finally a hole on the top of the right cup for the connection from the left one.

I’m lucky enough that work has let us build a makerspace. The makerspace has a Dremel drill press, which made it really easy to get the holes done right. I used the Dremel drill press to put in pilot holes, then measured the width of the 3.5mm jacks I ordered - and widened the holes to fit. I used the Dremel again for the widening, with a drum-shaped abrasive bit.

Wiring

I measured out and cut some lengths of two-conductor wire. One to connect the left channel to the left cup’s speaker driver, a second to connect the right channel to the top 3.5mm jack. Then finally a length to connect the right cup’s jack to that speaker driver. See this diagram:

Wiring diagram

A little soldering later, and I could put the sound blocking foam back and insert the drivers so that they’re held in place by the foam.

Wiring

Detailing

I used masking tape to mask off a logo and spray paint on a silver “//” to give it a personal touch. I cut the tape into shape with a hobby scalpel when needed.

I bought a brand of fancy masking tape called “Frog Tape” from Wickes to get a nice, sharp edge to the logo. It worked really well, and I’d recommend a fancier masking tape over the usual stuff.

Result

I think they look good. They also sound great!

Finished

They fulfill their original purpose and block out the office noise to let me concentrate at work. At the same time, they give me the chance to add some white noise or sweet tunes!

I think my co-workers appreciate that I look more approachable, I love how they sound, and I like that they feel personalized for myself.

It’s not a hard build - a few steps are time-consuming, like the sanding, but not difficult. If you decide to build some yourself - I’d love to see what you’ve made - please ping me on twitter, where I’m @anglerud.

If you think someone else might be interested in a build like this, please share a link to this with them.

Next

There is one more thing I’d like to do with these headphones. But, since it took me a year between finishing the prototype and making the finished headphones, it could be a while before I get around to it.

The makerspace at work has a sewing machine! Almost certainly one of the most useful things we got. I’m going to make some covers for the headband to make them slightly more comfortable, and hide more of the ear defender look.

I’ll need to find some suitable fabric and padding. I have some torn black jeans I might use - not sure about the padding yet though.

Exponential smoothing in Python

2014-02-12 3 min read coding

The problem

While measuring temperature using my Arduino and a TMP36, I found that the temperature reading fluctuated wildly. After reading up a little, it became clear that since the Arduino’s analog pin measures discrete values of 0-1023, and the voltage varies between 0-5V, the smallest difference that can be detected is ca. 4.9mV. For the TMP36, a temperature change of 1°C triggers a voltage change of 10mV, so the smallest temperature increment that can be registered is ±0.5°C. For additional fun, the sensor is only accurate to ±2°C, so plenty of opportunities for sensor fluctuations.

The solution

I figured I could get a better result by sampling more values and averaging them. Since I only needed actual readings every few seconds, I started by just sampling the previous few hundred values and taking a simple mean. The temperature, at least, was a lot more stable now. Then I tried a different method, comparing each temperature reading to the previous and adding 1% of the difference to the current value. This produced a relatively nice smoothing.

At this point, I realized this must be something which had a very common solution, and a brief bit of googling led me to the exponential moving average which seemed to be a good fit. I also quickly found a stackoverflow answer that had some sample Python code. It looked like a good start, but since I wanted to measure continuously, I wanted to refactor it a bit so that I could continuously smooth my readings without saving up a lot of previous values in memory. The below is what I ended up with.

The code

def exponential_moving_average(period=1000):
    """ Exponential moving average. Smooths the values over the period.  Send
    in values - at first it'll return a simple average, but as soon as it's
    gathered 'period' values, it'll start to use the Exponential Moving
    Averge to smooth the values.

    period: int - how many values to smooth over (default=1000). """
    multiplier = 2 / float(1 + period)
    cumulative_temp = yield None  # We are being primed

    # Start by just returning the simple average until we have enough data.
    for i in xrange(1, period + 1):
        cumulative_temp += yield cumulative_temp / float(i)

    # Grab the simple average,
    ema = cumulative_temp / period

    # and start calculating the exponentially smoothed average we want.
    while True:
        ema = (((yield ema) - ema) * multiplier) + ema


def temp_monitor(pin):
    """ Read from the temperature sensor - and smooth the value out. The
    sensor is noisy, so we use exponential smoothing. """
    ema = exponential_moving_average()
    next(ema)  # Prime the generator

    while True:
        yield ema.send(val_to_temp(pin.read()))  # pin.read() is sensor read.


def val_to_temp(v):
    """ Convert the sensor reading to a temperature in C. """
    # 10mV/C - and a correction factor of 0.5V.
    return (v * 5.0 - 0.5) * 100

Final words

As you can see, the exponential smoothing introduces some lag in the responsiveness, but I’m not going to measure anything that reacts that quickly. It’s the noise that’s the problem. By picking a smaller window to average over you can increase the responsiveness, but at the price of more jitter.

graph

By making the exponential_moving_average into a generator, you only need to keep a few values in memory, and you can feed it one value at a time getting a continuous stream of results. This is very convenient, and the only change needed is using .send() on the generator over just calling the function.

Networking monitoring with a RaspberryPI

2013-06-29 5 min read coding

A while back my house mates and I found that our internet connection was terribly slow in the evenings.

I decided to use my newly arrived Raspberry Pi to write some monitoring tools to see what the pattern was and how bad it was. This way we would also have something to give to Virgin Media to show there was a problem. At the same time, I had been wanting to write some software to push data into the graphing system Graphite, so this seemed like a good time to do that.

A note on the problem

A quick ping showed::

64 bytes from cpc2...virginmedia.com (...) time=2304 ms

which is terrible. Ping times were consistently worse than even that in the evenings, for several hours at a time. I started by just checking the ping times to the gateway using

export GATEWAY=`traceroute virginmedia.com | head -5 | grep
cable.virginmedia.com | cut -f 4 -d ' '`
traceroute virginmedia.co.uk > virginmedia_traceroute.txt
ping -c 100 $GATEWAY > gateway_ping.txt

It became quickly obvious that it would be much more interesting to see how this changed over the course of the day. I really didn’t want to try and wait for the problems to occur before getting data about it. What I wanted was to see the problem clearly, highlighted like this:

network latency graph

As you can see from the above image, ping times shot up to four seconds in the evening, so it’s not surprising that everything felt wrong.

My approach

The software I picked to write this consisted of

  • Python: my favourite programming language.
  • Graphite: the fast graphing toolkit.
  • Sh: an easy way to interact with the Unix command set from Python.
  • Ping: the standard way to see response times from hosts on the internet, this version is written in Python.
  • Argh: a wonderfully simple way to read command line arguments, much simpler than the built-in Argparse.

I could of course have used the Sh module to do all of this, or even just plain bash. However, it was enjoyable writing this little tool, and I didn’t want to spend too much time grepping text files. I wanted this to run for a fortnight or so to get the best possible data.

How it’s all wired together

First up, I call out to the system’s traceroute command, using the ‘sh’ module. I then look through the output to find the cable gateway, which I know contains the strings virginmedia.co.uk and cable. Since it’s a quick hack, I’ve hardcoded it - but it wouldn’t be much more work to get it as a default argument on the command-line. This is how I did it:

def find_gateway():
  gateway_lines = [l for l in sh.traceroute('virginmedia.co.uk').split('\n')
                   if '.cable.' in l]
  return gateway_lines[0].strip().split()[1]

A quick google showed there was a really convenient ‘python-ping’ module one could grab. The module is originally written by Matthew Dixon Cowles and was rewritten by Johannes Meyer. Using it to ping a host is as simple as:

def ping_host(host_name):
    delay = ping.do_one(host_name, 9)

    if not delay:
        return None

    return round(delay * 1000, 4)

Once I have the ping data the job is nearly done. The next task was to get this data into Graphite. There are a lot of nice Python libraries to push data to Graphite - but as I was sending such a simple set of metrics to it, at such a slow pace, I just connected straight to it. Sending a value is absolutely trivial:

def send_metric(name, value):
  sock = socket.socket()
  sock.connect( ("localhost", 2003) )
  sock.send("%s %d %d\n" % (name, value, now()))
  sock.close()

Gluing these things together is very simple. Find out what to ping, ping it repeatedly and tell Graphite how long each one took:

def main():
    failures = 0
    gateway_host = find_gateway()
    print 'Measuring ping latency to {}'.format(gateway_host)

    try:
        while True:
            ping_time = ping_host(gateway_host)

            if not ping_time:
                failures += 1
                send_metric("network.dropout", failures)
                print 'packet lost'
            else:
                send_metric("network.ping_time", ping_time)
                print 'ping_time: {}'.format(ping_time)

            time.sleep(10)
    except KeyboardInterrupt:
        pass

Then, to create a command-line interface, you can use the wonderful ‘Argh’ module. Argh makes it this simple:

if __name__ == '__main__':
    argh.dispatch_command(main)

I just can’t recommend the Argh module enough, it’s become a mainstay of my tool set. It makes creating command line interfaces as easy as creating a main method that receives the parameters you desire. Read about it at readthedocs. Even if the script I’m writing doesn’t take any arguments, I still use it as it produces nice help output. Once done, just run this software like this:

$ ./gateway_pinger.py

The Raspberry Pi

I wanted to run this for at least a week without having to keep my noisy computer on, and I also wanted to be able to access this through the browser. The Raspberry Pi would make both easy. Once I installed Graphite on to the Raspberry Pi, I could run this script on there.

Installing Graphite is as simple as apt-get install graphite-carbon. There is one small thing you’ll need to do, and that is to update the /etc/carbon/storage-schemas.conf file to store more data than the default. I’ve set mine to downsample data to 1m increments after a while.

[network]
pattern = ^network\.
retentions = 10s:6h,1m:7d,10m:1y

What did it accomplish?

I got to write some fun software, which I really enjoyed - and I got to use Graphite and found it to be both easier and more capable than I had first thought. I was also happy to have found a useful task for the Raspberry Pi.

The whole project took about an evening’s work, followed by some time tweaking it to be prettier.

The resulting graphs were shown to anyone willing to stay still for long enough, and the data was passed on to Virgin Media. The network graph is now beautifully smooth, indicating a lack of packet loss and latency spikes.

Coffeebot Heroine

2013-06-17 3 min read writing
Coffeebot Heroine This body is a hotel and a shrine. You can have it when I die. Hoxton separatists with Romanian AK-47s and grenades, probably bought wholesale somewhere south of the river in a little place you’ve never heard of. My trousers were uncomfortably soaked in petrol as I was pinned down by small arms fire, hiding behind a black cab. I’d explain, but I’m jonesing for my next double macchiato. Continue reading