Skip to content

Instantly share code, notes, and snippets.

@hfiennes
Created October 1, 2019 01:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hfiennes/7f8892b1856961b53c26b23d14ef60ee to your computer and use it in GitHub Desktop.
Save hfiennes/7f8892b1856961b53c26b23d14ef60ee to your computer and use it in GitHub Desktop.
Sensirion SPS30 air quality sensor
// Sensirion SPS30 air quality sensor
// hugo@electricimp.com
// uartABCD is on Grove J9
uart <- hardware.uartABCD;
// Current serial state and collected packet
state <- -1;
packet <- "";
// Send a packet to the sensor; this does the checksum calculation, stuffing, and HDLC framing
function sendpacket(addr, cmd, data) {
// Form core of packet
local packet = format("%c%c%c%s", addr, cmd, data.len(), data);
// Calculate checksum
local chk = 0;
foreach(c in packet) chk += c;
packet += format("%c", chk ^ 0xff);
// Byte-stuff as needed
local stuffed = "";
foreach(c in packet) {
if (c == 0x7d || c == 0x7e || c == 0x11 || c == 0x13) {
// Stuff it
stuffed += "\x7d"+(c ^ 0x20).tochar();
} else {
// No need to stuff
stuffed += c.tochar();
}
}
// Send it with start and stop
uart.write("\x7e"+stuffed+"\x7e");
}
// Callback to deal with receiving a single byte
function rx() {
local b = uart.read();
// Waiting for start of a packet?
if (state == -1) {
// Packets always start with 0x7e
if (b == 0x7e) {
packet = "";
state++;
return;
}
}
// Collect bytes until another 0x7e
if (b != 0x7e) packet += b.tochar();
else {
// Did we just see another 0x7e, with nothing collected?
// We're getting back in sync
if (packet == "") return;
// Process the received packet & reset state
rxpacket(packet);
state = -1;
}
}
// We've got a whole packet, check that it's valid
function rxpacket(pkt) {
// Unstuff packet
local unstuffed = "";
local xor = 0;
foreach(c in pkt) {
if (c == 0x7d) xor = 0x20;
else {
// Unstuff if needed, reset stuff indicator
unstuffed += (c ^ xor).tochar();
xor = 0;
}
}
// Remove checksum from received packet
local rxchk = unstuffed[unstuffed.len()-1];
unstuffed = unstuffed.slice(0, unstuffed.len()-1);
// Calculate checksum
local chk = 0;
foreach(c in unstuffed) chk += c;
chk = (chk & 0xff) ^ 0xff;
// Do they match?
if (chk == rxchk) {
// Process received packet
switch(unstuffed[1]) {
case 0x03: // Reading packet
// Check length is as expected
if (unstuffed.len() != 44) break;
// Move it into a blob to extract the floats
local b = blob();
b.writestring(unstuffed.slice(4));
b.seek(0, 'b');
// Swap endianness
b.swap4();
// Extract data: readings are IEEE754 floats
local reading = {
"mass_pm1_0": b.readn('f'),
"mass_pm2_5": b.readn('f'),
"mass_pm4_0": b.readn('f'),
"mass_pm10": b.readn('f'),
"number_pm0_5": b.readn('f'),
"number_pm1_0": b.readn('f'),
"number_pm2_5": b.readn('f'),
"number_pm4_0": b.readn('f'),
"number_pm10": b.readn('f'),
"typ_size": b.readn('f')
};
// Send to agent
agent.send("telemetry", reading);
break;
}
} else {
server.log(format("chk error rx %02x calc %02x", rxchk, chk))
}
}
// Configure UART and attach receive handler
uart.configure(115200, 8, PARITY_NONE, 1, NO_CTSRTS, rx);
const START_MEASUREMENT = 0x00;
const READ_MEASURED_VALUES = 0x03;
// Ensure fan is started
sendpacket(0, START_MEASUREMENT, "\x01\x03");
// Every second, kick off a reading; the receive handler will collect the
// data, parse it, and send it to the cloud
function takereading() {
// Ask sensor for data
sendpacket(0, READ_MEASURED_VALUES, "");
// Do it again in 10 seconds
imp.wakeup(10, takereading);
}
takereading();
server.log("AQ sensor online");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment