Tuesday, February 4, 2020

Send Line notification from ESP8266 when PM2.5 is too high

Introduction


Low-cost (US$19) Air Quality Monitoring System with Line notification


Purpose


The aim of this project is to receive Line notification when air quality is higher than certain level from Wemos. It uses PMS7003 sensor to measure air quality and visualizes the result with Google Chart API. Air quality measurement data is presented in gauge type. We can check current status via web browser using WiFi connection.

Features


This project is based on our previous project named "IoT Laboratory: ESP8266-based WiFi air quality monitoring system using PMS7003 sensor with Google Chart visualization." It adds Line notification functionality to notify user when the level of air quality hits a certain level which is specified by the user in the source code. The entire process for sending Line notification is like below. Wemos sends HTTP POST request to Line Notify website, then, the message will be delivered to our Line messenger.


For web server, we use ESPAsyncWebServer library and it is available at github. Additionally, we need to install EPS8266 Sketch Data Upload to upload web server related files to Wemos. For example, in this project, we use HTML, CSS files under data directory. In other words, we need to upload sketch and web related files separately.

Prerequisites


- Arduino IDE
ESP8266 package for Arduino IDE
EPS8266 Sketch Data Upload
ESPAsyncTCP Library
ESPAsyncWebServer Library
- Line account and Token

Requirements


Hardware
-Wemos D1 mini : US$1.77 on Aliexpress
-PMS7003 air quality sensor : US$16.80 on Aliexpress

Instructions


Step 1. Setup hardware

PMS7003 sensor comes with a small breakout board, which has TX, RX, VCC, GND pins. Connect TX of PMS7003 to D5 of Wemos, RX to D6, VCC to 5V, and GND to G. Finally, connect micro usb to Wemos for uploading firmware, and check serial monitor and serial plotter in Android IDE to make sure the sensor works correctly.



Step 2. Create Line account and Token for notification

To be able to receive Line notification, we need to get a token from Line Notify website and put it in our source sketch. Useful tutorial on how to create a token is available at https://engineering.linecorp.com/en/blog/using-line-notify-to-send-messages-to-line-from-the-command-line/. Take a good look at it to understand basic procedure to create a token.

First, login to the LINE Notify website


Click "My page"


Click "Generate token"


Important! Then, type any name for the token and choose who will receive Line notification. We can choose a single recipient or group. For testing purpose, let's choose myself here, then it will send notification only to me.

Step 3. Upload sketch to Wemos D1 mini

This step is to upload sketch to Wemos as usual. In the following sketch, following values need to be modified with your own.

WIFI_SSID  : Name of WiFi router
WIFI_PASS  Password of WiFi router
LINE_TOKEN : Token string from Line Notify website

In this example sketch, it send Line notification only when PM2.5 hit 20 or higher. And before sending again it waits 10 minutes. These values can be changed by user.

notifyLevel : PM2.5 threshold for Line notification
interval    : Waiting time (specify in milliseconds)



