Over the years I’ve built a few dust sensor systems (‘PM2.5’) with SHARP and SHINYEI sensors, but calibration was always a headache as they have analog output. Earlier this year (2019) i looked into the current sensor technology again, and found out that PLANTOWER has established itself as a leader, supplying laser based optical sensors (instead of infrared) with serial output. These sensors come with a built-in fan, and serial communication, so there is less room for errors. I picked up a few from Taobao for less than RMB 100 each (around US$13).
I will describe the hardware and software below, including some data visualisation combining the readings with those of official monitoring stations in Hong Kong.
There are quite a few models available and i think the PMSA003 is the latest, after the PMS7003, i think the A is hex for 10.
The sensor gives a lot of data points (see datasheet), including PM1, PM2.5, PM10 (in 2 variants: ‘standard’ and ‘atmospheric’). I’m only interested in ‘atmospheric’ PM2.5; the standard variant is for factory environments according to the datasheet.
I made 2 versions of this project:
- a portable Arduino NANO based one with an OLED screen, buzzer, and pot to set an alarm level, to be powered by powerbank 5V USB)
- a stationary ESP8266 based one with a buzzer, permanently plugged into 5V, sending data over WiFi to a cloud server
Reading the sensor is quite straightforward; in the default state, the unit outputs 32 bytes over serial at 9600 baud, which need to be parsed to extract the readings. The data comes about every second, with some variation according to the datasheet. I used code provided by the Scapeler project. As i was happy with this rate, i did not have to connect the sensor’s RX pin: i’m only receiving data from the sensor, not sending any instructions.
Let’s look at the PORTABLE system first.
I use an Arduino NANO because it’s cheap and small, and easy to plug into 2 rows of 15-pin female headers.
The 8-pin connector on the PMSA003 is tiny, and it’s really hard to solder on it, so i advise to get a adapter board. My adapter board has a connector for a 8-pin ribbon cable, but also 4 male pins with the standard 2.5mm spacing. It has: 5V, GND, TX, RX so that is enough for this version.
I added a 10K potentiometer on A0 to set the alarm level for a buzzer: i can set the level from 0 to 100ug/m3. The buzzer beeps as long as the level is exceeded.
I use a 0.96in OLED screen with SPI interface, with the U8G library.
As i still wanted the Serial Monitor to work, i could not use the hardware serial on D0/D1, so we use a software serial on D6 and D7, but D7 (TX) does not need to be connected as we’re only receiving from the sensor. The sensor will be active all the time, with the fan spinning.
The connections are further explained in the code, and the soldering is quite easy as you can see from the bottom of the perf board.
The sensor needs 30 seconds to stabilise, so the code does a countdown before showing the first reading. It shows a Simple Moving Average of the 5 most recent readings.
/* BuffaloLabs Dust Sensor PMSA003 Arduino NANO on perf board PMSA003 Plantower dust sensor serial output every second in active mode can switch to passive mode to request data - not used here output is ug/m3 for PM1, PM2.5, PM10 in 2 different assumptions: industrial standard particles, and atmospheric particles we will use atmospheric as reference also output of 6 number concentrations: 0.3 0.5 1 2.5 5 10 - not used here use Simple Moving Average of 5 most recent readings OLED on 13,11,8,9,10 BUZZER on 3 POT on A0 SENSOR TX on 6 (7 dummy for RX, not connected) all powered by 5V Tom Tobback BuffaloLabs June 2019 */ /* Copyright 2017 Scapeler Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /////////////// SENSOR /////////////////////////////////// #include <SoftwareSerial.h> SoftwareSerial Serial1(6, 7); // serial ports RX, TX // input byte variables int inputHigh = 0; int inputLow = 0; // variable to caclulate checksum input variables uint16_t inputChecksum = 0; // sensor variables uint16_t concPM1_0_CF1; uint16_t concPM2_5_CF1; uint16_t concPM10_0_CF1; uint16_t concPM1_0_amb; uint16_t concPM2_5_amb; uint16_t concPM10_0_amb; uint16_t rawGt0_3um; uint16_t rawGt0_5um; uint16_t rawGt1_0um; uint16_t rawGt2_5um; uint16_t rawGt5_0um; uint16_t rawGt10_0um; uint8_t version; uint8_t errorCode; uint16_t checksum; int pm2_5, pm10; // readings float pm2_5_avg; // average int pm2_5_hist [5] = {0, 0, 0, 0, 0}; // history for averaging, using simple moving average int alarm_level; unsigned long last_alarm_timestamp; const unsigned long alarm_interval = 5000; // min time between alarm beeps ////////////// OLED ////////////////////////////////////// #include "U8glib.h" U8GLIB_SSD1306_128X64 u8g(10, 9); // OLED display: HW SPI CS = 10, A0/DC = 9 (Hardware Pins are SCK/D0 = 13 and MOSI/D1 = 11) + RST to Arduino D8 unsigned long success_timestamp; const unsigned long no_data_interval = 5000; // warning if no fresh data ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// void setup() { pinMode(8, OUTPUT); // RST pin for OLED display digitalWrite(8, LOW); delay(100); digitalWrite(8, HIGH); drawIntro(); tone(3, 500); delay(100); tone(3, 1000); delay(100); noTone(3); Serial.begin(115200); Serial.println("BuffaloLabs PMSA003 dust sensor"); Serial1.begin(9600); // software serial for sensor while (Serial1.read() != -1) {}; //clear buffer Serial.println("Sensor port ready"); Serial.println("Countdown 30s to stabilise"); delay(3000); drawCountdown(); // sensor needs 30 seconds to stabilise Serial.println("5 latest readings and average PM2.5 (ug/m3)"); } ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// void loop () { alarm_level = map(analogRead(A0), 0, 1023, 1, 100); if (pms7003ReadData()) { // if successful, reading takes at least 700ms because it includes a delay(700) // calculate average of 5 most recent readings for (int i = 0; i < 4; i++) { pm2_5_hist[i] = pm2_5_hist[i + 1]; // shift historic readings one down, drop oldest [0] if (pm2_5_hist[i] == 0) pm2_5_hist[i] = pm2_5; // at startup, if 0 (no data), use current reading } pm2_5_hist[4] = pm2_5; // last one is current reading pm2_5_avg = ( pm2_5_hist[0] + pm2_5_hist[1] + pm2_5_hist[2] + pm2_5_hist[3] + pm2_5_hist[4]) / 5.0; // simple moving average for (int i = 0; i < 5; i++) { Serial.print(pm2_5_hist[i]); Serial.print("\t"); } Serial.print("average: "); Serial.println(pm2_5_avg); drawData(); success_timestamp = millis(); } else { if (millis() - success_timestamp > no_data_interval) { drawWarning(); } } if (pm2_5_avg > alarm_level && millis() - last_alarm_timestamp > alarm_interval) { beeps(); last_alarm_timestamp = millis(); } } ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// bool pms7003ReadData() { // while (Serial1.read()!=-1) {}; //clear buffer if (Serial1.available() < 32) { if (Serial1.available() == 0) { delay(150); return false; }; if (Serial1.available() > 16) { delay(10); return false; }; if (Serial1.available() > 0) { delay(30); return false; }; delay(100); return false; } if (Serial1.read() != 0x42) return false; if (Serial1.read() != 0x4D) return false; inputChecksum = 0x42 + 0x4D; inputHigh = Serial1.read(); inputLow = Serial1.read(); inputChecksum += inputHigh + inputLow; if (inputHigh != 0x00) return false; if (inputLow != 0x1c) return false; inputHigh = Serial1.read(); inputLow = Serial1.read(); inputChecksum += inputHigh + inputLow; concPM1_0_CF1 = inputLow + (inputHigh << 8); inputHigh = Serial1.read(); inputLow = Serial1.read(); inputChecksum += inputHigh + inputLow; concPM2_5_CF1 = inputLow + (inputHigh << 8); inputHigh = Serial1.read(); inputLow = Serial1.read(); inputChecksum += inputHigh + inputLow; concPM10_0_CF1 = inputLow + (inputHigh << 8); inputHigh = Serial1.read(); inputLow = Serial1.read(); inputChecksum += inputHigh + inputLow; concPM1_0_amb = inputLow + (inputHigh << 8); inputHigh = Serial1.read(); inputLow = Serial1.read(); inputChecksum += inputHigh + inputLow; concPM2_5_amb = inputLow + (inputHigh << 8); inputHigh = Serial1.read(); inputLow = Serial1.read(); inputChecksum += inputHigh + inputLow; concPM10_0_amb = inputLow + (inputHigh << 8); inputHigh = Serial1.read(); inputLow = Serial1.read(); inputChecksum += inputHigh + inputLow; rawGt0_3um = inputLow + (inputHigh << 8); inputHigh = Serial1.read(); inputLow = Serial1.read(); inputChecksum += inputHigh + inputLow; rawGt0_5um = inputLow + (inputHigh << 8); inputHigh = Serial1.read(); inputLow = Serial1.read(); inputChecksum += inputHigh + inputLow; rawGt1_0um = inputLow + (inputHigh << 8); inputHigh = Serial1.read(); inputLow = Serial1.read(); inputChecksum += inputHigh + inputLow; rawGt2_5um = inputLow + (inputHigh << 8); inputHigh = Serial1.read(); inputLow = Serial1.read(); inputChecksum += inputHigh + inputLow; rawGt5_0um = inputLow + (inputHigh << 8); inputHigh = Serial1.read(); inputLow = Serial1.read(); inputChecksum += inputHigh + inputLow; rawGt10_0um = inputLow + (inputHigh << 8); inputLow = Serial1.read(); inputChecksum += inputLow; version = inputLow; inputLow = Serial1.read(); inputChecksum += inputLow; errorCode = inputLow; /* Serial.print("PMSA003;\t"); Serial.print(concPM1_0_CF1); Serial.print(';'); Serial.print(concPM2_5_CF1); Serial.print(';'); Serial.print(concPM10_0_CF1); Serial.print(";\t"); Serial.print(concPM1_0_amb); Serial.print(';'); Serial.print(concPM2_5_amb); Serial.print(';'); Serial.print(concPM10_0_amb); Serial.print(";\t"); Serial.print(rawGt0_3um); Serial.print(';'); Serial.print(rawGt0_5um); Serial.print(';'); Serial.print(rawGt1_0um); Serial.print(';'); Serial.print(rawGt2_5um); Serial.print(';'); Serial.print(rawGt5_0um); Serial.print(';'); Serial.print(rawGt10_0um); Serial.print(';'); Serial.print(version); Serial.print(';'); Serial.print(errorCode); */ inputHigh = Serial1.read(); inputLow = Serial1.read(); checksum = inputLow + (inputHigh << 8); if (checksum != inputChecksum) { Serial.print(';'); Serial.print(checksum); Serial.print(';'); Serial.print(inputChecksum); Serial.println(" CHECKSUM ERROR, ignore data"); return false; } // Serial.print('\n'); delay(700); // higher will get you checksum errors pm2_5 = concPM2_5_amb; pm10 = concPM10_0_amb; return true; } //////////////////////////////////////////////////////////////////////// void drawIntro() { u8g.firstPage(); do { u8g.setFont(u8g_font_unifont); u8g.drawStr(10, 15, "BuffaloLabs"); // u8g.setFont(u8g_font_fub25r); u8g.drawStr(10, 40, "PM2.5 SENSOR"); u8g.drawStr(10, 55, "with alarm"); } while ( u8g.nextPage() ); } //////////////////////////////////////////////////////////////////////// void drawWarning() { u8g.firstPage(); do { u8g.setFont(u8g_font_unifont); u8g.drawStr(10, 15, "WARNING"); // u8g.setFont(u8g_font_fub25r); u8g.drawStr(10, 40, "no fresh data"); u8g.drawStr(10, 55, "sensor connected?"); } while ( u8g.nextPage() ); } //////////////////////////////////////////////////////////////////////// void drawData() { u8g.firstPage(); do { u8g.setFont(u8g_font_unifont); u8g.drawStr(10, 15, "PM2.5 sensor"); u8g.setFont(u8g_font_fub25r); u8g.setPrintPos(10, 45); u8g.print(pm2_5_avg, 0); u8g.setFont(u8g_font_unifont); u8g.print("ug/m3"); u8g.setPrintPos(10, 60); u8g.print("alarm:"); u8g.print(alarm_level); u8g.print("ug/m3"); } while ( u8g.nextPage() ); } //////////////////////////////////////////////////////////////////////// void drawCountdown() { while (30000 - millis() < 29000) { // funny way with unsigned long, more easy: millis() - 30000 < 0 alarm_level = map(analogRead(A0), 0, 1023, 1, 100); // allow alarm setting during wait u8g.firstPage(); do { u8g.setFont(u8g_font_unifont); u8g.drawStr(10, 15, "PM2.5 sensor"); u8g.drawStr(10, 30, "stabilising.."); u8g.setPrintPos(10, 45); u8g.print("wait "); u8g.print((30000 - millis()) / 1000); u8g.print(" sec"); u8g.setPrintPos(10, 60); u8g.print("alarm:"); u8g.print(alarm_level); u8g.print("ug/m3"); } while ( u8g.nextPage() ); delay(100); } } //////////////////////////////////////////////////////////////////////// void beeps() { for (int u = 0; u < 5; u++) { tone(3, 2000); delay(100); noTone(3); delay(100); } delay(500); // buzzer draws a lot of current, let arduino stabilise }
Now let’s look at the WIFI version with the ESP8266. I used a NodeMCUv1 module which includes a USB converter. It is powered from a 5V USB wall plug. The only external parts are a piezo buzzer with a 220ohm resistor in series to limit the current to the buzzer.
The sensor is powered by 5V and GND, and again we’re only interested in its TX pin as we’re not going to send any instructions to the sensor. To save power, and probably more importantly, to save wear of the components, i decided to use the SLEEP function of the sensor (pin 10). This pin can switch off the sensor, stopping the fan.
Again we want to use the hardware serial RX TX for debugging on the Serial Monitor, so we use a software serial for the sensor, at 9600 baud. The loop() wakes the sensor up with the SLEEP pin, waits for 30 seconds, then reads the sensor and sends the data. If the PM2.5 level exceeds a certain pre-set level, the buzzer beeps. Then the sensor is put to sleep for 1 minute.
For cloud server i use emoncms.org which has been available for more than 5 years and is well suited for this application; easy to visualise data over time.
Initially my main concern was outdoor air quality. I live in Mui Wo village in Hong Kong, with no official air quality monitoring stations nearby, somewhere half way between the stations of Tung Chung and Central/Western. I wanted to compare my readings to the official numbers, so i wrote some code for the ESP8266 to harvest the current official readings from the the website, e.g. for Tung Chung. I found that the data comes from an XML file, and my code is extracting the current PM2.5 and PM10 from that file. Same for data from Central/Western. If you don’t live in HK, just comment out the ‘updateEPDdata()’ function, or modify it for your own location!
Above graph, with my PMSA003 sensor data for PM2.5 in pink (filled), shows a good correlation with the official (HK EPD) data in red and yellow. The graph is too busy with also the PM10 showing, so let’s look at only PM2.5
The PMSA003 sensor readings (2 sensors: red and pink) correlate well with the official data, despite the fact that:
- both sensors are placed indoor; due to very imperfect insulation the indoor air generally seems to follow the outdoor air trend
- the 2 official stations are respectively 5 and 15km away
Both sensors have a few spikes, and these are due to soldering(!) activity indoor. The smoke created by soldering immediately puts the PM2.5 well above 25ug/m3 (WHO 24-hour mean guideline), and spreads from the ground floor (red) to the first floor (pink fill), lingering for several hours.
Running a small active carbon filter near the soldering activity does not seem to make much difference in PM2.5
I am not sure how harmful this soldering smoke is, but my advice to all: hot summer or freezing winter, open a window whenever you are soldering!
/* PM sensor PMSA003 with ESP8266 NodeMCU v1 serial output every second in active mode can switch to passive mode to request data - not used here output is ug/m3 for PM1, PM2.5, PM10 in 2 different assumptions: industrial standard particles, and atmospheric particles we will use atmospheric as reference also output of 6 number concentrations: 0.3 0.5 1 2.5 5 10 - not used here sensor pin connections to NodeMCU: pin1: 5V VIN pin3: GND GND pin9: TX D2 (GPIO4) pin10: SLEEP D5 (GPIO14) RX pin is not connected but software serial defined on dummy GPIO5 buzzer on D6 (GPIO12) with 220ohm resistor to limit current (esp8266 can only do 12mA?) connecting to EPD website to retrieve official PM10 and PM2.5 data for Tung Chung (78) and Central/West (80) see www.aqhi.gov.hk/en/aqhi/past-24-hours-pollutant-concentration45fd.html?stationid=80 based on Scapeler example github Copyright 2017 Scapeler Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ //------------------------------------------------------------------------------ /////////////////////////////// WIFI STUFF ///////////////////////////////////////////// #include <ESP8266WiFi.h> const char* ssid = "xxx"; const char* password = "xxx"; #define DST_IP "80.243.190.58" // emoncms.org #define APIKEY "xxx" #define NODE 31 /////////////////////////////// EPD DATA /////////////////////////////////////////////// float tc_pm10; float tc_pm25; float cw_pm10; float cw_pm25; /////////////////////////////// SENSOR ///////////////////////////////////////////////// #include <SoftwareSerial.h> SoftwareSerial soft_serial(4, 5); // serial ports RX, TX (D2/D1 but only need RX=D2) // input byte variables int inputHigh = 0; int inputLow = 0; // variable to caclulate checksum input variables uint16_t inputChecksum = 0; // sensor variables uint16_t concPM1_0_CF1; uint16_t concPM2_5_CF1; uint16_t concPM10_0_CF1; uint16_t concPM1_0_amb; uint16_t concPM2_5_amb; uint16_t concPM10_0_amb; uint16_t rawGt0_3um; uint16_t rawGt0_5um; uint16_t rawGt1_0um; uint16_t rawGt2_5um; uint16_t rawGt5_0um; uint16_t rawGt10_0um; uint8_t version; uint8_t errorCode; uint16_t checksum; int pm2_5, pm10; float avg_pm2_5, avg_pm10; // because average is not int const int SLEEP_PIN = 14; // D5 const int LED_PIN = 2; // on-board, inverted const int BUZZER_PIN = 12; // D6 const int samples = 5; const int sleep_seconds = 60; const int alarm_level = 40; /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// void setup() { pinMode(SLEEP_PIN, OUTPUT); // sleep pin of sensor digitalWrite(SLEEP_PIN, HIGH); // sensor awake pinMode(LED_PIN, OUTPUT); // on-board led digitalWrite(LED_PIN, HIGH); // led off tone(BUZZER_PIN, 500); delay(100); tone(BUZZER_PIN, 1000); delay(100); noTone(BUZZER_PIN); Serial.begin(115200); soft_serial.begin(9600); while (soft_serial.read() != -1) {}; //clear buffer Serial.println("============================="); Serial.println("BuffaloLabs PM2.5 sensor node"); Serial.println("ESP8266 with PMSA003 sensor"); Serial.println("Parameters:"); Serial.println("Samples: " + String(samples)); Serial.println("Sleep seconds: " + String(sleep_seconds)); Serial.println("Alarm level: " + String(alarm_level)); Serial.println("Sensor port ready"); WiFi.begin(ssid, password); Serial.print("connecting to wifi"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("OK"); /* Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); */ /* Serial.println(tc_pm10); Serial.println(tc_pm25); Serial.println(cw_pm10); Serial.println(cw_pm25); */ Serial.println("============================="); } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// void loop () { Serial.println("wake up sensor and wait 30 seconds..."); digitalWrite(SLEEP_PIN, HIGH); // wake up sensor updateEPDdata(); // this will take a few seconds Serial.print(">>> EPD data= "); Serial.print(tc_pm25); Serial.print("\t"); Serial.print(tc_pm10); Serial.print("\t"); Serial.print(cw_pm25); Serial.print("\t"); Serial.print(cw_pm10); Serial.println(); delay(30 * 1000); // wait for stability digitalWrite(LED_PIN, LOW); // led on // take average of 5 readings float cum_pm2_5 = 0; // will be int but average should be float so this assures the division returns a float float cum_pm10 = 0; int counter = 0; while (counter < samples) { while (!pmsA003ReadData()) {}; // wait for successful reading counter++; cum_pm2_5 += pm2_5; cum_pm10 += pm10; Serial.print(pm2_5); Serial.print("\t"); Serial.print(pm10); Serial.println(); } avg_pm2_5 = cum_pm2_5 / counter; avg_pm10 = cum_pm10 / counter; Serial.print(">>> PM2.5= "); Serial.print(avg_pm2_5); Serial.print("\t PM10= "); Serial.print(avg_pm10); Serial.println(" ug/m3"); digitalWrite(LED_PIN, HIGH); // led off Serial.println("sleep sensor..."); digitalWrite(SLEEP_PIN, LOW); // sleep sensor sendData(); if (avg_pm2_5 > alarm_level) { Serial.println(">>> ALARM beeps"); beeps(); } Serial.println("wait 1 minute before next cycle..."); Serial.println("--------------------------------------"); delay(sleep_seconds * 1000); } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// bool pmsA003ReadData() { // while (soft_serial.read()!=-1) {}; //clear buffer if (soft_serial.available() < 32) { if (soft_serial.available() == 0) { delay(150); return 0; }; if (soft_serial.available() > 16) { delay(10); return 0; }; if (soft_serial.available() > 0) { delay(30); return 0; }; delay(100); return 0; } if (soft_serial.read() != 0x42) return 0; if (soft_serial.read() != 0x4D) return 0; inputChecksum = 0x42 + 0x4D; inputHigh = soft_serial.read(); inputLow = soft_serial.read(); inputChecksum += inputHigh + inputLow; if (inputHigh != 0x00) return 0; if (inputLow != 0x1c) return 0; inputHigh = soft_serial.read(); inputLow = soft_serial.read(); inputChecksum += inputHigh + inputLow; concPM1_0_CF1 = inputLow + (inputHigh << 8); inputHigh = soft_serial.read(); inputLow = soft_serial.read(); inputChecksum += inputHigh + inputLow; concPM2_5_CF1 = inputLow + (inputHigh << 8); inputHigh = soft_serial.read(); inputLow = soft_serial.read(); inputChecksum += inputHigh + inputLow; concPM10_0_CF1 = inputLow + (inputHigh << 8); inputHigh = soft_serial.read(); inputLow = soft_serial.read(); inputChecksum += inputHigh + inputLow; concPM1_0_amb = inputLow + (inputHigh << 8); inputHigh = soft_serial.read(); inputLow = soft_serial.read(); inputChecksum += inputHigh + inputLow; concPM2_5_amb = inputLow + (inputHigh << 8); inputHigh = soft_serial.read(); inputLow = soft_serial.read(); inputChecksum += inputHigh + inputLow; concPM10_0_amb = inputLow + (inputHigh << 8); inputHigh = soft_serial.read(); inputLow = soft_serial.read(); inputChecksum += inputHigh + inputLow; rawGt0_3um = inputLow + (inputHigh << 8); inputHigh = soft_serial.read(); inputLow = soft_serial.read(); inputChecksum += inputHigh + inputLow; rawGt0_5um = inputLow + (inputHigh << 8); inputHigh = soft_serial.read(); inputLow = soft_serial.read(); inputChecksum += inputHigh + inputLow; rawGt1_0um = inputLow + (inputHigh << 8); inputHigh = soft_serial.read(); inputLow = soft_serial.read(); inputChecksum += inputHigh + inputLow; rawGt2_5um = inputLow + (inputHigh << 8); inputHigh = soft_serial.read(); inputLow = soft_serial.read(); inputChecksum += inputHigh + inputLow; rawGt5_0um = inputLow + (inputHigh << 8); inputHigh = soft_serial.read(); inputLow = soft_serial.read(); inputChecksum += inputHigh + inputLow; rawGt10_0um = inputLow + (inputHigh << 8); inputLow = soft_serial.read(); inputChecksum += inputLow; version = inputLow; inputLow = soft_serial.read(); inputChecksum += inputLow; errorCode = inputLow; /* Serial.print("PMSA003;\t"); Serial.print(concPM1_0_CF1); Serial.print(';'); Serial.print(concPM2_5_CF1); Serial.print(';'); Serial.print(concPM10_0_CF1); Serial.print(";\t"); Serial.print(concPM1_0_amb); Serial.print(';'); Serial.print(concPM2_5_amb); Serial.print(';'); Serial.print(concPM10_0_amb); Serial.print(";\t"); Serial.print(rawGt0_3um); Serial.print(';'); Serial.print(rawGt0_5um); Serial.print(';'); Serial.print(rawGt1_0um); Serial.print(';'); Serial.print(rawGt2_5um); Serial.print(';'); Serial.print(rawGt5_0um); Serial.print(';'); Serial.print(rawGt10_0um); Serial.print(';'); Serial.print(version); Serial.print(';'); Serial.print(errorCode); */ inputHigh = soft_serial.read(); inputLow = soft_serial.read(); checksum = inputLow + (inputHigh << 8); if (checksum != inputChecksum) { /* Serial.print(';'); Serial.print(checksum); Serial.print(';'); Serial.print(inputChecksum); */ return 0; } delay(700); // higher will get you checksum errors pm2_5 = concPM2_5_amb; pm10 = concPM10_0_amb; return 1; } /////////////////////////////////////////////////////////////////////////////////// void sendData() { Serial.print(">>> sending to emoncms.org.. "); WiFiClient client; if (!client.connect(DST_IP, 80)) { Serial.println("connection failed"); return; } String url = "/input/post.json?node="; url += NODE; url += "&apikey="; url += APIKEY; url += "&csv="; url += 0; // sharp sensor value url += ","; url += tc_pm10; url += ","; url += tc_pm25; url += ","; url += cw_pm10; url += ","; url += cw_pm25; url += ","; url += avg_pm2_5; // sensor value url += ","; url += avg_pm10; // sensor value client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: emoncms.org\r\n" + "Connection: close\r\n\r\n"); delay(10); // Serial.println(url); /* unsigned long send_timeout = millis(); while (client.available() == 0) { if (millis() - send_timeout > 5000) { Serial.println(">>> Client Timeout !"); client.stop(); return; } } */ // if server replies OK, send success to UNO if (client.find("200 OK")) { Serial.println(" sent OK"); } else { Serial.println(" failed"); } // flush rest of server reply while (client.available()) { // read the remaining text from serial otherwise connection cannot be closed client.read(); } client.stop(); } //////////////////////////////////////////////////////////////////////// void updateEPDdata() { float pm10, pm25; // Serial.println("start update EPD"); WiFiClient client; if (!client.connect("www.aqhi.gov.hk", 80)) { // Serial.println("connection failed"); return; } String url = "/epd/ddata/html/h24_concentration_Eng_78.xml"; // tung chung client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: www.aqhi.gov.hk\r\n\r\n"); // "Connection: close\r\n\r\n"); // do not close delay(10); // Serial.println(url); unsigned long timeout = millis(); while (client.available() == 0) { if (millis() - timeout > 5000) { // Serial.println(">>> Client Timeout !"); client.stop(); return; } } // Read all the lines of the reply from server and print them to Serial if (client.available()) { // parse the XML page; look for station name, then count columns if (client.find("Tung Chung")) { for (int i = 0; i < 11; i++) { client.find("H24C_ColItem"); } pm10 = client.parseFloat(); client.find("H24C_ColItem"); pm25 = client.parseFloat(); if (pm10 > 0) tc_pm10 = pm10; if (pm25 > 0) tc_pm25 = pm25; } } // flush rest of server reply while (client.available()) { // read the remaining text from serial otherwise connection cannot be closed client.read(); } //////////////////////////////////////////////////////////////////////////////////// // new request url = "/epd/ddata/html/h24_concentration_Eng_80.xml"; // central western client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: www.aqhi.gov.hk\r\n" + "Connection: close\r\n\r\n"); delay(10); // Serial.println(url); timeout = millis(); while (client.available() == 0) { if (millis() - timeout > 5000) { // Serial.println(">>> Client Timeout !"); client.stop(); return; } } // Read all the lines of the reply from server and print them to Serial if (client.available()) { // parse the XML page; look for station name, then count columns if (client.find("Central")) { for (int i = 0; i < 11; i++) { client.find("H24C_ColItem"); } pm10 = client.parseFloat(); client.find("H24C_ColItem"); pm25 = client.parseFloat(); if (pm10 > 0) cw_pm10 = pm10; if (pm25 > 0) cw_pm25 = pm25; } } // flush rest of server reply while (client.available()) { // read the remaining text from serial otherwise connection cannot be closed client.read(); } client.stop(); } //////////////////////////////////////////////////////////////////////// void beeps() { for (int u = 0; u < 5; u++) { tone(BUZZER_PIN, 2000); delay(100); noTone(BUZZER_PIN); delay(100); } }
Hey,
please what is the name of the adapter? ( between PMSA003 and µController)
thank you
hi, it came with the sensor; please check with your sensor supplier
Hey, ok thank you.
I have two questions please,
1) is it necessary to implement the checksum in the code? or can the data be read directly via UART commands,?
2) SET & RESET pins could be left floating?