first commit
This commit is contained in:
10
src/http_static.cpp
Normal file
10
src/http_static.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include "http_static.h"
|
||||
|
||||
namespace http_static {
|
||||
const char index_html[] PROGMEM = R"rawliteral(
|
||||
#include "./static/index.html"
|
||||
)rawliteral";
|
||||
const char sensor_things_resp[] PROGMEM = R"rawliteral(
|
||||
#include "./static/sensor_things_tmpl.json"%"
|
||||
)rawliteral";
|
||||
}
|
258
src/main.cpp
Normal file
258
src/main.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
/***************************************************
|
||||
|
||||
Wemos D1 mini or NodeMCU 1.0
|
||||
VCC - 3.3V
|
||||
GND - G
|
||||
SCL - D1 -- GPIO 5
|
||||
SDA - D2 -- GPIO 4
|
||||
WAK - D3 -- GPIO 0
|
||||
|
||||
ESP32
|
||||
VCC - 3.3V
|
||||
GND - G
|
||||
SCL - 19
|
||||
SDA - 18
|
||||
WAK - 23
|
||||
|
||||
****************************************************/
|
||||
|
||||
#include "ESPAsyncWebServer.h"
|
||||
#include <WiFi.h>
|
||||
#include <Wire.h>
|
||||
#include "ClosedCube_HDC1080.h" // HDC1080 library - https://github.com/closedcube/ClosedCube_HDC1080_Arduino // 14.04.2019
|
||||
#include "ccs811.h" // CCS811 library - https://github.com/maarten-pennings/CCS811 // 13.03.2020
|
||||
#include <time.h>
|
||||
//#include "src/ESPinfluxdb.h" // https://github.com/hwwong/ESP_influxdb // 14.04.2019
|
||||
#include "http_static.h" // HTTP pages and JSON request templates
|
||||
// ********************** Config **********************
|
||||
|
||||
// DeepSleep time – send data every 60 seconds
|
||||
const int sleepTimeS = 60;
|
||||
|
||||
//Global sensor objects
|
||||
#define CCS811_WAK 23
|
||||
|
||||
CCS811 ccs811(CCS811_WAK);
|
||||
ClosedCube_HDC1080 hdc1080;
|
||||
|
||||
// WiFi Config
|
||||
#define WiFi_SSID "Ischtar"
|
||||
#define WiFi_Password "highfive"
|
||||
|
||||
// NTP conf
|
||||
const char* ntpServer = "pool.ntp.org";
|
||||
const long gmtOffset_sec = 0;
|
||||
const int daylightOffset_sec = 3600;
|
||||
|
||||
// Create AsyncWebServer object on port 80
|
||||
AsyncWebServer server(80);
|
||||
|
||||
// Globals for CCS811
|
||||
uint16_t eco2, etvoc, errstat, raw;
|
||||
// Globals for timestamp
|
||||
struct tm timeinfo;
|
||||
|
||||
// loop cycle
|
||||
#define workCycle 60 //seconds
|
||||
// ******************** Config End ********************
|
||||
|
||||
String readHDC1080Temperature() {
|
||||
// Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
|
||||
float t = hdc1080.readTemperature();
|
||||
if (isnan(t)) {
|
||||
Serial.println("Failed to read from HDC1080 sensor!");
|
||||
return "--";
|
||||
}
|
||||
else {
|
||||
return String(t);
|
||||
}
|
||||
}
|
||||
|
||||
String readHDC1080Humidity() {
|
||||
// Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
|
||||
float h = hdc1080.readHumidity();
|
||||
if (isnan(h)) {
|
||||
Serial.println("Failed to read from HDC1080 sensor!");
|
||||
return "--";
|
||||
}
|
||||
else {
|
||||
return String(h);
|
||||
}
|
||||
}
|
||||
|
||||
String processCCS811Error(err_t errstat) {
|
||||
if ( errstat == CCS811_ERRSTAT_OK_NODATA ) {
|
||||
Serial.println("CCS811: waiting for (new) data");
|
||||
return "loading";
|
||||
} else if ( errstat & CCS811_ERRSTAT_I2CFAIL ) {
|
||||
Serial.println("CCS811: I2C error");
|
||||
return "i2c error";
|
||||
} else {
|
||||
Serial.print("CCS811: errstat="); Serial.print(errstat, HEX);
|
||||
Serial.print("="); Serial.println( ccs811.errstat_str(errstat) );
|
||||
return "<span color='red' title='" + String(ccs811.errstat_str(errstat)) + "'>CCS811 sensor error</span>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
String readCCS811TVOC() {
|
||||
return String(etvoc);
|
||||
}
|
||||
String readCCS811ECO2() {
|
||||
return String(eco2);
|
||||
}
|
||||
|
||||
String formatISO8601() {
|
||||
char timestamp[20]; // Buffer for timestamp
|
||||
|
||||
// Format the time into ISO 8601 format
|
||||
strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", &timeinfo);
|
||||
|
||||
// Convert the formatted timestamp to a String object
|
||||
return String(timestamp);
|
||||
}
|
||||
|
||||
|
||||
// Replaces placeholder in HTML template with real values
|
||||
// SSR if you will
|
||||
String processor(const String& var){
|
||||
if(var == "TEMPERATURE"){
|
||||
return readHDC1080Temperature();
|
||||
}
|
||||
else if(var == "HUMIDITY"){
|
||||
return readHDC1080Humidity();
|
||||
}
|
||||
else if(var == "TVOC"){
|
||||
return readCCS811TVOC();
|
||||
}
|
||||
else if(var == "ECO2"){
|
||||
return readCCS811ECO2();
|
||||
} else if(var == "TIMESTAMP"){
|
||||
return formatISO8601();
|
||||
}
|
||||
return String();
|
||||
}
|
||||
|
||||
void connectToWiFi() {
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(WiFi_SSID, WiFi_Password);
|
||||
|
||||
Serial.println();
|
||||
Serial.print("Connecting to WiFi: ");
|
||||
Serial.print(WiFi_SSID);
|
||||
|
||||
int attempts = 0;
|
||||
while (WiFi.status() != WL_CONNECTED && attempts < 10) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
attempts++;
|
||||
}
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Serial.println("\nWiFi connected");
|
||||
Serial.print("IP address: http://");
|
||||
Serial.println(WiFi.localIP());
|
||||
} else {
|
||||
Serial.println("\nFailed to connect to WiFi");
|
||||
// Handle connection failure, e.g., retry or reset the ESP32
|
||||
}
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
delay(10);
|
||||
Serial.println("");
|
||||
|
||||
connectToWiFi();
|
||||
delay(10);
|
||||
Serial.println("");
|
||||
// Config NTP
|
||||
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
|
||||
|
||||
// hdc1080 info
|
||||
hdc1080.begin(0x40);
|
||||
Serial.print("Manufacturer ID=0x");
|
||||
Serial.println(hdc1080.readManufacturerId(), HEX); // 0x5449 ID of Texas Instruments
|
||||
Serial.print("Device ID=0x");
|
||||
Serial.println(hdc1080.readDeviceId(), HEX); // 0x1050 ID of the device
|
||||
|
||||
// i2c
|
||||
Wire.begin();
|
||||
|
||||
Serial.println("CCS811 test");
|
||||
// Enable CCS811
|
||||
bool ok = ccs811.begin();
|
||||
if ( !ok ) Serial.println("setup: CCS811 begin FAILED");
|
||||
|
||||
// Print CCS811 versions
|
||||
Serial.print("setup: hardware version: "); Serial.println(ccs811.hardware_version(), HEX);
|
||||
Serial.print("setup: bootloader version: "); Serial.println(ccs811.bootloader_version(), HEX);
|
||||
Serial.print("setup: application version: "); Serial.println(ccs811.application_version(), HEX);
|
||||
|
||||
// Start measuring
|
||||
ok = ccs811.start(CCS811_MODE_1SEC);
|
||||
if ( !ok ) Serial.println("init: CCS811 start FAILED");
|
||||
|
||||
// Pages and JSONs
|
||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
request->send_P(200, "text/html", http_static::index_html, processor);
|
||||
});
|
||||
server.on("/api/sensors.json", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
request->send_P(200, "text/html", http_static::sensor_things_resp, processor);
|
||||
});
|
||||
|
||||
// lightweight named endpoints
|
||||
server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
request->send_P(200, "text/plain", readHDC1080Temperature().c_str());
|
||||
});
|
||||
server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
request->send_P(200, "text/plain", readHDC1080Humidity().c_str());
|
||||
});
|
||||
server.on("/tvoc", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
request->send_P(200, "text/plain", readCCS811TVOC().c_str());
|
||||
});
|
||||
server.on("/eco2", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
request->send_P(200, "text/plain", readCCS811ECO2().c_str());
|
||||
});
|
||||
|
||||
// Start server
|
||||
server.begin();
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
Serial.println("WiFi connection lost. Reconnecting...");
|
||||
connectToWiFi();
|
||||
}
|
||||
|
||||
if(!getLocalTime(&timeinfo)){
|
||||
Serial.println("Failed to obtain time");
|
||||
return;
|
||||
}
|
||||
Serial.print(&timeinfo, "[%M-%d-%Y--%H:%M:%S]: ");
|
||||
|
||||
Serial.print("H="); Serial.print(readHDC1080Temperature()); Serial.print(" °C ");
|
||||
Serial.print("T="); Serial.print(readHDC1080Temperature()); Serial.print(" % ");
|
||||
// Read CCS811
|
||||
ccs811.read(&eco2,&etvoc,&errstat,&raw);
|
||||
|
||||
// Process CCS811
|
||||
if( errstat==CCS811_ERRSTAT_OK ) {
|
||||
Serial.print("eco2="); Serial.print(eco2); Serial.print(" ppm ");
|
||||
Serial.print("etvoc="); Serial.print(etvoc); Serial.print(" ppb ");
|
||||
} else if( errstat==CCS811_ERRSTAT_OK_NODATA ) {
|
||||
Serial.print("waiting for (new) data");
|
||||
} else if( errstat & CCS811_ERRSTAT_I2CFAIL ) {
|
||||
Serial.print("I2C error");
|
||||
} else {
|
||||
Serial.print( "error: " );
|
||||
Serial.print( ccs811.errstat_str(errstat) );
|
||||
}
|
||||
Serial.println();
|
||||
|
||||
// Wait
|
||||
delay(workCycle*1000);
|
||||
}
|
97
src/static/index.html
Normal file
97
src/static/index.html
Normal file
@@ -0,0 +1,97 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>ESP32 WxServer</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
|
||||
<style>
|
||||
html {
|
||||
font-family: Arial;
|
||||
display: inline-block;
|
||||
margin: 0px auto;
|
||||
text-align: center;
|
||||
}
|
||||
h2 { font-size: 3.0rem; }
|
||||
p { font-size: 3.0rem; }
|
||||
.units { font-size: 1.2rem; }
|
||||
.sensor-lable{
|
||||
font-size: 1.5rem;
|
||||
vertical-align:middle;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>ESP32 CCS881 & HDC1080 Server</h2>
|
||||
<h3>Loaded: %TIMESTAMP%</h3>
|
||||
<p>
|
||||
<i class="fas fa-thermometer-half" style="color:#059e8a;"></i>
|
||||
<span class="sensor-lable">Temperature</span>
|
||||
<span id="temperature">%TEMPERATURE%</span>
|
||||
<sup class="units">°C</sup>
|
||||
</p>
|
||||
<p>
|
||||
<i class="fas fa-tint" style="color:#00add6;"></i>
|
||||
<span class="sensor-lable">Humidity</span>
|
||||
<span id="humidity">%HUMIDITY%</span>
|
||||
<sup class="units">%</sup>
|
||||
</p>
|
||||
<p>
|
||||
<i class="fas fa-gas-pump" style="color:#6959cd;"></i>
|
||||
<span class="sensor-lable">CO2 concentration</span>
|
||||
<span id="eco2">%ECO2%</span>
|
||||
<sup class="units">ppm</sup>
|
||||
</p>
|
||||
<p>
|
||||
<i class="fas fa-smog" style="color:#8b008b;"></i>
|
||||
<span class="sensor-lable">TVOC concentration:</span>
|
||||
<span id="tvoc">%TVOC%</span>
|
||||
<sup class="units">ppb</sup>
|
||||
</p>
|
||||
</body>
|
||||
<script>
|
||||
setInterval(function ( ) {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
document.getElementById("temperature").innerHTML = this.responseText;
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", "/temperature", true);
|
||||
xhttp.send();
|
||||
}, 10000 ) ;
|
||||
|
||||
setInterval(function ( ) {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
document.getElementById("tvoc").innerHTML = this.responseText;
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", "/tvoc", true);
|
||||
xhttp.send();
|
||||
}, 10000 ) ;
|
||||
|
||||
setInterval(function ( ) {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
document.getElementById("eco2").innerHTML = this.responseText;
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", "/eco2", true);
|
||||
xhttp.send();
|
||||
}, 10000 ) ;
|
||||
|
||||
setInterval(function ( ) {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
document.getElementById("humidity").innerHTML = this.responseText;
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", "/humidity", true);
|
||||
xhttp.send();
|
||||
}, 10000 ) ;
|
||||
</script>
|
||||
</html>
|
105
src/static/sensor_things_tmpl.json
Normal file
105
src/static/sensor_things_tmpl.json
Normal file
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"id": "urn:myweatherstation:sensor1",
|
||||
"name": "WeatherStation001",
|
||||
"description": "A weather station providing temperature, humidity, TVOC, and eCO2 readings",
|
||||
"encodingType": "application/json",
|
||||
"metadata": "https://myweatherstation.com/metadata",
|
||||
"Datastreams": [
|
||||
{
|
||||
"id": "urn:myweatherstation:datastream:temperature",
|
||||
"name": "Temperature",
|
||||
"description": "Temperature readings",
|
||||
"unitOfMeasurement": {
|
||||
"name": "Degree Celsius",
|
||||
"symbol": "°C",
|
||||
"definition": "http://www.qudt.org/qudt/owl/1.0.0/unit/Instances.html#DegreeCelsius"
|
||||
},
|
||||
"ObservedProperty": {
|
||||
"name": "Temperature",
|
||||
"definition": "http://www.qudt.org/qudt/owl/1.0.0/quantity/Instances.html#Temperature"
|
||||
},
|
||||
"Sensor": {
|
||||
"name": "Temperature Sensor",
|
||||
"description": "Sensor for measuring temperature"
|
||||
},
|
||||
"Observations": [
|
||||
{
|
||||
"phenomenonTime": "%TIMESTAMP%",
|
||||
"result": "%TEMPERATURE%"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "urn:myweatherstation:datastream:humidity",
|
||||
"name": "Humidity",
|
||||
"description": "Humidity readings",
|
||||
"unitOfMeasurement": {
|
||||
"name": "Percent",
|
||||
"symbol": "%%",
|
||||
"definition": "http://www.qudt.org/qudt/owl/1.0.0/unit/Instances.html#Percent"
|
||||
},
|
||||
"ObservedProperty": {
|
||||
"name": "Relative Humidity",
|
||||
"definition": "http://www.qudt.org/qudt/owl/1.0.0/quantity/Instances.html#RelativeHumidity"
|
||||
},
|
||||
"Sensor": {
|
||||
"name": "Humidity Sensor",
|
||||
"description": "Sensor for measuring humidity"
|
||||
},
|
||||
"Observations": [
|
||||
{
|
||||
"phenomenonTime": "%TIMESTAMP%",
|
||||
"result": "%HUMIDITY%"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "urn:myweatherstation:datastream:TVOC",
|
||||
"name": "TVOC",
|
||||
"description": "Total Volatile Organic Compounds readings",
|
||||
"unitOfMeasurement": {
|
||||
"name": "Parts Per Billion",
|
||||
"symbol": "ppb",
|
||||
"definition": "http://www.qudt.org/qudt/owl/1.0.0/unit/Instances.html#PartsPerBillion"
|
||||
},
|
||||
"ObservedProperty": {
|
||||
"name": "Total Volatile Organic Compounds",
|
||||
"definition": "http://www.qudt.org/qudt/owl/1.0.0/quantity/Instances.html#TotalVolatileOrganicCompounds"
|
||||
},
|
||||
"Sensor": {
|
||||
"name": "TVOC Sensor",
|
||||
"description": "Sensor for measuring Total Volatile Organic Compounds"
|
||||
},
|
||||
"Observations": [
|
||||
{
|
||||
"phenomenonTime": "%TIMESTAMP%",
|
||||
"result": "%TVOC%"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "urn:myweatherstation:datastream:eCO2",
|
||||
"name": "eCO2",
|
||||
"description": "Equivalent Carbon Dioxide readings",
|
||||
"unitOfMeasurement": {
|
||||
"name": "Parts Per Million",
|
||||
"symbol": "ppm",
|
||||
"definition": "http://www.qudt.org/qudt/owl/1.0.0/unit/Instances.html#PartsPerMillion"
|
||||
},
|
||||
"ObservedProperty": {
|
||||
"name": "Equivalent Carbon Dioxide",
|
||||
"definition": "http://www.qudt.org/qudt/owl/1.0.0/quantity/Instances.html#CarbonDioxideConcentration"
|
||||
},
|
||||
"Sensor": {
|
||||
"name": "eCO2 Sensor",
|
||||
"description": "Sensor for measuring Equivalent Carbon Dioxide"
|
||||
},
|
||||
"Observations": [
|
||||
{
|
||||
"phenomenonTime": "%TIMESTAMP%",
|
||||
"result": "%ECO2%"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user