first commit
This commit is contained in:
commit
6a390b6197
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.pio
|
||||||
|
.clang_complete
|
||||||
|
.ccls
|
||||||
|
/logs/
|
11
README.org
Normal file
11
README.org
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
* Balcony WX station de UT3UMS
|
||||||
|
** Inspiration
|
||||||
|
+ [[https://how2electronics.com/monitor-ccs811-co2-tvoc-on-esp8266-esp32-webserver/][Monitor CCS811 CO2 & TVOC on ESP8266/ESP32 Webserver]]
|
||||||
|
It has only equivalent carbon dioxide (eCO2) with metal oxide (MOX) levels
|
||||||
|
+ https://github.com/bfaliszek/CJMCU-8118_InfluxDB
|
||||||
|
|
||||||
|
|
||||||
|
** Levels
|
||||||
|
#+BEGIN_EXAMPLE
|
||||||
|
In the event of TVOC issues, inspections should be carried out to find the root cause of the problem and address it as exposure to the VOCs may harm health. Indoor VOC levels up to 350 ppb are acceptable. However they should not exceed 500ppb. – CO2 ppm range of 450 to 2000 ppm (parts per million).
|
||||||
|
#+END_EXAMPLE
|
39
include/README
Normal file
39
include/README
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
This directory is intended for project header files.
|
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions
|
||||||
|
to be shared between several project source files. You request the use of a
|
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder
|
||||||
|
by including it, with the C preprocessing directive `#include'.
|
||||||
|
|
||||||
|
```src/main.c
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file
|
||||||
|
into each source file that needs it. Such copying would be time-consuming
|
||||||
|
and error-prone. With a header file, the related declarations appear
|
||||||
|
in only one place. If they need to be changed, they can be changed in one
|
||||||
|
place, and programs that include the header file will automatically use the
|
||||||
|
new version when next recompiled. The header file eliminates the labor of
|
||||||
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
|
In C, the usual convention is to give header files names that end with `.h'.
|
||||||
|
It is most portable to use only letters, digits, dashes, and underscores in
|
||||||
|
header file names, and at most one dot.
|
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
* Include Syntax
|
||||||
|
* Include Operation
|
||||||
|
* Once-Only Headers
|
||||||
|
* Computed Includes
|
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
11
include/http_static.h
Normal file
11
include/http_static.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// http_static.h
|
||||||
|
#include <Arduino.h>
|
||||||
|
#ifndef HTTP_STATIC_H
|
||||||
|
#define HTTP_STATIC_H
|
||||||
|
|
||||||
|
namespace http_static {
|
||||||
|
extern const char index_html[] PROGMEM;
|
||||||
|
extern const char sensor_things_resp[] PROGMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
101
logplotter.py
Normal file
101
logplotter.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import re
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
def parse_sensor_log(filename):
|
||||||
|
timestamps = []
|
||||||
|
temperatures = []
|
||||||
|
humidities = []
|
||||||
|
eco2_concentrations = []
|
||||||
|
tvoc_concentrations = []
|
||||||
|
|
||||||
|
# Extract start time from the filename
|
||||||
|
# start_time_str = re.search(r'(\d{6})', filename).group(1)
|
||||||
|
start_time_str = re.search(r'-(\d{6})\.', filename).group(1)
|
||||||
|
print(start_time_str)
|
||||||
|
start_time = datetime.strptime(start_time_str, '%H%M%S')
|
||||||
|
|
||||||
|
with open(filename, 'r') as file:
|
||||||
|
lines = file.readlines()
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if "Waiting" in line:
|
||||||
|
# Increment the current time by one minute
|
||||||
|
start_time += timedelta(minutes=1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
match = re.match(r'^temperature: (.+) C$', line)
|
||||||
|
if match:
|
||||||
|
temperatures.append(float(match.group(1)))
|
||||||
|
timestamps.append(start_time)
|
||||||
|
continue
|
||||||
|
|
||||||
|
match = re.match(r'^humidity: (.+) %$', line)
|
||||||
|
if match:
|
||||||
|
humidities.append(float(match.group(1)))
|
||||||
|
continue
|
||||||
|
|
||||||
|
match = re.match(r'^eCO2 concentration: (.+) ppm$', line)
|
||||||
|
if match:
|
||||||
|
eco2_concentrations.append(float(match.group(1)))
|
||||||
|
continue
|
||||||
|
|
||||||
|
match = re.match(r'^TVOC concentration: (.+) ppb$', line)
|
||||||
|
if match:
|
||||||
|
tvoc_concentrations.append(float(match.group(1)))
|
||||||
|
continue
|
||||||
|
|
||||||
|
return timestamps, temperatures, humidities, eco2_concentrations, tvoc_concentrations
|
||||||
|
|
||||||
|
# Multiline
|
||||||
|
def plot_sensor_data(timestamps, temperatures, humidities, eco2_concentrations, tvoc_concentrations):
|
||||||
|
plt.figure(figsize=(12, 8))
|
||||||
|
|
||||||
|
plt.subplot(2, 2, 1)
|
||||||
|
plt.plot(timestamps, temperatures, 'r.-')
|
||||||
|
plt.xlabel('Time')
|
||||||
|
plt.ylabel('Temperature (C)')
|
||||||
|
plt.title('Temperature over Time')
|
||||||
|
|
||||||
|
plt.subplot(2, 2, 2)
|
||||||
|
plt.plot(timestamps, humidities, 'g.-')
|
||||||
|
plt.xlabel('Time')
|
||||||
|
plt.ylabel('Humidity (%)')
|
||||||
|
plt.title('Humidity over Time')
|
||||||
|
|
||||||
|
plt.subplot(2, 2, 3)
|
||||||
|
plt.plot(timestamps, eco2_concentrations, 'b.-')
|
||||||
|
plt.xlabel('Time')
|
||||||
|
plt.ylabel('eCO2 Concentration (ppm)')
|
||||||
|
plt.title('eCO2 Concentration over Time')
|
||||||
|
|
||||||
|
plt.subplot(2, 2, 4)
|
||||||
|
plt.plot(timestamps, tvoc_concentrations, 'm.-')
|
||||||
|
plt.xlabel('Time')
|
||||||
|
plt.ylabel('TVOC Concentration (ppb)')
|
||||||
|
plt.title('TVOC Concentration over Time')
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
# def plot_sensor_data(timestamps, temperatures, humidities, eco2_concentrations, tvoc_concentrations):
|
||||||
|
# plt.figure(figsize=(12, 8))
|
||||||
|
|
||||||
|
# plt.plot(timestamps, temperatures, 'r.-', label='Temperature (C)')
|
||||||
|
# plt.plot(timestamps, humidities, 'g.-', label='Humidity (%)')
|
||||||
|
# plt.plot(timestamps, eco2_concentrations, 'b.-', label='eCO2 Concentration (ppm)')
|
||||||
|
# plt.plot(timestamps, tvoc_concentrations, 'm.-', label='TVOC Concentration (ppb)')
|
||||||
|
|
||||||
|
# plt.xlabel('Time')
|
||||||
|
# plt.ylabel('Sensor Readings')
|
||||||
|
# plt.title('Sensor Readings over Time')
|
||||||
|
# plt.legend()
|
||||||
|
# plt.grid(True)
|
||||||
|
|
||||||
|
# plt.tight_layout()
|
||||||
|
# plt.show()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
filename = './logs/device-monitor-240403-030529.log'
|
||||||
|
timestamps, temperatures, humidities, eco2_concentrations, tvoc_concentrations = parse_sensor_log(filename)
|
||||||
|
plot_sensor_data(timestamps, temperatures, humidities, eco2_concentrations, tvoc_concentrations)
|
21
platformio.ini
Normal file
21
platformio.ini
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:upesy_wroom]
|
||||||
|
platform = espressif32
|
||||||
|
board = upesy_wroom
|
||||||
|
framework = arduino
|
||||||
|
lib_deps =
|
||||||
|
maarten-pennings/CCS811@^12.0.0
|
||||||
|
closedcube/ClosedCube HDC1080@^1.3.2
|
||||||
|
https://github.com/me-no-dev/ESPAsyncWebServer.git#master
|
||||||
|
monitor_speed = 115200
|
||||||
|
monitor_filters = default, log2file
|
||||||
|
extra_scripts = populate_static.py
|
41
populate_static.py
Executable file
41
populate_static.py
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os, re
|
||||||
|
|
||||||
|
# Define the file paths
|
||||||
|
tmpl_file = "./src/http_static.cpp.tmpl"
|
||||||
|
out_file = "./src/http_static.cpp"
|
||||||
|
static_dir = "./src/static/"
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Print current working directory and username
|
||||||
|
print("Current working directory:", os.getcwd())
|
||||||
|
print("Current user:", os.getlogin())
|
||||||
|
|
||||||
|
# Read template file content
|
||||||
|
with open(tmpl_file, 'r') as f:
|
||||||
|
body = f.read()
|
||||||
|
|
||||||
|
# Iterate over placeholder expressions in the template
|
||||||
|
for tmpl_import in find_placeholder_expressions(body):
|
||||||
|
print("Found expression:", tmpl_import)
|
||||||
|
tmpl_filename = tmpl_import[1:-1]
|
||||||
|
template_path = os.path.join(static_dir, tmpl_filename)
|
||||||
|
|
||||||
|
# Read content of the file specified by the placeholder
|
||||||
|
with open(template_path, 'r') as f:
|
||||||
|
template = f.read()
|
||||||
|
|
||||||
|
# Replace placeholder with content of the file
|
||||||
|
body = body.replace(tmpl_import, template)
|
||||||
|
|
||||||
|
# Write modified content to the output file
|
||||||
|
with open(out_file, 'w') as f:
|
||||||
|
f.write(body)
|
||||||
|
|
||||||
|
def find_placeholder_expressions(body):
|
||||||
|
# Find and return placeholder expressions in the body
|
||||||
|
return [expr.group() for expr in re.finditer(r"%[^%]*%", body)]
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
26
populate_static.sh
Executable file
26
populate_static.sh
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
LANG=C
|
||||||
|
# Define the file path
|
||||||
|
tmpl_file="./src/http_static.cpp.tmpl";
|
||||||
|
out_file="./src/http_static.cpp";
|
||||||
|
static_dir="./src/static/";
|
||||||
|
echo pwd;
|
||||||
|
echo whoami;
|
||||||
|
|
||||||
|
# Define the body variable with multiline content
|
||||||
|
body=$(cat "$tmpl_file")
|
||||||
|
|
||||||
|
# Iterate over the array
|
||||||
|
while IFS= read -r tmpl_import; do
|
||||||
|
echo "Found expression: $tmpl_import"
|
||||||
|
|
||||||
|
tmpl_filename="${tmpl_import:1:-1}"
|
||||||
|
template=$(cat "${static_dir}${tmpl_filename}")
|
||||||
|
|
||||||
|
body="${body//"$tmpl_import"/"$template"}"
|
||||||
|
|
||||||
|
# Print or save the output
|
||||||
|
done < <(grep -o "%[^%]*%" <<< "$body")
|
||||||
|
|
||||||
|
echo "$body" > "$out_file"
|
||||||
|
b
|
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%"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
11
test/README
Normal file
11
test/README
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
This directory is intended for PlatformIO Test Runner and project tests.
|
||||||
|
|
||||||
|
Unit Testing is a software testing method by which individual units of
|
||||||
|
source code, sets of one or more MCU program modules together with associated
|
||||||
|
control data, usage procedures, and operating procedures, are tested to
|
||||||
|
determine whether they are fit for use. Unit testing finds problems early
|
||||||
|
in the development cycle.
|
||||||
|
|
||||||
|
More information about PlatformIO Unit Testing:
|
||||||
|
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
Loading…
Reference in New Issue
Block a user