Skip to content

Instantly share code, notes, and snippets.

@FLamparski
Last active June 22, 2019 15:46
Show Gist options
  • Save FLamparski/93af1ac4f49c2fde550a36f14a0d9446 to your computer and use it in GitHub Desktop.
Save FLamparski/93af1ac4f49c2fde550a36f14a0d9446 to your computer and use it in GitHub Desktop.
A program for collecting data from an SDS-011 PM 2.5 sensor
#include <ArduinoJson.h>
#include <WiFi.h>
#include <SdsDustSensor.h>
#include <PubSubClient.h>
// v1.0 - 2019-06-16 - Initial public release of script
// v1.1 - 2019-06-17 - Turn off sensor if resetting due to lack of connectivity
// XXX Change these to your WiFi details
// It may be a good idea to run a separate, segregated WLAN for
// Internet of Things devices. Up to you.
#define WIFI_SSID "CHANGE ME"
#define WIFI_PASS "CHANGE ME"
// XXX Change this if running more than one sensor
#define CLIENT_ID "airquality0"
// Which topic to publish to
#define TOPIC "airquality"
// How many retries for wifi connection before reset
#define MAX_TRIES 5
// How many samples to average out per round of measurement
#define N_SAMPLES_PER_ROUND 10
// What pin is the battery on
#define BATTERY_PIN 34
// What pin is the onboard LED on
#define ONBOARD_LED 2
#define SLEEP_SECONDS 30
#define SEC_TO_USEC_FACTOR 1000000
// measureJson from ArduinoJson reports the JSON size WITHOUT trailing null byte,
// this helper macro adds it
#define MEASURE_JSON_BUFFER(doc) (measureJson(doc) + 1)
// The dust/PM sensor is on Serial2
SdsDustSensor sds(Serial2);
// Set up a MQTT client on WiFi
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
// TODO: verify battery voltage reporting
void wifi_reconnect();
void mqtt_reconnect();
void setup_sensor();
int do_measurement(float* avg_pm25, float* avg_pm10);
float get_battery(int n_samples);
// The main entry point
void setup() {
// Configure GPIO
pinMode(ONBOARD_LED, OUTPUT);
digitalWrite(ONBOARD_LED, LOW);
adcAttachPin(BATTERY_PIN);
analogSetPinAttenuation(BATTERY_PIN, ADC_11db);
// Configure debug
Serial.begin(115200);
// Configure the SDS sensor
setup_sensor();
// Get sensor info
Serial.printf("[SDS] version %s\n", sds.queryFirmwareVersion().toString().c_str()); // prints firmware version
// Connect to MQTT - this also gives time for the sensor to stabilise (hacky, but w/e)
wifi_reconnect();
mqtt_reconnect();
// Do the measurement and turn off the sensor when done
float avg_pm25, avg_pm10;
int n_fail = do_measurement(&avg_pm25, &avg_pm10);
sds.sleep();
// Get battery level (average of 5 measurements)
float battery_voltage = get_battery(5);
Serial.printf("battery = %.2fv\n", battery_voltage);
// Put everything into a JSON document
StaticJsonDocument<512> doc;
doc["client"] = CLIENT_ID;
doc["pm25"] = avg_pm25;
doc["pm10"] = avg_pm10;
doc["batt"] = battery_voltage;
doc["err"] = n_fail;
// How much size do we need to allocate for the JSON buffer?
size_t payload_len = MEASURE_JSON_BUFFER(doc);
char payload[payload_len];
// Serialize the JSON document and publish it to MQTT
serializeJson(doc, payload, payload_len);
mqtt.publish(TOPIC, payload);
// Wait to make sure everything is published
delay(500);
mqtt.loop();
// And now, shut down WiFi and go to sleep.
digitalWrite(ONBOARD_LED, LOW);
WiFi.disconnect(true);
ESP.deepSleep(SLEEP_SECONDS * SEC_TO_USEC_FACTOR);
}
void loop() {
// Never reached
}
// Connects to WiFi, blinks onboard LED while connecting
void wifi_reconnect() {
digitalWrite(ONBOARD_LED, LOW);
// Connect to WiFi
Serial.println("[WL ] connecting");
WiFi.begin(WIFI_SSID, WIFI_PASS);
for (int n = 0; n < MAX_TRIES && WiFi.status() != WL_CONNECTED; n++) {
digitalWrite(ONBOARD_LED, LOW);
delay(500);
digitalWrite(ONBOARD_LED, HIGH);
delay(500);
}
if (WiFi.status() != WL_CONNECTED) {
digitalWrite(ONBOARD_LED, LOW);
Serial.println("[WL ] max tries exceeded, trying to reboot");
WiFi.disconnect();
sds.sleep();
delay(1000);
ESP.deepSleep(SLEEP_SECONDS * SEC_TO_USEC_FACTOR);
}
Serial.println("[WL ] connected");
}
// Connects to MQTT, possibly blinks onboard LED
void mqtt_reconnect() {
mqtt.setServer("iot-server.lan", 1883);
for (int tries = 0; tries < MAX_TRIES && !mqtt.connected(); tries++) {
digitalWrite(ONBOARD_LED, HIGH);
if (!mqtt.connect(CLIENT_ID)) {
digitalWrite(ONBOARD_LED, LOW);
Serial.printf("[MQ ] fail: %d\n", mqtt.state());
delay(500);
}
}
if (mqtt.connected()) {
digitalWrite(ONBOARD_LED, HIGH);
} else {
digitalWrite(ONBOARD_LED, LOW);
Serial.println("[MQ ] max tries exceeded, trying to reboot");
WiFi.disconnect(true);
sds.sleep();
delay(1000);
ESP.deepSleep(SLEEP_SECONDS * SEC_TO_USEC_FACTOR);
}
}
// Wakes up and resets the SDS-011 to a known state
void setup_sensor() {
sds.begin();
sds.wakeup();
sds.setQueryReportingMode(); // only report when asked
sds.setContinuousWorkingPeriod(); // run continuously
}
// Performs a measurement on the SDS-011
int do_measurement(float* avg_pm25, float* avg_pm10) {
int n_ok = 0;
int n_fail = 0;
float sum_pm25 = 0.0f;
float sum_pm10 = 0.0f;
for (int n = 0; n < N_SAMPLES_PER_ROUND; n++) {
Serial.print("*");
PmResult res = sds.queryPm();
if (res.isOk()) {
n_ok++;
sum_pm25 += res.pm25;
sum_pm10 += res.pm10;
} else {
n_fail++;
}
mqtt.loop();
delay(1000);
}
Serial.println();
*avg_pm25 = sum_pm25 / n_ok;
*avg_pm10 = sum_pm10 / n_ok;
return n_fail;
}
// Reads battery level
float get_battery(int n_samples) {
float sum_reading = 0.0;
for (int n = 0; n < n_samples; n++) {
int batteryAdcCount = analogRead(BATTERY_PIN);
// Battery voltage is given by the ADC as x/4096 * 3.3 for the voltage divider
// and * 2 for the full voltage.
sum_reading += ((float) batteryAdcCount) / 4096.0 * 3.3 * 2;
}
return sum_reading / ((float) n_samples);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment