Add sgp30 sensor

This commit is contained in:
SeanOMik 2023-07-23 23:02:56 -04:00
parent d70c4e8e30
commit c34cd4d5cc
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
5 changed files with 169 additions and 22 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.pio
.vscode/

24
platformio.ini Normal file
View File

@ -0,0 +1,24 @@
; 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]
platform = espressif8266
board = huzzah
framework = arduino
lib_deps =
Wire
SPI
ESP8266WiFi
adafruit/Adafruit SGP30 Sensor@^2.0.0
adafruit/Adafruit DHT Unified@^1.0.0
adafruit/DHT sensor library@^1.4.4
[env:huzzah]

15
shell.nix Normal file
View File

@ -0,0 +1,15 @@
{ pkgs ? import <nixpkgs> { } }:
with pkgs;
mkShell rec {
nativeBuildInputs = [
platformio
python39
xdg-user-dirs
];
buildInputs = [
];
LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs;
}

2
src/.gitignore vendored
View File

@ -1 +1 @@
/config.h config.h

View File

@ -1,6 +1,9 @@
//#include <Android.h>
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <ESP8266WebServer.h> #include <ESP8266WebServer.h>
#include <DHTesp.h> #include <DHT.h>
#include <DHT_U.h>
#include <Adafruit_SGP30.h>
#include "config.h" #include "config.h"
#include "version.h" #include "version.h"
@ -14,40 +17,77 @@ enum LogLevel {
}; };
void setup_dht_sensor(); void setup_dht_sensor();
void setup_sgp_sensor();
void setup_wifi(); void setup_wifi();
void setup_http_server(); void setup_http_server();
void handle_http_root();
void handle_http_metrics();
void handle_http_not_found();
void log_request();
void read_humidity_sensor();
void read_temperature_sensor();
void read_heat_index();
void read_sgp();
void handle_http_home_client(); void handle_http_home_client();
void handle_http_metrics_client(); void handle_http_metrics_client();
void read_sensors(boolean force=false); void read_sensors(boolean force=false);
bool read_sensor(float (*function)(), float *value); bool read_sensor(float (*function)(), float *value);
void log(char const *message, LogLevel level=LogLevel::INFO); void log(char const *message, LogLevel level=LogLevel::INFO);
void get_http_method_name(char *name, size_t name_length, HTTPMethod method);
DHT dht_sensor(DHT_PIN, DHT_TYPE);
Adafruit_SGP30 sgp_sensor;
DHTesp dht_sensor;
ESP8266WebServer http_server(HTTP_SERVER_PORT); ESP8266WebServer http_server(HTTP_SERVER_PORT);
char sgp30_serial[13];
float humidity, temperature, heat_index; float humidity, temperature, heat_index;
uint32_t sgp30_counter = 0;
uint16_t tvoc, co2, h2, ethanol;
uint32_t previous_read_time = 0; uint32_t previous_read_time = 0;
void setup(void) { void setup(void) {
char message[128]; char message[128];
Serial.begin(9600); Serial.begin(9600);
setup_dht_sensor(); setup_dht_sensor();
setup_sgp_sensor();
// Test all the sensors
read_sensors(true);
setup_wifi(); setup_wifi();
setup_http_server(); setup_http_server();
snprintf(message, 128, "Prometheus namespace: %s", PROM_NAMESPACE); snprintf(message, 128, "Prometheus namespace: %s", PROM_NAMESPACE);
log(message); log(message);
log("Setup done"); log("Setup done");
} }
void setup_dht_sensor() { void setup_dht_sensor() {
log("Setting up DHT sensor"); log("Setting up DHT sensor...");
dht_sensor.setup(DHT_PIN, DHTesp::DHT_TYPE); dht_sensor.begin();
delay(dht_sensor.getMinimumSamplingPeriod()); delay(1000);
// Test read
read_sensors(true);
log("DHT sensor ready", LogLevel::DEBUG); log("DHT sensor ready", LogLevel::DEBUG);
} }
void setup_sgp_sensor() {
log("Setting up SGP30 sensor...");
if (!sgp_sensor.begin()) {
log("SGP30 sensor failed to begin!", LogLevel::ERROR);
log("Failed to setup SGP30 sensor!", LogLevel::ERROR);
return;
}
snprintf(sgp30_serial, 13, "%x%x%x", sgp_sensor.serialnumber[0], sgp_sensor.serialnumber[1], sgp_sensor.serialnumber[2]);
char message[32];
snprintf(message, 32, "SGP30 serial #%s", sgp30_serial);
sgp_sensor.softReset();
}
void setup_wifi() { void setup_wifi() {
char message[128]; char message[128];
log("Setting up Wi-Fi"); log("Setting up Wi-Fi");
@ -107,6 +147,7 @@ void setup_wifi() {
snprintf(message, 128, "Secondary DNS server: %s", WiFi.dnsIP(1).toString().c_str()); snprintf(message, 128, "Secondary DNS server: %s", WiFi.dnsIP(1).toString().c_str());
log(message); log(message);
} }
void setup_http_server() { void setup_http_server() {
char message[128]; char message[128];
log("Setting up HTTP server"); log("Setting up HTTP server");
@ -139,12 +180,17 @@ void handle_http_root() {
void handle_http_metrics() { void handle_http_metrics() {
log_request(); log_request();
static size_t const BUFSIZE = 1024; static size_t const BUFSIZE = 2000;
static char const *response_template = static char const *response_template =
"# HELP " PROM_NAMESPACE "_info Metadata about the device.\n" "# HELP " PROM_NAMESPACE "_info Metadata about the device.\n"
"# TYPE " PROM_NAMESPACE "_info gauge\n" "# TYPE " PROM_NAMESPACE "_info gauge\n"
"# UNIT " PROM_NAMESPACE "_info \n" "# UNIT " PROM_NAMESPACE "_info \n"
PROM_NAMESPACE "_info{version=\"%s\",board=\"%s\",sensor=\"%s\"} 1\n" PROM_NAMESPACE "_info{version=\"%s\",board=\"%s\"} 1\n"
"# HELP " PROM_NAMESPACE "_info Metadata about a sensor.\n"
"# TYPE " PROM_NAMESPACE "_info gauge\n"
"# UNIT " PROM_NAMESPACE "_info \n"
PROM_NAMESPACE "_sensor{sensor=\"%s\",serial=\"%s\"} 1\n"
PROM_NAMESPACE "_sensor{sensor=\"%s\",serial=\"%s\"} 1\n"
"# HELP " PROM_NAMESPACE "_air_humidity_percent Air humidity.\n" "# HELP " PROM_NAMESPACE "_air_humidity_percent Air humidity.\n"
"# TYPE " PROM_NAMESPACE "_air_humidity_percent gauge\n" "# TYPE " PROM_NAMESPACE "_air_humidity_percent gauge\n"
"# UNIT " PROM_NAMESPACE "_air_humidity_percent %%\n" "# UNIT " PROM_NAMESPACE "_air_humidity_percent %%\n"
@ -156,16 +202,32 @@ void handle_http_metrics() {
"# HELP " PROM_NAMESPACE "_air_heat_index_celsius Apparent air temperature, based on temperature and humidity.\n" "# HELP " PROM_NAMESPACE "_air_heat_index_celsius Apparent air temperature, based on temperature and humidity.\n"
"# TYPE " PROM_NAMESPACE "_air_heat_index_celsius gauge\n" "# TYPE " PROM_NAMESPACE "_air_heat_index_celsius gauge\n"
"# UNIT " PROM_NAMESPACE "_air_heat_index_celsius \u00B0C\n" "# UNIT " PROM_NAMESPACE "_air_heat_index_celsius \u00B0C\n"
PROM_NAMESPACE "_air_heat_index_celsius %f\n"; PROM_NAMESPACE "_air_heat_index_celsius %f\n"
"# HELP " PROM_NAMESPACE "_air_quality_eco2 Equivalent calculated carbon-dioxide (eCO2).\n"
"# TYPE " PROM_NAMESPACE "_air_quality_eco2 gauge\n"
"# UNIT " PROM_NAMESPACE "_air_quality_eco2 ppm\n"
PROM_NAMESPACE "_air_quality_eco2 %d\n"
"# HELP " PROM_NAMESPACE "_air_quality_tvoc Total Volatile Organic Compound (TVOC).\n"
"# TYPE " PROM_NAMESPACE "_air_quality_tvoc gauge\n"
"# UNIT " PROM_NAMESPACE "_air_quality_tvoc ppb\\t\n"
PROM_NAMESPACE "_air_quality_tvoc %d\n"
"# HELP " PROM_NAMESPACE "_air_quality_h2 Hydrogen.\n"
"# TYPE " PROM_NAMESPACE "_air_quality_h2 gauge\n"
"# UNIT " PROM_NAMESPACE "_air_quality_h2 ppm\n"
PROM_NAMESPACE "_air_quality_h2 %d\n"
"# HELP " PROM_NAMESPACE "_air_quality_ethanol Ethanol.\n"
"# TYPE " PROM_NAMESPACE "_air_quality_ethanol gauge\n"
"# UNIT " PROM_NAMESPACE "_air_quality_ethanol ppm\n"
PROM_NAMESPACE "_air_quality_ethanol %d\n";
read_sensors(); read_sensors();
if (isnan(humidity) || isnan(temperature) || isnan(heat_index)) { if (isnan(humidity) || isnan(temperature) || isnan(heat_index) || isnan(tvoc) || isnan(co2) || isnan(h2) || isnan(ethanol)) {
http_server.send(500, "text/plain; charset=utf-8", "Sensor error."); http_server.send(500, "text/plain; charset=utf-8", "Sensor error.");
return; return;
} }
char response[BUFSIZE]; char response[BUFSIZE];
snprintf(response, BUFSIZE, response_template, VERSION, BOARD_NAME, DHT_NAME, humidity, temperature, heat_index); snprintf(response, BUFSIZE, response_template, VERSION, BOARD_NAME, DHT_NAME, "n/a", "SGP30", sgp30_serial, humidity, temperature, heat_index, tvoc, co2, h2, ethanol);
http_server.send(200, "text/plain; charset=utf-8", response); http_server.send(200, "text/plain; charset=utf-8", response);
} }
@ -175,8 +237,9 @@ void handle_http_not_found() {
} }
void read_sensors(boolean force) { void read_sensors(boolean force) {
uint32_t min_interval = max(dht_sensor.getMinimumSamplingPeriod(), READ_INTERVAL); uint32_t min_interval = max(READ_INTERVAL, 1000);
uint32_t current_time = millis(); uint32_t current_time = millis();
if (!force && current_time - previous_read_time < min_interval) { if (!force && current_time - previous_read_time < min_interval) {
log("Sensors were recently read, will not read again yet.", LogLevel::DEBUG); log("Sensors were recently read, will not read again yet.", LogLevel::DEBUG);
return; return;
@ -186,14 +249,17 @@ void read_sensors(boolean force) {
read_humidity_sensor(); read_humidity_sensor();
read_temperature_sensor(); read_temperature_sensor();
read_heat_index(); read_heat_index();
read_sgp();
} }
void read_humidity_sensor() { void read_humidity_sensor() {
log("Reading humidity sensor ...", LogLevel::DEBUG); log("Reading humidity sensor ...", LogLevel::DEBUG);
bool result = read_sensor([] {
return dht_sensor.getHumidity(); read_sensor([] {
return dht_sensor.readHumidity();
}, &humidity); }, &humidity);
if (result) {
if (humidity) {
humidity += HUMIDITY_CORRECTION_OFFSET; humidity += HUMIDITY_CORRECTION_OFFSET;
} else { } else {
log("Failed to read humidity sensor.", LogLevel::ERROR); log("Failed to read humidity sensor.", LogLevel::ERROR);
@ -202,10 +268,12 @@ void read_humidity_sensor() {
void read_temperature_sensor() { void read_temperature_sensor() {
log("Reading temperature sensor ...", LogLevel::DEBUG); log("Reading temperature sensor ...", LogLevel::DEBUG);
bool result = read_sensor([] {
return dht_sensor.getTemperature(); read_sensor([] {
return dht_sensor.readTemperature();
}, &temperature); }, &temperature);
if (result) {
if (temperature) {
temperature += TEMPERATURE_CORRECTION_OFFSET; temperature += TEMPERATURE_CORRECTION_OFFSET;
} else { } else {
log("Failed to read temperature sensor.", LogLevel::ERROR); log("Failed to read temperature sensor.", LogLevel::ERROR);
@ -214,12 +282,50 @@ void read_temperature_sensor() {
void read_heat_index() { void read_heat_index() {
if (!isnan(humidity) && !isnan(temperature)) { if (!isnan(humidity) && !isnan(temperature)) {
heat_index = dht_sensor.computeHeatIndex(temperature, humidity, false); heat_index = dht_sensor.computeHeatIndex(temperature, humidity, false); // compute in celsius
} else { } else {
heat_index = NAN; heat_index = NAN;
} }
} }
float getAbsoluteHumidity(float t, float h) {
float absoluteHumidity = (2.167 * 6.112) * h ;
absoluteHumidity *= exp((17.62 * t)/(243.12 + t));
absoluteHumidity /= (273.15 + t);
return absoluteHumidity;
}
void read_sgp() {
log("Reading air quality (SGP30) sensor ...", LogLevel::DEBUG);
sgp_sensor.setHumidity(getAbsoluteHumidity(temperature, humidity));
if (sgp_sensor.IAQmeasure() && sgp_sensor.IAQmeasureRaw()) {
tvoc = sgp_sensor.TVOC;
co2 = sgp_sensor.eCO2;
h2 = sgp_sensor.rawH2;
ethanol = sgp_sensor.rawEthanol;
sgp30_counter++;
if (sgp30_counter == 30) {
sgp30_counter = 0;
uint16_t tvoc_base, eco2_base;
if (!sgp_sensor.getIAQBaseline(&eco2_base, &tvoc_base)) {
log("Failed to get baseline readings", LogLevel::ERROR);
return;
}
char message[128];
snprintf(message, 128, "Baseline values: eCO2: 0x%s & TVOC: 0x%s", String(eco2_base, HEX).c_str(), String(tvoc_base, HEX).c_str());
log(message, LogLevel::DEBUG);
}
} else {
log("Failed to read air quality sensor.", LogLevel::ERROR);
}
}
bool read_sensor(float (*function)(), float *value) { bool read_sensor(float (*function)(), float *value) {
bool success = false; bool success = false;
for (int i = 0; i < READ_TRY_COUNT; i++) { for (int i = 0; i < READ_TRY_COUNT; i++) {