Plantower PMS7003 Particulate sensor

A recent project has reminded me that I wanted to get a particulate sensor working. I’d previously purchased a cheap dust sensor but never got round to using it. Also I wasn’t confident that the data it produced would be of much use.

Cheap dust sensor

With that in mind I had a look for a newer sensor and found the Plantower PMS7003 on ebay for less than £20. Its a laser particulate sensor that can not only count the number of particles but can differentiate between particle sizes from 0.3 to 10 microns. The datasheet says that particle counting efficiency is 50% at 0.3um and 98% at >0.5um. While the data output by the PMS7003 does include particle counts for <0.5um, it is likely more useful to look at the standard PM1.0/2.5 & 10.0 outputs anyway so that isn’t a problem.

Plantower PMS7003

Looking at the datasheet and some other code examples the payload is as follow:

32 bytes in total

2 bytes with fixed content that are the start bytes. They contain 0x42 and 0x4d.

The next 2 bytes are the frame length which looks like it will always be 28 (in the default mode – payload minus start bytes and checksum bytes).

The next 6 bytes are the 3 standard PM1.0, PM2.5 & PM10.0 measurements (each being two bytes) and are measured as micro grams per meter cubed (ug/m3). The first byte is the high byte.

The next 6 bytes are 3 atmospheric concentration measurements (PM1.0, PM2.5 & PM10.0). Again, two bytes per reading.

The next 12 bytes are 6 ‘particle size per 0.1litre of air’ counts (2 bytes each). They are 0.3, 0.5, 1.0, 2.5, 5.0 & 10.0um. Each count measures particles of that size and larger. Looking at the data from the sensor they do not appear to include the count of particles over the next size.

Next we have 1 byte for version and 1 byte for error codes.

The last 2 bytes are the checksum.

The data from the sensor using a test Python script are as follows:

PMS7003 Sensor Data:
PM1.0 = 2 ug/m3
PM2.5 = 3 ug/m3
PM10 = 4 ug/m3
PM1 Atmospheric concentration = 2 ug/m3
PM2.5 Atmospheric concentration = 3 ug/m3
PM10 Atmospheric concentration = 4 ug/m3
Count: 0.3um = 384 per 0.1l
Count: 0.5um = 118 per 0.1l
Count: 1.0um = 20 per 0.1l
Count: 2.5um = 5 per 0.1l
Count: 5.0um = 2 per 0.1l
Count: 10um = 0 per 0.1l
Version = 151
Error Code = 0
Frame length = 28

With a working sensor I can now try and hook it up to an ESP8266 and expand my Raspberry Pi Node Red mqtt home environmental monitoring system.

Node red temperature and humidity sensor dashboard

Crunchy Pi – Pi Zero PWM Audio board

A while ago I thought it would be a good idea to have a Pi Zero with audio out that I could hook up to some speakers… I can’t remember why I thought that or what project I was going to use it for but the idea to design the board has remained.

This is the result… The Crunchy Pi v0.2!

Why Crunchy? Because if you turn the volume up too much, it distorts!

The amplifier is a module based on the PAM8403 which is a 3W (into 4ohms) Class D Stereo amp. The analogue filter is the same as the Pi3.

It runs straight off the Pis 5v rail… yeah yeah, more decoupling etc, but it seems to work well enough with the official Raspberry Pi PSU and some 4ohm speakers. Just don’t power up or down the amp (that’s what the jumper does) when the Pi is running or it will reboot and potentially corrupt your SD card!

The PAM8403 is designed to be permanently connected in a low power device so is prone to damage when powered up with no load. So yeah… don’t power the Amp without speakers connected! That said, the modules are cheap enough on ebay that if it does die, it can be de-soldered and replaced.

The headphone socket can be used at the same time as the amp, but is directly connected so will probably attenuate the volume (it wasn’t noticeable when I tried it with some ear buds).

To get this thing to work we have to enable the PWM audio output. Enabling PWM audio on the Pi Zero requires a config.txt change. Add the following to /boot/config.txt on your Pi:

# Pi Zero PWM Audio settings
  • The dtoverlay line routes the PWM signals to pins 13 & 18 .
  • The pwm mode enables sigma-delta pwm which is much cleaner than the orignal mode 1.
  • The disable audio dither can remove the hiss when no audio is playing.

See this page for more details.

you might also have to enable analog audio in the raspi-config utility.

As usual, getting PCBs made in China, I ended up with a bunch of these that are surplus to requirements, so am offering the spares for sale. Maybe other people can think of some cool use-cases?

I’ve hand soldered all the components and the eagle eyed may have spotted that not all of the 40 pin female header are soldered. I’ve only soldered the pins that are actually connected and the ground pins. I’ve also tested all the boards I’m offering for sale.

Price is £12 each plus £2 postage in the UK. Please get in touch via the contact form if you’d like to order one. If you are outside the UK get in touch and i’ll get a price for shipping.

V0.1 PCBs were made by iTead (I made another stupid mistake with the headphone socket pinout and decided to add screw terminals for the speaker output instead of pin headers) The v0.2 PCB is by PCBway. They do a surprisingly fast turn-around and the boards are as good as iTead.

