diff --git a/lib/Adafruit_SGP30-1.0.0.13/.github/ISSUE_TEMPLATE.md b/lib/Adafruit_SGP30-1.0.3/.github/ISSUE_TEMPLATE.md old mode 100644 new mode 100755 similarity index 100% rename from lib/Adafruit_SGP30-1.0.0.13/.github/ISSUE_TEMPLATE.md rename to lib/Adafruit_SGP30-1.0.3/.github/ISSUE_TEMPLATE.md diff --git a/lib/Adafruit_SGP30-1.0.0.13/.github/PULL_REQUEST_TEMPLATE.md b/lib/Adafruit_SGP30-1.0.3/.github/PULL_REQUEST_TEMPLATE.md old mode 100644 new mode 100755 similarity index 100% rename from lib/Adafruit_SGP30-1.0.0.13/.github/PULL_REQUEST_TEMPLATE.md rename to lib/Adafruit_SGP30-1.0.3/.github/PULL_REQUEST_TEMPLATE.md diff --git a/lib/Adafruit_SGP30-1.0.0.13/.gitignore b/lib/Adafruit_SGP30-1.0.3/.gitignore old mode 100644 new mode 100755 similarity index 100% rename from lib/Adafruit_SGP30-1.0.0.13/.gitignore rename to lib/Adafruit_SGP30-1.0.3/.gitignore diff --git a/lib/Adafruit_SGP30-1.0.0.13/.travis.yml b/lib/Adafruit_SGP30-1.0.3/.travis.yml old mode 100644 new mode 100755 similarity index 100% rename from lib/Adafruit_SGP30-1.0.0.13/.travis.yml rename to lib/Adafruit_SGP30-1.0.3/.travis.yml diff --git a/lib/Adafruit_SGP30-1.0.0.13/Adafruit_SGP30.cpp b/lib/Adafruit_SGP30-1.0.3/Adafruit_SGP30.cpp old mode 100644 new mode 100755 similarity index 83% rename from lib/Adafruit_SGP30-1.0.0.13/Adafruit_SGP30.cpp rename to lib/Adafruit_SGP30-1.0.3/Adafruit_SGP30.cpp index b2ccbe8da..ce6116863 --- a/lib/Adafruit_SGP30-1.0.0.13/Adafruit_SGP30.cpp +++ b/lib/Adafruit_SGP30-1.0.3/Adafruit_SGP30.cpp @@ -37,7 +37,7 @@ //#define I2C_DEBUG /**************************************************************************/ -/*! +/*! @brief Instantiates a new SGP30 class */ /**************************************************************************/ @@ -45,7 +45,7 @@ Adafruit_SGP30::Adafruit_SGP30() { } /**************************************************************************/ -/*! +/*! @brief Setups the hardware and detects a valid SGP30. Initializes I2C then reads the serialnumber and checks that we are talking to an SGP30 @param theWire Optional pointer to I2C interface, otherwise use Wire @@ -60,31 +60,32 @@ boolean Adafruit_SGP30::begin(TwoWire *theWire) { _i2c = theWire; } - _i2c->begin(); +// assume i2c initialized already to avoid resetting clock stretching +// _i2c->begin(); + - uint8_t command[2]; command[0] = 0x36; command[1] = 0x82; - if (! readWordFromCommand(command, 2, 10, serialnumber, 3)) + if (! readWordFromCommand(command, 2, 10, serialnumber, 3)) return false; uint16_t featureset; command[0] = 0x20; command[1] = 0x2F; - if (! readWordFromCommand(command, 2, 10, &featureset, 1)) + if (! readWordFromCommand(command, 2, 10, &featureset, 1)) return false; //Serial.print("Featureset 0x"); Serial.println(featureset, HEX); - if (featureset != SGP30_FEATURESET) + if (featureset != SGP30_FEATURESET) return false; - if (! IAQinit()) + if (! IAQinit()) return false; return true; } /**************************************************************************/ -/*! +/*! @brief Commands the sensor to begin the IAQ algorithm. Must be called after startup. @returns True if command completed successfully, false if something went wrong! */ @@ -97,7 +98,7 @@ boolean Adafruit_SGP30::IAQinit(void) { } /**************************************************************************/ -/*! +/*! @brief Commands the sensor to take a single eCO2/VOC measurement. Places results in {@link TVOC} and {@link eCO2} @returns True if command completed successfully, false if something went wrong! */ @@ -113,9 +114,9 @@ boolean Adafruit_SGP30::IAQmeasure(void) { eCO2 = reply[0]; return true; } - + /**************************************************************************/ -/*! +/*! @brief Request baseline calibration values for both CO2 and TVOC IAQ calculations. Places results in parameter memory locaitons. @param eco2_base A pointer to a uint16_t which we will save the calibration value to @param tvoc_base A pointer to a uint16_t which we will save the calibration value to @@ -135,7 +136,7 @@ boolean Adafruit_SGP30::getIAQBaseline(uint16_t *eco2_base, uint16_t *tvoc_base) } /**************************************************************************/ -/*! +/*! @brief Assign baseline calibration values for both CO2 and TVOC IAQ calculations. @param eco2_base A uint16_t which we will save the calibration value from @param tvoc_base A uint16_t which we will save the calibration value from @@ -157,7 +158,30 @@ boolean Adafruit_SGP30::setIAQBaseline(uint16_t eco2_base, uint16_t tvoc_base) { } /**************************************************************************/ -/*! +/*! + @brief Set the absolute humidity value [mg/m^3] for compensation to increase precision of TVOC and eCO2. + @param absolute_humidity A uint32_t [mg/m^3] which we will be used for compensation. If the absolute humidity is set to zero, humidity compensation will be disabled. + @returns True if command completed successfully, false if something went wrong! +*/ +/**************************************************************************/ +boolean Adafruit_SGP30::setHumidity(uint32_t absolute_humidity) { + if (absolute_humidity > 256000) { + return false; + } + + uint16_t ah_scaled = (uint16_t)(((uint64_t)absolute_humidity * 256 * 16777) >> 24); + uint8_t command[5]; + command[0] = 0x20; + command[1] = 0x61; + command[2] = ah_scaled >> 8; + command[3] = ah_scaled & 0xFF; + command[4] = generateCRC(command+2, 2); + + return readWordFromCommand(command, 5, 10); +} + +/**************************************************************************/ +/*! @brief I2C low level interfacing */ /**************************************************************************/ @@ -186,16 +210,16 @@ boolean Adafruit_SGP30::readWordFromCommand(uint8_t command[], uint8_t commandLe delay(delayms); - if (readlen == 0) + if (readlen == 0) return true; uint8_t replylen = readlen * (SGP30_WORD_LEN +1); - if (_i2c->requestFrom(_i2caddr, replylen) != replylen) + if (_i2c->requestFrom(_i2caddr, replylen) != replylen) return false; uint8_t replybuffer[replylen]; #ifdef I2C_DEBUG Serial.print("\t\t<- "); -#endif +#endif for (uint8_t i=0; iread(); #ifdef I2C_DEBUG diff --git a/lib/Adafruit_SGP30-1.0.0.13/Adafruit_SGP30.h b/lib/Adafruit_SGP30-1.0.3/Adafruit_SGP30.h old mode 100644 new mode 100755 similarity index 97% rename from lib/Adafruit_SGP30-1.0.0.13/Adafruit_SGP30.h rename to lib/Adafruit_SGP30-1.0.3/Adafruit_SGP30.h index cc95fa54b..6f27aad04 --- a/lib/Adafruit_SGP30-1.0.0.13/Adafruit_SGP30.h +++ b/lib/Adafruit_SGP30-1.0.3/Adafruit_SGP30.h @@ -42,6 +42,7 @@ class Adafruit_SGP30 { boolean getIAQBaseline(uint16_t *eco2_base, uint16_t *tvoc_base); boolean setIAQBaseline(uint16_t eco2_base, uint16_t tvoc_base); + boolean setHumidity(uint32_t absolute_humidity); /** * The last measurement of the IAQ-calculated Total Volatile Organic Compounds in ppb. This value is set when you call {@link IAQmeasure()} diff --git a/lib/Adafruit_SGP30-1.0.0.13/README.md b/lib/Adafruit_SGP30-1.0.3/README.md old mode 100644 new mode 100755 similarity index 100% rename from lib/Adafruit_SGP30-1.0.0.13/README.md rename to lib/Adafruit_SGP30-1.0.3/README.md diff --git a/lib/Adafruit_SGP30-1.0.0.13/examples/sgp30test/sgp30test.ino b/lib/Adafruit_SGP30-1.0.3/examples/sgp30test/sgp30test.ino old mode 100644 new mode 100755 similarity index 58% rename from lib/Adafruit_SGP30-1.0.0.13/examples/sgp30test/sgp30test.ino rename to lib/Adafruit_SGP30-1.0.3/examples/sgp30test/sgp30test.ino index 6b9b3ea05..b7ff8a70c --- a/lib/Adafruit_SGP30-1.0.0.13/examples/sgp30test/sgp30test.ino +++ b/lib/Adafruit_SGP30-1.0.3/examples/sgp30test/sgp30test.ino @@ -3,6 +3,17 @@ Adafruit_SGP30 sgp; +/* return absolute humidity [mg/m^3] with approximation formula +* @param temperature [°C] +* @param humidity [%RH] +*/ +uint32_t getAbsoluteHumidity(float temperature, float humidity) { + // approximation formula from Sensirion SGP30 Driver Integration chapter 3.15 + const float absoluteHumidity = 216.7f * ((humidity / 100.0f) * 6.112f * exp((17.62f * temperature) / (243.12f + temperature)) / (273.15f + temperature)); // [g/m^3] + const uint32_t absoluteHumidityScaled = static_cast(1000.0f * absoluteHumidity); // [mg/m^3] + return absoluteHumidityScaled; +} + void setup() { Serial.begin(9600); Serial.println("SGP30 test"); @@ -22,6 +33,11 @@ void setup() { int counter = 0; void loop() { + // If you have a temperature / humidity sensor, you can set the absolute humidity to enable the humditiy compensation for the air quality signals + //float temperature = 22.1; // [°C] + //float humidity = 45.2; // [%RH] + //sgp.setHumidity(getAbsoluteHumidity(temperature, humidity)); + if (! sgp.IAQmeasure()) { Serial.println("Measurement failed"); return; diff --git a/lib/Adafruit_SGP30-1.0.0.13/library.properties b/lib/Adafruit_SGP30-1.0.3/library.properties old mode 100644 new mode 100755 similarity index 95% rename from lib/Adafruit_SGP30-1.0.0.13/library.properties rename to lib/Adafruit_SGP30-1.0.3/library.properties index 3520b4d38..6c86464d1 --- a/lib/Adafruit_SGP30-1.0.0.13/library.properties +++ b/lib/Adafruit_SGP30-1.0.3/library.properties @@ -1,5 +1,5 @@ name=Adafruit SGP30 Sensor -version=1.0.2 +version=1.0.3 author=Adafruit maintainer=Adafruit sentence=This is an Arduino library for the Adafruit SGP30 Gas / Air Quality Sensor diff --git a/lib/Adafruit_SGP30-1.0.0.13/license.txt b/lib/Adafruit_SGP30-1.0.3/license.txt old mode 100644 new mode 100755 similarity index 100% rename from lib/Adafruit_SGP30-1.0.0.13/license.txt rename to lib/Adafruit_SGP30-1.0.3/license.txt diff --git a/sonoff/xsns_21_sgp30.ino b/sonoff/xsns_21_sgp30.ino index d8241f1d6..27d1a43f9 100644 --- a/sonoff/xsns_21_sgp30.ino +++ b/sonoff/xsns_21_sgp30.ino @@ -34,55 +34,122 @@ Adafruit_SGP30 sgp; uint8_t sgp30_type = 0; uint8_t sgp30_ready = 0; -uint8_t sgp30_counter = 0; +float sgp30_abshum; /********************************************************************************************/ +void sgp30_Init(void) { + if (sgp.begin()) { + sgp30_type = 1; +// snprintf_P(log_data, sizeof(log_data), PSTR("SGP: Serialnumber 0x%04X-0x%04X-0x%04X"), sgp.serialnumber[0], sgp.serialnumber[1], sgp.serialnumber[2]); +// AddLog(LOG_LEVEL_DEBUG); + snprintf_P(log_data, sizeof(log_data), S_LOG_I2C_FOUND_AT, "SGP30", 0x58); + AddLog(LOG_LEVEL_DEBUG); + } +} + +//#define POW_FUNC pow +#define POW_FUNC FastPrecisePow + +float sgp30_AbsoluteHumidity(float temperature, float humidity,char tempUnit) { + //taken from https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/ + //precision is about 0.1°C in range -30 to 35°C + //August-Roche-Magnus 6.1094 exp(17.625 x T)/(T + 243.04) + //Buck (1981) 6.1121 exp(17.502 x T)/(T + 240.97) + //reference https://www.eas.ualberta.ca/jdwilson/EAS372_13/Vomel_CIRES_satvpformulae.html + float temp = NAN; + const float mw = 18.01534; // molar mass of water g/mol + const float r = 8.31447215; // Universal gas constant J/mol/K + + if (isnan(temperature) || isnan(humidity) ) { + return NAN; + } + + if (tempUnit != 'C') { + temperature = (temperature - 32.0) * (5.0 / 9.0); /*conversion to [°C]*/ + } + + temp = POW_FUNC(2.718281828, (17.67 * temperature) / (temperature + 243.5)); + + + + //return (6.112 * temp * humidity * 2.1674) / (273.15 + temperature); //simplified version + return (6.112 * temp * humidity * mw) / ((273.15 + temperature) * r); //long version +} + +#define SAVE_PERIOD 30 + void Sgp30Update(void) // Perform every second to ensure proper operation of the baseline compensation algorithm { sgp30_ready = 0; - if (!sgp30_type) { - if (sgp.begin()) { - sgp30_type = 1; -// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SGP: Serialnumber 0x%04X-0x%04X-0x%04X"), sgp.serialnumber[0], sgp.serialnumber[1], sgp.serialnumber[2]); - AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "SGP30", 0x58); + if (!sgp.IAQmeasure() || !sgp30_type) { + // retry to init every 100 seconds + if (21 == (uptime %100)) { + sgp30_Init(); } - } else { - if (!sgp.IAQmeasure()) return; // Measurement failed - sgp30_counter++; - if (30 == sgp30_counter) { - sgp30_counter = 0; + return; // Measurement failed + } + if (global_update) { + // abs hum in mg/m3 + sgp30_abshum=sgp30_AbsoluteHumidity(global_temperature,global_humidity,TempUnit()); + sgp.setHumidity(sgp30_abshum*1000); + } + sgp30_ready = 1; - uint16_t TVOC_base; - uint16_t eCO2_base; + // these should normally be stored permanently and used for fast restart + if (!(uptime%SAVE_PERIOD)) { + // store settings every N seconds + uint16_t TVOC_base; + uint16_t eCO2_base; + + if (!sgp.getIAQBaseline(&eCO2_base, &TVOC_base)) return; // Failed to get baseline readings +// snprintf_P(log_data, sizeof(log_data), PSTR("SGP: Baseline values eCO2 0x%04X, TVOC 0x%04X"), eCO2_base, TVOC_base); +// AddLog(LOG_LEVEL_DEBUG); - if (!sgp.getIAQBaseline(&eCO2_base, &TVOC_base)) return; // Failed to get baseline readings -// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SGP: Baseline values eCO2 0x%04X, TVOC 0x%04X"), eCO2_base, TVOC_base); - } - sgp30_ready = 1; } } + +#ifdef USE_WEBSERVER const char HTTP_SNS_SGP30[] PROGMEM = "{s}SGP30 " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}" // {s} = , {m} = , {e} = "{s}SGP30 " D_TVOC "{m}%d " D_UNIT_PARTS_PER_BILLION "{e}"; +const char HTTP_SNS_AHUM[] PROGMEM = "{s}SGP30 " "Abs Humidity" "{m}%s g/m3{e}"; +#endif + +#define D_JSON_AHUM "aHumidity" void Sgp30Show(bool json) { if (sgp30_ready) { + char abs_hum[33]; + + if (global_update) { + // has humidity + temperature + dtostrfd(sgp30_abshum,4,abs_hum); + } + if (json) { - ResponseAppend_P(PSTR(",\"SGP30\":{\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TVOC "\":%d}"), sgp.eCO2, sgp.TVOC); + ResponseAppend_P(PSTR(",\"SGP30\":{\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TVOC "\":%d"), sgp.eCO2, sgp.TVOC); + if (global_update) { + ResponseAppend_P(PSTR(",\"" D_JSON_AHUM "\":%s"),abs_hum); + } + ResponseAppend_P(PSTR("}")); #ifdef USE_DOMOTICZ if (0 == tele_period) DomoticzSensor(DZ_AIRQUALITY, sgp.eCO2); #endif // USE_DOMOTICZ #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_SGP30, sgp.eCO2, sgp.TVOC); + if (global_update) { + WSContentSend_PD(HTTP_SNS_AHUM, abs_hum); + } #endif } } } + /*********************************************************************************************\ * Interface \*********************************************************************************************/ @@ -93,6 +160,9 @@ bool Xsns21(uint8_t function) if (i2c_flg) { switch (function) { + case FUNC_INIT: + sgp30_Init(); + break; case FUNC_EVERY_SECOND: Sgp30Update(); break; @@ -110,4 +180,4 @@ bool Xsns21(uint8_t function) } #endif // USE_SGP30 -#endif // USE_I2C \ No newline at end of file +#endif // USE_I2C