/**
* Air quality monitoring system using PMS7003 sensor with WIFI, WEBSERVER, LINE NOTIFY
*
* Hardware : Wemos D1 mini, PMS7003
* Software : Arduino IDE, EPS8266 Sketch Data Upload
* Library : ESPAsyncTCP, ESPAsyncWebServer
*
* LINE API Reference :
* https://engineering.linecorp.com/en/blog/using-line-notify-to-send-messages-to-line-from-the-command-line/
*
* January 2020. Brian Kim
*/
#include <SoftwareSerial.h>
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include <WiFiClientSecureAxTLS.h>
/**
* WiFi credentials
*/
#define WIFI_SSID "your-ssid"
#define WIFI_PASS "your-password"
/**
* LINE Notify credential
*/
#define LINE_TOKEN "your-line-token"
/**
* PMS7003 sensor pin map and packet header
*/
#define PMS7003_TX D5 // GPIO12
#define PMS7003_RX D6 // GPIO14
#define PMS7003_PREAMBLE_1 0x42 // From PMS7003 datasheet
#define PMS7003_PREAMBLE_2 0x4D
#define PMS7003_DATA_LENGTH 31
/**
* Wemos serial RX - TX PMS7003
* TX - RX
*/
SoftwareSerial _serial(PMS7003_TX, PMS7003_RX); // RX, TX
AsyncWebServer server(80);
int _pm1, _pm25, _pm10;
unsigned long previousMillis = 0;
const long interval = 600000; // Waiting time(milliseconds)
const int notifyLevel = 20; // PM2.5 threshold for notification
bool waitFlag = false;
void connectWifi() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
void notifyLine(String msg) {
WiFiClientSecure client;
client.setInsecure();
if (!client.connect("notify-api.line.me", 443)) {
Serial.println ("ERROR Connection failed");
return;
}
String req = "POST /api/notify HTTP/1.1\r\n";
req += "Host: notify-api.line.me\r\n";
req += "Authorization: Bearer " + String(LINE_TOKEN) + "\r\n";
req += "Cache-Control: no-cache\r\n";
req += "User-Agent: ESP8266\r\n";
req += "Connection: close\r\n";
req += "Content-Type: application/x-www-form-urlencoded\r\n";
req += "Content-Length: " + String(String("message=" + msg).length()) + "\r\n";
req += "\r\n";
req += "message=" + msg;
client.print(req);
Serial.print(req);
while (client.connected()) {
String line = client.readStringUntil('\n');
if (line == "\r") {
// delay(20000);
break;
}
Serial.println(line);
}
}
String readSensor() {
int checksum = 0;
unsigned char pms[32] = {0,};
String ret;
/**
* Search preamble for Packet
* Solve trouble caused by delay function
*/
while( _serial.available() &&
_serial.read() != PMS7003_PREAMBLE_1 &&
_serial.peek() != PMS7003_PREAMBLE_2 ) {
}
if( _serial.available() >= PMS7003_DATA_LENGTH ){
pms[0] = PMS7003_PREAMBLE_1;
checksum += pms[0];
for(int j=1; j<32 ; j++){
pms[j] = _serial.read();
if(j < 30)
checksum += pms[j];
}
_serial.flush();
if( pms[30] != (unsigned char)(checksum>>8)
|| pms[31]!= (unsigned char)(checksum) ){
Serial.println("Checksum error");
ret = String(_pm1) + " " + String(_pm25) + " " + String(_pm10);
return ret;
}
if( pms[0]!=0x42 || pms[1]!=0x4d ) {
Serial.println("Packet error");
ret = String(_pm1) + " " + String(_pm25) + " " + String(_pm10);
return ret;
}
_pm1 = makeWord(pms[10],pms[11]);
_pm25 = makeWord(pms[12],pms[13]);
_pm10 = makeWord(pms[14],pms[15]);
ret = String(_pm1) + " " + String(_pm25) + " " + String(_pm10);
// Serial.printf("PM1.0:%d PM2.5:%d PM10.0:%d\n", _pm1, _pm25, _pm10);
return ret;
}
}
void setup()
{
Serial.begin(115200); // For debugging
_serial.begin(9600); // For communicating with PMS7003 sensor
Serial.printf("\nAir quality monitoring system using PMS7003 sensor with WIFI, WEBSERVER, LINE NOTIFY\n");
Serial.printf("PM2.5 notify level:%d Waiting time:%d seconds\n",notifyLevel, interval/1000);
// Initialize filesystem
if(!SPIFFS.begin()){
Serial.println("ERROR SPIFFS failed");
return;
}
connectWifi(); // Connect to WiFi
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
// request->send(SPIFFS, "/index.html", String(), false, templateHandler);
request->send(SPIFFS, "/index.html", "text/html");
});
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/style.css", "text/css");
});
server.on("/line_logo.png", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/line_logo.png", "image/png");
});
/**
* Send measured data(PM1.0, PM2.5, PM10.0) back to client
* Data format : Each value is separated by white space
* Example : 11 22 33
*/
server.on("/updatesensorreading", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/plain", readSensor().c_str());
});
server.begin();
}
void loop()
{
unsigned long currentMillis = 0;
if (_pm25 >= notifyLevel && !waitFlag) {
String msg = "PM2.5 is ";
Serial.printf("PM2.5: %d\n",_pm25);
msg += String(_pm25);
notifyLine(msg);
waitFlag = true;
previousMillis = millis();
}
if (waitFlag) {
currentMillis = millis();
if ( currentMillis - previousMillis >= interval ) {
waitFlag = false;
previousMillis = currentMillis;
}
}
}
Step 4. Upload data to Wemos D1 mini