As usual i’ll put the schematic and board layout files on github at some point.

Robot Framework and the Dialogs library

The built in library ‘Dialogs’ is handy for pausing execution of a test script to wait for some manual intervention, but if you happen to be running a default install of Centos7 you will likely see an error like this:

[ ERROR ] Error in file '/root/robot_test_2/resources/keywords.robot': Importing test library 'robot.libraries.Dialogs' failed: ImportError: No module named tkinter

This is because the Python GUI library ‘tkinter’ isn’t installed. You can install it with:

yum install tkinter

Now the dialogs your test script creates should appear.

Jenkins, Robot Framework, Selenium2Library and Linux

I’ve been learning Robot Framework recently and I wanted to see how it ties in with Jenkins so I can get a handle on Continuous Integration.

A while back I installed Robot Framework and the Selenium2Library on a CentOS7 VM and found the Robot Framework reasonably easy to setup and use. After that I thought it would be useful to see how much work is involved in setting up a Jenkins system. So I installed Jenkins on the same Linux machine using

yum install jenkins

(I had to add the repos for the yum command to work – see the install guide for how to do that), then in Jenkins, created a ‘Freestyle’ project and added a ‘Build’ step that called a Robot Framework test.

So far, so easy! The Robot Framework Selenium tests should start running (along with the browser window executing the tests).

At least according to the demos I had seen, that should have been enough to get it working but It didn’t work for me.

I was seeing

'Keyword 'Capture Page Screenshot' could not be run on failure: No browser is open'


| FAIL |
WebDriverException: Message: The browser appears to have exited before we could connect. If you specified a log_file in the FirefoxBinary constructor, check it for details.

Lots of googling suggested it was something to do with running headless. Except the Linux VM in question isn’t running headless, but as far as Jenkins and/or Selenium were concerned, it was headless.

The options were:

  • Run Jenkins from the console  so that any browser sessions launched by Jenkins would run in the fore ground.
  • Run Jenkins as a service. The theory is that the init scripts should be configured correctly to allow any launched browser sessions to open in the foreground. But I was already running this way because the yum install setup the init scripts for me.
  • Run some virtual desktop software like vnc or Xvfb.

As I was already running as a service and the first option doesn’t seem robust, I figured I would try the last option.

Yum install Xvfb got me the software. I ran it from the console with

Xvfb :1

which starts an Xwindows virtual frame buffer which listens on ‘server number’ 1.

Next, I had to tell Jenkins that it should use ‘server number’ 1 as the display. I guess there are other places I could do this, but for the sake of expediency I added it to the console ‘Build’ step.

My ‘Build’ step then looked like this:

export DISPLAY=:1
robot /tmp/robot_test/valid_login.robot

Where the export line tells Jenkins to send the display to ‘server number’ 1 and then executes the robot tests successfully.

If that works you can install the Jenkins Xvfb plugin to automatically manage the virtual frame buffer which means you don’t need to run it and you don’t need the export DISPLAY=:1 line in the build step. Apart from supplying Jenkins with the location of the xvfb executable and a name, the defaults (leave the fields blank) work fine and the documentation for the plugin is pretty good.

In reality the Jenkins server is not likely to be executing the browser tests. These would be farmed out to other servers and I read a few posts about running Jenkins slave servers in the foreground because of this issue, but the above seems reasonable.

Arduino Nano and Raspberry Pi NRF24L01+ adaptor boards

While working on another MakeBournemouth project, I made some NRF24L01+ adaptor boards for the Arduino Nano and the Raspberry Pi and thought others might find them useful for their own projects so I am offering a few spare boards, in kit form, for sale.


The board makes it easy to connect an NRF24L01 to an Arduino Nano or Raspberry Pi to test it works or to talk to other NRF24L01 wireless modules in your own projects.


There is also a PCB design for the Uno/Leonardo but the PCBs would be a bit more pricey so haven’t ordered any of those. If there is any interest, get in touch and I could be persuaded to get some made.

The nano version is £4.00 + £0.95 shipping in the UK. (Nano and NRF not included. For shipping outside the UK, please get in touch.)


The kit consists of:


  • 1 x Nano NRF adaptor PCB
  • 1 x 2×4 pin socket
  • 2 x 1×15 pin or 2 x 1×16 pin sockets
  • 1 x 100uF Capacitor

(The 1×15 pin sockets are hard to find and often expensive while 1×16 pin sockets are much cheaper and easier to find so kits may be supplied with 1×16 pin sockets depending on what I have available. The extra pin can be cut flush or left hanging over the edge of the board but I would advise you to leave the plastic housing alone. When I’ve tried cutting the plastic housing the pins tend to fall out and the housing gets chewed).

The NRF is powered by the Nanos 3.3v output (the Arduino runs off USB power) and is connected to the SPI bus with CE & CSN pins connected to Arduino digital pins 9 & 10 respectively (the NRF data pins are 5v tolerant). So when using the RF24 library you call the library like this:

RF24(9, 10)

