Last active
June 22, 2019 15:46
-
-
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
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
#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 | |
// v1.2 - 2019-06-22 - Increase WiFi connection timeout | |
// 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 MQTT_MAX_TRIES 5 | |
#define WIFI_MAX_TRIES 15 | |
// How many samples to average out per round of measurement | |
#define N_SAMPLES_PER_ROUND 10 | |
#define N_BATTERY_SAMPLES 5 | |
// 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(N_BATTERY_SAMPLES); | |
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 < WIFI_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 < MQTT_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