Browse Source

Add SPS30 support (#1)

Add support for the SPS30 particulate matter sensor.

- Fork Sensiron's I2C driver, add as a submodule, and add a shared object build for use with python
- Add a device driver to rpjios.devices
- Add a sensor driver to rpjios.sensors, using above driver
- Update setup.sh to build embedded-sps library
tags/0.0.0
Ryan Joseph 1 year ago
committed by GitHub
parent
commit
0cbd8b7e41
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 171 additions and 12 deletions
  1. +2
    -0
      .gitignore
  2. +3
    -0
      .gitmodules
  3. +11
    -3
      config.json
  4. +1
    -0
      embedded-sps
  5. +1
    -0
      lib/rpjios/devices/.gitignore
  6. +86
    -0
      lib/rpjios/devices/SPS30.py
  7. +0
    -0
      lib/rpjios/devices/__init__.py
  8. +18
    -0
      lib/rpjios/sensors/SPS30.py
  9. +49
    -9
      setup.sh

+ 2
- 0
.gitignore View File

@@ -1,2 +1,4 @@
*.pyc
env
*.stdout
*.stderr

+ 3
- 0
.gitmodules View File

@@ -0,0 +1,3 @@
[submodule "embedded-sps"]
path = embedded-sps
url = git@github.com:rpj/embedded-sps.git

+ 11
- 3
config.json View File

@@ -15,12 +15,12 @@
},
{
"name": "NetInfo",
"disabled": false,
"disabled": true,
"config": { "frequency": 0.1 }
},
{
"name": "SysInfo",
"disabled": false,
"disabled": true,
"config": {
"frequency": 0.2
}
@@ -66,12 +66,20 @@
},
{
"name": "DHTXX",
"disabled": false,
"disabled": true,
"config": {
"data_pin": 23,
"variant": 11,
"frequency": 0.2
}
},
{
"name": "SPS30",
"disabled": false,
"config": {
"frequency": 0.5,
"shared_object_path": "/home/pi/rpi/env/lib/python2.7/site-packages/rpjios/devices/libsps30.so"
}
}
]
}

+ 1
- 0
embedded-sps

@@ -0,0 +1 @@
Subproject commit 1aabaead20059262d66e113d511157c6fda4133a

+ 1
- 0
lib/rpjios/devices/.gitignore View File

@@ -0,0 +1 @@
*.so

+ 86
- 0
lib/rpjios/devices/SPS30.py View File

@@ -0,0 +1,86 @@
import ctypes as ct

class SPS30(object):
class Measurement(ct.Structure):
_fields_ = [("mc_1p0", ct.c_float),
("mc_2p5", ct.c_float),
("mc_4p0", ct.c_float),
("mc_10p0", ct.c_float),
("nc_0p5", ct.c_float),
("nc_1p0", ct.c_float),
("nc_2p5", ct.c_float),
("nc_4p0", ct.c_float),
("nc_10p0", ct.c_float),
("typical_particle_size", ct.c_float)]

@property
def driver_version(self):
return self._dver

@property
def serial_number(self):
return repr(self._devsn.value)

# an SPS30.Measurement instance
@property
def measurement(self):
return self._get_measurement()

def __init__(self, shared_object_path="libsps30.so", *args, **kwargs):
self._lib = ct.cdll.LoadLibrary(shared_object_path)
if not self._lib:
raise BaseException("Unable to load shared SPS30 library from '{}'".format(shared_object_path))

self._lib.sps_get_driver_version.restype = ct.c_char_p
self._dver = self._lib.sps_get_driver_version()

if self._lib.sps30_probe() != 0:
raise BaseException("Probing SPS30 failed")

self._devsn = ct.create_string_buffer('\000' * 32)
if self._lib.sps30_get_serial(self._devsn) != 0:
raise BaseException("Unable to get SPS30 serial number")

self._measurement_running = False
self._latest_measurement = None

def __del__(self):
self._stop()

def _stop(self):
if self._measurement_running:
self._measurement_running = bool(self._lib.sps30_stop_measurement())

def _get_measurement(self):
if not self._measurement_running:
if self._lib.sps30_start_measurement() != 0:
raise BaseException("Failed to put SPS30 into measurement mode!")
self._measurement_running = True
data_ready = ct.c_byte()
if self._lib.sps30_read_data_ready(ct.byref(data_ready)) == 0:
if bool(data_ready.value):
self._latest_measurement = SPS30.Measurement()
if self._lib.sps30_read_measurement(ct.byref(self._latest_measurement)) != 0:
raise BaseException("read_measurement")
return self._latest_measurement

if __name__ == "__main__":
import time
sps30 = SPS30()
print "SPS30 S/N {}, driver '{}'".format(sps30.serial_number, sps30.driver_version)
while 1:
meas = sps30.measurement
if meas is not None:
print ""
print "PM1.0:\t{}".format(meas.mc_1p0)
print "PM2.5:\t{}".format(meas.mc_2p5)
print "PM4.0:\t{}".format(meas.mc_4p0)
print "PM10.0:\t{}".format(meas.mc_10p0)
print "NC0.5:\t{}".format(meas.nc_0p5)
print "NC1.0:\t{}".format(meas.nc_1p0)
print "NC2.5:\t{}".format(meas.nc_2p5)
print "NC4.0:\t{}".format(meas.nc_4p0)
print "NC10.0:\t{}".format(meas.nc_10p0)
print "TypSz:\t{}".format(meas.typical_particle_size)
time.sleep(2)


