Contents

Sensirion Sps30 Experiments

Sensirion SPS30 Experiments

For some days an air purifier is doing its work here, leading to significant improvements but only if running in manual mode, the automatic mode seems lacking, at least according to my nose.

Because this is not very scientific, I decided to connect another sensor to my pc and see if the values somewhere else in the room differ from what the purifier measures.

A possible reason: to measure airquality, you need to transport enough air to the sensor, but what happens if the automatic mode drops fan speeds so low that it’s not enough to produce accurate reads?

It may never measure high enough particle values to spin up the fan again.

Idea

Using a sensor further away from the purifier should lead to better results.

I decided to try out the sensirion sps30 since I still got one lying around.

Challenge

The sensor I have at hand is connected to the usb evaluation kit, so I should be able to poll those values.

Sensirion provides a GitHub repository for usage with the uart interface.

Turns out I found no easy way to do this, partly because I lack knowledge in handling C/C++1

Experiments

The only language I can handle okayish is python, so lets see how far we can get here.

# create and activate python venv
mkdir -p sensirion
cd sensirion
python3 -m venv .venv
./.venv/bin/activate
# we need the pyserial module so lets install it right away
./.venv/bin/python3 -m pip install pyserial

Let’s try some basic stuff to see if we can interface with the usb device.

If you get access denied, you should find out what group has access to the usb device:

stat /dev/ttyUSB0
File: /dev/ttyUSB0
Size: 0               Blocks: 0          IO Block: 4096   character special file
Device: 0,6     Inode: 1032        Links: 1     Device type: 188,0
Access: (0660/crw-rw----)  Uid: (    0/    root)   Gid: (  986/    uucp)
Access: 2025-06-09 10:27:32.294929519 +0200
Modify: 2025-06-09 10:27:32.294929519 +0200
Change: 2025-06-09 10:27:32.294929519 +0200
Birth: 2025-06-09 10:27:32.191891998 +0200

I’m on arch, so it’s uucp. This differs depending on the flavor you prefer.

sudo usermod -aG uucp <username>

Remember to logout&login and check the output of groups if you still get access denied. Sometimes a reboot is needed.

Let’s see on what port the evaluation kit is connected.

lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 0403:6001 Future Technology Devices International, Ltd FT232 Serial (UART) IC
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
[...]
ls -al /dev/ttyUSB*
crw-rw---- 188,0 root  9 Jun 11:54  /dev/ttyUSB0

The only available Port is ttyUSB0 so we can go with that one.

import serial
ser = serial.Serial('/dev/ttyUSB0')  # open serial port
print(ser.name)         # check which port was really used
ser.write(b'hello')     # write a string
ser.close()             # close port

Should print something like this:

/dev/ttyUSB0

This means we are basically able to handle connections to the port.

Retreiving Data

The easy way now is to use an existing solution2.

I decided to just copy the sps30.py, comment out the import for paho.mqtt.publish and give it a try.

Looks like this:

./.venv/bin/python3 sps30.py
['/dev/ttyUSB0']
Starting
Wait: 0
Wait: 7
Wait: 0
Wait: 25
Wait: 0
Wait: 47
created: Jun2025
SPS_<serial>,06/09/25 11:40:51,0.87,0.90,0.90,0.90,6.00,6.94,6.96,6.96,6.96,0.50
Wait: 0
Wait: 47
SPS_<serial>,06/09/25 11:42:13,0.77,0.80,0.80,0.80,5.32,6.15,6.17,6.17,6.17,0.39
Wait: 0
Wait: 49
SPS_<serial>,06/09/25 11:43:35,1.89,1.95,1.95,1.95,13.06,15.11,15.15,15.15,15.15,0.43
[...]

What does it mean? A hint is inside the mqtt part of the code:

header = ['sensor', 'time', 'PM1', 'PM25', 'PM4', 'PM10', 'b0305',
              'b031', 'b0325', 'b034', 'b0310', 'tsize']

A line like SPS_<serial>,06/09/25 11:40:51,0.87,0.90,0.90,0.90,6.00,6.94,6.96,6.96,6.96,0.50 gets translated to:

sensor: SPS_<serial>
time: 06/09/25 11:40:51
PM1: 0.87   # Mass Concentration PM1.0   [μg/m³]
PM25: 0.90  # Mass Concentration PM2.5   [μg/m³]
PM5: 0.90   # Mass Concentration PM4.0   [μg/m³] //Note: the sps30 is measuring PM4.0 not 5.0
PM10: 0.90  # Mass Concentration PM10    [μg/m³]
B0305: 6.00 # Number Concentration PM0.5 [#/cm³]
B031: 6.94  # Number Concentration PM1.0 [#/cm³]
B0325: 6.96 # Number Concentration PM2.5 [#/cm³]
B034: 6.96  # Number Concentration PM4.0 [#/cm³]
B0310: 6.96 # Number Concentration PM10  [#/cm³]
tsize: 0.50 # Typical Particle Size8     [μm]

To understand what we see, the datasheet3 helps along while also looking into the python code.

While someone was cooking, both the air purifier and the sps30 noticed the spike in particles.

After the purifier spun down again the line keeps at the minimum while the distant sensor is still able to pick up on elevated values.

Let’s keep this running for some more hours and see if this proves our assumption.

../Screenshot_20250609_131136.png

../Screenshot_20250609_131541.png