Created
February 1, 2019 03:47
-
-
Save hfiennes/6876140e6a574ead6865f5c1399b7876 to your computer and use it in GitHub Desktop.
Sensirion SPS30 air quality sensor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Ensure we have a default reading | |
lastreading <- { "mass_pm1.0":0, "mass_pm2.5":0, "mass_pm4.0":0, "mass_pm10":0 }; | |
// Code to convert particlate density to AQI index | |
// based on https://gist.github.com/kfury/822bbba2cb0f946abb73baa156722ab1 | |
function Linear(AQIhigh, AQIlow, Conchigh, Conclow, Conc) { | |
local a=((Conc-Conclow)/(Conchigh-Conclow))*(AQIhigh-AQIlow)+AQIlow; | |
return math.floor(a+0.5); | |
} | |
function PM25toAQI(Conc) { | |
local c=(math.floor(10*Conc))/10; | |
local AQI = 9999; | |
if (c>=0 && c<12.1) { | |
AQI=Linear(50,0,12,0,c); | |
} else if (c>=12.1 && c<35.5) { | |
AQI=Linear(100,51,35.4,12.1,c); | |
} else if (c>=35.5 && c<55.5) { | |
AQI=Linear(150,101,55.4,35.5,c); | |
} else if (c>=55.5 && c<150.5) { | |
AQI=Linear(200,151,150.4,55.5,c); | |
} else if (c>=150.5 && c<250.5) { | |
AQI=Linear(300,201,250.4,150.5,c); | |
} else if (c>=250.5 && c<350.5) { | |
AQI=Linear(400,301,350.4,250.5,c); | |
} else if (c>=350.5 && c<500.5) { | |
AQI=Linear(500,401,500.4,350.5,c); | |
} | |
return AQI; | |
} | |
// When we get data from the device, log it and keep a copy | |
device.on("aq", function(v) { | |
lastreading = v; | |
//server.log(http.jsonencode(lastreading)); | |
}); | |
// Handle slack calls | |
http.onrequest(function(req, res) { | |
local status = format("Current air quality (Sensirion SPS30): PM1.0: %.1fug/m3, PM2.5: %.1fug/m3, PM4: %.1fug/m3, PM10: %.f1ug/m3, ", | |
lastreading["mass_pm1.0"], lastreading["mass_pm2.5"], | |
lastreading["mass_pm4.0"], lastreading["mass_pm10"]); | |
local aqi = PM25toAQI(lastreading["mass_pm2.5"]); | |
status += format("PM2.5 AQI index %d ", aqi); | |
if (aqi < 51) status += "(good)"; | |
else if (aqi < 101) status += "(moderate)"; | |
else if (aqi < 151) status += "(unhealthy for sensitive groups)"; | |
else if (aqi < 201) status += "(unhealthy)"; | |
else if (aqi < 301) status += "(very unhealthy)"; | |
else if (aqi < 501) status += "(hazardous)"; | |
else status += "(get out of there)"; | |
server.log("http request from: "+req.headers["x-forwarded-for"]+" - "+status); | |
// Check for slack | |
if (req.path == "/slack") { | |
res.send(200, http.jsonencode({"text": status})); | |
} else { | |
// Otherwise, if someone hits the agent, just send status as plain text | |
res.send(200, status+"\n"); | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Sensirion SPS30 air quality sensor | |
// hugo@electricimp.com 20190131 | |
// Connected to an imp001 april board: | |
// pin 1 on SPS30 to VCC on april | |
// pin 2 on SPS30 to pin 8 on april | |
// pin 3 on SPS30 to pin 9 on april | |
// pin 4 on SPS30 not connected | |
// pin 5 on SPS30 to GND on april | |
uart <- hardware.uart1289; | |
// 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"); | |
} | |
// 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 | |
rxpacket(packet); | |
// Reset state for next one | |
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 | |
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') | |
}; | |
agent.send("aq", 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 a second | |
imp.wakeup(1, 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