esp8266-sensor/main.ino

753 lines
19 KiB
C++

// The ESP8266 RTC memory is arranged into blocks of 4 bytes. The access methods read and write 4 bytes at a time,
// so the RTC data structure should be padded to a 4-byte multiple.
struct {
uint32_t crc32; // 4 bytes
uint8_t channel; // 1 byte, 5 in total
uint8_t bssid[6]; // 6 bytes, 11 in total
uint8_t txPow = 0; // 1 byte, 12 in total
IPAddress mqttServer = mqttBroker; //save the dns lookup
#ifdef CCS_USE
bool ccsInit = false;
#endif
#ifdef SGP30_USE
bool sgpInit = false;
#endif
} rtcData;
/************************* RTC Mem *********************************/
#define WiFi_RTC 0
/************************* SSD1306 SPI *********************************/
#ifdef OLED_OUTPUT
#include <SSD1306.h>
SSD1306 display(0x3c, D4, D3);
//#include "OLEDDisplayUi.h"
#include "DejaVu_Sans_Mono_18.h"
// Initialize the OLED display using SPI
// D5 -> CLK
// D7 -> MOSI (DOUT)
// D1 -> RES
// D2 -> DC
// D8 -> CS
SSD1306Spi display(D1, D2, D8);
OLEDDisplayUi ui( &display );
#endif
/************************* BMP280 I2C *********************************/
#ifdef BME2_USE
//https://github.com/finitespace/BME280#usage
#include <BME280I2C.h>
#include "EnvironmentCalculations.h"
BME280I2C bme;
BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
BME280::PresUnit presUnit(BME280::PresUnit_hPa);
#endif
/************************* BMP680 I2C *********************************/
#ifdef BME6_USE //ToDo
/*
//https://github.com/finitespace/BME280#usage
#include <BME280I2C.h>
#include "EnvironmentCalculations.h"
BME280I2C bme6;
const bool metric = true;
BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
BME280::PresUnit presUnit(BME280::PresUnit_hPa);
*/
#endif
/************************* SHTXX I2C *********************************/
#ifdef SHT_USE
#include <SHTSensor.h>
SHTSensor sht(SHTSensor::SHT3X_ALT);
#endif
/************************* VEML6075 I2C *********************************/
#ifdef VEML_USE
//https://github.com/schizobovine/VEML6075
#include <VEML6075.h>
//#include <SparkFun_VEML6075_Arduino_Library.h>
VEML6075 uvMeter;
#endif
/************************* BH1750 I2C *********************************/
#ifdef BH_USE
//https://github.com/claws/BH1750
#include <BH1750.h>
BH1750 lightMeter;
#endif
/************************* CCS811 I2C *********************************/
#ifdef CCS_USE
//https://github.com/LucAce/CCS811
#include "CCS811.h"
// Error ID code
enum {
CCS811_ERROR_CODE_WRITE_REG_INVALID = B00000001,
CCS811_ERROR_CODE_READ_REG_INVALID = B00000010,
CCS811_ERROR_CODE_MEASMODE_INVALID = B00000100,
CCS811_ERROR_CODE_MAX_RESISTANCE = B00001000,
CCS811_ERROR_CODE_HEATER_FAULT = B00010000,
CCS811_ERROR_CODE_HEATER_SUPPLY = B00100000,
};
#ifdef SERIAL_DEBUG
//*****************************************************************************
// Function: errorDecode
// Environment decodes the single bits from the error return
//*****************************************************************************
void errorDecode(uint8_t error_code) {
if (bitRead(error_code, CCS811_ERROR_CODE_WRITE_REG_INVALID)) Serial.println(F("ERROR: Wrong write request received"));
if (bitRead(error_code, CCS811_ERROR_CODE_READ_REG_INVALID)) Serial.println(F("ERROR: Wrong read request received"));
if (bitRead(error_code, CCS811_ERROR_CODE_MEASMODE_INVALID)) Serial.println(F("ERROR: Wrong meausrement mode request received"));
if (bitRead(error_code, CCS811_ERROR_CODE_MAX_RESISTANCE)) Serial.println(F("ERROR: Maximum sensor resistance reached!"));
if (bitRead(error_code, CCS811_ERROR_CODE_HEATER_FAULT)) Serial.println(F("ERROR: Heater current out of range!"));
if (bitRead(error_code, CCS811_ERROR_CODE_HEATER_SUPPLY)) Serial.println(F("ERROR: Heater supply voltage out of bounce!"));
}
#endif
// CCS811 I2C Interface
CCS811 ccs;
#define CCSINTERRUPT D7 //GPIO13
#define CCSRESET D6 //GPIO12
#endif
#ifdef SGP30_USE
#include "SparkFun_SGP30_Arduino_Library.h"
SGP30 sgp;
#endif
void messageReceived(String &topic, String &payload) {
#ifdef SERIAL_DEBUG
Serial.println("incoming: " + topic + " - " + payload);
#endif
}
/************** generic settings **************************/
void sleep(int duration, RFMode type) {
#ifdef SERIAL_DEBUG
//flush UART (pg. 11 esp8266 low power solution)
Serial.flush();
delay(10);
#endif
ESP.deepSleep(duration, type);
yield();
delay(100);
}
uint32_t calculateCRC32( const uint8_t *data, size_t length ) {
uint32_t crc = 0xffffffff;
while ( length-- ) {
uint8_t c = *data++;
for ( uint32_t i = 0x80; i > 0; i >>= 1 ) {
bool bit = crc & 0x80000000;
if ( c & i ) {
bit = !bit;
}
crc <<= 1;
if ( bit ) {
crc ^= 0x04c11db7;
}
}
}
return crc;
}
#ifdef BATTERY_USE
uint32_t voltageDivCorrelation(uint32_t u3) {
const unsigned int offset = 8;
if (u3 < offset) {
return BATT_MEASUREMENT_OFFSET;
}
//translate the 10bit value to reference voltage of 1.0V
//Resistors are usually in kOhm
//ToDo: Not working! adc contains millivoltage at native adc port
uint32_t adc = map (u3 - offset, 0, 1023, 0 , 1000);
const uint32_t r1 = 395;
const uint32_t r2 = 220;
const uint32_t r3 = 100;
uint32_t rges = r1 + r2 + r3;
uint32_t uges = u3 * rges / r3;
//TODO why?!
uges = uges / 10;
return uges;
}
uint32_t adc = 0;
#endif
#if defined(BME2_USE) || defined (BME6_USE)
float temperature(NAN);
float humidity(NAN);
float absHumidity(NAN);
float pressure(NAN);
float dew(NAN);
String temp_str;
String hum_str;
String absHum_str;
String pres_str;
char temp[7];
char hum[8];
char absHum[8];
char pres[7];
#endif
#ifdef BME6_USE
float iaqF(NAN);
float iaqprecF(NAN);
String iaq_str;
String iaqprec_str;
char iaq[5];
char iaqprec[5];
#endif
#if defined(SHT_USE) || defined(SI7021_USE)
float temperature(NAN);
float humidity(NAN);
float absHumidity(NAN);
float dew(NAN);
String temp_str;
String hum_str;
String absHum_str;
char temp[7];
char hum[8];
char absHum[8];
#endif
#ifdef BH_USE
float vislight(NAN);
String light_str;
char light[8];
#endif
#ifdef VEML_USE
float uvalight(NAN);
float uvblight(NAN);
float uvi(NAN);
String uva_str;
String uvb_str;
String uvindex_str;
char uva[8];
char uvb[8];
char uvindex[5];
#endif
#if defined(CCS_USE) || defined(SGP30_USE)
float eco2(NAN);
String eco2_str;
char eco2C[8];
float tvoc(NAN);
String tvoc_str;
char tvocC[8];
#endif
#ifdef BATTERY_USE
float battery(NAN);
String batt_str;
char batt[7];
#endif
bool wifiCheck = false;
bool mqttCheck = false;
bool bme280Check = false;
bool bme680Check = false;
bool veml6075Check = false;
bool bh1750Check = false;
bool ccs811Check = false;
bool sht30Check = false;
bool sgp30Check = false;
WiFiClient mqttSocket;
MQTTClient mqttClient;
/*************************** Sketch Code ************************************/
void initSensors() {
//assign i2c pins to different pins. SPI is hardware defined. | Wire.begin(SDA,SCL) | CLK D3 | DATA D4 --> be aware that a lot libs do that internally
//Wire.begin(D4, D3); //nodemcu
Wire.begin(D2, D1); //wemos D1 mini
/*********************SSD1306 OLED init **************************************/
#ifdef OLED_OUTPUT
display.init();
display.flipScreenVertically();
//display.setFont(DejaVu_Sans_Mono_18);
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.displayOn();
#endif
/*********************BH1750 I2C init **************************************/
#ifdef BH_USE
#ifdef SERIAL_DEBUG
Serial.print("\tBH1750 init...\t");
#endif
#ifdef OLED_OUTPUT
// clear the display
display.clear();
display.drawStringMaxWidth(0, 0, 128, "BH1750 init" );
// write the buffer to the display
display.display();
#endif
if (!lightMeter.begin(BH1750::ONE_TIME_HIGH_RES_MODE))
{
#ifdef SERIAL_DEBUG
Serial.println("no BH1750 anwering");
#endif
#ifdef OLED_OUTPUT
display.drawStringMaxWidth(0, 16, 128, "BH1750 FAILED" );
// write the buffer to the display
display.display();
#endif
}
else {
bh1750Check = true;
#ifdef SERIAL_DEBUG
Serial.println("BH1750 ready.");
#endif
#ifdef OLED_OUTPUT
display.drawStringMaxWidth(0, 16, 128, "BH1750 ready" );
// write the buffer to the display
display.display();
#endif
}
#endif
/*********************VEML6075 I2C init **************************************/
#ifdef VEML_USE
#ifdef SERIAL_DEBUG
Serial.print(F("\tVEML6075 init...\t"));
#endif
#ifdef OLED_OUTPUT
// clear the display
display.clear();
display.drawStringMaxWidth(0, 0, 128, "VEML6075 init" );
// write the buffer to the display
display.display();
#endif
uvMeter.begin();
if (!uvMeter.getDevID())
{
delay(10);
#ifdef SERIAL_DEBUG
Serial.println(F("no VEML6075 detected!"));
#endif
#ifdef OLED_OUTPUT
display.drawStringMaxWidth(0, 16, 128, "VEML6075 FAILED" );
// write the buffer to the display
display.display();
#endif
}
else {
veml6075Check = true;
//set one time measurementw
#ifdef SERIAL_DEBUG
Serial.println(F("VEML6075 ready."));
#endif
#ifdef OLED_OUTPUT
display.drawStringMaxWidth(0, 16, 128, "VEML6075 ready" );
// write the buffer to the display
display.display();
#endif
}
#endif
/*********************BME680 I2C init **************************************/
#ifdef BME6_USE
#ifdef SERIAL_DEBUG
Serial.print(F("\tBME680 init...\t"));
#endif
#ifdef OLED_OUTPUT
// clear the display
display.clear();
display.drawStringMaxWidth(0, 0, 128, "BME680 init" );
// write the buffer to the display
display.display();
#endif
if (!bme6.begin())
{
delay(10);
#ifdef SERIAL_DEBUG
Serial.println(F("no BME680 detected!"));
#endif
#ifdef OLED_OUTPUT
display.drawStringMaxWidth(0, 16, 128, "BME680 FAILED" );
// write the buffer to the display
display.display();
#endif
}
else {
bme680Check = true;
#ifdef SERIAL_DEBUG
Serial.println(F("BME680 ready."));
#endif
#ifdef OLED_OUTPUT
display.drawStringMaxWidth(0, 16, 128, "BME680 ready" );
// write the buffer to the display
display.display();
#endif
}
#endif
/*********************BME280 I2C init **************************************/
#ifdef BME2_USE
#ifdef SERIAL_DEBUG
Serial.print(F("\tBME280 init...\t"));
#endif
#ifdef OLED_OUTPUT
// clear the display
display.clear();
display.drawStringMaxWidth(0, 0, 128, "BME280 init" );
// write the buffer to the display
display.display();
#endif
if (!bme.begin())
{
#ifdef SERIAL_DEBUG
Serial.println(F("no BME280 detected!"));
#endif
#ifdef OLED_OUTPUT
display.drawStringMaxWidth(0, 16, 128, "BME280 FAILED" );
// write the buffer to the display
display.display();
#endif
}
else {
bme280Check = true;
#ifdef SERIAL_DEBUG
Serial.println(F("BME280 ready."));
#endif
#ifdef OLED_OUTPUT
display.drawStringMaxWidth(0, 16, 128, "BME280 ready" );
// write the buffer to the display
display.display();
#endif
}
#endif
/*********************SHT3X I2C init **************************************/
#ifdef SHT_USE
#ifdef SERIAL_DEBUG
Serial.print(F("\tSHT30 init...\t"));
#endif
#ifdef OLED_OUTPUT
// clear the display
display.clear();
display.drawStringMaxWidth(0, 0, 128, "SHT30 init" );
// write the buffer to the display
display.display();
#endif
if (!sht.init())
{
#ifdef SERIAL_DEBUG
Serial.println(F("no SHT30 detected!"));
#endif
#ifdef OLED_OUTPUT
display.drawStringMaxWidth(0, 16, 128, "SHT30 FAILED" );
// write the buffer to the display
display.display();
#endif
}
else {
sht30Check = true;
sht.setAccuracy(SHTSensor::SHT_ACCURACY_MEDIUM); // only supported by SHT3x
yield();
#ifdef SERIAL_DEBUG
Serial.println(F("SHT30 ready."));
#endif
#ifdef OLED_OUTPUT
display.drawStringMaxWidth(0, 16, 128, "SHT30 ready" );
// write the buffer to the display
display.display();
#endif
}
#endif
/*********************CCS811 I2C init **************************************/
#ifdef CCS_USE
// Extend I2C clock stretch timeout (See Notes)
//Wire.setClockStretchLimit(500);
Wire.setClockStretchLimit(200000);
pinMode(CCSINTERRUPT, INPUT);
//pinMode(CCSRESET, OUTPUT);
delay(10);
digitalWrite(CCSRESET, HIGH);
delay(10);
#ifdef SERIAL_DEBUG
Serial.print(F("\tCCS811 init...\t"));
#endif
#ifdef OLED_OUTPUT
// clear the display
display.clear();
display.drawStringMaxWidth(0, 0, 128, "CCS811 init" );
// write the buffer to the display
display.display();
#endif
if (!ccs.begin())
{
delay(10);
#ifdef SERIAL_DEBUG
Serial.println(F("no CCS811 detected!"));
#endif
#ifdef OLED_OUTPUT
display.drawStringMaxWidth(0, 16, 128, "CCS811 FAILED" );
// write the buffer to the display
display.display();
#endif
}
else {
ccs811Check = true;
if (!rtcData.ccsInit) {
Serial.println(F("CCS811 not initialised"));
rtcData.ccsInit = true;
}
//set CCS811 measurement mode
ccs.writeMeasModeRegister(CCS811_DRIVE_MODE_1SEC , 1 , 0);
//Serial.println(F("INFO: Set measurement refresh to 1s"));
//enable Interrupt
ccs.enableInterrupt();
//Serial.println(F("INFO: Enable interrupt"));
/*while (!digitalRead(CCSINTERRUPT))
{
delay(200);
Serial.print(".");
}
while (!ccs.isDATA_READY())
{
delay(100);
ccs.readStatusRegister();
Serial.print(".");
};
*/
// ToDo why and reasons
//float temp = ccs.calculateTemperature();
ccs.setTempOffset(temperature - 25.0);
#ifdef SERIAL_DEBUG
Serial.println(F("CCS811 ready."));
/*
// Print CCS811 sensor information
Serial.println(F("CCS811 Sensor Enabled:"));
Serial.print(F("Hardware ID: 0x"));
Serial.println(ccs.getHWID(), HEX);
Serial.print(F("Hardware Version: 0x"));
Serial.println(ccs.getHWVersion(), HEX);
Serial.print(F("Firmware Boot Version: 0x"));
Serial.println(ccs.getFWBootVersion(), HEX);
Serial.print(F("Firmware App Version: 0x"));
Serial.println(ccs.getFWAppVersion(), HEX);
Serial.println(); */
#endif
#ifdef OLED_OUTPUT
display.drawStringMaxWidth(0, 16, 128, "CCS811 ready" );
// write the buffer to the display
display.display();
#endif
}
#endif
/*********************SGP30 I2C init **************************************/
#ifdef SGP30_USE
// Extend I2C clock stretch timeout (See Notes)
//Wire.setClockStretchLimit(500);
Wire.setClockStretchLimit(400000);
#ifdef SERIAL_DEBUG
Serial.print(F("\tSGP30 init...\t"));
#endif
#ifdef OLED_OUTPUT
// clear the display
display.clear();
display.drawStringMaxWidth(0, 0, 128, "SGP30 init" );
// write the buffer to the display
display.display();
#endif
if (!sgp.begin())
{
delay(10);
#ifdef SERIAL_DEBUG
Serial.println(F("no SGP30 detected!"));
#endif
#ifdef OLED_OUTPUT
display.drawStringMaxWidth(0, 16, 128, "SGP30 FAILED" );
// write the buffer to the display
display.display();
#endif
}
else {
sgp30Check = true;
delay(100);
/*
//Get SGP30's ID
sgp.getSerialID();
//Get version number
sgp.getFeatureSetVersion();
Serial.print("SerialID: 0x");
Serial.print((unsigned long)sgp.serialID, HEX);
Serial.print("\tFeature Set Version: 0x");
Serial.println(sgp.featureSetVersion, HEX);*/
SGP30ERR error;
//measureTest() should not be called after a call to initAirQuality()
error = sgp.measureTest();
if (error == SUCCESS) {
Serial.println("Success!");
}
else if (error == ERR_BAD_CRC) {
Serial.println("CRC Failed");
}
else if (error == ERR_I2C_TIMEOUT) {
Serial.println("I2C Timed out");
}
else if (error == SELF_TEST_FAIL) {
Serial.println("Self Test Failed");
}
//if (!rtcData.sgpInit) {
if (error == SELF_TEST_FAIL)
{
Serial.println(F("SGP30 not initialised"));
sgp.initAirQuality();
Serial.println(F("SGP30 warm up "));
error = sgp.measureTest();
if (error == SUCCESS) {
rtcData.sgpInit = true;
}
}
#ifdef SERIAL_DEBUG
Serial.println(F("SGP30 ready."));
#endif
#ifdef OLED_OUTPUT
display.drawStringMaxWidth(0, 16, 128, "SGP30 ready" );
// write the buffer to the display
display.display();
#endif
}
#endif
/******************** i2c init END ****************/
}
void setup() {
pinMode(D0, WAKEUP_PULLUP);
// if serial is not initialized all following calls to serial end dead.
#ifdef SERIAL_DEBUG
Serial.flush();
Serial.begin(9600);
while (!Serial) {
delay(1);
} // Wait
Serial.print(F("\nReset reason : \t\t\t"));
Serial.println(ESP.getResetReason());
#endif
WiFi.forceSleepBegin(); // send wifi to sleep to reduce power consumption
yield();
delay(100);
#ifdef BATTERY_USE
//check battery status
//Battery Voltage Measurement
//pinMode(D6, OUTPUT);
//digitalWrite(D6, HIGH); //activate transistor
//yield();
//delay(10);
pinMode(A0, INPUT);
yield();
adc = analogRead(A0);
yield();
delay(10);
#ifdef SERIAL_DEBUG
Serial.print(F("ADC_measurement: \t\t\t"));
Serial.println (String(adc));
Serial.print(F("VoltageDividerCorrelation in V: \t"));
Serial.println(String(voltageDivCorrelation(adc)));
#endif
yield();
delay(10);
// go to sleep if to low
if (voltageDivCorrelation(adc) - BATT_MEASUREMENT_OFFSET < BATT_WARNING_VOLTAGE) {
#ifdef SERIAL_DEBUG
Serial.println(F("Low Battery Voltage. Got to sleep"));
#endif
sleep(SLEEP_TIME_LOW_BAT, WAKE_RF_DISABLED);
}
#endif
//go further if not
//init wifi
wifiCheck = wifi_connect();
if (wifiCheck) {
//connect mqtt
if (!mqttClient.loop()) {
//connect mqtt
mqttCheck = mqtt_connect();
}
if (mqttCheck) {
#ifdef SERIAL_DEBUG
Serial.println(F("Sensor init routine"));
yield();
#endif
initSensors();
yield();
#ifdef SERIAL_DEBUG
Serial.println(F("--------measurement--------"));
yield();
#endif
measurement();
yield();
#ifdef SERIAL_DEBUG
Serial.println(F("---------------------------"));
yield();
#endif
mqtt_disconnect();
yield();
}
//firmware update check
/*if (firmware_available()
//{
// get firmware
}*/
//regular --check whether 5min leads to wifi timeout
wifi_disconnect();
yield();
if (mqttCheck) {
#ifdef SERIAL_DEBUG
Serial.println(F("regular sleep"));
Serial.println(F("***********************************************************"));
#endif
sleep(SLEEP_TIME_MEASUREMENT, WAKE_RF_DEFAULT);
}
else {
//ToDo Do more useful stuff
#ifdef SERIAL_DEBUG
Serial.println(F("mqtt failed - wifi timeout sleep"));
#endif
sleep(SLEEP_TIME_WIFI_TIMEOUT, WAKE_RF_DEFAULT);
}
}
//no wifi available
else {
#ifdef SERIAL_DEBUG
Serial.println(F("Wifi failed"));
#endif
sleep(SLEEP_TIME_WIFI_TIMEOUT, WAKE_RFCAL);
}
}
void loop() {
yield();
}