The Raspberry Pi version is £5.00 + £0.95 shipping in the UK. (Pi and NRF not included. For shipping outside the UK, please get in touch.)

The kit consists of:


  • 1 x Raspberry Pi 2/3 Adaptor PCB
  • 1 x 2x4pin socket
  • 1 x 2x20pin socket
  • 1 x 100uF Capacitor

The NRF is powered by the Pi 3.3v output and is connected to the SPI bus (GPIO9,10 & 11 = MISO, MOSI & SCK) with CE & CSN pins connected to GPIO25 and GPIO8 (As per

See also (Don’t forget to edit the GPIO pins in the python example (something like: radio(25,8)) and then run it as root or you will get a segmentation fault!)

If you are interested, email mark at (or use the contact form on this site) and i’ll get in touch with availability and we can arrange shipping and payment via paypal.

Quantities are very limited as these are spares from a small batch PCB production run.

These are also available from ebay:

Nano NRF Adaptor here

Raspberry Pi NRF Adaptor here

Collectd could not find plugin rrdtool

This is an annoying gotcha that I keep meaning to document. When installing collectd and rrdtool via yum on Centos 6/7, I always miss out the rrdtool lib. This manifests as

Starting collectd: Could not find plugin rrdtool.

when restarting the services.
Thankfully it is easy to fix. Run

yum install collectd-rrdtool

and restart the collectd service and it should start without complaint.

Collectd-web and hostnames?

In my previous post I asserted that collectd-web would display hostnames based on the directory created by collectd to store the rrd files, but that doesn’t appear to be true.

After staging a server and installing collectd & collectd-web and getting that and everything else I needed working, I changed its hostname as it was replacing an existing server.

Collectd-web now displays host entries for the old temporary hostname and the new one but I don’t know where collectd-web is getting or even storing those hostnames.

If I figure out how to remove the redundant hostnames i’ll update this post.

Update: Looks like I was right, but I didn’t bother to check the directory. Collectd is creating the directories based on the hostname. For each change of hostname (and after restarting collectd) collectd created a new set of RRD files in a new directory that reflects the change of hostname.

In the default collectd dir I see something like:




So that is where collectd-web is getting its hostnames from. Remove the ones you don’t want (Note: You will lose any rrd data collected under those hostnames) and the hostnames will vanish from collectd-web. Easy!

Configuring Collectd-web for collectd on CentOS 6

If you have tried to get collectd-web running and were presented with the nice graphs without having to mess around for hours, you are lucky. It never ‘just works’ for me…

collectd-webAfter installing collectd and making sure the rrd files were being updated, I ‘installed’ collectd-web. This just involves getting the files from git.

I copied the files to a directory under apache (/var/www/collectd-web), then in the apache config file, made that the root, modified the cgi-bin directory to point to the one in the collectd-web folder. Restarted apache, started the collectd python server (You don’t need this if you are running under apache, but that isn’t obvious from the instructions) and loaded the collectd page which gave me a few menus and not much content.

First off it took me a while to discover that you need to create a config file called ‘collection.conf’ in ‘/etc/collectd’ and in that file, you tell collectd-web where your collectd rrd files are located with the ‘DataDir:’ directive. In a default collectd install on Centos 6 the rrd files are located in ‘/var/lib/collectd/rrd/” under a directory named the same as the host. So within the collection.conf file you have this:

DataDir: "/var/lib/collectd/rrd/"

Collectd-web then lists ‘hosts’ (in its WeUI) for every directory under this one. So if you have a directory called ‘host1’ then you will see a host listed in the WebUI called ‘host1’.

Clicking on the host shows the available ‘plugins’ but clicking on them, I still wasn’t seeing graphs…

Eventually I tracked down an error in the apache error logs.

Can't locate in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .) at /var/www/collectd-web/cgi-bin/collection.modified.cgi

A quick google for “Can’t locate” suggest that ‘librrds-perl’ is missing, however on Centos,

yum install perl-rrdtool

fixed the problem.

After refreshing the page I still don’t see graphs – clicking the host and then a plugin finally showed some graphs!


Programming one USBasp with another USBasp

The lazy way to update the firmware on a USBasp without faffing with an Arduino Uno as a programmer and lots of jumper leads? Get another USBasp and use it to program the other one.

Instructions based on this blog post.

1) Plug both USBasp programmers together using the 10 way cable.

2) Short JP2 on the target USBasp (the one you want to update).

3) Plug the source USBasp into your computers USB port. Do NOT plug the target USBasp into a USB port!

4) In a terminal type

avrdude -c usbasp -p atmega8 -u -U hfuse:w:0xc9:m -U lfuse:w:0xef:m

to set the fuses. “-c usbasp” tells avrdude that you are using a USBasp as your programmer and “-p atmega8” tells avrdude that the target device (the other USBasp) is based on an Atmega8 microcontroller.

5) Then program the firmware (you can download the precompiled hex file from the blog post I mentioned earlier).

avrdude -c usbasp -p atmega8 -U flash:w:usbasp.atmega8.2011-05-28.hex

If everything worked you should be done. Disconnect everything, remove the jumper on JP2 and give the new firmware a spin burning bootloaders.