+ 0
- 0
lib/rpjios/devices/__init__.py View File


+ 18
- 0
lib/rpjios/sensors/SPS30.py View File

@@ -0,0 +1,18 @@
from rpjios.devices.SPS30 import SPS30
from rpjios.SensorBase import Sensor, SensorName, SensorDesc

class Factory(Sensor):
@SensorName("SPS30")
@SensorDesc("Sensiron SPS30 particulate matter sensor")
def __init__(self, *args, **kwargs):
if 'frequency' in kwargs and int(kwargs['frequency']) > 1:
raise BaseException("SPS30 only supports frequencies of 1Hz or less")
super(Factory, self).__init__(*args, **kwargs)
self._sps = SPS30(**kwargs)
self._meta['serial_number'] = self._sps.serial_number
self._meta['driver_version'] = self._sps.driver_version

def _runloop(self):
meas = self._sps.measurement
if meas is not None:
self.publish(self._attrs_to_dict(meas))

+ 49
- 9
setup.sh View File

@@ -20,8 +20,8 @@ check_and_install() {
if [ -z "$_N" ]; then
_N=$PKGNAME
fi
echo "* Trying to install '${_N}..."
sudo apt -y install ${_N}
echo "* Trying to install ${_N}..."
sudo apt -y install ${_N} > ${_N}_apt-install.stdout 2> ${_N}_apt-install.stderr
else
echo "found"
fi
@@ -33,11 +33,38 @@ link_local_py_lib() {
echo "* Linking local Python library '${LIBDIR}'"
if [ ! -L "env/lib/python2.7/site-packages/${LIBDIR}" ]; then
pushd "env/lib/python2.7/site-packages/" > /dev/null
ln -s "../../../../${LIBDIR}"
ln -s "../../../../${LIBDIR}" 2> /dev/null > /dev/null
popd > /dev/null
fi
}

build_embedded_sps() {
echo -n "* Fetching embedded-sps submodule: "
git submodule init >> git-setup.stdout 2>> git-setup.stderr
git submodule update --recursive >> git-setup.stdout 2>> git-setup.stderr
pushd embedded-sps > /dev/null
git submodule init >> git-setup.stdout 2>> git-setup.stderr
git submodule update --recursive >> git-setup.stdout 2>> git-setup.stderr
echo "done"
echo -n "* Building embedded-sps submodule: "
make release > make.release.stdout 2> make.release.stderr
pushd release/sps30-i2c > /dev/null
pushd hw_i2c > /dev/null
mv sensirion_hw_i2c_implementation.c sensirion_hw_i2c_implementation.c.orig
ln -s sample-implementations/linux_user_space/sensirion_hw_i2c_implementation.c
popd > /dev/null
make > make.stdout 2> make.stderr
if [ -f libsps30.so ]; then
numSyms=`nm libsps30.so | grep -i sps | wc -l`
cp libsps30.so ../../../lib/rpjios/devices/
echo "success (${numSyms})"
else
echo "failure!"
fi
popd > /dev/null
popd > /dev/null
}


cat /etc/os-release | perl -ne "exit(1), if (/ID_LIKE=debian/)"
if [ $? != 1 ]; then
@@ -50,22 +77,34 @@ REQ_FILE=requirements.txt

cat /etc/os-release | perl -ne "exit(1), if (/ID=raspbian/)"
if [ $? != 1 ]; then
echo "* Looks like you're installing on a non-RPi platform; omitting unneeded modules."
echo "*** Non-RPi platform detected: omitting unneeded modules."
IS_RPI=0
REQ_FILE=requirements-nonRPi.txt
else
echo "*** RPi platform detected: building sensor drivers and including hardware interface modules."
fi

echo

check_and_install "which" "virtualenv"
check_and_install "which" "redis-server" "redis"
check_and_install "apt" "redis-server"
check_and_install "which" "zip"
check_and_install "apt" "python-dev"

if [ ${IS_RPI} == 1 ]; then
check_and_install "apt" "python-smbus"
build_embedded_sps
fi

if [ ! -d "./env" ]; then
echo "* Initializing virtualenv:"
virtualenv --system-site-packages --prompt="(rpjios virtualenv) " ./env
echo -n "* Initializing virtualenv: "
virtualenv --system-site-packages --prompt="(rpjios venv) " ./env > virtualenv-init.stdout 2> virtualenv-init.stderr
if [ $? != 0 ]; then
echo "failed! Cannot continue."
exit -1
else
echo "done"
fi
fi

source env/bin/activate
@@ -73,11 +112,12 @@ source env/bin/activate
link_local_py_lib "lib/rpjios"

echo -n "* Installing required python modules from '${REQ_FILE}': "
pip install -r ${REQ_FILE}
pip install -r ${REQ_FILE} > pip-install.stdout 2> pip-install.stderr
echo "done"

if [ $? == 0 ]; then
echo ""
echo "*** Done! Run 'source env/bin/activate' to get started."
echo "*** Success! Run 'source env/bin/activate' to get started."
else
echo "*** ERROR ***"
fi

Loading…
Cancel
Save