This step is to upload web server related files (HTML, CSS) to Wemos. These files are located in directory named data. Click "ESP8266 Sketch Data Upload" under Tools menu in Arduino IDE to upload these files to Wemos. Once it shows the measurement data, it will refresh every 3 seconds automatically.

HTML file

On web interface, the size of gauge is defined in width, height of options variable. Just change these values to customize chart size. And if you want to modify refresh rate, change the value of 30000 in setInterval function to other value.

<!-- Air quality monitoring system using PMS7003 sensor with WIFI, WEBSERVER
Hardware : Wemos D1 mini, PMS7003
Software : Arduino IDE, EPS8266 Sketch Data Upload
Library : ESPAsyncTCP, ESPAsyncWebServer -->
<!DOCTYPE html>
<html>
<head>
<title>Air quality monitor</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<link rel="stylesheet" type="text/css" href="style.css">
<script src="https://www.gstatic.com/charts/loader.js"></script>
<script>
google.charts.load('current', {'packages':['gauge','corechart']});
// Display clock above air quality gauge
function startClock() {
var now = new Date();
var hour = now.getHours();
var minute = now.getMinutes();
var second = now.getSeconds();
minute = minute < 10 ? "0" + minute : minute;
second = second < 10 ? "0" + second : second;
document.getElementById("clock_div").innerHTML =
hour + ":" + minute + ":" + second;
var tmp = setTimeout(startClock, 500);
}
</script>
</head>
<body onload="startClock()">
<h1>Air quality monitor</h1>
<div id="clock_div"></div>
<div id="chart_div"></div>
<script>
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Label', 'Value'],
['PM1.0', 0],
['PM2.5', 0],
['PM10', 0]
]);
var options = {
width: 800, height: 240,
redFrom: 90, redTo: 100,
yellowFrom:75, yellowTo: 90,
minorTicks: 5
};
var chart = new google.visualization.Gauge(document.getElementById('chart_div'));
chart.draw(data, options);
setInterval(function ( ) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var pms = this.responseText.split(" ");
data.setValue(0, 1, pms[0]);
data.setValue(1, 1, pms[1]);
data.setValue(2, 1, pms[2]);
chart.draw(data, options);
}
};
xhttp.open("GET", "/updatesensorreading", true);
xhttp.send();
}, 3000 );
}
</script>
</body>
</html>
view raw index.html hosted with ❤ by GitHub
CSS file

/* Air quality monitoring system using PMS7003 sensor with WIFI, WEBSERVER
Hardware : Wemos D1 mini, PMS7003
Software : Arduino IDE, EPS8266 Sketch Data Upload
Library : ESPAsyncTCP, ESPAsyncWebServer */
html {
font-family: monospace;
}
h1 {
font-size:45px;
}
body {
text-align: center;
background-image: url("line_logo.png");
background-repeat: no-repeat;
}
div {
display: table;
margin: 0 auto;
}
#clock_div {
font-size:27px;
}

Results


After uploading firmware, Wemos restarts itself automatically. Once Wemos D1 mini has restarted, serial monitor shows a welcome message, PM2.5 threshold, and waiting period. When PM2.5 is higher than threshold it will send Line notification to the user as below.


One Wemos sends notification, Line messenger will show you the content of it as below. Notice that "Air quality" in the message is actually the token name we typed when the token was created.


Use web browser to connect to the device, then it will show current time and visualized information, which is based on Google Chart API. It will refresh itself every 3 seconds automatically.


References

Source codes at github

No comments:

Post a Comment