Running a SDS011 particulate sensor on a Mac using PHP

The Novafit SDS011 particulate sensor is an a very affordable sensor for detecting particulate pollution. It is capable of detecting both PM2.5 and PM10 with a relative error margin of +/- 10µg/m3.

It can output data via it’s serial port. The one I bought came with a serial to USB adaptor, allowing it to be plugged into my Mac. It did need a driver, and I used ch340g-ch34g-ch34x-mac-os-x-driver.

Data is sent at 9600 baud, with 8 data bits, no parity bit, and 1 stop bit. 10 bytes are sent at a time.

Byte Name Content
0 Message Header AA
1 Commander No C0
2 DATA 1 PM2.5 Low byte
3 DATA 2 PM2.5 High byte
4 DATA 3 PM10 Low byte
5 DATA 4 PM10 High byte
6 DATA 5 ID byte 1
7 DATA 6 ID byte 2
8 Check-sum DATA 1+DATA 2+..+DATA 6
9 Message tail AB

PM2.5 (μg /m3) = ((PM2.5 High byte *256) + PM2.5 low byte)/10
PM10 (μg /m3) = ((PM10 high byte*256) + PM10 low byte)/10

We can read this using PHP. The following PHP script can be run on the command line and outputs the PM2.5 and PM10 levels every 2 seconds to a terminal window.

<?php
exec('stty -f /dev/cu.wchusbserial1420 9600 raw');
while (true) {
    $handle = @fopen( '/dev/cu.wchusbserial1420', 'r' ); # Open device for Read access
    if ($handle) {
        $binarydata = fread( $handle, 10 ); # Read data from device
        $data = unpack('H2header/H2commander/vpm25/vpm10/Sid/H2checksum/H2tail', $binarydata);
        echo sprintf("PM2.5: %dµg/m³\nPM10:  %dµg/m³\n", $data['pm25']/10, $data['pm10']/10);
        fclose ($handle); # Close device file
    } else {
        echo "Unable to connect to pollution sensor\n";
    }
    sleep(2);
}

The device is used by reading the 10 bytes it returns each message from the USB device. We can use PHP’s unpack function to break the binary data returned into an associative array for ease of use. We do have to do divide the PM2.5 and PM10 results by 10 to get their real values though. This can then be echo’d to the terminal.

Using a motion sensor on a Raspberry Pi with PHP

Adding a motion sensor to a Raspberry Pi is easy. In this article I’ll show you how to use it with PHP.

The HC-SR501 is a pyroelectric infrared PIR motion sensor module, that connects to the Raspberry Pi using just 3 wires.

Connect GND to GND on the Pi, VCC to +5V on the Pi, and OUT to GPIO 17 on the Pi. That’s all that’s needed.

Added a HC-SR501 motion detector to a Raspberry Pi

To use the sensor with PHP we use the PHPi library. This library supports event driven bindings for the Raspberry Pi GPIO. We install this using Composer.

composer require calcinai/phpi

To use the PHPi library we need to bring it in using the autoloader, and add some namespace shortcuts.

require 'vendor/autoload.php';

use Calcinai\PHPi\Pin;
use Calcinai\PHPi\Pin\PinFunction;

We then create a board

$board = \Calcinai\PHPi\Factory::create();

We now need to declare that we’re using GPIO pin 17 for input.

$pin = $board->getPin(17)
             ->setFunction(PinFunction::INPUT)
             ->setPull(Pin::PULL_UP);

As this is event driven programming, we need to listen to the pin to see when it changes. When the pin goes high, we know motion has been detected and we can do something. In this example, we’ll just write a message to the screen.

$pin->on('level.high', function() {
        echo "Motion detected\n";
});

Finally we start the start the loop running, so the script is listening and reacting to events.

$board->getLoop()->run();

Putting it all together

Here’s the final working PHP to detect motion using the HC-SR501

<?php

require 'vendor/autoload.php';

use Calcinai\PHPi\Pin;
use Calcinai\PHPi\Pin\PinFunction;

$board = \Calcinai\PHPi\Factory::create();

$pin = $board->getPin(17) //BCM pin number
             ->setFunction(PinFunction::INPUT)
             ->setPull(Pin::PULL_UP);

$pin->on('level.high', function() {
        echo "Motion detected\n";
});

$board->getLoop()->run();

Reading a temperature sensor using PHP on a Raspberry Pi

It’s easy to add a temperature sensor to a Raspberry Pi. In this example I’ll explain how to set it up and access the data using PHP.

The DS18b20 is a great digital temperature sensor. It only needs three wires and a resistor to get it working on the Raspberry Pi.

The red wire is +3.3v, the black is ground, and yellow is data.

The resistor is connected between red and yellow to pull up the voltage on the data line.

Red is connected to pin 1 on the GPIO, black to pin 6, and yellow to pin 7.

Connecting a temperature sensor to a Raspberry Pi

Reading the temperature

Now the circuit is ready, we can access the data. We need to enable the relevant modules on the Raspberry Pi to do this.

modprobe w1-gpio
modprobe w1-therm

If we now look in the /sys/bus/w1/devices/ directory, we should see a directory starting with 28. This is where we can find the temperature data. Inside this directory is a file called w1_slave. This is the file we read get the data. When we read it, it actually asks the sensor for the data and return it. This means there is a slight delay before the data returns.

pi@Nowscreen:~ $ cat /sys/bus/w1/devices/28-031683a865ff/w1_slave
95 01 4b 46 7f ff 0c 10 65 : crc=65 YES
95 01 4b 46 7f ff 0c 10 65 t=25312

The temperature is the value t=25312. We divide this by 1000 to get the temperature of 25.312 degrees celcius.

Reading the temperature with PHP

The first thing we need to do is to find the directory where the w1_slave file is. We can use globbing to help here.

$base_dir = '/sys/bus/w1/devices/';
$device_folder = glob($base_dir . '28*')[0];
$device_file = $device_folder . '/w1_slave';

Now we need to read in the data. We can use the file method as this returns each line of the file in an array.

$data = file($device_file, FILE_IGNORE_NEW_LINES);

Now we extract the temperature. We check the first line is correct by checking for the value “YES” at the end of the line. If this is present we get the value for “t=” at the end of the second line. Finally we divide the value by 1000, and return it.

$temperature = null;
if (preg_match('/YES$/', $data[0])) {
    if (preg_match('/t=(\d+)$/', $data[1], $matches, PREG_OFFSET_CAPTURE)) {
        $temperature = $matches[1][0] / 1000;
    }
}

Now we can display the temperature.

if ($temperature) {
    echo "Temperature is ${temperature}C\n";
} else {
    echo "Unable to get temperature\n";
}

Final PHP temperature sensor code

Here’s the finished code. I’ve also included two system calls to modprobe to ensure the necessary modules are loaded before reading.

<?php

exec('modprobe w1-gpio');
exec('modprobe w1-therm');

$base_dir = '/sys/bus/w1/devices/';
$device_folder = glob($base_dir . '28*')[0];
$device_file = $device_folder . '/w1_slave';

$data = file($device_file, FILE_IGNORE_NEW_LINES);

$temperature = null;
if (preg_match('/YES$/', $data[0])) {
    if (preg_match('/t=(\d+)$/', $data[1], $matches, PREG_OFFSET_CAPTURE)) {
        $temperature = $matches[1][0] / 1000;
    }
}

if ($temperature) {
    echo "Temperature is ${temperature}C\n";
} else {
    echo "Unable to get temperature\n";
}