diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5b3e8cf --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode +*.DS_Store diff --git a/README.md b/README.md index 783a97e..c647e90 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ If you have any questions or problems, check out [our forum](https://forum.airgr - [Sensirion Core](https://github.com/Sensirion/arduino-core/) - [Sensirion I2C SGP41](https://github.com/Sensirion/arduino-i2c-sgp41) - [Sensirion I2C SHT4x](https://github.com/Sensirion/arduino-i2c-sht4x) +- [Sensirion I2C SHT3x](https://github.com/Sensirion/arduino-i2c-sht3x) - [PMS](https://github.com/fu-hsi/pms) ## License diff --git a/examples/.DS_Store b/examples/.DS_Store deleted file mode 100644 index d659c58..0000000 Binary files a/examples/.DS_Store and /dev/null differ diff --git a/examples/BASIC_v4/BASIC_v4.ino b/examples/BASIC_v4/BASIC_v4.ino index a630dbe..dd96b3a 100644 --- a/examples/BASIC_v4/BASIC_v4.ino +++ b/examples/BASIC_v4/BASIC_v4.ino @@ -1,7 +1,9 @@ /* -This is the code for the AirGradient DIY BASIC Air Quality Monitor with an D1 ESP8266 Microcontroller. +This is the code for the AirGradient DIY BASIC Air Quality Monitor with an D1 +ESP8266 Microcontroller. -It is an air quality monitor for PM2.5, CO2, Temperature and Humidity with a small display and can send data over Wifi. +It is an air quality monitor for PM2.5, CO2, Temperature and Humidity with a +small display and can send data over Wifi. Open source air quality monitors and kits are available: Indoor Monitor: https://www.airgradient.com/indoor/ @@ -15,11 +17,13 @@ Following libraries need to be installed: "Arduino_JSON" by Arduino version 0.2.0 "U8g2" by oliver version 2.34.22 -Please make sure you have esp8266 board manager installed. Tested with version 3.1.2. +Please make sure you have esp8266 board manager installed. Tested with +version 3.1.2. Set board to "LOLIN(WEMOS) D1 R2 & mini" -Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3) can be set through the AirGradient dashboard. +Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3) +can be set through the AirGradient dashboard. If you have any questions please visit our forum at https://forum.airgradient.com/ @@ -32,67 +36,353 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License #include #include #include -#include #include #include -typedef struct { - bool inF; /** Temperature unit */ - bool inUSAQI; /** PMS standard */ - uint8_t ledBarMode; /** @ref UseLedBar*/ - char model[16]; /** Model string value, Just define, don't know how much - memory usage */ - char mqttBroker[128]; /** Mqtt broker link */ - uint32_t _check; /** Checksum configuration data */ -} ServerConfig_t; -static ServerConfig_t serverConfig; +#define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */ +#define WIFI_CONNECT_RETRY_MS 10000 /** ms */ +#define LED_BAR_COUNT_INIT_VALUE (-1) /** */ +#define LED_BAR_ANIMATION_PERIOD 100 /** ms */ +#define DISP_UPDATE_INTERVAL 5000 /** ms */ +#define SERVER_CONFIG_UPDATE_INTERVAL 30000 /** ms */ +#define SERVER_SYNC_INTERVAL 60000 /** ms */ +#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */ +#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */ +#define SENSOR_CO2_UPDATE_INTERVAL 5000 /** ms */ +#define SENSOR_PM_UPDATE_INTERVAL 5000 /** ms */ +#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 5000 /** ms */ +#define DISPLAY_DELAY_SHOW_CONTENT_MS 3000 /** ms */ +#define WIFI_HOTSPOT_PASSWORD_DEFAULT \ + "cleanair" /** default WiFi AP password \ + */ -AirGradient ag = AirGradient(BOARD_DIY_BASIC_KIT); +/** + * @brief Use use LED bar state + */ +typedef enum { + UseLedBarOff, /** Don't use LED bar */ + UseLedBarPM, /** Use LED bar for PMS */ + UseLedBarCO2, /** Use LED bar for CO2 */ +} UseLedBar; -// CONFIGURATION START +/** + * @brief Schedule handle with timing period + * + */ +class AgSchedule { +public: + AgSchedule(int period, void (*handler)(void)) + : period(period), handler(handler) {} + void run(void) { + uint32_t ms = (uint32_t)(millis() - count); + if (ms >= period) { + /** Call handler */ + handler(); -// set to the endpoint you would like to use -String APIROOT = "http://hw.airgradient.com/"; + Serial.printf("[AgSchedule] handle 0x%08x, period: %d(ms)\r\n", + (unsigned int)handler, period); -String wifiApPass = "cleanair"; + /** Update period time */ + count = millis(); + } + } -// set to true if you want to connect to wifi. You have 60 seconds to connect. -// Then it will go into an offline mode. -boolean connectWIFI = true; +private: + void (*handler)(void); + int period; + int count; +}; -// CONFIGURATION END +/** + * @brief AirGradient server configuration and sync data + * + */ +class AgServer { +public: + void begin(void) { + inF = false; + inUSAQI = false; + configFailed = false; + serverFailed = false; + memset(models, 0, sizeof(models)); + memset(mqttBroker, 0, sizeof(mqttBroker)); + } -unsigned long currentMillis = 0; + /** + * @brief Get server configuration + * + * @param id Device ID + * @return true Success + * @return false Failure + */ + bool pollServerConfig(String id) { + String uri = + "http://hw.airgradient.com/sensors/airgradient:" + id + "/one/config"; -const int oledInterval = 5000; -unsigned long previousOled = 0; -bool co2CalibrationRequest = false; -uint32_t serverConfigLoadTime = 0; -String HOSTPOT = ""; + /** Init http client */ + WiFiClient wifiClient; + HTTPClient client; + if (client.begin(wifiClient, uri) == false) { + configFailed = true; + return false; + } -const int sendToServerInterval = 60000; -const int pollServerConfigInterval = 30000; -const int co2CalibCountdown = 5; /** Seconds */ -unsigned long previoussendToServer = 0; + /** Get */ + int retCode = client.GET(); + if (retCode != 200) { + client.end(); + configFailed = true; + return false; + } -const int co2Interval = 5000; -unsigned long previousCo2 = 0; -int Co2 = 0; + /** clear failed */ + configFailed = false; -const int pm25Interval = 5000; -unsigned long previousPm25 = 0; -int pm25 = 0; + /** Get response string */ + String respContent = client.getString(); + client.end(); + Serial.println("Get server config: " + respContent); -const int tempHumInterval = 2500; -unsigned long previousTempHum = 0; -float temp = 0; -int hum = 0; -long val; + /** Parse JSON */ + JSONVar root = JSON.parse(respContent); + if (JSON.typeof(root) == "undefined") { + /** JSON invalid */ + return false; + } -void failedHandler(String msg); -void boardInit(void); -void getServerConfig(void); -void co2Calibration(void); + /** Get "country" */ + if (JSON.typeof_(root["country"]) == "string") { + String country = root["country"]; + if (country == "US") { + inF = true; + } else { + inF = false; + } + } + + /** Get "pmsStandard" */ + if (JSON.typeof_(root["pmsStandard"]) == "string") { + String standard = root["pmsStandard"]; + if (standard == "ugm3") { + inUSAQI = false; + } else { + inUSAQI = true; + } + } + + /** Get "co2CalibrationRequested" */ + if (JSON.typeof_(root["co2CalibrationRequested"]) == "boolean") { + co2Calib = root["co2CalibrationRequested"]; + } + + /** Get "ledBarMode" */ + if (JSON.typeof_(root["ledBarMode"]) == "string") { + String mode = root["ledBarMode"]; + if (mode == "co2") { + ledBarMode = UseLedBarCO2; + } else if (mode == "pm") { + ledBarMode = UseLedBarPM; + } else if (mode == "off") { + ledBarMode = UseLedBarOff; + } else { + ledBarMode = UseLedBarOff; + } + } + + /** Get model */ + if (JSON.typeof_(root["model"]) == "string") { + String model = root["model"]; + if (model.length()) { + int len = + model.length() < sizeof(models) ? model.length() : sizeof(models); + memset(models, 0, sizeof(models)); + memcpy(models, model.c_str(), len); + } + } + + /** Get "mqttBrokerUrl" */ + if (JSON.typeof_(root["mqttBrokerUrl"]) == "string") { + String mqtt = root["mqttBrokerUrl"]; + if (mqtt.length()) { + int len = mqtt.length() < sizeof(mqttBroker) ? mqtt.length() + : sizeof(mqttBroker); + memset(mqttBroker, 0, sizeof(mqttBroker)); + memcpy(mqttBroker, mqtt.c_str(), len); + } + } + + /** Get 'abcDays' */ + if (JSON.typeof_(root["abcDays"]) == "number") { + co2AbcCalib = root["abcDays"]; + } else { + co2AbcCalib = -1; + } + + /** Show configuration */ + showServerConfig(); + + return true; + } + + bool postToServer(String id, String payload) { + /** + * @brief Only post data if WiFi is connected + */ + if (WiFi.isConnected() == false) { + return false; + } + + Serial.printf("Post payload: %s\r\n", payload.c_str()); + + String uri = + "http://hw.airgradient.com/sensors/airgradient:" + id + "/measures"; + + WiFiClient wifiClient; + HTTPClient client; + if (client.begin(wifiClient, uri.c_str()) == false) { + return false; + } + client.addHeader("content-type", "application/json"); + int retCode = client.POST(payload); + client.end(); + + if ((retCode == 200) || (retCode == 429)) { + serverFailed = false; + return true; + } + serverFailed = true; + return false; + } + + /** + * @brief Get temperature configuration unit + * + * @return true F unit + * @return false C Unit + */ + bool isTemperatureUnitF(void) { return inF; } + + /** + * @brief Get PMS standard unit + * + * @return true USAQI + * @return false ugm3 + */ + bool isPMSinUSAQI(void) { return inUSAQI; } + + /** + * @brief Get status of get server coniguration is failed + * + * @return true Failed + * @return false Success + */ + bool isConfigFailed(void) { return configFailed; } + + /** + * @brief Get status of post server configuration is failed + * + * @return true Failed + * @return false Success + */ + bool isServerFailed(void) { return serverFailed; } + + /** + * @brief Get request calibration CO2 + * + * @return true Requested. If result = true, it's clear after function call + * @return false Not-requested + */ + bool isCo2Calib(void) { + bool ret = co2Calib; + if (ret) { + co2Calib = false; + } + return ret; + } + + /** + * @brief Get the Co2 auto calib period + * + * @return int days, -1 if invalid. + */ + int getCo2Abccalib(void) { return co2AbcCalib; } + + /** + * @brief Get device configuration model name + * + * @return String Model name, empty string if server failed + */ + String getModelName(void) { return String(models); } + + /** + * @brief Get mqttBroker url + * + * @return String Broker url, empty if server failed + */ + String getMqttBroker(void) { return String(mqttBroker); } + + /** + * @brief Show server configuration parameter + */ + void showServerConfig(void) { + Serial.println("Server configuration: "); + Serial.printf(" inF: %s\r\n", inF ? "true" : "false"); + Serial.printf(" inUSAQI: %s\r\n", inUSAQI ? "true" : "false"); + Serial.printf(" useRGBLedBar: %d\r\n", (int)ledBarMode); + Serial.printf(" Model: %s\r\n", models); + Serial.printf(" Mqtt Broker: %s\r\n", mqttBroker); + Serial.printf(" S8 calib period: %d\r\n", co2AbcCalib); + } + + /** + * @brief Get server config led bar mode + * + * @return UseLedBar + */ + UseLedBar getLedBarMode(void) { return ledBarMode; } + +private: + bool inF; /** Temperature unit, true: F, false: C */ + bool inUSAQI; /** PMS unit, true: USAQI, false: ugm3 */ + bool configFailed; /** Flag indicate get server configuration failed */ + bool serverFailed; /** Flag indicate post data to server failed */ + bool co2Calib; /** Is co2Ppmcalibration requset */ + int co2AbcCalib = -1; /** update auto calibration number of day */ + UseLedBar ledBarMode = UseLedBarCO2; /** */ + char models[20]; /** */ + char mqttBroker[256]; /** */ +}; +AgServer agServer; + +/** Create airgradient instance for 'DIY_BASIC' board */ +AirGradient ag = AirGradient(DIY_BASIC); + +static int co2Ppm = -1; +static int pm25 = -1; +static float temp = -1; +static int hum = -1; +static long val; +static String wifiSSID = ""; +static bool wifiHasConfig = false; /** */ + +static void boardInit(void); +static void failedHandler(String msg); +static void co2Calibration(void); +static void serverConfigPoll(void); +static void co2Poll(void); +static void pmPoll(void); +static void tempHumPoll(void); +static void sendDataToServer(void); +static void dispHandler(void); +static String getDevId(void); +static void updateWiFiConnect(void); + +AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, serverConfigPoll); +AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer); +AgSchedule dispSchedule(DISP_UPDATE_INTERVAL, dispHandler); +AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Poll); +AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmPoll); +AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumPoll); void setup() { Serial.begin(115200); @@ -103,85 +393,53 @@ void setup() { /** Board init */ boardInit(); + /** Init AirGradient server */ + agServer.begin(); + /** Show boot display */ - displayShowText("Basic v4", "Lib:" + ag.getVersion(), ""); + displayShowText("DIY basic", "Lib:" + ag.getVersion(), ""); delay(2000); - if (connectWIFI) { - connectToWifi(); + /** WiFi connect */ + connectToWifi(); + if (WiFi.status() == WL_CONNECTED) { + wifiHasConfig = true; + sendPing(); + + agServer.pollServerConfig(getDevId()); + if (agServer.isCo2Calib()) { + co2Calibration(); + } } /** Show display */ displayShowText("Warm Up", "Serial#", String(ESP.getChipId(), HEX)); - delay(10000); - - getServerConfig(); + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); } void loop() { - currentMillis = millis(); - updateOLED(); - updateCo2(); - updatePm25(); - updateTempHum(); - sendToServer(); - getServerConfig(); + configSchedule.run(); + serverSchedule.run(); + dispSchedule.run(); + co2Schedule.run(); + pmsSchedule.run(); + tempHumSchedule.run(); + + updateWiFiConnect(); } -void updateCo2() { - if (currentMillis - previousCo2 >= co2Interval) { - previousCo2 += co2Interval; - Co2 = ag.s8.getCo2(); - Serial.println(String(Co2)); - } -} - -void updatePm25() { - if (currentMillis - previousPm25 >= pm25Interval) { - previousPm25 += pm25Interval; - if (ag.pms5003.readData()) { - pm25 = ag.pms5003.getPm25Ae(); - Serial.printf("PM25: %d\r\n", pm25); - } - } -} - -void updateTempHum() { - if (currentMillis - previousTempHum >= tempHumInterval) { - previousTempHum += tempHumInterval; - - /** Get temperature and humidity */ - temp = ag.sht.getTemperature(); - hum = ag.sht.getRelativeHumidity(); - - /** Print debug message */ - Serial.printf("SHT Humidity: %d%, Temperature: %0.2f\r\n", hum, temp); - } -} - -void updateOLED() { - if (currentMillis - previousOled >= oledInterval) { - previousOled += oledInterval; - - String ln1; - String ln2; - String ln3; - - if (serverConfig.inUSAQI) { - ln1 = "AQI:" + String(ag.pms5003.convertPm25ToUsAqi(pm25)); - } else { - ln1 = "PM :" + String(pm25) + " ug"; - } - ln2 = "CO2:" + String(Co2); - - if (serverConfig.inF) { - ln3 = - String((temp * 9 / 5) + 32).substring(0, 4) + " " + String(hum) + "%"; - } else { - ln3 = String(temp).substring(0, 4) + " " + String(hum) + "%"; - } - displayShowText(ln1, ln2, ln3); +static void sendPing() { + JSONVar root; + root["wifi"] = WiFi.RSSI(); + root["boot"] = 0; + + // delay(1500); + if (agServer.postToServer(getDevId(), JSON.stringify(root))) { + // Ping Server succses + } else { + // Ping server failed } + // delay(DISPLAY_DELAY_SHOW_CONTENT_MS); } void displayShowText(String ln1, String ln2, String ln3) { @@ -198,58 +456,24 @@ void displayShowText(String ln1, String ln2, String ln3) { ag.display.show(); } -void sendToServer() { - if (currentMillis - previoussendToServer >= sendToServerInterval) { - previoussendToServer += sendToServerInterval; - - String payload = "{\"wifi\":" + String(WiFi.RSSI()) + - (Co2 < 0 ? "" : ", \"rco2\":" + String(Co2)) + - (pm25 < 0 ? "" : ", \"pm02\":" + String(pm25)) + - ", \"atmp\":" + String(temp) + - (hum < 0 ? "" : ", \"rhum\":" + String(hum)) + "}"; - - if (WiFi.status() == WL_CONNECTED) { - Serial.println(payload); - String POSTURL = APIROOT + - "sensors/airgradient:" + String(ESP.getChipId(), HEX) + - "/measures"; - Serial.println(POSTURL); - WiFiClient client; - HTTPClient http; - http.begin(client, POSTURL); - http.addHeader("content-type", "application/json"); - int httpCode = http.POST(payload); - String response = http.getString(); - Serial.println(httpCode); - Serial.println(response); - http.end(); - } else { - Serial.println("WiFi Disconnected"); - } - } -} - // Wifi Manager void connectToWifi() { WiFiManager wifiManager; - // WiFi.disconnect(); //to delete previous saved hotspot - String HOTSPOT = "AG-" + String(ESP.getChipId(), HEX); - // displayShowText("Connect", "AG-", String(ESP.getChipId(), HEX)); - delay(2000); - // wifiManager.setTimeout(90); + wifiSSID = "AG-" + String(ESP.getChipId(), HEX); wifiManager.setConfigPortalBlocking(false); - wifiManager.setConfigPortalTimeout(180); - wifiManager.autoConnect(HOTSPOT.c_str(), wifiApPass.c_str()); + wifiManager.setConfigPortalTimeout(WIFI_CONNECT_COUNTDOWN_MAX); + wifiManager.autoConnect(wifiSSID.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT); uint32_t lastTime = millis(); - int count = 179; - displayShowText("180 sec", "SSID:",HOTSPOT); + int count = WIFI_CONNECT_COUNTDOWN_MAX; + displayShowText(String(WIFI_CONNECT_COUNTDOWN_MAX) + " sec", + "SSID:", wifiSSID); while (wifiManager.getConfigPortalActive()) { wifiManager.process(); uint32_t ms = (uint32_t)(millis() - lastTime); if (ms >= 1000) { lastTime = millis(); - displayShowText(String(count) + " sec", "SSID:",HOTSPOT); + displayShowText(String(count) + " sec", "SSID:", wifiSSID); count--; // Timeout @@ -261,20 +485,13 @@ void connectToWifi() { if (!WiFi.isConnected()) { displayShowText("Booting", "offline", "mode"); Serial.println("failed to connect and hit timeout"); - delay(6000); + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); } } -void failedHandler(String msg) { - while (true) { - Serial.println(msg); - delay(1000); - } -} - -void boardInit(void) { +static void boardInit(void) { /** Init SHT sensor */ - if (ag.sht.begin(Wire) == false) { + if (ag.sht4x.begin(Wire) == false) { failedHandler("SHT init failed"); } @@ -293,152 +510,18 @@ void boardInit(void) { ag.display.setTextColor(1); } -void showConfig(void) { - Serial.println("Server configuration: "); - Serial.printf(" inF: %s\r\n", serverConfig.inF ? "true" : "false"); - Serial.printf(" inUSAQI: %s\r\n", - serverConfig.inUSAQI ? "true" : "false"); - Serial.printf("useRGBLedBar: %d\r\n", (int)serverConfig.ledBarMode); - Serial.printf(" Model: %.*s\r\n", sizeof(serverConfig.model), - serverConfig.model); - Serial.printf(" Mqtt Broker: %.*s\r\n", sizeof(serverConfig.mqttBroker), - serverConfig.mqttBroker); -} - -void updateServerConfigLoadTime(void) { - serverConfigLoadTime = millis(); - if (serverConfigLoadTime == 0) { - serverConfigLoadTime = 1; +static void failedHandler(String msg) { + while (true) { + Serial.println(msg); + delay(1000); } } -void getServerConfig(void) { - /** Only trigger load configuration again after pollServerConfigInterval sec - */ - if (serverConfigLoadTime) { - uint32_t ms = (uint32_t)(millis() - serverConfigLoadTime); - if (ms < pollServerConfigInterval) { - return; - } - } - - updateServerConfigLoadTime(); - - Serial.println("Trigger load server configuration"); - if (WiFi.status() != WL_CONNECTED) { - Serial.println( - "Ignore get server configuration because WIFI not connected"); - return; - } - - // WiFiClient wifiClient; - HTTPClient httpClient; - - String getUrl = "http://hw.airgradient.com/sensors/airgradient:" + - String(ESP.getChipId(), HEX) + "/one/config"; - Serial.println("HttpClient get: " + getUrl); - WiFiClient client; - if (httpClient.begin(client, getUrl) == false) { - Serial.println("HttpClient init failed"); - updateServerConfigLoadTime(); - return; - } - - int respCode = httpClient.GET(); - - /** get failure */ - if (respCode != 200) { - Serial.printf("HttpClient get failed: %d\r\n", respCode); - updateServerConfigLoadTime(); - return; - } - - String respContent = httpClient.getString(); - Serial.println("Server config: " + respContent); - - /** Parse JSON */ - JSONVar root = JSON.parse(respContent); - if (JSON.typeof_(root) == "undefined") { - Serial.println("Server configura JSON invalid"); - updateServerConfigLoadTime(); - return; - } - - /** Get "country" */ - bool inF = serverConfig.inF; - if (JSON.typeof_(root["country"]) == "string") { - String country = root["country"]; - if (country == "US") { - inF = true; - } else { - inF = false; - } - } - - /** Get "pmStandard" */ - bool inUSAQI = serverConfig.inUSAQI; - if (JSON.typeof_(root["pmStandard"]) == "string") { - String standard = root["pmStandard"]; - if (standard == "ugm3") { - inUSAQI = false; - } else { - inUSAQI = true; - } - } - - /** Get CO2 "co2CalibrationRequested" */ - co2CalibrationRequest = false; - if (JSON.typeof_(root["co2CalibrationRequested"]) == "boolean") { - co2CalibrationRequest = root["co2CalibrationRequested"]; - } - - /** get "model" */ - String model = ""; - if (JSON.typeof_(root["model"]) == "string") { - String _model = root["model"]; - model = _model; - } - - /** get "mqttBrokerUrl" */ - String mqtt = ""; - if (JSON.typeof_(root["mqttBrokerUrl"]) == "string") { - String _mqtt = root["mqttBrokerUrl"]; - mqtt = _mqtt; - } - - if (inF != serverConfig.inF) { - serverConfig.inF = inF; - } - if (inUSAQI != serverConfig.inUSAQI) { - serverConfig.inUSAQI = inUSAQI; - } - if (model.length()) { - if (model != String(serverConfig.model)) { - memset(serverConfig.model, 0, sizeof(serverConfig.model)); - memcpy(serverConfig.model, model.c_str(), model.length()); - } - } - if (mqtt.length()) { - if (mqtt != String(serverConfig.mqttBroker)) { - memset(serverConfig.mqttBroker, 0, sizeof(serverConfig.mqttBroker)); - memcpy(serverConfig.mqttBroker, mqtt.c_str(), mqtt.length()); - } - } - - /** Show server configuration */ - showConfig(); - - /** Calibration */ - if (co2CalibrationRequest) { - co2Calibration(); - } -} - -void co2Calibration(void) { +static void co2Calibration(void) { /** Count down for co2CalibCountdown secs */ - for (int i = 0; i < co2CalibCountdown; i++) { + for (int i = 0; i < SENSOR_CO2_CALIB_COUNTDOWN_MAX; i++) { displayShowText("CO2 calib", "after", - String(co2CalibCountdown - i) + " sec"); + String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec"); delay(1000); } @@ -452,9 +535,115 @@ void co2Calibration(void) { count++; } displayShowText("Finish", "after", String(count) + " sec"); - delay(2000); + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); } else { displayShowText("Calib", "failure!!!", ""); - delay(2000); + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); } } + +static void serverConfigPoll(void) { + if (agServer.pollServerConfig(getDevId())) { + if (agServer.isCo2Calib()) { + co2Calibration(); + } + if (agServer.getCo2Abccalib() > 0) { + if (ag.s8.setAutoCalib(agServer.getCo2Abccalib() * 24) == false) { + Serial.println("Set S8 auto calib failed"); + } + } + } +} + +static void co2Poll() { + co2Ppm = ag.s8.getCo2(); + Serial.printf("CO2 index: %d\r\n", co2Ppm); +} + +void pmPoll() { + if (ag.pms5003.readData()) { + pm25 = ag.pms5003.getPm25Ae(); + Serial.printf("PMS2.5: %d\r\n", pm25); + } else { + pm25 = -1; + } +} + +static void tempHumPoll() { + temp = ag.sht4x.getTemperature(); + hum = ag.sht4x.getRelativeHumidity(); + + Serial.printf("Temperature: %0.2f\r\n", temp); + Serial.printf(" Humidity: %d\r\n", hum); +} + +static void sendDataToServer() { + JSONVar root; + root["wifi"] = WiFi.RSSI(); + if (co2Ppm >= 0) { + root["rco2"] = co2Ppm; + } + if (pm25 >= 0) { + root["pm02"] = pm25; + } + if (temp >= 0) { + root["atmp"] = temp; + } + if (hum >= 0) { + root["rhum"] = hum; + } + + if (agServer.postToServer(getDevId(), JSON.stringify(root)) == false) { + Serial.println("Post to server failed"); + } +} + +static void dispHandler() { + String ln1 = ""; + String ln2 = ""; + String ln3 = ""; + + if (agServer.isPMSinUSAQI()) { + ln1 = "AQI:" + String(ag.pms5003.convertPm25ToUsAqi(pm25)); + } else { + ln1 = "PM :" + String(pm25) + " ug"; + } + ln2 = "CO2:" + String(co2Ppm); + + if (agServer.isTemperatureUnitF()) { + ln3 = String((temp * 9 / 5) + 32).substring(0, 4) + " " + String(hum) + "%"; + } else { + ln3 = String(temp).substring(0, 4) + " " + String(hum) + "%"; + } + displayShowText(ln1, ln2, ln3); +} + +static String getDevId(void) { return getNormalizedMac(); } + +/** + * @brief WiFi reconnect handler + */ +static void updateWiFiConnect(void) { + static uint32_t lastRetry; + if (wifiHasConfig == false) { + return; + } + if (WiFi.isConnected()) { + lastRetry = millis(); + return; + } + uint32_t ms = (uint32_t)(millis() - lastRetry); + if (ms >= WIFI_CONNECT_RETRY_MS) { + lastRetry = millis(); + WiFi.reconnect(); + + Serial.printf("Re-Connect WiFi\r\n"); + } +} + +String getNormalizedMac() { + String mac = WiFi.macAddress(); + mac.replace(":", ""); + mac.toLowerCase(); + return mac; +} diff --git a/examples/ONE_I-9PSL/ONE_I-9PSL.ino b/examples/ONE_I-9PSL/ONE_I-9PSL.ino index 979f31f..142e118 100644 --- a/examples/ONE_I-9PSL/ONE_I-9PSL.ino +++ b/examples/ONE_I-9PSL/ONE_I-9PSL.ino @@ -1,7 +1,9 @@ /* -This is the code for the AirGradient ONE open-source hardware indoor Air Quality Monitor with an ESP32-C3 Microcontroller. +This is the code for the AirGradient ONE open-source hardware indoor Air Quality +Monitor with an ESP32-C3 Microcontroller. -It is an air quality monitor for PM2.5, CO2, TVOCs, NOx, Temperature and Humidity with a small display, an RGB led bar and can send data over Wifi. +It is an air quality monitor for PM2.5, CO2, TVOCs, NOx, Temperature and +Humidity with a small display, an RGB led bar and can send data over Wifi. Open source air quality monitors and kits are available: Indoor Monitor: https://www.airgradient.com/indoor/ @@ -14,7 +16,8 @@ The codes needs the following libraries installed: "Arduino_JSON" by Arduino version 0.2.0 "U8g2" by oliver version 2.34.22 -Please make sure you have esp32 board manager installed. Tested with version 2.0.11. +Please make sure you have esp32 board manager installed. Tested with +version 2.0.11. Important flashing settings: - Set board to "ESP32C3 Dev Module" @@ -25,7 +28,8 @@ Important flashing settings: - Partition scheme "Default 4MB with spiffs (1.2MB APP/1,5MB SPIFFS)" - JTAG adapter "Disabled" -Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3) can be set through the AirGradient dashboard. +Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3) +can be set through the AirGradient dashboard. If you have any questions please visit our forum at https://forum.airgradient.com/ @@ -78,8 +82,9 @@ enum { APP_SM_NORMAL, }; -#define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */ -#define LED_BAR_COUNT_INIT_VALUE (-1) +#define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */ +#define WIFI_CONNECT_RETRY_MS 10000 /** ms */ +#define LED_BAR_COUNT_INIT_VALUE (-1) /** */ #define LED_BAR_ANIMATION_PERIOD 100 /** ms */ #define DISP_UPDATE_INTERVAL 5000 /** ms */ #define SERVER_CONFIG_UPDATE_INTERVAL 30000 /** ms */ @@ -89,11 +94,13 @@ enum { #define SENSOR_CO2_UPDATE_INTERVAL 5000 /** ms */ #define SENSOR_PM_UPDATE_INTERVAL 5000 /** ms */ #define SENSOR_TEMP_HUM_UPDATE_INTERVAL 5000 /** ms */ -#define WIFI_HOTSPOT_PASSWORD_DEFAULT "cleanair" +#define DISPLAY_DELAY_SHOW_CONTENT_MS 3000 /** ms */ +#define WIFI_HOTSPOT_PASSWORD_DEFAULT \ + "cleanair" /** default WiFi AP password \ + */ /** * @brief Use use LED bar state - * */ typedef enum { UseLedBarOff, /** Don't use LED bar */ @@ -101,99 +108,376 @@ typedef enum { UseLedBarCO2, /** Use LED bar for CO2 */ } UseLedBar; -typedef struct { - bool inF; /** Temperature unit */ - bool inUSAQI; /** PMS standard */ - uint8_t ledBarMode; /** @ref UseLedBar*/ - char model[16]; /** Model string value, Just define, don't know how much - memory usage */ - char mqttBroker[128]; /** Mqtt broker link */ - uint32_t _check; /** Checksum configuration data */ -} ServerConfig_t; -static ServerConfig_t serverConfig; +/** + * @brief Schedule handle with timing period + * + */ +class AgSchedule { +public: + AgSchedule(int period, void (*handler)(void)) + : period(period), handler(handler) {} + void run(void) { + uint32_t ms = (uint32_t)(millis() - count); + if (ms >= period) { + /** Call handler */ + handler(); -#define DEBUG true + Serial.printf("[AgSchedule] handle 0x%08x, period: %d(ms)\r\n", + (unsigned int)handler, period); -/** Create airgradient instance */ -AirGradient ag(BOARD_ONE_INDOOR_MONITOR_V9_0); + /** Update period time */ + count = millis(); + } + } -WiFiManager wifiManager; /** wifi manager instance */ +private: + void (*handler)(void); + int period; + int count; +}; + +/** + * @brief AirGradient server configuration and sync data + * + */ +class AgServer { +public: + void begin(void) { + inF = false; + inUSAQI = false; + configFailed = false; + serverFailed = false; + memset(models, 0, sizeof(models)); + memset(mqttBroker, 0, sizeof(mqttBroker)); + } + + /** + * @brief Get server configuration + * + * @param id Device ID + * @return true Success + * @return false Failure + */ + bool pollServerConfig(String id) { + String uri = + "http://hw.airgradient.com/sensors/airgradient:" + id + "/one/config"; + + /** Init http client */ + HTTPClient client; + if (client.begin(uri) == false) { + configFailed = true; + return false; + } + + /** Get */ + int retCode = client.GET(); + if (retCode != 200) { + client.end(); + configFailed = true; + return false; + } + + /** clear failed */ + configFailed = false; + + /** Get response string */ + String respContent = client.getString(); + client.end(); + Serial.println("Get server config: " + respContent); + + /** Parse JSON */ + JSONVar root = JSON.parse(respContent); + if (JSON.typeof(root) == "undefined") { + /** JSON invalid */ + return false; + } + + /** Get "country" */ + if (JSON.typeof_(root["country"]) == "string") { + String country = root["country"]; + if (country == "US") { + inF = true; + } else { + inF = false; + } + } + + /** Get "pmsStandard" */ + if (JSON.typeof_(root["pmsStandard"]) == "string") { + String standard = root["pmsStandard"]; + if (standard == "ugm3") { + inUSAQI = false; + } else { + inUSAQI = true; + } + } + + /** Get "co2CalibrationRequested" */ + if (JSON.typeof_(root["co2CalibrationRequested"]) == "boolean") { + co2Calib = root["co2CalibrationRequested"]; + } else { + co2Calib = false; + } + + /** Get "ledBarMode" */ + if (JSON.typeof_(root["ledBarMode"]) == "string") { + String mode = root["ledBarMode"]; + if (mode == "co2") { + ledBarMode = UseLedBarCO2; + } else if (mode == "pm") { + ledBarMode = UseLedBarPM; + } else if (mode == "off") { + ledBarMode = UseLedBarOff; + } else { + ledBarMode = UseLedBarOff; + } + } + + /** Get model */ + if (JSON.typeof_(root["model"]) == "string") { + String model = root["model"]; + if (model.length()) { + int len = + model.length() < sizeof(models) ? model.length() : sizeof(models); + memset(models, 0, sizeof(models)); + memcpy(models, model.c_str(), len); + } + } + + /** Get "mqttBrokerUrl" */ + if (JSON.typeof_(root["mqttBrokerUrl"]) == "string") { + String mqtt = root["mqttBrokerUrl"]; + if (mqtt.length()) { + int len = mqtt.length() < sizeof(mqttBroker) ? mqtt.length() + : sizeof(mqttBroker); + memset(mqttBroker, 0, sizeof(mqttBroker)); + memcpy(mqttBroker, mqtt.c_str(), len); + } + } + + /** Get 'abcDays' */ + if (JSON.typeof_(root["abcDays"]) == "number") { + co2AbcCalib = root["abcDays"]; + } else { + co2AbcCalib = -1; + } + + /** Get "ledBarTestRequested" */ + if (JSON.typeof_(root["ledBarTestRequested"]) == "boolean") { + ledBarTestRequested = root["ledBarTestRequested"]; + } else { + ledBarTestRequested = false; + } + + /** Show configuration */ + showServerConfig(); + + return true; + } + + bool postToServer(String id, String payload) { + /** + * @brief Only post data if WiFi is connected + */ + if (WiFi.isConnected() == false) { + return false; + } + + Serial.printf("Post payload: %s\r\n", payload.c_str()); + + String uri = + "http://hw.airgradient.com/sensors/airgradient:" + id + "/measures"; + + WiFiClient wifiClient; + HTTPClient client; + if (client.begin(wifiClient, uri.c_str()) == false) { + return false; + } + client.addHeader("content-type", "application/json"); + int retCode = client.POST(payload); + client.end(); + + if ((retCode == 200) || (retCode == 429)) { + serverFailed = false; + return true; + } + serverFailed = true; + return false; + } + + /** + * @brief Get temperature configuration unit + * + * @return true F unit + * @return false C Unit + */ + bool isTemperatureUnitF(void) { return inF; } + + /** + * @brief Get PMS standard unit + * + * @return true USAQI + * @return false ugm3 + */ + bool isPMSinUSAQI(void) { return inUSAQI; } + + /** + * @brief Get status of get server coniguration is failed + * + * @return true Failed + * @return false Success + */ + bool isConfigFailed(void) { return configFailed; } + + /** + * @brief Get status of post server configuration is failed + * + * @return true Failed + * @return false Success + */ + bool isServerFailed(void) { return serverFailed; } + + /** + * @brief Get request calibration CO2 + * + * @return true Requested. If result = true, it's clear after function call + * @return false Not-requested + */ + bool isCo2Calib(void) { + bool ret = co2Calib; + if (ret) { + co2Calib = false; + } + return ret; + } + + /** + * @brief Get request LedBar test + * + * @return true Requested. If result = true, it's clear after function call + * @return false Not-requested + */ + bool isLedBarTestRequested(void) { + bool ret = ledBarTestRequested; + if (ret) { + ledBarTestRequested = false; + } + return ret; + } + + /** + * @brief Get the Co2 auto calib period + * + * @return int days, -1 if invalid. + */ + int getCo2Abccalib(void) { return co2AbcCalib; } + + /** + * @brief Get device configuration model name + * + * @return String Model name, empty string if server failed + */ + String getModelName(void) { return String(models); } + + /** + * @brief Get mqttBroker url + * + * @return String Broker url, empty if server failed + */ + String getMqttBroker(void) { return String(mqttBroker); } + + /** + * @brief Show server configuration parameter + */ + void showServerConfig(void) { + Serial.println("Server configuration: "); + Serial.printf(" inF: %s\r\n", inF ? "true" : "false"); + Serial.printf(" inUSAQI: %s\r\n", inUSAQI ? "true" : "false"); + Serial.printf(" useRGBLedBar: %d\r\n", (int)ledBarMode); + Serial.printf(" Model: %s\r\n", models); + Serial.printf(" Mqtt Broker: %s\r\n", mqttBroker); + Serial.printf(" S8 calib period: %d\r\n", co2AbcCalib); + } + + /** + * @brief Get server config led bar mode + * + * @return UseLedBar + */ + UseLedBar getLedBarMode(void) { return ledBarMode; } + +private: + bool inF; /** Temperature unit, true: F, false: C */ + bool inUSAQI; /** PMS unit, true: USAQI, false: ugm3 */ + bool configFailed; /** Flag indicate get server configuration failed */ + bool serverFailed; /** Flag indicate post data to server failed */ + bool co2Calib; /** Is co2Ppmcalibration requset */ + bool ledBarTestRequested; /** */ + int co2AbcCalib = -1; /** update auto calibration number of day */ + UseLedBar ledBarMode = UseLedBarCO2; /** */ + char models[20]; /** */ + char mqttBroker[256]; /** */ +}; +AgServer agServer; + +/** Create airgradient instance for 'ONE_INDOOR' board */ +AirGradient ag(ONE_INDOOR); + +/** Create u8g2 instance */ +U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE); + +/** wifi manager instance */ +WiFiManager wifiManager; + +static bool wifiHasConfig = false; /** */ static int connectCountDown; /** wifi configuration countdown */ static int ledCount; /** For LED animation */ static int ledSmState = APP_SM_NORMAL; /** Save display SM */ static int dispSmState = APP_SM_NORMAL; /** Save LED SM */ -static bool configFailed = false; /** Save is get server configuration failed */ -static bool serverFailed = false; /** Save is send server failed */ -// Display bottom right -U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE); +static int tvocIndex = -1; +static int noxIndex = -1; +static int co2Ppm = -1; +static int pm25 = -1; +static int pm01 = -1; +static int pm10 = -1; +static int pm03PCount = -1; +static float temp = -1; +static int hum = -1; +static int bootCount; +static String wifiSSID = ""; -String APIROOT = "http://hw.airgradient.com/"; +static void boardInit(void); +static void failedHandler(String msg); +static void serverConfigPoll(void); +static void co2Calibration(void); +static void setRGBledPMcolor(int pm25Value); +static void ledSmHandler(int sm); +static void ledSmHandler(void); +static void dispSmHandler(int sm); +static void sensorLedColorHandler(void); +static void appLedHandler(void); +static void appDispHandler(void); +static void updateWiFiConnect(void); +static void updateDispLedBar(void); +static void tvocPoll(void); +static void pmPoll(void); +static void sendDataToServer(void); +static void tempHumPoll(void); +static void co2Poll(void); -bool co2CalibrationRequest = false; -bool ledBarTestRequested = false; -uint32_t serverConfigLoadTime = 0; -String HOSTPOT = ""; -static bool wifiHasConfig = false; - -// set to true if you want to connect to wifi. You have 60 seconds to connect. -// Then it will go into an offline mode. -boolean connectWIFI = true; - -int loopCount = 0; - -unsigned long currentMillis = 0; -unsigned long previousOled = 0; -unsigned long previoussendToServer = 0; - -unsigned long previousTVOC = 0; -int TVOC = -1; -int NOX = -1; - -unsigned long previousCo2 = 0; -int Co2 = 0; - -unsigned long previousPm = 0; -int pm25 = -1; -int pm01 = -1; -int pm10 = -1; -int pm03PCount = -1; - -unsigned long previousTempHum = 0; -float temp; -int hum; - -int buttonConfig = 0; -PushButton::State lastState = PushButton::BUTTON_RELEASED; -PushButton::State currentState; -unsigned long pressedTime = 0; -unsigned long releasedTime = 0; - -void boardInit(void); -void failedHandler(String msg); -void getServerConfig(void); -void co2Calibration(void); -void setRGBledPMcolor(int pm25Value); -void setWifiConnectLedColor(void); -void setWifiConnectingLedColor(void); -void ledSmHandler(int sm); -void ledSmHandler(void); -void dispSmHandler(int sm); -void sensorLedColorHandler(void); -void appLedHandler(void); -void appDispHandler(void); -void appLedUnUseHandler(void); -void updateWiFiConnect(void); +/** Init schedule */ +AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, updateDispLedBar); +AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, serverConfigPoll); +AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer); +AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Poll); +AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmPoll); +AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumPoll); +AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, tvocPoll); void setup() { - if (DEBUG) { - Serial.begin(115200); - } - - /** Init default server configuration */ - serverConfig.inF = false; - serverConfig.inUSAQI = false; - serverConfig.ledBarMode = UseLedBarCO2; + /** Serial fore print debug message */ + Serial.begin(115200); /** Init I2C */ Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin()); @@ -203,15 +487,20 @@ void setup() { /** Show boot display */ displayShowText("One V9", "Lib Ver: " + ag.getVersion(), ""); - delay(2000); /** Boot display wait */ + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); /** Init sensor */ boardInit(); + /** Init AirGradient server */ + agServer.begin(); + /** WIFI connect */ - if (connectWIFI) { - connectToWifi(); - } + connectToWifi(); + + /** + * Send first data to ping server and get server configuration + */ if (WiFi.status() == WL_CONNECTED) { sendPing(); Serial.println(F("WiFi connected!")); @@ -219,35 +508,37 @@ void setup() { Serial.println(WiFi.localIP()); /** Get first connected to wifi */ - getServerConfig(); - if (configFailed) { + agServer.pollServerConfig(getDevId()); + if (agServer.isConfigFailed()) { dispSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED); ledSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED); - delay(5000); + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); } } /** Show display Warning up */ displayShowText("Warming Up", "Serial Number:", String(getNormalizedMac())); - delay(10000); + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); appLedHandler(); appDispHandler(); } void loop() { - currentMillis = millis(); - updateTVOC(); - updateDispLedBar(); - updateCo2(); - updatePm(); - updateTempHum(); - sendToServer(); - getServerConfig(); + /** Handle schedule */ + dispLedSchedule.run(); + configSchedule.run(); + serverSchedule.run(); + co2Schedule.run(); + pmsSchedule.run(); + tempHumSchedule.run(); + tvocSchedule.run(); + + /** Check for handle WiFi reconnect */ updateWiFiConnect(); } -void ledTest() { +static void ledTest() { displayShowText("LED Test", "running", "....."); setRGBledColor('r'); delay(1000); @@ -259,80 +550,62 @@ void ledTest() { delay(1000); setRGBledColor('n'); delay(1000); - // LED Test } -void updateTVOC() { - if (currentMillis - previousTVOC >= SENSOR_TVOC_UPDATE_INTERVAL) { - previousTVOC += SENSOR_TVOC_UPDATE_INTERVAL; - - TVOC = ag.sgp41.getTvocIndex(); - NOX = ag.sgp41.getNoxIndex(); - - Serial.println(String(TVOC)); - Serial.println(String(NOX)); - } +static void co2Poll(void) { + co2Ppm = ag.s8.getCo2(); + Serial.printf("CO2 index: %d\r\n", co2Ppm); } -void updateCo2() { - if (currentMillis - previousCo2 >= SENSOR_CO2_UPDATE_INTERVAL) { - previousCo2 += SENSOR_CO2_UPDATE_INTERVAL; - Co2 = ag.s8.getCo2(); - Serial.println(String(Co2)); - } -} +static void sendPing() { + JSONVar root; + root["wifi"] = WiFi.RSSI(); + root["boot"] = bootCount; -void updatePm() { - if (currentMillis - previousPm >= SENSOR_PM_UPDATE_INTERVAL) { - previousPm += SENSOR_PM_UPDATE_INTERVAL; - if (ag.pms5003.readData()) { - pm01 = ag.pms5003.getPm01Ae(); - pm25 = ag.pms5003.getPm25Ae(); - pm10 = ag.pms5003.getPm10Ae(); - pm03PCount = ag.pms5003.getPm03ParticleCount(); - } else { - pm01 = -1; - pm25 = -1; - pm10 = -1; - pm03PCount = -1; - } - } -} - -void updateTempHum() { - if (currentMillis - previousTempHum >= SENSOR_TEMP_HUM_UPDATE_INTERVAL) { - previousTempHum += SENSOR_TEMP_HUM_UPDATE_INTERVAL; - temp = ag.sht.getTemperature(); - hum = ag.sht.getRelativeHumidity(); - } -} - -void updateDispLedBar() { - if (currentMillis - previousOled >= DISP_UPDATE_INTERVAL) { - previousOled += DISP_UPDATE_INTERVAL; - appDispHandler(); - appLedHandler(); - } -} - -void sendPing() { - String payload = - "{\"wifi\":" + String(WiFi.RSSI()) + ", \"boot\":" + loopCount + "}"; + /** Change disp and led state */ dispSmHandler(APP_SM_WIFI_OK_SERVER_CONNECTING); + ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNECTING); + + /** Task handle led connecting animation */ + xTaskCreate( + [](void *obj) { + for (;;) { + ledSmHandler(); + if (ledSmState != APP_SM_WIFI_OK_SERVER_CONNECTING) { + break; + } + delay(LED_BAR_ANIMATION_PERIOD); + } + vTaskDelete(NULL); + }, + "task_led", 2048, NULL, 5, NULL); + delay(1500); - if (postToServer(payload)) { + if (agServer.postToServer(getDevId(), JSON.stringify(root))) { dispSmHandler(APP_SM_WIFI_OK_SERVER_CONNNECTED); ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNNECTED); } else { dispSmHandler(APP_SM_WIFI_OK_SERVER_CONNECT_FAILED); ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNECT_FAILED); } - delay(5000); + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); + + ledSmHandler(APP_SM_NORMAL); } -void displayShowText(String ln1, String ln2, String ln3) { - char buf[9]; - // u8g2.firstPage(); +static void displayShowWifiText(String ln1, String ln2, String ln3, + String ln4) { + u8g2.firstPage(); + do { + u8g2.setFont(u8g2_font_t0_16_tf); + u8g2.drawStr(1, 10, ln1.c_str()); + u8g2.drawStr(1, 25, ln2.c_str()); + u8g2.drawStr(1, 40, ln3.c_str()); + u8g2.drawStr(1, 55, ln4.c_str()); + } while (u8g2.nextPage()); +} + +static void displayShowText(String ln1, String ln2, String ln3) { u8g2.firstPage(); do { u8g2.setFont(u8g2_font_t0_16_tf); @@ -342,8 +615,8 @@ void displayShowText(String ln1, String ln2, String ln3) { } while (u8g2.nextPage()); } -void displayShowDashboard(String err) { - char buf[9]; +static void displayShowDashboard(String err) { + char strBuf[10]; /** Clear display dashboard */ u8g2.firstPage(); @@ -353,35 +626,33 @@ void displayShowDashboard(String err) { u8g2.setFont(u8g2_font_t0_16_tf); if ((err == NULL) || err.isEmpty()) { /** Show temperature */ - if (serverConfig.inF) { + if (agServer.isTemperatureUnitF()) { if (temp > -10001) { float tempF = (temp * 9 / 5) + 32; - sprintf(buf, "%.1f°F", tempF); + sprintf(strBuf, "%.1f°F", tempF); } else { - sprintf(buf, "-°F"); + sprintf(strBuf, "-°F"); } - u8g2.drawUTF8(1, 10, buf); + u8g2.drawUTF8(1, 10, strBuf); } else { if (temp > -10001) { - sprintf(buf, "%.1f°C", temp); + sprintf(strBuf, "%.1f°C", temp); } else { - sprintf(buf, "-°C"); + sprintf(strBuf, "-°C"); } - u8g2.drawUTF8(1, 10, buf); + u8g2.drawUTF8(1, 10, strBuf); } /** Show humidity */ if (hum >= 0) { - sprintf(buf, "%d%%", hum); + sprintf(strBuf, "%d%%", hum); } else { - sprintf(buf, " -%%"); + sprintf(strBuf, " -%%"); } if (hum > 99) { - u8g2.drawStr(97, 10, buf); + u8g2.drawStr(97, 10, strBuf); } else { - u8g2.drawStr(105, 10, buf); - // there might also be single digits, not considered, sprintf might - // actually support a leading space + u8g2.drawStr(105, 10, strBuf); } } else { Serial.println("Disp show error: " + err); @@ -398,19 +669,20 @@ void displayShowDashboard(String err) { /** Show CO2 value */ u8g2.setFont(u8g2_font_t0_22b_tf); - if (Co2 > 0) { - sprintf(buf, "%d", Co2); + if (co2Ppm > 0) { + sprintf(strBuf, "%d", co2Ppm); } else { - sprintf(buf, "%s", "-"); + sprintf(strBuf, "%s", "-"); } - u8g2.drawStr(1, 48, buf); + u8g2.drawStr(1, 48, strBuf); /** Show CO2 value index */ u8g2.setFont(u8g2_font_t0_12_tf); u8g2.drawStr(1, 61, "ppm"); /** Draw vertical line */ - u8g2.drawLine(45, 15, 45, 64); + u8g2.drawLine(45, 14, 45, 64); + u8g2.drawLine(82, 14, 82, 64); /** Draw PM2.5 label */ u8g2.setFont(u8g2_font_t0_12_tf); @@ -418,121 +690,62 @@ void displayShowDashboard(String err) { /** Draw PM2.5 value */ u8g2.setFont(u8g2_font_t0_22b_tf); - if (serverConfig.inUSAQI) { + if (agServer.isPMSinUSAQI()) { if (pm25 >= 0) { - sprintf(buf, "%d", ag.pms5003.convertPm25ToUsAqi(pm25)); + sprintf(strBuf, "%d", ag.pms5003.convertPm25ToUsAqi(pm25)); } else { - sprintf(buf, "%s", "-"); + sprintf(strBuf, "%s", "-"); } - u8g2.drawStr(48, 48, buf); + u8g2.drawStr(48, 48, strBuf); u8g2.setFont(u8g2_font_t0_12_tf); u8g2.drawUTF8(48, 61, "AQI"); } else { if (pm25 >= 0) { - sprintf(buf, "%d", pm25); + sprintf(strBuf, "%d", pm25); } else { - sprintf(buf, "%s", "-"); + sprintf(strBuf, "%s", "-"); } - u8g2.drawStr(48, 48, buf); + u8g2.drawStr(48, 48, strBuf); u8g2.setFont(u8g2_font_t0_12_tf); u8g2.drawUTF8(48, 61, "ug/m³"); } - /** Draw vertical line */ - u8g2.drawLine(82, 15, 82, 64); - - /** Draw TVOC label */ + /** Draw tvocIndexlabel */ u8g2.setFont(u8g2_font_t0_12_tf); - u8g2.drawStr(85, 27, "TVOC:"); + u8g2.drawStr(85, 27, "tvoc:"); - /** Draw TVOC value */ - if (TVOC >= 0) { - sprintf(buf, "%d", TVOC); + /** Draw tvocIndexvalue */ + if (tvocIndex >= 0) { + sprintf(strBuf, "%d", tvocIndex); } else { - sprintf(buf, "%s", "-"); + sprintf(strBuf, "%s", "-"); } - u8g2.drawStr(85, 39, buf); + u8g2.drawStr(85, 39, strBuf); /** Draw NOx label */ u8g2.drawStr(85, 53, "NOx:"); - if (NOX >= 0) { - sprintf(buf, "%d", NOX); + if (noxIndex >= 0) { + sprintf(strBuf, "%d", noxIndex); } else { - sprintf(buf, "%s", "-"); + sprintf(strBuf, "%s", "-"); } - u8g2.drawStr(85, 63, buf); + u8g2.drawStr(85, 63, strBuf); } while (u8g2.nextPage()); } -bool postToServer(String payload) { - Serial.println(payload); - String POSTURL = APIROOT + - "sensors/airgradient:" + String(getNormalizedMac()) + - "/measures"; - Serial.println(POSTURL); - WiFiClient client; - HTTPClient http; - http.begin(client, POSTURL); - http.addHeader("content-type", "application/json"); - int httpCode = http.POST(payload); - String response = http.getString(); - Serial.println(httpCode); - Serial.println(response); - http.end(); +/** + * @brief Must reset each 5min to avoid ESP32 reset + */ +static void resetWatchdog() { ag.watchdog.reset(); } - return (httpCode == 200); -} - -void sendToServer() { - if (currentMillis - previoussendToServer >= SERVER_SYNC_INTERVAL) { - previoussendToServer += SERVER_SYNC_INTERVAL; - String payload = - "{\"wifi\":" + String(WiFi.RSSI()) + - (Co2 < 0 ? "" : ", \"rco2\":" + String(Co2)) + - (pm01 < 0 ? "" : ", \"pm01\":" + String(pm01)) + - (pm25 < 0 ? "" : ", \"pm02\":" + String(pm25)) + - (pm10 < 0 ? "" : ", \"pm10\":" + String(pm10)) + - (pm03PCount < 0 ? "" : ", \"pm003_count\":" + String(pm03PCount)) + - (TVOC < 0 ? "" : ", \"tvoc_index\":" + String(TVOC)) + - (NOX < 0 ? "" : ", \"nox_index\":" + String(NOX)) + - ", \"atmp\":" + String(temp) + - (hum < 0 ? "" : ", \"rhum\":" + String(hum)) + - ", \"boot\":" + loopCount + "}"; - if (WiFi.status() == WL_CONNECTED) { - Serial.println(payload); - if (postToServer(payload)) { - serverFailed = false; - } else { - serverFailed = true; - } - resetWatchdog(); - loopCount++; - } else { - Serial.println("WiFi Disconnected"); - } - } -} - -void countdown(int from) { - debug("\n"); - while (from > 0) { - debug(String(from--)); - debug(" "); - delay(1000); - } - debug("\n"); -} - -void resetWatchdog() { ag.watchdog.reset(); } - -bool wifiMangerClientConnected(void) { +static bool wifiMangerClientConnected(void) { return WiFi.softAPgetStationNum() ? true : false; } // Wifi Manager -void connectToWifi() { +static void connectToWifi() { /** get wifi AP ssid */ - HOSTPOT = "airgradient-" + String(getNormalizedMac()); + wifiSSID = "airgradient-" + String(getNormalizedMac()); wifiManager.setConfigPortalBlocking(false); wifiManager.setTimeout(WIFI_CONNECT_COUNTDOWN_MAX); @@ -555,7 +768,7 @@ void connectToWifi() { dispSmState = APP_SM_WIFI_MANAGER_STA_CONNECTING; ledSmState = APP_SM_WIFI_MANAGER_STA_CONNECTING; }); - wifiManager.autoConnect(HOSTPOT.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT); + wifiManager.autoConnect(wifiSSID.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT); xTaskCreate( [](void *obj) { while (wifiManager.getConfigPortalActive()) { @@ -621,34 +834,16 @@ void connectToWifi() { appLedHandler(); } -void debug(String msg) { - if (DEBUG) - Serial.print(msg); -} +static String getDevId(void) { return getNormalizedMac(); } -void debug(int msg) { - if (DEBUG) - Serial.print(msg); -} - -void debugln(String msg) { - if (DEBUG) - Serial.println(msg); -} - -void debugln(int msg) { - if (DEBUG) - Serial.println(msg); -} - -String getNormalizedMac() { +static String getNormalizedMac() { String mac = WiFi.macAddress(); mac.replace(":", ""); mac.toLowerCase(); return mac; } -void setRGBledCO2color(int co2Value) { +static void setRGBledCO2color(int co2Value) { if (co2Value >= 300 && co2Value < 800) { setRGBledColor('g'); } else if (co2Value >= 800 && co2Value < 1000) { @@ -666,7 +861,7 @@ void setRGBledCO2color(int co2Value) { } } -void setRGBledColor(char color) { +static void setRGBledColor(char color) { int r = 0; int g = 0; int b = 0; @@ -706,14 +901,17 @@ void setRGBledColor(char color) { } /** Sensor LED indicator has only show status on last 2 LED on LED Bar */ - int ledNum = ag.ledBar.getNumberOfLed() - 1; + int ledNum = ag.ledBar.getNumberOfLeds() - 1; ag.ledBar.setColor(r, g, b, ledNum); - ledNum = ag.ledBar.getNumberOfLed() - 2; + ledNum = ag.ledBar.getNumberOfLeds() - 2; ag.ledBar.setColor(r, g, b, ledNum); } -void boardInit(void) { +/** + * @brief Initialize board + */ +static void boardInit(void) { /** Init LED Bar */ ag.ledBar.begin(); @@ -726,7 +924,7 @@ void boardInit(void) { } /** INit SHT */ - if (ag.sht.begin(Wire) == false) { + if (ag.sht4x.begin(Wire) == false) { failedHandler("Init SHT failed"); } @@ -744,193 +942,42 @@ void boardInit(void) { } } -void failedHandler(String msg) { +/** + * @brief Failed handler + * + * @param msg Failure message + */ +static void failedHandler(String msg) { while (true) { Serial.println(msg); vTaskDelay(1000); } } -void getServerConfigLoadTime(void) { - serverConfigLoadTime = millis(); - if (serverConfigLoadTime == 0) { - serverConfigLoadTime = 1; +/** + * @brief Send data to server + */ +static void serverConfigPoll(void) { + if (agServer.pollServerConfig(getDevId())) { + if (agServer.isCo2Calib()) { + co2Calibration(); + } + if (agServer.getCo2Abccalib() > 0) { + if (ag.s8.setAutoCalib(agServer.getCo2Abccalib() * 24) == false) { + Serial.println("Set S8 auto calib failed"); + } + } + if (agServer.isLedBarTestRequested()) { + ledTest(); + } } } -void getServerConfig(void) { - /** Only trigger load configuration again after pollServerConfigInterval sec - */ - if (serverConfigLoadTime) { - uint32_t ms = (uint32_t)(millis() - serverConfigLoadTime); - if (ms < SERVER_CONFIG_UPDATE_INTERVAL) { - return; - } - } - - getServerConfigLoadTime(); - - Serial.println("Trigger load server configuration"); - if (WiFi.status() != WL_CONNECTED) { - Serial.println( - "Ignore get server configuration because WIFI not connected"); - return; - } - - // WiFiClient wifiClient; - HTTPClient httpClient; - - String getUrl = "http://hw.airgradient.com/sensors/airgradient:" + - String(getNormalizedMac()) + "/one/config"; - Serial.println("HttpClient get: " + getUrl); - if (httpClient.begin(getUrl) == false) { - Serial.println("HttpClient init failed"); - getServerConfigLoadTime(); - configFailed = true; - return; - } - - int respCode = httpClient.GET(); - - /** get failure */ - if (respCode != 200) { - Serial.printf("HttpClient get failed: %d\r\n", respCode); - getServerConfigLoadTime(); - - /** Close client */ - httpClient.end(); - - configFailed = true; - - return; - } - configFailed = false; - - /** Get configuration content */ - String respContent = httpClient.getString(); - Serial.println("Server config: " + respContent); - - /** close client */ - httpClient.end(); - - /** Parse JSON */ - JSONVar root = JSON.parse(respContent); - if (JSON.typeof_(root) == "undefined") { - Serial.println("Server configura JSON invalid"); - getServerConfigLoadTime(); - return; - } - - /** Get "country" */ - bool inF = serverConfig.inF; - if (JSON.typeof_(root["country"]) == "string") { - String country = root["country"]; - if (country == "US") { - inF = true; - } else { - inF = false; - } - } - - /** Get "pmStandard" */ - bool inUSAQI = serverConfig.inUSAQI; - if (JSON.typeof_(root["pmStandard"]) == "string") { - String standard = root["pmStandard"]; - if (standard == "ugm3") { - inUSAQI = false; - } else { - inUSAQI = true; - } - } - - /** Get CO2 "co2CalibrationRequested" */ - co2CalibrationRequest = false; - if (JSON.typeof_(root["co2CalibrationRequested"]) == "boolean") { - co2CalibrationRequest = root["co2CalibrationRequested"]; - } - - /** Get "ledBarTestRequested" */ - if (JSON.typeof_(root["ledBarTestRequested"]) == "boolean") { - ledBarTestRequested = root["ledBarTestRequested"]; - } - - /** get "ledBarMode" */ - uint8_t ledBarMode = serverConfig.ledBarMode; - if (JSON.typeof_(root["ledBarMode"]) == "string") { - String _ledBarMode = root["ledBarMode"]; - if (_ledBarMode == "co2") { - ledBarMode = UseLedBarCO2; - } else if (_ledBarMode == "pm") { - ledBarMode = UseLedBarPM; - } else if (_ledBarMode == "off") { - ledBarMode = UseLedBarOff; - } - } - - /** get "model" */ - String model = ""; - if (JSON.typeof_(root["model"]) == "string") { - String _model = root["model"]; - model = _model; - } - - /** get "mqttBrokerUrl" */ - String mqtt = ""; - if (JSON.typeof_(root["mqttBrokerUrl"]) == "string") { - String _mqtt = root["mqttBrokerUrl"]; - mqtt = _mqtt; - } - - if (inF != serverConfig.inF) { - serverConfig.inF = inF; - } - if (inUSAQI != serverConfig.inUSAQI) { - serverConfig.inUSAQI = inUSAQI; - } - if (ledBarMode != serverConfig.ledBarMode) { - serverConfig.ledBarMode = ledBarMode; - } - if (model.length()) { - if (model != String(serverConfig.model)) { - memset(serverConfig.model, 0, sizeof(serverConfig.model)); - memcpy(serverConfig.model, model.c_str(), model.length()); - } - } - if (mqtt.length()) { - if (mqtt != String(serverConfig.mqttBroker)) { - memset(serverConfig.mqttBroker, 0, sizeof(serverConfig.mqttBroker)); - memcpy(serverConfig.mqttBroker, mqtt.c_str(), mqtt.length()); - } - } - - /** Show server configuration */ - showServerConfig(); - - /** Calibration */ - if (ledBarTestRequested) { - ledTest(); - } - if (co2CalibrationRequest) { - co2Calibration(); - } - - /** Update LED status */ - appLedHandler(); -} - -void showServerConfig(void) { - Serial.println("Server configuration: "); - Serial.printf(" inF: %s\r\n", serverConfig.inF ? "true" : "false"); - Serial.printf(" inUSAQI: %s\r\n", - serverConfig.inUSAQI ? "true" : "false"); - Serial.printf("useRGBLedBar: %d\r\n", (int)serverConfig.ledBarMode); - Serial.printf(" Model: %.*s\r\n", sizeof(serverConfig.model), - serverConfig.model); - Serial.printf(" Mqtt Broker: %.*s\r\n", sizeof(serverConfig.mqttBroker), - serverConfig.mqttBroker); -} - -void co2Calibration(void) { +/** + * @brief Calibration CO2 sensor, it's base calibration, after calib complete + * the value will be start at 400 if do calib on clean environment + */ +static void co2Calibration(void) { /** Count down for co2CalibCountdown secs */ for (int i = 0; i < SENSOR_CO2_CALIB_COUNTDOWN_MAX; i++) { displayShowText( @@ -959,7 +1006,12 @@ void co2Calibration(void) { appDispHandler(); } -void setRGBledPMcolor(int pm25Value) { +/** + * @brief Set LED color for special PMS value + * + * @param pm25Value PMS2.5 value + */ +static void setRGBledPMcolor(int pm25Value) { if (pm25Value >= 0 && pm25Value < 10) setRGBledColor('g'); if (pm25Value >= 10 && pm25Value < 35) @@ -974,36 +1026,37 @@ void setRGBledPMcolor(int pm25Value) { setRGBledColor('p'); } -void setWifiConnectLedColor(void) { ag.ledBar.setColor(0, 0, 255); } - -void setWifiConnectingLedColor(void) { ag.ledBar.setColor(255, 255, 255); } - -void singleLedAnimation(uint8_t r, uint8_t g, uint8_t b) { +static void singleLedAnimation(uint8_t r, uint8_t g, uint8_t b) { if (ledCount < 0) { ledCount = 0; ag.ledBar.setColor(r, g, b, ledCount); } else { ledCount++; - if (ledCount >= ag.ledBar.getNumberOfLed()) { + if (ledCount >= ag.ledBar.getNumberOfLeds()) { ledCount = 0; } ag.ledBar.setColor(r, g, b, ledCount); } } -void ledSmHandler(int sm) { +/** + * @brief LED state machine handler + * + * @param sm APP state machine + */ +static void ledSmHandler(int sm) { if (sm > APP_SM_NORMAL) { return; } ledSmState = sm; - ag.ledBar.clear(); // Set all LED OFF + ag.ledBar.clear(); // Set all LED OFF switch (sm) { case APP_SM_WIFI_MANAGER_MODE: { /** In WiFi Manager Mode */ /** Turn LED OFF */ /** Turn midle LED Color */ - ag.ledBar.setColor(0, 0, 255, ag.ledBar.getNumberOfLed() / 2); + ag.ledBar.setColor(0, 0, 255, ag.ledBar.getNumberOfLeds() / 2); break; } case APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE: { @@ -1071,7 +1124,7 @@ void ledSmHandler(int sm) { case APP_SM_SENSOR_CONFIG_FAILED: { /** Server is reachable but there is some configuration issue to be fixed on * the server side */ - + ag.ledBar.setColor(139, 24, 248, 0); /** Show CO2 or PM color status */ @@ -1079,7 +1132,7 @@ void ledSmHandler(int sm) { break; } case APP_SM_NORMAL: { - + sensorLedColorHandler(); break; } @@ -1089,9 +1142,17 @@ void ledSmHandler(int sm) { ag.ledBar.show(); } -void ledSmHandler(void) { ledSmHandler(ledSmState); } +/** + * @brief LED state machine handler + */ +static void ledSmHandler(void) { ledSmHandler(ledSmState); } -void dispSmHandler(int sm) { +/** + * @brief Display state machine handler + * + * @param sm APP state machine + */ +static void dispSmHandler(int sm) { if (sm > APP_SM_NORMAL) { return; } @@ -1102,14 +1163,15 @@ void dispSmHandler(int sm) { case APP_SM_WIFI_MANAGER_MODE: case APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE: { if (connectCountDown >= 0) { - displayShowText(String(connectCountDown) + "s to connect", - "to airgradient-", String(getNormalizedMac())); + displayShowWifiText(String(connectCountDown) + "s to connect", + "to WiFi hotspot:", "\"airgradient-", + getDevId() + "\""); connectCountDown--; } break; } case APP_SM_WIFI_MANAGER_STA_CONNECTING: { - displayShowText("Trying to", "connect to WiFi", ".."); + displayShowText("Trying to", "connect to WiFi", "..."); break; } case APP_SM_WIFI_MANAGER_STA_CONNECTED: { @@ -1129,7 +1191,7 @@ void dispSmHandler(int sm) { break; } case APP_SM_WIFI_OK_SERVER_CONNECT_FAILED: { - //displayShowText("Server not", "reachable", ""); + // displayShowText("Server not", "reachable", ""); break; } case APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED: { @@ -1156,46 +1218,58 @@ void dispSmHandler(int sm) { } } -void sensorLedColorHandler(void) { - switch (serverConfig.ledBarMode) { +/** + * @brief Handle change LED color base on sensor value of CO2 and PMS + */ +static void sensorLedColorHandler(void) { + switch (agServer.getLedBarMode()) { case UseLedBarCO2: - setRGBledCO2color(Co2); + setRGBledCO2color(co2Ppm); break; case UseLedBarPM: setRGBledPMcolor(pm25); break; default: - ag.ledBar.setColor(0, 0, 0, ag.ledBar.getNumberOfLed() - 1); - ag.ledBar.setColor(0, 0, 0, ag.ledBar.getNumberOfLed() - 2); + ag.ledBar.setColor(0, 0, 0, ag.ledBar.getNumberOfLeds() - 1); + ag.ledBar.setColor(0, 0, 0, ag.ledBar.getNumberOfLeds() - 2); break; } } -void appLedHandler(void) { +/** + * @brief APP LED color handler + */ +static void appLedHandler(void) { uint8_t state = APP_SM_NORMAL; if (WiFi.isConnected() == false) { state = APP_SM_WIFI_LOST; - } else if (configFailed) { + } else if (agServer.isConfigFailed()) { state = APP_SM_SENSOR_CONFIG_FAILED; - } else if (serverFailed) { + } else if (agServer.isServerFailed()) { state = APP_SM_SERVER_LOST; } ledSmHandler(state); } -void appDispHandler(void) { +/** + * @brief APP display content handler + */ +static void appDispHandler(void) { uint8_t state = APP_SM_NORMAL; if (WiFi.isConnected() == false) { state = APP_SM_WIFI_LOST; - } else if (configFailed) { + } else if (agServer.isConfigFailed()) { state = APP_SM_SENSOR_CONFIG_FAILED; - } else if (serverFailed) { + } else if (agServer.isServerFailed()) { state = APP_SM_SERVER_LOST; } dispSmHandler(state); } -void updateWiFiConnect(void) { +/** + * @brief WiFi reconnect handler + */ +static void updateWiFiConnect(void) { static uint32_t lastRetry; if (wifiHasConfig == false) { return; @@ -1205,8 +1279,108 @@ void updateWiFiConnect(void) { return; } uint32_t ms = (uint32_t)(millis() - lastRetry); - if (ms >= 10000) { + if (ms >= WIFI_CONNECT_RETRY_MS) { lastRetry = millis(); WiFi.reconnect(); + + Serial.printf("Re-Connect WiFi\r\n"); } } + +/** + * @brief APP display and LED handler + * + */ +static void updateDispLedBar(void) { + appDispHandler(); + appLedHandler(); +} + +/** + * @brief Update tvocIndexindex + * + */ +static void tvocPoll(void) { + tvocIndex = ag.sgp41.getTvocIndex(); + noxIndex = ag.sgp41.getNoxIndex(); + + Serial.printf("tvocIndexindex: %d\r\n", tvocIndex); + Serial.printf(" NOx index: %d\r\n", noxIndex); +} + +/** + * @brief Update PMS data + * + */ +static void pmPoll(void) { + if (ag.pms5003.readData()) { + pm01 = ag.pms5003.getPm01Ae(); + pm25 = ag.pms5003.getPm25Ae(); + pm10 = ag.pms5003.getPm10Ae(); + pm03PCount = ag.pms5003.getPm03ParticleCount(); + + Serial.printf(" PMS0.1: %d\r\n", pm01); + Serial.printf(" PMS2.5: %d\r\n", pm25); + Serial.printf(" PMS10.0: %d\r\n", pm10); + Serial.printf("PMS3.0 Count: %d\r\n", pm03PCount); + } else { + pm01 = -1; + pm25 = -1; + pm10 = -1; + pm03PCount = -1; + } +} + +/** + * @brief Send data to server + * + */ +static void sendDataToServer(void) { + JSONVar root; + root["wifi"] = WiFi.RSSI(); + if (co2Ppm >= 0) { + root["rco2"] = co2Ppm; + } + if (pm01 >= 0) { + root["pm01"] = pm01; + } + if (pm25 >= 0) { + root["pm02"] = pm25; + } + if (pm10 >= 0) { + root["pm10"] = pm10; + } + if (pm03PCount >= 0) { + root["pm003_count"] = pm03PCount; + } + if (tvocIndex >= 0) { + root["tvoc_index"] = tvocIndex; + } + if (noxIndex >= 0) { + root["noxIndex"] = noxIndex; + } + if (temp >= 0) { + root["atmp"] = temp; + } + if (hum >= 0) { + root["rhum"] = hum; + } + root["boot"] = bootCount; + + // NOTE Need determine offline mode to reset watchdog timer + if (agServer.postToServer(getDevId(), JSON.stringify(root))) { + resetWatchdog(); + } + bootCount++; +} + +/** + * @brief Update temperature and humidity value + */ +static void tempHumPoll(void) { + temp = ag.sht4x.getTemperature(); + hum = ag.sht4x.getRelativeHumidity(); + + Serial.printf("Temperature: %0.2f\r\n", temp); + Serial.printf(" Humidity: %d\r\n", hum); +} diff --git a/examples/Open_Air_O-1PP/Open_Air_O-1PP.ino b/examples/Open_Air_O-1PP/Open_Air_O-1PP.ino new file mode 100644 index 0000000..f726db3 --- /dev/null +++ b/examples/Open_Air_O-1PP/Open_Air_O-1PP.ino @@ -0,0 +1,775 @@ +/* +This is the code for the AirGradient Open Air open-source hardware outdoor Air Quality Monitor with an ESP32-C3 Microcontroller. + +It is an air quality monitor for PM2.5, CO2, TVOCs, NOx, Temperature and Humidity and can send data over Wifi. + +Open source air quality monitors and kits are available: +Indoor Monitor: https://www.airgradient.com/indoor/ +Outdoor Monitor: https://www.airgradient.com/outdoor/ + +Build Instructions: https://www.airgradient.com/documentation/open-air-pst-kit-1-3/ + +The codes needs the following libraries installed: +“WifiManager by tzapu, tablatronix” tested with version 2.0.16-rc.2 +"Arduino_JSON" by Arduino Version 0.2.0 + +Please make sure you have esp32 board manager installed. Tested with version 2.0.11. + +Important flashing settings: +- Set board to "ESP32C3 Dev Module" +- Enable "USB CDC On Boot" +- Flash frequency "80Mhz" +- Flash mode "QIO" +- Flash size "4MB" +- Partition scheme "Default 4MB with spiffs (1.2MB APP/1,5MB SPIFFS)" +- JTAG adapter "Disabled" + +If you have any questions please visit our forum at +https://forum.airgradient.com/ + +CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License + +*/ + +#include +#include +#include +#include +#include +#include + +/** + * + * @brief Application state machine state + * + */ +enum { + APP_SM_WIFI_MANAGER_MODE, /** In WiFi Manger Mode */ + APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE, /** WiFi Manager has connected to mobile + phone */ + APP_SM_WIFI_MANAGER_STA_CONNECTING, /** After SSID and PW entered and OK + clicked, connection to WiFI network is + attempted*/ + APP_SM_WIFI_MANAGER_STA_CONNECTED, /** Connecting to WiFi worked */ + APP_SM_WIFI_OK_SERVER_CONNECTING, /** Once connected to WiFi an attempt to + reach the server is performed */ + APP_SM_WIFI_OK_SERVER_CONNNECTED, /** Server is reachable, all fine */ + /** Exceptions during WIFi Setup */ + APP_SM_WIFI_MANAGER_CONNECT_FAILED, /** Cannot connect to WiFi (e.g. wrong + password, WPA Enterprise etc.) */ + APP_SM_WIFI_OK_SERVER_CONNECT_FAILED, /** Connected to WiFi but server not + reachable, e.g. firewall block/ + whitelisting needed etc. */ + APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED, /** Server reachable but sensor + not configured correctly*/ + + /** During Normal Operation */ + APP_SM_WIFI_LOST, /** Connection to WiFi network failed credentials incorrect + encryption not supported etc. */ + APP_SM_SERVER_LOST, /** Connected to WiFi network but the server cannot be + reached through the internet, e.g. blocked by firewall + */ + APP_SM_SENSOR_CONFIG_FAILED, /** Server is reachable but there is some + configuration issue to be fixed on the server + side */ + APP_SM_NORMAL, +}; + +#define LED_FAST_BLINK_DELAY 250 /** ms */ +#define LED_SLOW_BLINK_DELAY 1000 /** ms */ +#define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */ +#define WIFI_CONNECT_RETRY_MS 10000 /** ms */ +#define LED_BAR_COUNT_INIT_VALUE (-1) /** */ +#define LED_BAR_ANIMATION_PERIOD 100 /** ms */ +#define DISP_UPDATE_INTERVAL 5000 /** ms */ +#define SERVER_CONFIG_UPDATE_INTERVAL 30000 /** ms */ +#define SERVER_SYNC_INTERVAL 60000 /** ms */ +#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */ +#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */ +#define SENSOR_CO2_UPDATE_INTERVAL 5000 /** ms */ +#define SENSOR_PM_UPDATE_INTERVAL 5000 /** ms */ +#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 5000 /** ms */ +#define DISPLAY_DELAY_SHOW_CONTENT_MS 3000 /** ms */ +#define WIFI_HOTSPOT_PASSWORD_DEFAULT \ + "cleanair" /** default WiFi AP password \ + */ + +/** + * @brief Use use LED bar state + */ +typedef enum { + UseLedBarOff, /** Don't use LED bar */ + UseLedBarPM, /** Use LED bar for PMS */ + UseLedBarCO2, /** Use LED bar for CO2 */ +} UseLedBar; + +/** + * @brief Schedule handle with timing period + * + */ +class AgSchedule { +public: + AgSchedule(int period, void (*handler)(void)) + : period(period), handler(handler) {} + void run(void) { + uint32_t ms = (uint32_t)(millis() - count); + if (ms >= period) { + /** Call handler */ + handler(); + + Serial.printf("[AgSchedule] handle 0x%08x, period: %d(ms)\r\n", + (unsigned int)handler, period); + + /** Update period time */ + count = millis(); + } + } + +private: + void (*handler)(void); + int period; + int count; +}; + +/** + * @brief AirGradient server configuration and sync data + * + */ +class AgServer { +public: + void begin(void) { + inF = false; + inUSAQI = false; + configFailed = false; + serverFailed = false; + memset(models, 0, sizeof(models)); + memset(mqttBroker, 0, sizeof(mqttBroker)); + } + + /** + * @brief Get server configuration + * + * @param id Device ID + * @return true Success + * @return false Failure + */ + bool pollServerConfig(String id) { + String uri = + "http://hw.airgradient.com/sensors/airgradient:" + id + "/one/config"; + + /** Init http client */ + HTTPClient client; + if (client.begin(uri) == false) { + configFailed = true; + return false; + } + + /** Get */ + int retCode = client.GET(); + if (retCode != 200) { + client.end(); + configFailed = true; + return false; + } + + /** clear failed */ + configFailed = false; + + /** Get response string */ + String respContent = client.getString(); + client.end(); + Serial.println("Get server config: " + respContent); + + /** Parse JSON */ + JSONVar root = JSON.parse(respContent); + if (JSON.typeof(root) == "undefined") { + /** JSON invalid */ + return false; + } + + /** Get "country" */ + if (JSON.typeof_(root["country"]) == "string") { + String country = root["country"]; + if (country == "US") { + inF = true; + } else { + inF = false; + } + } + + /** Get "pmsStandard" */ + if (JSON.typeof_(root["pmsStandard"]) == "string") { + String standard = root["pmsStandard"]; + if (standard == "ugm3") { + inUSAQI = false; + } else { + inUSAQI = true; + } + } + + /** Get "co2CalibrationRequested" */ + if (JSON.typeof_(root["co2CalibrationRequested"]) == "boolean") { + co2Calib = root["co2CalibrationRequested"]; + } + + /** Get "ledBarMode" */ + if (JSON.typeof_(root["ledBarMode"]) == "string") { + String mode = root["ledBarMode"]; + if (mode == "co2") { + ledBarMode = UseLedBarCO2; + } else if (mode == "pm") { + ledBarMode = UseLedBarPM; + } else if (mode == "off") { + ledBarMode = UseLedBarOff; + } else { + ledBarMode = UseLedBarOff; + } + } + + /** Get model */ + if (JSON.typeof_(root["model"]) == "string") { + String model = root["model"]; + if (model.length()) { + int len = + model.length() < sizeof(models) ? model.length() : sizeof(models); + memset(models, 0, sizeof(models)); + memcpy(models, model.c_str(), len); + } + } + + /** Get "mqttBrokerUrl" */ + if (JSON.typeof_(root["mqttBrokerUrl"]) == "string") { + String mqtt = root["mqttBrokerUrl"]; + if (mqtt.length()) { + int len = mqtt.length() < sizeof(mqttBroker) ? mqtt.length() + : sizeof(mqttBroker); + memset(mqttBroker, 0, sizeof(mqttBroker)); + memcpy(mqttBroker, mqtt.c_str(), len); + } + } + + /** Show configuration */ + showServerConfig(); + + return true; + } + + bool postToServer(String id, String payload) { + /** + * @brief Only post data if WiFi is connected + */ + if (WiFi.isConnected() == false) { + return false; + } + + Serial.printf("Post payload: %s\r\n", payload.c_str()); + + String uri = + "http://hw.airgradient.com/sensors/airgradient:" + id + "/measures"; + + WiFiClient wifiClient; + HTTPClient client; + if (client.begin(wifiClient, uri.c_str()) == false) { + return false; + } + client.addHeader("content-type", "application/json"); + int retCode = client.POST(payload); + client.end(); + + if ((retCode == 200) || (retCode == 429)) { + serverFailed = false; + return true; + } + serverFailed = true; + return false; + } + + /** + * @brief Get temperature configuration unit + * + * @return true F unit + * @return false C Unit + */ + bool isTemperatureUnitF(void) { return inF; } + + /** + * @brief Get PMS standard unit + * + * @return true USAQI + * @return false ugm3 + */ + bool isPMSinUSAQI(void) { return inUSAQI; } + + /** + * @brief Get status of get server coniguration is failed + * + * @return true Failed + * @return false Success + */ + bool isConfigFailed(void) { return configFailed; } + + /** + * @brief Get status of post server configuration is failed + * + * @return true Failed + * @return false Success + */ + bool isServerFailed(void) { return serverFailed; } + + /** + * @brief Get request calibration CO2 + * + * @return true Requested. If result = true, it's clear after function call + * @return false Not-requested + */ + bool isCo2Calib(void) { + bool ret = co2Calib; + if (ret) { + co2Calib = false; + } + return ret; + } + + /** + * @brief Get device configuration model name + * + * @return String Model name, empty string if server failed + */ + String getModelName(void) { return String(models); } + + /** + * @brief Get mqttBroker url + * + * @return String Broker url, empty if server failed + */ + String getMqttBroker(void) { return String(mqttBroker); } + + /** + * @brief Show server configuration parameter + */ + void showServerConfig(void) { + Serial.println("Server configuration: "); + Serial.printf(" inF: %s\r\n", inF ? "true" : "false"); + Serial.printf(" inUSAQI: %s\r\n", inUSAQI ? "true" : "false"); + Serial.printf("useRGBLedBar: %d\r\n", (int)ledBarMode); + Serial.printf(" Model: %s\r\n", models); + Serial.printf(" Mqtt Broker: %s\r\n", mqttBroker); + } + + /** + * @brief Get server config led bar mode + * + * @return UseLedBar + */ + UseLedBar getLedBarMode(void) { return ledBarMode; } + +private: + bool inF; /** Temperature unit, true: F, false: C */ + bool inUSAQI; /** PMS unit, true: USAQI, false: ugm3 */ + bool configFailed; /** Flag indicate get server configuration failed */ + bool serverFailed; /** Flag indicate post data to server failed */ + bool co2Calib; /** Is co2Ppmcalibration requset */ + UseLedBar ledBarMode = UseLedBarCO2; /** */ + char models[20]; /** */ + char mqttBroker[256]; /** */ +}; +AgServer agServer; + +/** Create airgradient instance for 'OPEN_AIR_OUTDOOR' board */ +AirGradient ag(OPEN_AIR_OUTDOOR); + +static float pm1Value01 = 0; +static float pm1Value25 = 0; +static float pm1Value10 = 0; +static float pm1PCount = 0; +static float pm1temp = 0; +static float pm1hum = 0; + +static float pm2Value01 = 0; +static float pm2Value25 = 0; +static float pm2Value10 = 0; +static float pm2PCount = 0; +static float pm2temp = 0; +static float pm2hum = 0; + +static int countPosition = 0; +static int targetCount = 20; + +WiFiManager wifiManager; /** wifi manager instance */ + +int loopCount = 0; + +static int ledSmState = APP_SM_NORMAL; +static bool wifiHasConfig = false; +static String wifiSSID = ""; + +static void boardInit(void); +static void failedHandler(String msg); +static String getDevId(void); +static void sendDataToServerHandler(void); +static void serverConfigPoll(void); +static void updateWiFiConnect(void); + +AgSchedule agSyncDataSchedule(2000, sendDataToServerHandler); +AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, serverConfigPoll); + +void setup() { + Serial.begin(115200); + + /** Board init */ + boardInit(); + + /** Server init */ + agServer.begin(); + + /** WiFi connect */ + connectToWifi(); + + if (WiFi.isConnected()) { + sendPing(); + + if (agServer.pollServerConfig(getDevId())) { + if (agServer.isConfigFailed()) { + ledSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED); + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); + } + } + } + + ledSmHandler(APP_SM_NORMAL); +} + +void loop() { + agSyncDataSchedule.run(); + updateWiFiConnect(); +} + +void sendPing() { + JSONVar root; + root["wifi"] = WiFi.RSSI(); + root["boot"] = loopCount; + if (agServer.postToServer(getDevId(), JSON.stringify(root))) { + ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNNECTED); + } else { + ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNECT_FAILED); + } + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); +} + +void sendToServer(int pm1Value01, int pm1Value25, int pm1Value10, int pm1PCount, + float pm1temp, float pm1hum, int pm2Value01, int pm2Value25, + int pm2Value10, int pm2PCount, float pm2temp, float pm2hum) { + + JSONVar root; + root["wifi"] = WiFi.RSSI(); + root["boot"] = loopCount; + root["pm01"] = (int)((pm1Value01 + pm2Value01) / 2); + root["pm02"] = (int)((pm1Value25 + pm2Value25) / 2); + root["pm003_count"] = (int)((pm1PCount + pm2PCount) / 2); + root["atmp"] = (int)((pm1temp + pm2temp) / 2); + root["rhum"] = (int)((pm1hum + pm2hum) / 2); + root["channels"]["1"]["pm01"] = pm1Value01; + root["channels"]["1"]["pm02"] = pm1Value25; + root["channels"]["1"]["pm10"] = pm1Value10; + root["channels"]["1"]["pm003_count"] = pm1PCount; + root["channels"]["1"]["atmp"] = pm1temp; + root["channels"]["1"]["rhum"] = pm1hum; + root["channels"]["2"]["pm01"] = pm2Value01; + root["channels"]["2"]["pm02"] = pm2Value25; + root["channels"]["2"]["pm10"] = pm2Value10; + root["channels"]["2"]["pm003_count"] = pm2PCount; + root["channels"]["2"]["atmp"] = pm2temp; + root["channels"]["2"]["rhum"] = pm2hum; + + if (agServer.postToServer(getDevId(), JSON.stringify(root))) { + resetWatchdog(); + } + loopCount++; +} + +void countdown(int from) { + while (from > 0) { + Serial.print(from); + delay(1000); + from--; + } + Serial.println(); +} + +void resetWatchdog() { ag.watchdog.reset(); } + +// Wifi Manager +void connectToWifi() { + wifiSSID = "airgradient-" + String(getNormalizedMac()); + + wifiManager.setConfigPortalBlocking(false); + wifiManager.setTimeout(WIFI_CONNECT_COUNTDOWN_MAX); + + wifiManager.setAPCallback([](WiFiManager *obj) { + /** This callback if wifi connnected failed and try to start configuration + * portal */ + ledSmState = APP_SM_WIFI_MANAGER_MODE; + }); + wifiManager.setSaveConfigCallback([]() { + /** Wifi connected save the configuration */ + ledSmHandler(APP_SM_WIFI_MANAGER_STA_CONNECTED); + }); + wifiManager.setSaveParamsCallback([]() { + /** Wifi set connect: ssid, password */ + ledSmHandler(APP_SM_WIFI_MANAGER_STA_CONNECTING); + }); + wifiManager.autoConnect(wifiSSID.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT); + + xTaskCreate( + [](void *obj) { + while (wifiManager.getConfigPortalActive()) { + wifiManager.process(); + } + vTaskDelete(NULL); + }, + "wifi_cfg", 4096, NULL, 10, NULL); + + uint32_t stimer = millis(); + bool clientConnectChanged = false; + while (wifiManager.getConfigPortalActive()) { + if (WiFi.isConnected() == false) { + if (ledSmState == APP_SM_WIFI_MANAGER_MODE) { + uint32_t ms = (uint32_t)(millis() - stimer); + if (ms >= 100) { + stimer = millis(); + ledSmHandler(ledSmState); + } + } + + /** Check for client connect to change led color */ + bool clientConnected = wifiMangerClientConnected(); + if (clientConnected != clientConnectChanged) { + clientConnectChanged = clientConnected; + if (clientConnectChanged) { + ledSmHandler(APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE); + } else { + ledSmHandler(APP_SM_WIFI_MANAGER_MODE); + } + } + } + } + + /** Show display wifi connect result failed */ + if (WiFi.isConnected() == false) { + ledSmHandler(APP_SM_WIFI_MANAGER_CONNECT_FAILED); + } else { + wifiHasConfig = true; + } +} + +void ledBlinkDelay(uint32_t tdelay) { + ag.statusLed.setOn(); + delay(tdelay); + ag.statusLed.setOff(); + delay(tdelay); +} + +String getNormalizedMac() { + String mac = WiFi.macAddress(); + mac.replace(":", ""); + mac.toLowerCase(); + return mac; +} + +void boardInit(void) { + ag.watchdog.begin(); + ag.statusLed.begin(); + ag.button.begin(); + if (ag.pms5003t_1.begin(Serial0) == false) { + failedHandler("PMS5003T_1 init failed"); + } + if (ag.pms5003t_2.begin(Serial1) == false) { + failedHandler("PMS5003T_2 init failed"); + } +} + +void failedHandler(String msg) { + while (true) { + Serial.println(msg); + delay(1000); + } +} + +static String getDevId(void) { return getNormalizedMac(); } + +static void sendDataToServerHandler(void) { + if (ag.pms5003t_1.readData() && ag.pms5003t_2.readData()) { + /** Get data */ + pm1Value01 = pm1Value01 + ag.pms5003t_1.getPm01Ae(); + pm1Value25 = pm1Value25 + ag.pms5003t_1.getPm25Ae(); + pm1Value10 = pm1Value10 + ag.pms5003t_1.getPm10Ae(); + pm1PCount = pm1PCount + ag.pms5003t_1.getPm03ParticleCount(); + pm1temp = pm1temp + ag.pms5003t_1.getTemperature(); + pm1hum = pm1hum + ag.pms5003t_1.getRelativeHumidity(); + pm2Value01 = pm2Value01 + ag.pms5003t_2.getPm01Ae(); + pm2Value25 = pm2Value25 + ag.pms5003t_2.getPm25Ae(); + pm2Value10 = pm2Value10 + ag.pms5003t_2.getPm10Ae(); + pm2PCount = pm2PCount + ag.pms5003t_2.getPm03ParticleCount(); + pm2temp = pm2temp + ag.pms5003t_2.getTemperature(); + pm2hum = pm2hum + ag.pms5003t_2.getRelativeHumidity(); + countPosition++; + if (countPosition == targetCount) { + pm1Value01 = pm1Value01 / targetCount; + pm1Value25 = pm1Value25 / targetCount; + pm1Value10 = pm1Value10 / targetCount; + pm1PCount = pm1PCount / targetCount; + pm1temp = pm1temp / targetCount; + pm1hum = pm1hum / targetCount; + pm2Value01 = pm2Value01 / targetCount; + pm2Value25 = pm2Value25 / targetCount; + pm2Value10 = pm2Value10 / targetCount; + pm2PCount = pm2PCount / targetCount; + pm2temp = pm2temp / targetCount; + pm2hum = pm2hum / targetCount; + + /** Send data */ + sendToServer(pm1Value01, pm1Value25, pm1Value10, pm1PCount, pm1temp, + pm1hum, pm2Value01, pm2Value25, pm2Value10, pm2PCount, + pm2temp, pm2hum); + + /** Reset data */ + countPosition = 0; + pm1Value01 = 0; + pm1Value25 = 0; + pm1Value10 = 0; + pm1PCount = 0; + pm1temp = 0; + pm1hum = 0; + pm2Value01 = 0; + pm2Value25 = 0; + pm2Value10 = 0; + pm2PCount = 0; + pm2temp = 0; + pm2hum = 0; + } + } +} + +static void serverConfigPoll(void) { + if (agServer.pollServerConfig(getDevId())) { + Serial.println("Get server configure success"); + } else { + Serial.println("Get server configure failure"); + } +} + +static void updateWiFiConnect(void) { + static uint32_t lastRetry; + if (wifiHasConfig == false) { + return; + } + if (WiFi.isConnected()) { + lastRetry = millis(); + return; + } + uint32_t ms = (uint32_t)(millis() - lastRetry); + if (ms >= WIFI_CONNECT_RETRY_MS) { + lastRetry = millis(); + WiFi.reconnect(); + + Serial.printf("Re-Connect WiFi\r\n"); + } +} + +void ledSmHandler(int sm) { + if (sm > APP_SM_NORMAL) { + return; + } + + ledSmState = sm; + switch (sm) { + case APP_SM_WIFI_MANAGER_MODE: { + ag.statusLed.setToggle(); + break; + } + case APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE: { + ag.statusLed.setOn(); + break; + } + case APP_SM_WIFI_MANAGER_STA_CONNECTING: { + ag.statusLed.setOff(); + break; + } + case APP_SM_WIFI_MANAGER_STA_CONNECTED: { + ag.statusLed.setOff(); + break; + } + case APP_SM_WIFI_OK_SERVER_CONNECTING: { + ag.statusLed.setOff(); + break; + } + case APP_SM_WIFI_OK_SERVER_CONNNECTED: { + ag.statusLed.setOff(); + + /** two time slow blink, then off */ + for (int i = 0; i < 2; i++) { + ledBlinkDelay(LED_SLOW_BLINK_DELAY); + } + + ag.statusLed.setOff(); + break; + } + case APP_SM_WIFI_MANAGER_CONNECT_FAILED: { + /** Three time fast blink then 2 sec pause. Repeat 3 times */ + ag.statusLed.setOff(); + + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 3; i++) { + ledBlinkDelay(LED_FAST_BLINK_DELAY); + } + delay(2000); + } + ag.statusLed.setOff(); + break; + } + case APP_SM_WIFI_OK_SERVER_CONNECT_FAILED: { + ag.statusLed.setOff(); + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 4; i++) { + ledBlinkDelay(LED_FAST_BLINK_DELAY); + } + delay(2000); + } + ag.statusLed.setOff(); + break; + } + case APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED: { + ag.statusLed.setOff(); + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 5; i++) { + ledBlinkDelay(LED_FAST_BLINK_DELAY); + } + delay(2000); + } + ag.statusLed.setOff(); + break; + } + case APP_SM_WIFI_LOST: { + ag.statusLed.setOff(); + break; + } + case APP_SM_SERVER_LOST: { + ag.statusLed.setOff(); + break; + } + case APP_SM_SENSOR_CONFIG_FAILED: { + ag.statusLed.setOff(); + break; + } + case APP_SM_NORMAL: { + ag.statusLed.setOff(); + break; + } + default: + break; + } +} + +bool wifiMangerClientConnected(void) { + return WiFi.softAPgetStationNum() ? true : false; +} diff --git a/examples/Open_Air_O-1PPT/Open_Air_O-1PPT.ino b/examples/Open_Air_O-1PPT/Open_Air_O-1PPT.ino new file mode 100644 index 0000000..e95198b --- /dev/null +++ b/examples/Open_Air_O-1PPT/Open_Air_O-1PPT.ino @@ -0,0 +1,792 @@ +/* +This is the code for the AirGradient Open Air open-source hardware outdoor Air Quality Monitor with an ESP32-C3 Microcontroller. + +It is an air quality monitor for PM2.5, CO2, TVOCs, NOx, Temperature and Humidity and can send data over Wifi. + +Open source air quality monitors and kits are available: +Indoor Monitor: https://www.airgradient.com/indoor/ +Outdoor Monitor: https://www.airgradient.com/outdoor/ + +Build Instructions: https://www.airgradient.com/documentation/open-air-pst-kit-1-3/ + +The codes needs the following libraries installed: +“WifiManager by tzapu, tablatronix” tested with version 2.0.16-rc.2 +"Arduino_JSON" by Arduino Version 0.2.0 + +Please make sure you have esp32 board manager installed. Tested with version 2.0.11. + +Important flashing settings: +- Set board to "ESP32C3 Dev Module" +- Enable "USB CDC On Boot" +- Flash frequency "80Mhz" +- Flash mode "QIO" +- Flash size "4MB" +- Partition scheme "Default 4MB with spiffs (1.2MB APP/1,5MB SPIFFS)" +- JTAG adapter "Disabled" + +If you have any questions please visit our forum at +https://forum.airgradient.com/ + +CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License + +*/ + +#include +#include +#include +#include +#include +#include + +/** + * + * @brief Application state machine state + * + */ +enum { + APP_SM_WIFI_MANAGER_MODE, /** In WiFi Manger Mode */ + APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE, /** WiFi Manager has connected to mobile + phone */ + APP_SM_WIFI_MANAGER_STA_CONNECTING, /** After SSID and PW entered and OK + clicked, connection to WiFI network is + attempted*/ + APP_SM_WIFI_MANAGER_STA_CONNECTED, /** Connecting to WiFi worked */ + APP_SM_WIFI_OK_SERVER_CONNECTING, /** Once connected to WiFi an attempt to + reach the server is performed */ + APP_SM_WIFI_OK_SERVER_CONNNECTED, /** Server is reachable, all fine */ + /** Exceptions during WIFi Setup */ + APP_SM_WIFI_MANAGER_CONNECT_FAILED, /** Cannot connect to WiFi (e.g. wrong + password, WPA Enterprise etc.) */ + APP_SM_WIFI_OK_SERVER_CONNECT_FAILED, /** Connected to WiFi but server not + reachable, e.g. firewall block/ + whitelisting needed etc. */ + APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED, /** Server reachable but sensor + not configured correctly*/ + + /** During Normal Operation */ + APP_SM_WIFI_LOST, /** Connection to WiFi network failed credentials incorrect + encryption not supported etc. */ + APP_SM_SERVER_LOST, /** Connected to WiFi network but the server cannot be + reached through the internet, e.g. blocked by firewall + */ + APP_SM_SENSOR_CONFIG_FAILED, /** Server is reachable but there is some + configuration issue to be fixed on the server + side */ + APP_SM_NORMAL, +}; + +#define LED_FAST_BLINK_DELAY 250 /** ms */ +#define LED_SLOW_BLINK_DELAY 1000 /** ms */ +#define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */ +#define WIFI_CONNECT_RETRY_MS 10000 /** ms */ +#define LED_BAR_COUNT_INIT_VALUE (-1) /** */ +#define LED_BAR_ANIMATION_PERIOD 100 /** ms */ +#define DISP_UPDATE_INTERVAL 5000 /** ms */ +#define SERVER_CONFIG_UPDATE_INTERVAL 30000 /** ms */ +#define SERVER_SYNC_INTERVAL 60000 /** ms */ +#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */ +#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */ +#define SENSOR_CO2_UPDATE_INTERVAL 5000 /** ms */ +#define SENSOR_PM_UPDATE_INTERVAL 5000 /** ms */ +#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 5000 /** ms */ +#define DISPLAY_DELAY_SHOW_CONTENT_MS 3000 /** ms */ +#define WIFI_HOTSPOT_PASSWORD_DEFAULT \ + "cleanair" /** default WiFi AP password \ + */ + +/** + * @brief Use use LED bar state + */ +typedef enum { + UseLedBarOff, /** Don't use LED bar */ + UseLedBarPM, /** Use LED bar for PMS */ + UseLedBarCO2, /** Use LED bar for CO2 */ +} UseLedBar; + +/** + * @brief Schedule handle with timing period + * + */ +class AgSchedule { +public: + AgSchedule(int period, void (*handler)(void)) + : period(period), handler(handler) {} + void run(void) { + uint32_t ms = (uint32_t)(millis() - count); + if (ms >= period) { + /** Call handler */ + handler(); + + Serial.printf("[AgSchedule] handle 0x%08x, period: %d(ms)\r\n", + (unsigned int)handler, period); + + /** Update period time */ + count = millis(); + } + } + +private: + void (*handler)(void); + int period; + int count; +}; + +/** + * @brief AirGradient server configuration and sync data + * + */ +class AgServer { +public: + void begin(void) { + inF = false; + inUSAQI = false; + configFailed = false; + serverFailed = false; + memset(models, 0, sizeof(models)); + memset(mqttBroker, 0, sizeof(mqttBroker)); + } + + /** + * @brief Get server configuration + * + * @param id Device ID + * @return true Success + * @return false Failure + */ + bool pollServerConfig(String id) { + String uri = + "http://hw.airgradient.com/sensors/airgradient:" + id + "/one/config"; + + /** Init http client */ + HTTPClient client; + if (client.begin(uri) == false) { + configFailed = true; + return false; + } + + /** Get */ + int retCode = client.GET(); + if (retCode != 200) { + client.end(); + configFailed = true; + return false; + } + + /** clear failed */ + configFailed = false; + + /** Get response string */ + String respContent = client.getString(); + client.end(); + Serial.println("Get server config: " + respContent); + + /** Parse JSON */ + JSONVar root = JSON.parse(respContent); + if (JSON.typeof(root) == "undefined") { + /** JSON invalid */ + return false; + } + + /** Get "country" */ + if (JSON.typeof_(root["country"]) == "string") { + String country = root["country"]; + if (country == "US") { + inF = true; + } else { + inF = false; + } + } + + /** Get "pmsStandard" */ + if (JSON.typeof_(root["pmsStandard"]) == "string") { + String standard = root["pmsStandard"]; + if (standard == "ugm3") { + inUSAQI = false; + } else { + inUSAQI = true; + } + } + + /** Get "co2CalibrationRequested" */ + if (JSON.typeof_(root["co2CalibrationRequested"]) == "boolean") { + co2Calib = root["co2CalibrationRequested"]; + } + + /** Get "ledBarMode" */ + if (JSON.typeof_(root["ledBarMode"]) == "string") { + String mode = root["ledBarMode"]; + if (mode == "co2") { + ledBarMode = UseLedBarCO2; + } else if (mode == "pm") { + ledBarMode = UseLedBarPM; + } else if (mode == "off") { + ledBarMode = UseLedBarOff; + } else { + ledBarMode = UseLedBarOff; + } + } + + /** Get model */ + if (JSON.typeof_(root["model"]) == "string") { + String model = root["model"]; + if (model.length()) { + int len = + model.length() < sizeof(models) ? model.length() : sizeof(models); + memset(models, 0, sizeof(models)); + memcpy(models, model.c_str(), len); + } + } + + /** Get "mqttBrokerUrl" */ + if (JSON.typeof_(root["mqttBrokerUrl"]) == "string") { + String mqtt = root["mqttBrokerUrl"]; + if (mqtt.length()) { + int len = mqtt.length() < sizeof(mqttBroker) ? mqtt.length() + : sizeof(mqttBroker); + memset(mqttBroker, 0, sizeof(mqttBroker)); + memcpy(mqttBroker, mqtt.c_str(), len); + } + } + + /** Show configuration */ + showServerConfig(); + + return true; + } + + bool postToServer(String id, String payload) { + /** + * @brief Only post data if WiFi is connected + */ + if (WiFi.isConnected() == false) { + return false; + } + + Serial.printf("Post payload: %s\r\n", payload.c_str()); + + String uri = + "http://hw.airgradient.com/sensors/airgradient:" + id + "/measures"; + + WiFiClient wifiClient; + HTTPClient client; + if (client.begin(wifiClient, uri.c_str()) == false) { + return false; + } + client.addHeader("content-type", "application/json"); + int retCode = client.POST(payload); + client.end(); + + if ((retCode == 200) || (retCode == 429)) { + serverFailed = false; + return true; + } + serverFailed = true; + return false; + } + + /** + * @brief Get temperature configuration unit + * + * @return true F unit + * @return false C Unit + */ + bool isTemperatureUnitF(void) { return inF; } + + /** + * @brief Get PMS standard unit + * + * @return true USAQI + * @return false ugm3 + */ + bool isPMSinUSAQI(void) { return inUSAQI; } + + /** + * @brief Get status of get server coniguration is failed + * + * @return true Failed + * @return false Success + */ + bool isConfigFailed(void) { return configFailed; } + + /** + * @brief Get status of post server configuration is failed + * + * @return true Failed + * @return false Success + */ + bool isServerFailed(void) { return serverFailed; } + + /** + * @brief Get request calibration CO2 + * + * @return true Requested. If result = true, it's clear after function call + * @return false Not-requested + */ + bool isCo2Calib(void) { + bool ret = co2Calib; + if (ret) { + co2Calib = false; + } + return ret; + } + + /** + * @brief Get device configuration model name + * + * @return String Model name, empty string if server failed + */ + String getModelName(void) { return String(models); } + + /** + * @brief Get mqttBroker url + * + * @return String Broker url, empty if server failed + */ + String getMqttBroker(void) { return String(mqttBroker); } + + /** + * @brief Show server configuration parameter + */ + void showServerConfig(void) { + Serial.println("Server configuration: "); + Serial.printf(" inF: %s\r\n", inF ? "true" : "false"); + Serial.printf(" inUSAQI: %s\r\n", inUSAQI ? "true" : "false"); + Serial.printf("useRGBLedBar: %d\r\n", (int)ledBarMode); + Serial.printf(" Model: %s\r\n", models); + Serial.printf(" Mqtt Broker: %s\r\n", mqttBroker); + } + + /** + * @brief Get server config led bar mode + * + * @return UseLedBar + */ + UseLedBar getLedBarMode(void) { return ledBarMode; } + +private: + bool inF; /** Temperature unit, true: F, false: C */ + bool inUSAQI; /** PMS unit, true: USAQI, false: ugm3 */ + bool configFailed; /** Flag indicate get server configuration failed */ + bool serverFailed; /** Flag indicate post data to server failed */ + bool co2Calib; /** Is co2Ppmcalibration requset */ + UseLedBar ledBarMode = UseLedBarCO2; /** */ + char models[20]; /** */ + char mqttBroker[256]; /** */ +}; +AgServer agServer; + +/** Create airgradient instance for 'OPEN_AIR_OUTDOOR' board */ +AirGradient ag(OPEN_AIR_OUTDOOR); + +float pm1Value01 = 0; +float pm1Value25 = 0; +float pm1Value10 = 0; +float pm1PCount = 0; +float pm1temp = 0; +float pm1hum = 0; + +float pm2Value01 = 0; +float pm2Value25 = 0; +float pm2Value10 = 0; +float pm2PCount = 0; +float pm2temp = 0; +float pm2hum = 0; + +int countPosition = 0; +int targetCount = 20; + +static int ledSmState = APP_SM_NORMAL; +static bool wifiHasConfig = false; +static String wifiSSID = ""; + +WiFiManager wifiManager; /** wifi manager instance */ + +int loopCount = 0; +int tvocIndex = -1; +int noxIndex = -1; + +void boardInit(void); +void failedHandler(String msg); +static String getDevId(void); +static void sendDataToServerHandler(void); +static void serverConfigPoll(void); +static void tvocPoll(void); +static void updateWiFiConnect(void); + +AgSchedule agSyncDataSchedule(2000, sendDataToServerHandler); +AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, serverConfigPoll); +AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, tvocPoll); + +// select board LOLIN C3 mini to flash +void setup() { + Serial.begin(115200); + + /** Board init */ + boardInit(); + + /** Init AgServer */ + agServer.begin(); + + /** WiFi connect */ + connectToWifi(); + + if (WiFi.isConnected()) { + sendPing(); + + agServer.pollServerConfig(getDevId()); + if (agServer.isConfigFailed()) { + ledSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED); + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); + } + } + + ledSmHandler(APP_SM_NORMAL); +} + +void loop() { + agSyncDataSchedule.run(); + configSchedule.run(); + tvocSchedule.run(); + updateWiFiConnect(); +} + +void sendPing() { + JSONVar root; + root["wifi"] = WiFi.RSSI(); + root["boot"] = loopCount; + if (agServer.postToServer(getDevId(), JSON.stringify(root))) { + ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNNECTED); + } else { + ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNECT_FAILED); + } + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); +} + +void sendToServer(int pm1Value01, int pm1Value25, int pm1Value10, int pm1PCount, + float pm1temp, float pm1hum, int pm2Value01, int pm2Value25, + int pm2Value10, int pm2PCount, float pm2temp, float pm2hum, + int tvoc, int nox) { + JSONVar root; + root["wifi"] = WiFi.RSSI(); + root["boot"] = loopCount; + if (tvocIndex > 0) { + root["tvoc_index"] = loopCount; + } + if (noxIndex > 0) { + root["nox_index"] = loopCount; + } + root["pm01"] = (int)((pm1Value01 + pm2Value01) / 2); + root["pm02"] = (int)((pm1Value25 + pm2Value25) / 2); + root["pm003_count"] = (int)((pm1PCount + pm2PCount) / 2); + root["atmp"] = (int)((pm1temp + pm2temp) / 2); + root["rhum"] = (int)((pm1hum + pm2hum) / 2); + root["channels"]["1"]["pm01"] = pm1Value01; + root["channels"]["1"]["pm02"] = pm1Value25; + root["channels"]["1"]["pm10"] = pm1Value10; + root["channels"]["1"]["pm003_count"] = pm1PCount; + root["channels"]["1"]["atmp"] = pm1temp; + root["channels"]["1"]["rhum"] = pm1hum; + root["channels"]["2"]["pm01"] = pm2Value01; + root["channels"]["2"]["pm02"] = pm2Value25; + root["channels"]["2"]["pm10"] = pm2Value10; + root["channels"]["2"]["pm003_count"] = pm2PCount; + root["channels"]["2"]["atmp"] = pm2temp; + root["channels"]["2"]["rhum"] = pm2hum; + + if (agServer.postToServer(getDevId(), JSON.stringify(root))) { + resetWatchdog(); + } + loopCount++; +} + +void resetWatchdog() { ag.watchdog.reset(); } + +bool wifiMangerClientConnected(void) { + return WiFi.softAPgetStationNum() ? true : false; +} + +// Wifi Manager +void connectToWifi() { + wifiSSID = "airgradient-" + getDevId(); + + wifiManager.setConfigPortalBlocking(false); + wifiManager.setTimeout(WIFI_CONNECT_COUNTDOWN_MAX); + + wifiManager.setAPCallback([](WiFiManager *obj) { + /** This callback if wifi connnected failed and try to start configuration + * portal */ + ledSmState = APP_SM_WIFI_MANAGER_MODE; + }); + wifiManager.setSaveConfigCallback([]() { + /** Wifi connected save the configuration */ + ledSmHandler(APP_SM_WIFI_MANAGER_STA_CONNECTED); + }); + wifiManager.setSaveParamsCallback([]() { + /** Wifi set connect: ssid, password */ + ledSmHandler(APP_SM_WIFI_MANAGER_STA_CONNECTING); + }); + wifiManager.autoConnect(wifiSSID.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT); + + xTaskCreate( + [](void *obj) { + while (wifiManager.getConfigPortalActive()) { + wifiManager.process(); + } + vTaskDelete(NULL); + }, + "wifi_cfg", 4096, NULL, 10, NULL); + + uint32_t stimer = millis(); + bool clientConnectChanged = false; + while (wifiManager.getConfigPortalActive()) { + if (WiFi.isConnected() == false) { + if (ledSmState == APP_SM_WIFI_MANAGER_MODE) { + uint32_t ms = (uint32_t)(millis() - stimer); + if (ms >= 100) { + stimer = millis(); + ledSmHandler(ledSmState); + } + } + + /** Check for client connect to change led color */ + bool clientConnected = wifiMangerClientConnected(); + if (clientConnected != clientConnectChanged) { + clientConnectChanged = clientConnected; + if (clientConnectChanged) { + ledSmHandler(APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE); + } else { + ledSmHandler(APP_SM_WIFI_MANAGER_MODE); + } + } + } + } + + /** Show display wifi connect result failed */ + if (WiFi.isConnected() == false) { + ledSmHandler(APP_SM_WIFI_MANAGER_CONNECT_FAILED); + } else { + wifiHasConfig = true; + } +} + +String getNormalizedMac() { + String mac = WiFi.macAddress(); + mac.replace(":", ""); + mac.toLowerCase(); + return mac; +} + +void boardInit(void) { + ag.watchdog.begin(); + ag.statusLed.begin(); + ag.button.begin(); + if (ag.pms5003t_1.begin(Serial0) == false) { + failedHandler("PMS5003T_1 init failed"); + } + if (ag.pms5003t_2.begin(Serial1) == false) { + failedHandler("PMS5003T_2 init failed"); + } + + /** Init I2C */ + Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin()); + /** Init sensor SGP41 */ + if (ag.sgp41.begin(Wire) == false) { + failedHandler("Init SGP41 failed"); + } +} + +void failedHandler(String msg) { + while (true) { + Serial.println(msg); + delay(1000); + } +} + +static String getDevId(void) { return getNormalizedMac(); } + +static void sendDataToServerHandler(void) { + if (ag.pms5003t_1.readData() && ag.pms5003t_2.readData()) { + pm1Value01 = pm1Value01 + ag.pms5003t_1.getPm01Ae(); + pm1Value25 = pm1Value25 + ag.pms5003t_1.getPm25Ae(); + pm1Value10 = pm1Value10 + ag.pms5003t_1.getPm10Ae(); + pm1PCount = pm1PCount + ag.pms5003t_1.getPm03ParticleCount(); + pm1temp = pm1temp + ag.pms5003t_1.getTemperature(); + pm1hum = pm1hum + ag.pms5003t_1.getRelativeHumidity(); + pm2Value01 = pm2Value01 + ag.pms5003t_2.getPm01Ae(); + pm2Value25 = pm2Value25 + ag.pms5003t_2.getPm25Ae(); + pm2Value10 = pm2Value10 + ag.pms5003t_2.getPm10Ae(); + pm2PCount = pm2PCount + ag.pms5003t_2.getPm03ParticleCount(); + pm2temp = pm2temp + ag.pms5003t_2.getTemperature(); + pm2hum = pm2hum + ag.pms5003t_2.getRelativeHumidity(); + countPosition++; + if (countPosition == targetCount) { + pm1Value01 = pm1Value01 / targetCount; + pm1Value25 = pm1Value25 / targetCount; + pm1Value10 = pm1Value10 / targetCount; + pm1PCount = pm1PCount / targetCount; + pm1temp = pm1temp / targetCount; + pm1hum = pm1hum / targetCount; + pm2Value01 = pm2Value01 / targetCount; + pm2Value25 = pm2Value25 / targetCount; + pm2Value10 = pm2Value10 / targetCount; + pm2PCount = pm2PCount / targetCount; + pm2temp = pm2temp / targetCount; + pm2hum = pm2hum / targetCount; + sendToServer(pm1Value01, pm1Value25, pm1Value10, pm1PCount, pm1temp, + pm1hum, pm2Value01, pm2Value25, pm2Value10, pm2PCount, + pm2temp, pm2hum, tvocIndex, noxIndex); + + countPosition = 0; + pm1Value01 = 0; + pm1Value25 = 0; + pm1Value10 = 0; + pm1PCount = 0; + pm1temp = 0; + pm1hum = 0; + pm2Value01 = 0; + pm2Value25 = 0; + pm2Value10 = 0; + pm2PCount = 0; + pm2temp = 0; + pm2hum = 0; + } + } +} + +static void serverConfigPoll(void) { + if (agServer.pollServerConfig(getDevId())) { + Serial.println("Get server configure success"); + } else { + Serial.println("Get server configure failure"); + } +} + +static void tvocPoll(void) { + tvocIndex = ag.sgp41.getTvocIndex(); + noxIndex = ag.sgp41.getNoxIndex(); + + Serial.printf("tvocIndexindex: %d\r\n", tvocIndex); + Serial.printf(" NOx index: %d\r\n", noxIndex); +} + +/** + * @brief WiFi reconnect handler + */ +static void updateWiFiConnect(void) { + static uint32_t lastRetry; + if (wifiHasConfig == false) { + return; + } + if (WiFi.isConnected()) { + lastRetry = millis(); + return; + } + uint32_t ms = (uint32_t)(millis() - lastRetry); + if (ms >= WIFI_CONNECT_RETRY_MS) { + lastRetry = millis(); + WiFi.reconnect(); + + Serial.printf("Re-Connect WiFi\r\n"); + } +} + +void ledBlinkDelay(uint32_t tdelay) { + ag.statusLed.setOn(); + delay(tdelay); + ag.statusLed.setOff(); + delay(tdelay); +} + +void ledSmHandler(int sm) { + if (sm > APP_SM_NORMAL) { + return; + } + + ledSmState = sm; + switch (sm) { + case APP_SM_WIFI_MANAGER_MODE: { + ag.statusLed.setToggle(); + break; + } + case APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE: { + ag.statusLed.setOn(); + break; + } + case APP_SM_WIFI_MANAGER_STA_CONNECTING: { + ag.statusLed.setOff(); + break; + } + case APP_SM_WIFI_MANAGER_STA_CONNECTED: { + ag.statusLed.setOff(); + break; + } + case APP_SM_WIFI_OK_SERVER_CONNECTING: { + ag.statusLed.setOff(); + break; + } + case APP_SM_WIFI_OK_SERVER_CONNNECTED: { + ag.statusLed.setOff(); + + /** two time slow blink, then off */ + for (int i = 0; i < 2; i++) { + ledBlinkDelay(LED_SLOW_BLINK_DELAY); + } + + ag.statusLed.setOff(); + break; + } + case APP_SM_WIFI_MANAGER_CONNECT_FAILED: { + /** Three time fast blink then 2 sec pause. Repeat 3 times */ + ag.statusLed.setOff(); + + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 3; i++) { + ledBlinkDelay(LED_FAST_BLINK_DELAY); + } + delay(2000); + } + ag.statusLed.setOff(); + break; + } + case APP_SM_WIFI_OK_SERVER_CONNECT_FAILED: { + ag.statusLed.setOff(); + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 4; i++) { + ledBlinkDelay(LED_FAST_BLINK_DELAY); + } + delay(2000); + } + ag.statusLed.setOff(); + break; + } + case APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED: { + ag.statusLed.setOff(); + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 5; i++) { + ledBlinkDelay(LED_FAST_BLINK_DELAY); + } + delay(2000); + } + ag.statusLed.setOff(); + break; + } + case APP_SM_WIFI_LOST: { + ag.statusLed.setOff(); + break; + } + case APP_SM_SERVER_LOST: { + ag.statusLed.setOff(); + break; + } + case APP_SM_SENSOR_CONFIG_FAILED: { + ag.statusLed.setOff(); + break; + } + case APP_SM_NORMAL: { + ag.statusLed.setOff(); + break; + } + default: + break; + } +} diff --git a/examples/Open_Air_O-1PST/Open_Air_O-1PST.ino b/examples/Open_Air_O-1PST/Open_Air_O-1PST.ino index f91a0a9..8a8eed2 100644 --- a/examples/Open_Air_O-1PST/Open_Air_O-1PST.ino +++ b/examples/Open_Air_O-1PST/Open_Air_O-1PST.ino @@ -1,19 +1,23 @@ /* -This is the code for the AirGradient Open Air open-source hardware outdoor Air Quality Monitor with an ESP32-C3 Microcontroller. +This is the code for the AirGradient Open Air open-source hardware outdoor Air +Quality Monitor with an ESP32-C3 Microcontroller. -It is an air quality monitor for PM2.5, CO2, TVOCs, NOx, Temperature and Humidity and can send data over Wifi. +It is an air quality monitor for PM2.5, CO2, TVOCs, NOx, Temperature and +Humidity and can send data over Wifi. Open source air quality monitors and kits are available: Indoor Monitor: https://www.airgradient.com/indoor/ Outdoor Monitor: https://www.airgradient.com/outdoor/ -Build Instructions: https://www.airgradient.com/documentation/open-air-pst-kit-1-3/ +Build Instructions: +https://www.airgradient.com/documentation/open-air-pst-kit-1-3/ The codes needs the following libraries installed: “WifiManager by tzapu, tablatronix” tested with version 2.0.16-rc.2 "Arduino_JSON" by Arduino Version 0.2.0 -Please make sure you have esp32 board manager installed. Tested with version 2.0.11. +Please make sure you have esp32 board manager installed. Tested with +version 2.0.11. Important flashing settings: - Set board to "ESP32C3 Dev Module" @@ -75,63 +79,336 @@ enum { APP_SM_NORMAL, }; -#define DEBUG true +#define LED_FAST_BLINK_DELAY 250 /** ms */ +#define LED_SLOW_BLINK_DELAY 1000 /** ms */ +#define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */ +#define WIFI_CONNECT_RETRY_MS 10000 /** ms */ +#define LED_BAR_COUNT_INIT_VALUE (-1) /** */ +#define LED_BAR_ANIMATION_PERIOD 100 /** ms */ +#define DISP_UPDATE_INTERVAL 5000 /** ms */ +#define SERVER_CONFIG_UPDATE_INTERVAL 30000 /** ms */ +#define SERVER_SYNC_INTERVAL 60000 /** ms */ +#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */ +#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */ +#define SENSOR_CO2_UPDATE_INTERVAL 5000 /** ms */ +#define SENSOR_PM_UPDATE_INTERVAL 5000 /** ms */ +#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 5000 /** ms */ +#define DISPLAY_DELAY_SHOW_CONTENT_MS 3000 /** ms */ +#define WIFI_HOTSPOT_PASSWORD_DEFAULT \ + "cleanair" /** default WiFi AP password \ + */ -#define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */ -#define WIFI_HOTSPOT_PASSWORD_DEFAULT "cleanair" +/** + * @brief Use use LED bar state + */ +typedef enum { + UseLedBarOff, /** Don't use LED bar */ + UseLedBarPM, /** Use LED bar for PMS */ + UseLedBarCO2, /** Use LED bar for CO2 */ +} UseLedBar; -AirGradient ag(BOARD_OUTDOOR_MONITOR_V1_3); +/** + * @brief Schedule handle with timing period + * + */ +class AgSchedule { +public: + AgSchedule(int period, void (*handler)(void)) + : period(period), handler(handler) {} + void run(void) { + uint32_t ms = (uint32_t)(millis() - count); + if (ms >= period) { + /** Call handler */ + handler(); -// time in seconds needed for NOx conditioning -uint16_t conditioning_s = 10; + Serial.printf("[AgSchedule] handle 0x%08x, period: %d(ms)\r\n", + (unsigned int)handler, period); -String APIROOT = "http://hw.airgradient.com/"; + /** Update period time */ + count = millis(); + } + } -typedef struct { - bool inF; /** Temperature unit */ - bool inUSAQI; /** PMS standard */ - uint8_t ledBarMode; /** @ref UseLedBar*/ - char model[16]; /** Model string value, Just define, don't know how much - memory usage */ - char mqttBroker[128]; /** Mqtt broker link */ - uint32_t _check; /** Checksum configuration data */ -} ServerConfig_t; -static ServerConfig_t serverConfig; +private: + void (*handler)(void); + int period; + int count; +}; -// set to true if you want to connect to wifi. You have 60 seconds to connect. -// Then it will go into an offline mode. -boolean connectWIFI = true; +/** + * @brief AirGradient server configuration and sync data + * + */ +class AgServer { +public: + void begin(void) { + inF = false; + inUSAQI = false; + configFailed = false; + serverFailed = false; + memset(models, 0, sizeof(models)); + memset(mqttBroker, 0, sizeof(mqttBroker)); + } + + /** + * @brief Get server configuration + * + * @param id Device ID + * @return true Success + * @return false Failure + */ + bool pollServerConfig(String id) { + String uri = + "http://hw.airgradient.com/sensors/airgradient:" + id + "/one/config"; + + /** Init http client */ + HTTPClient client; + if (client.begin(uri) == false) { + configFailed = true; + return false; + } + + /** Get */ + int retCode = client.GET(); + if (retCode != 200) { + client.end(); + configFailed = true; + return false; + } + + /** clear failed */ + configFailed = false; + + /** Get response string */ + String respContent = client.getString(); + client.end(); + Serial.println("Get server config: " + respContent); + + /** Parse JSON */ + JSONVar root = JSON.parse(respContent); + if (JSON.typeof(root) == "undefined") { + /** JSON invalid */ + return false; + } + + /** Get "country" */ + if (JSON.typeof_(root["country"]) == "string") { + String country = root["country"]; + if (country == "US") { + inF = true; + } else { + inF = false; + } + } + + /** Get "pmsStandard" */ + if (JSON.typeof_(root["pmsStandard"]) == "string") { + String standard = root["pmsStandard"]; + if (standard == "ugm3") { + inUSAQI = false; + } else { + inUSAQI = true; + } + } + + /** Get "co2CalibrationRequested" */ + if (JSON.typeof_(root["co2CalibrationRequested"]) == "boolean") { + co2Calib = root["co2CalibrationRequested"]; + } + + /** Get "ledBarMode" */ + if (JSON.typeof_(root["ledBarMode"]) == "string") { + String mode = root["ledBarMode"]; + if (mode == "co2") { + ledBarMode = UseLedBarCO2; + } else if (mode == "pm") { + ledBarMode = UseLedBarPM; + } else if (mode == "off") { + ledBarMode = UseLedBarOff; + } else { + ledBarMode = UseLedBarOff; + } + } + + /** Get model */ + if (JSON.typeof_(root["model"]) == "string") { + String model = root["model"]; + if (model.length()) { + int len = + model.length() < sizeof(models) ? model.length() : sizeof(models); + memset(models, 0, sizeof(models)); + memcpy(models, model.c_str(), len); + } + } + + /** Get "mqttBrokerUrl" */ + if (JSON.typeof_(root["mqttBrokerUrl"]) == "string") { + String mqtt = root["mqttBrokerUrl"]; + if (mqtt.length()) { + int len = mqtt.length() < sizeof(mqttBroker) ? mqtt.length() + : sizeof(mqttBroker); + memset(mqttBroker, 0, sizeof(mqttBroker)); + memcpy(mqttBroker, mqtt.c_str(), len); + } + } + + /** Get 'abcDays' */ + if (JSON.typeof_(root["abcDays"]) == "number") { + co2AbcCalib = root["abcDays"]; + } else { + co2AbcCalib = -1; + } + + /** Show configuration */ + showServerConfig(); + + return true; + } + + bool postToServer(String id, String payload) { + /** + * @brief Only post data if WiFi is connected + */ + if (WiFi.isConnected() == false) { + return false; + } + + Serial.printf("Post payload: %s\r\n", payload.c_str()); + + String uri = + "http://hw.airgradient.com/sensors/airgradient:" + id + "/measures"; + + WiFiClient wifiClient; + HTTPClient client; + if (client.begin(wifiClient, uri.c_str()) == false) { + return false; + } + client.addHeader("content-type", "application/json"); + int retCode = client.POST(payload); + client.end(); + + if ((retCode == 200) || (retCode == 429)) { + serverFailed = false; + return true; + } + serverFailed = true; + return false; + } + + /** + * @brief Get temperature configuration unit + * + * @return true F unit + * @return false C Unit + */ + bool isTemperatureUnitF(void) { return inF; } + + /** + * @brief Get PMS standard unit + * + * @return true USAQI + * @return false ugm3 + */ + bool isPMSinUSAQI(void) { return inUSAQI; } + + /** + * @brief Get status of get server coniguration is failed + * + * @return true Failed + * @return false Success + */ + bool isConfigFailed(void) { return configFailed; } + + /** + * @brief Get status of post server configuration is failed + * + * @return true Failed + * @return false Success + */ + bool isServerFailed(void) { return serverFailed; } + + /** + * @brief Get request calibration CO2 + * + * @return true Requested. If result = true, it's clear after function call + * @return false Not-requested + */ + bool isCo2Calib(void) { + bool ret = co2Calib; + if (ret) { + co2Calib = false; + } + return ret; + } + + /** + * @brief Get the Co2 auto calib period + * + * @return int days, -1 if invalid. + */ + int getCo2Abccalib(void) { return co2AbcCalib; } + + /** + * @brief Get device configuration model name + * + * @return String Model name, empty string if server failed + */ + String getModelName(void) { return String(models); } + + /** + * @brief Get mqttBroker url + * + * @return String Broker url, empty if server failed + */ + String getMqttBroker(void) { return String(mqttBroker); } + + /** + * @brief Show server configuration parameter + */ + void showServerConfig(void) { + Serial.println("Server configuration: "); + Serial.printf(" inF: %s\r\n", inF ? "true" : "false"); + Serial.printf(" inUSAQI: %s\r\n", inUSAQI ? "true" : "false"); + Serial.printf(" useRGBLedBar: %d\r\n", (int)ledBarMode); + Serial.printf(" Model: %s\r\n", models); + Serial.printf(" Mqtt Broker: %s\r\n", mqttBroker); + Serial.printf(" S8 calib period: %d\r\n", co2AbcCalib); + } + + /** + * @brief Get server config led bar mode + * + * @return UseLedBar + */ + UseLedBar getLedBarMode(void) { return ledBarMode; } + +private: + bool inF; /** Temperature unit, true: F, false: C */ + bool inUSAQI; /** PMS unit, true: USAQI, false: ugm3 */ + bool configFailed; /** Flag indicate get server configuration failed */ + bool serverFailed; /** Flag indicate post data to server failed */ + bool co2Calib; /** Is co2Ppmcalibration requset */ + int co2AbcCalib = -1; /** update auto calibration number of day */ + UseLedBar ledBarMode = UseLedBarCO2; /** */ + char models[20]; /** */ + char mqttBroker[256]; /** */ +}; +AgServer agServer; + +/** Create airgradient instance for 'OPEN_AIR_OUTDOOR' board */ +AirGradient ag(OPEN_AIR_OUTDOOR); static int ledSmState = APP_SM_NORMAL; -static bool serverFailed = false; -static bool configFailed = false; -static bool wifiHasConfig = false; int loopCount = 0; WiFiManager wifiManager; /** wifi manager instance */ +static bool wifiHasConfig = false; +static String wifiSSID = ""; -unsigned long currentMillis = 0; - -const int oledInterval = 5000; -unsigned long previousOled = 0; - -const int sendToServerInterval = 60000; -const int pollServerConfigInterval = 30000; -const int co2CalibCountdown = 5; /** Seconds */ -unsigned long previoussendToServer = 0; - -const int tvocInterval = 1000; -unsigned long previousTVOC = 0; -int TVOC = -1; -int NOX = -1; - -const int co2Interval = 5000; -unsigned long previousCo2 = 0; -int Co2 = 0; - -const int pmInterval = 5000; -unsigned long previousPm = 0; +int tvocIndex = -1; +int noxIndex = -1; +int co2Ppm = 0; int pm25 = -1; int pm01 = -1; int pm10 = -1; @@ -139,156 +416,107 @@ int pm03PCount = -1; float temp; int hum; -bool co2CalibrationRequest = false; -uint32_t serverConfigLoadTime = 0; -String HOTSPOT = ""; - -// const int tempHumInterval = 2500; -// unsigned long previousTempHum = 0; - void boardInit(void); void failedHandler(String msg); -void getServerConfig(void); void co2Calibration(void); +static String getDevId(void); +static void updateWiFiConnect(void); +static void tvocPoll(void); +static void pmPoll(void); +static void sendDataToServer(void); +static void co2Poll(void); +static void serverConfigPoll(void); + +AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, serverConfigPoll); +AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer); +AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Poll); +AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmPoll); +AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, tvocPoll); void setup() { - if (DEBUG) { - Serial.begin(115200); - } + Serial.begin(115200); /** Board init */ boardInit(); - delay(500); + /** Server init */ + agServer.begin(); - countdown(3); + /** WiFi connect */ + connectToWifi(); - if (connectWIFI) { - connectToWifi(); - } - - if (WiFi.status() == WL_CONNECTED) { + if (WiFi.isConnected()) { + wifiHasConfig = true; sendPing(); - Serial.println(F("WiFi connected!")); - Serial.println("IP address: "); - Serial.println(WiFi.localIP()); - } - getServerConfig(); - if (configFailed) { - ledSmHandler(APP_SM_SENSOR_CONFIG_FAILED); - delay(5000); + agServer.pollServerConfig(getDevId()); + if (agServer.isConfigFailed()) { + ledSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED); + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); + } } ledSmHandler(APP_SM_NORMAL); } void loop() { - currentMillis = millis(); - updateTVOC(); - updateCo2(); - updatePm(); - sendToServer(); - getServerConfig(); -} - -void updateTVOC() { - delay(1000); - - if (currentMillis - previousTVOC >= tvocInterval) { - previousTVOC += tvocInterval; - TVOC = ag.sgp41.getTvocIndex(); - NOX = ag.sgp41.getNoxIndex(); - } -} - -void updateCo2() { - if (currentMillis - previousCo2 >= co2Interval) { - previousCo2 += co2Interval; - Co2 = ag.s8.getCo2(); - Serial.printf("CO2: %d\r\n", Co2); - } -} - -void updatePm() { - if (currentMillis - previousPm >= pmInterval) { - previousPm += pmInterval; - if (ag.pms5003t_1.readData()) { - pm01 = ag.pms5003t_1.getPm01Ae(); - pm25 = ag.pms5003t_1.getPm25Ae(); - pm10 = ag.pms5003t_1.getPm10Ae(); - pm03PCount = ag.pms5003t_1.getPm03ParticleCount(); - temp = ag.pms5003t_1.getTemperature(); - hum = ag.pms5003t_1.getRelativeHumidity(); - } - } + configSchedule.run(); + serverSchedule.run(); + co2Schedule.run(); + pmsSchedule.run(); + tvocSchedule.run(); + updateWiFiConnect(); } void sendPing() { - String payload = - "{\"wifi\":" + String(WiFi.RSSI()) + ", \"boot\":" + loopCount + "}"; - if (postToServer(payload)) { + JSONVar root; + root["wifi"] = WiFi.RSSI(); + root["boot"] = loopCount; + if (agServer.postToServer(getDevId(), JSON.stringify(root))) { ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNNECTED); } else { ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNECT_FAILED); } - delay(5000); + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); } -bool postToServer(String &payload) { - String POSTURL = APIROOT + - "sensors/airgradient:" + String(getNormalizedMac()) + - "/measures"; - WiFiClient client; - HTTPClient http; - - ag.statusLed.setOn(); - - http.begin(client, POSTURL); - http.addHeader("content-type", "application/json"); - int httpCode = http.POST(payload); - Serial.printf("Post to %s, %d\r\n", POSTURL.c_str(), httpCode); - http.end(); - - ag.statusLed.setOff(); - - return (httpCode == 200); -} - -void sendToServer() { - if (currentMillis - previoussendToServer >= sendToServerInterval) { - previoussendToServer += sendToServerInterval; - String payload = - "{\"wifi\":" + String(WiFi.RSSI()) + - (Co2 < 0 ? "" : ", \"rco2\":" + String(Co2)) + - (pm01 < 0 ? "" : ", \"pm01\":" + String(pm01)) + - (pm25 < 0 ? "" : ", \"pm02\":" + String(pm25)) + - (pm10 < 0 ? "" : ", \"pm10\":" + String(pm10)) + - (pm03PCount < 0 ? "" : ", \"pm003_count\":" + String(pm03PCount)) + - (TVOC < 0 ? "" : ", \"tvoc_index\":" + String(TVOC)) + - (NOX < 0 ? "" : ", \"nox_index\":" + String(NOX)) + - ", \"atmp\":" + String(temp) + - (hum < 0 ? "" : ", \"rhum\":" + String(hum)) + - ", \"boot\":" + loopCount + "}"; - - if (WiFi.status() == WL_CONNECTED) { - postToServer(payload); - resetWatchdog(); - loopCount++; - } else { - Serial.println("WiFi Disconnected"); - } +static void sendDataToServer(void) { + JSONVar root; + root["wifi"] = WiFi.RSSI(); + if (co2Ppm >= 0) { + root["rco2"] = co2Ppm; } -} - -void countdown(int from) { - debug("\n"); - while (from > 0) { - debug(String(from--)); - debug(" "); - delay(1000); + if (pm01 >= 0) { + root["pm01"] = pm01; } - debug("\n"); + if (pm25 >= 0) { + root["pm02"] = pm25; + } + if (pm10 >= 0) { + root["pm10"] = pm10; + } + if (pm03PCount >= 0) { + root["pm003_count"] = pm03PCount; + } + if (tvocIndex >= 0) { + root["tvoc_index"] = tvocIndex; + } + if (noxIndex >= 0) { + root["noxIndex"] = noxIndex; + } + if (temp >= 0) { + root["atmp"] = temp; + } + if (hum >= 0) { + root["rhum"] = hum; + } + root["boot"] = loopCount; + + // NOTE Need determine offline mode to reset watchdog timer + if (agServer.postToServer(getDevId(), JSON.stringify(root))) { + resetWatchdog(); + } + loopCount++; } void resetWatchdog() { @@ -302,7 +530,7 @@ bool wifiMangerClientConnected(void) { // Wifi Manager void connectToWifi() { - HOTSPOT = "airgradient-" + String(getNormalizedMac()); + wifiSSID = "airgradient-" + String(getNormalizedMac()); wifiManager.setConfigPortalBlocking(false); wifiManager.setTimeout(WIFI_CONNECT_COUNTDOWN_MAX); @@ -320,7 +548,7 @@ void connectToWifi() { /** Wifi set connect: ssid, password */ ledSmHandler(APP_SM_WIFI_MANAGER_STA_CONNECTING); }); - wifiManager.autoConnect(HOTSPOT.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT); + wifiManager.autoConnect(wifiSSID.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT); xTaskCreate( [](void *obj) { @@ -357,33 +585,13 @@ void connectToWifi() { } /** Show display wifi connect result failed */ + ag.statusLed.setOff(); + delay(2000); if (WiFi.isConnected() == false) { ledSmHandler(APP_SM_WIFI_MANAGER_CONNECT_FAILED); - } else { - wifiHasConfig = true; } } -void debug(String msg) { - if (DEBUG) - Serial.print(msg); -} - -void debug(int msg) { - if (DEBUG) - Serial.print(msg); -} - -void debugln(String msg) { - if (DEBUG) - Serial.println(msg); -} - -void debugln(int msg) { - if (DEBUG) - Serial.println(msg); -} - String getNormalizedMac() { String mac = WiFi.macAddress(); mac.replace(":", ""); @@ -422,155 +630,11 @@ void failedHandler(String msg) { } } -void updateServerConfigLoadTime(void) { - serverConfigLoadTime = millis(); - if (serverConfigLoadTime == 0) { - serverConfigLoadTime = 1; - } -} - -void showConfig(void) { - Serial.println("Server configuration: "); - Serial.printf(" inF: %s\r\n", serverConfig.inF ? "true" : "false"); - Serial.printf(" inUSAQI: %s\r\n", - serverConfig.inUSAQI ? "true" : "false"); - Serial.printf("useRGBLedBar: %d\r\n", (int)serverConfig.ledBarMode); - Serial.printf(" Model: %.*s\r\n", sizeof(serverConfig.model), - serverConfig.model); - Serial.printf(" Mqtt Broker: %.*s\r\n", sizeof(serverConfig.mqttBroker), - serverConfig.mqttBroker); -} - -void getServerConfig(void) { - /** Only trigger load configuration again after pollServerConfigInterval sec - */ - if (serverConfigLoadTime) { - uint32_t ms = (uint32_t)(millis() - serverConfigLoadTime); - if (ms < pollServerConfigInterval) { - return; - } - } - - updateServerConfigLoadTime(); - - Serial.println("Trigger load server configuration"); - if (WiFi.status() != WL_CONNECTED) { - Serial.println( - "Ignore get server configuration because WIFI not connected"); - return; - } - - // WiFiClient wifiClient; - HTTPClient httpClient; - - String getUrl = "http://hw.airgradient.com/sensors/airgradient:" + - String(getNormalizedMac()) + "/one/config"; - Serial.println("HttpClient get: " + getUrl); - if (httpClient.begin(getUrl) == false) { - Serial.println("HttpClient init failed"); - updateServerConfigLoadTime(); - return; - } - - int respCode = httpClient.GET(); - - /** get failure */ - if (respCode != 200) { - Serial.printf("HttpClient get failed: %d\r\n", respCode); - updateServerConfigLoadTime(); - httpClient.end(); - configFailed = true; - return; - } - - String respContent = httpClient.getString(); - Serial.println("Server config: " + respContent); - httpClient.end(); - - /** Parse JSON */ - JSONVar root = JSON.parse(respContent); - if (JSON.typeof_(root) == "undefined") { - Serial.println("Server configura JSON invalid"); - updateServerConfigLoadTime(); - configFailed = true; - return; - } - configFailed = false; - - /** Get "country" */ - bool inF = serverConfig.inF; - if (JSON.typeof_(root["country"]) == "string") { - String country = root["country"]; - if (country == "US") { - inF = true; - } else { - inF = false; - } - } - - /** Get "pmStandard" */ - bool inUSAQI = serverConfig.inUSAQI; - if (JSON.typeof_(root["pmStandard"]) == "string") { - String standard = root["pmStandard"]; - if (standard == "ugm3") { - inUSAQI = false; - } else { - inUSAQI = true; - } - } - - /** Get CO2 "co2CalibrationRequested" */ - co2CalibrationRequest = false; - if (JSON.typeof_(root["co2CalibrationRequested"]) == "boolean") { - co2CalibrationRequest = root["co2CalibrationRequested"]; - } - - /** get "model" */ - String model = ""; - if (JSON.typeof_(root["model"]) == "string") { - String _model = root["model"]; - model = _model; - } - - /** get "mqttBrokerUrl" */ - String mqtt = ""; - if (JSON.typeof_(root["mqttBrokerUrl"]) == "string") { - String _mqtt = root["mqttBrokerUrl"]; - mqtt = _mqtt; - } - - if (inF != serverConfig.inF) { - serverConfig.inF = inF; - } - if (inUSAQI != serverConfig.inUSAQI) { - serverConfig.inUSAQI = inUSAQI; - } - if (model.length()) { - if (model != String(serverConfig.model)) { - memset(serverConfig.model, 0, sizeof(serverConfig.model)); - memcpy(serverConfig.model, model.c_str(), model.length()); - } - } - if (mqtt.length()) { - if (mqtt != String(serverConfig.mqttBroker)) { - memset(serverConfig.mqttBroker, 0, sizeof(serverConfig.mqttBroker)); - memcpy(serverConfig.mqttBroker, mqtt.c_str(), mqtt.length()); - } - } - - /** Show server configuration */ - showConfig(); - - /** Calibration */ - if (co2CalibrationRequest) { - co2Calibration(); - } -} - void co2Calibration(void) { /** Count down for co2CalibCountdown secs */ - for (int i = 0; i < co2CalibCountdown; i++) { - Serial.printf("Start CO2 calib after %d sec\r\n", co2CalibCountdown - i); + for (int i = 0; i < SENSOR_CO2_CALIB_COUNTDOWN_MAX; i++) { + Serial.printf("Start CO2 calib after %d sec\r\n", + SENSOR_CO2_CALIB_COUNTDOWN_MAX - i); delay(1000); } @@ -591,6 +655,81 @@ void co2Calibration(void) { } } +/** + * @brief WiFi reconnect handler + */ +static void updateWiFiConnect(void) { + static uint32_t lastRetry; + if (wifiHasConfig == false) { + return; + } + if (WiFi.isConnected()) { + lastRetry = millis(); + return; + } + uint32_t ms = (uint32_t)(millis() - lastRetry); + if (ms >= WIFI_CONNECT_RETRY_MS) { + lastRetry = millis(); + WiFi.reconnect(); + + Serial.printf("Re-Connect WiFi\r\n"); + } +} + +/** + * @brief Update tvocIndexindex + * + */ +static void tvocPoll(void) { + tvocIndex = ag.sgp41.getTvocIndex(); + noxIndex = ag.sgp41.getNoxIndex(); + + Serial.printf("tvocIndexindex: %d\r\n", tvocIndex); + Serial.printf(" NOx index: %d\r\n", noxIndex); +} + +/** + * @brief Update PMS data + * + */ +static void pmPoll(void) { + if (ag.pms5003t_1.readData()) { + pm01 = ag.pms5003t_1.getPm01Ae(); + pm25 = ag.pms5003t_1.getPm25Ae(); + pm25 = ag.pms5003t_1.getPm10Ae(); + pm03PCount = ag.pms5003t_1.getPm03ParticleCount(); + temp = ag.pms5003t_1.getTemperature(); + hum = ag.pms5003t_1.getRelativeHumidity(); + } +} + +static void co2Poll(void) { + co2Ppm = ag.s8.getCo2(); + Serial.printf("CO2 index: %d\r\n", co2Ppm); +} + +static void serverConfigPoll(void) { + if (agServer.pollServerConfig(getDevId())) { + if (agServer.isCo2Calib()) { + co2Calibration(); + } + if (agServer.getCo2Abccalib() > 0) { + if (ag.s8.setAutoCalib(agServer.getCo2Abccalib() * 24) == false) { + Serial.println("Set S8 auto calib failed"); + } + } + } +} + +static String getDevId(void) { return getNormalizedMac(); } + +void ledBlinkDelay(uint32_t tdelay) { + ag.statusLed.setOn(); + delay(tdelay); + ag.statusLed.setOff(); + delay(tdelay); +} + void ledSmHandler(int sm) { if (sm > APP_SM_NORMAL) { return; @@ -620,21 +759,22 @@ void ledSmHandler(int sm) { } case APP_SM_WIFI_OK_SERVER_CONNNECTED: { ag.statusLed.setOff(); - ag.statusLed.setOn(); - delay(50); - ag.statusLed.setOff(); - delay(950); - ag.statusLed.setOn(); - delay(50); + + /** two time slow blink, then off */ + for (int i = 0; i < 2; i++) { + ledBlinkDelay(LED_SLOW_BLINK_DELAY); + } + ag.statusLed.setOff(); break; } case APP_SM_WIFI_MANAGER_CONNECT_FAILED: { + /** Three time fast blink then 2 sec pause. Repeat 3 times */ ag.statusLed.setOff(); + for (int j = 0; j < 3; j++) { - for (int i = 0; i < 3 * 2; i++) { - ag.statusLed.setToggle(); - delay(100); + for (int i = 0; i < 3; i++) { + ledBlinkDelay(LED_FAST_BLINK_DELAY); } delay(2000); } @@ -644,9 +784,8 @@ void ledSmHandler(int sm) { case APP_SM_WIFI_OK_SERVER_CONNECT_FAILED: { ag.statusLed.setOff(); for (int j = 0; j < 3; j++) { - for (int i = 0; i < 4 * 2; i++) { - ag.statusLed.setToggle(); - delay(100); + for (int i = 0; i < 4; i++) { + ledBlinkDelay(LED_FAST_BLINK_DELAY); } delay(2000); } @@ -656,9 +795,8 @@ void ledSmHandler(int sm) { case APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED: { ag.statusLed.setOff(); for (int j = 0; j < 3; j++) { - for (int i = 0; i < 5 * 2; i++) { - ag.statusLed.setToggle(); - delay(100); + for (int i = 0; i < 5; i++) { + ledBlinkDelay(LED_FAST_BLINK_DELAY); } delay(2000); } diff --git a/examples/TestCO2/TestCO2.ino b/examples/TestCO2/TestCO2.ino index d7c4e32..9768e29 100644 --- a/examples/TestCO2/TestCO2.ino +++ b/examples/TestCO2/TestCO2.ino @@ -2,41 +2,47 @@ This is sample code for the AirGradient library with a minimal implementation to read CO2 values from the SenseAir S8 sensor. CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License + */ #include #ifdef ESP8266 -AirGradient ag = AirGradient(BOARD_DIY_PRO_INDOOR_V4_2); -// AirGradient ag = AirGradient(BOARD_DIY_BASIC_KIT); +AirGradient ag = AirGradient(DIY_BASIC); #else -// AirGradient ag = AirGradient(BOARD_ONE_INDOOR_MONITOR_V9_0); -AirGradient ag = AirGradient(BOARD_OUTDOOR_MONITOR_V1_3); +/** Create airgradient instance for 'OPEN_AIR_OUTDOOR' board */ +AirGradient ag = AirGradient(OPEN_AIR_OUTDOOR); #endif void failedHandler(String msg); -void setup() { +void setup() +{ Serial.begin(115200); /** Init CO2 sensor */ #ifdef ESP8266 - if (ag.s8.begin(&Serial) == false) { + if (ag.s8.begin(&Serial) == false) + { #else - if (ag.s8.begin(Serial1) == false) { + if (ag.s8.begin(Serial1) == false) + { #endif failedHandler("SenseAir S8 init failed"); } } -void loop() { - int CO2 = ag.s8.getCo2(); - Serial.printf("CO2: %d\r\n", CO2); +void loop() +{ + int co2Ppm = ag.s8.getCo2(); + Serial.printf("CO2: %d\r\n", co2Ppm); delay(5000); } -void failedHandler(String msg) { - while (true) { +void failedHandler(String msg) +{ + while (true) + { Serial.println(msg); delay(1000); } diff --git a/examples/TestESP32/TestESP32.ino b/examples/TestESP32/TestESP32.ino index e548d0f..1bd7ba5 100644 --- a/examples/TestESP32/TestESP32.ino +++ b/examples/TestESP32/TestESP32.ino @@ -11,8 +11,8 @@ /** * @brief Define test board */ -#define TEST_BOARD_OUTDOOR_MONITOR_V1_3 0 -#define TEST_BOARD_ONE_INDOOR_MONITOR_V9_0 1 +#define TEST_OPEN_AIR_OUTDOOR 0 +#define TEST_ONE_INDOOR 1 /** * @brief Define test sensor */ @@ -21,21 +21,21 @@ #define TEST_SENSOR_SGP4x 0 #define TEST_SWITCH 0 #define TEST_OLED 0 -#if TEST_BOARD_OUTDOOR_MONITOR_V1_3 +#if TEST_OPEN_AIR_OUTDOOR #define TEST_STATUS_LED 0 #define TEST_PMS5003T 1 #endif #define TEST_WATCHDOG 1 -#if TEST_BOARD_ONE_INDOOR_MONITOR_V9_0 +#if TEST_ONE_INDOOR #define TEST_LED_BAR 1 #define TEST_SENSOR_PMS5003 0 #endif -#if TEST_BOARD_OUTDOOR_MONITOR_V1_3 -AirGradient ag(BOARD_OUTDOOR_MONITOR_V1_3); -#elif TEST_BOARD_ONE_INDOOR_MONITOR_V9_0 -AirGradient ag(BOARD_ONE_INDOOR_MONITOR_V9_0); +#if TEST_OPEN_AIR_OUTDOOR +AirGradient ag(OPEN_AIR_OUTDOOR); +#elif TEST_ONE_INDOOR +AirGradient ag(ONE_INDOOR); #else #error "Must enable board test #endif @@ -99,7 +99,7 @@ void setup() { #if TEST_SENSOR_SHT4x - if (ag.sht.begin(Wire)) { + if (ag.sht4x.begin(Wire)) { log_i("SHT init success"); } else { log_i("SHT init failed"); @@ -218,9 +218,9 @@ void loop() { ms = (uint32_t)(millis() - shtTime); if (ms >= 1000) { shtTime = millis(); - log_i("Get sht temperature: %0.2f (degree celsius)", - ag.sht.getTemperature()); - log_i("Get sht temperature: %0.2f (%%)", ag.sht.getRelativeHumidity()); + log_i("Get sht4x temperature: %0.2f (degree celsius)", + ag.sht4x.getTemperature()); + log_i("Get sht4x temperature: %0.2f (%%)", ag.sht4x.getRelativeHumidity()); } #endif @@ -237,14 +237,14 @@ void loop() { #if TEST_LED static uint32_t ledTime; -#if TEST_BOARD_OUTDOOR_MONITOR_V1_3 +#if TEST_OPEN_AIR_OUTDOOR // ms = (uint32_t)(millis() - ledTime); // if(ms >= 500) // { // ledTime = millis(); // led.ledToggle(); // } -#elif TEST_BOARD_ONE_INDOOR_MONITOR_V9_0 +#elif TEST_ONE_INDOOR static int ledIndex; static int ledIndexOld; @@ -262,7 +262,7 @@ void loop() { led.ledSetColor(255, 0, 0, ledIndex); ledIndexOld = ledIndex; ledIndex++; - if (ledIndex >= led.getNumberOfLed()) { + if (ledIndex >= led.getNumberOfLeds()) { ledIndex = 0; } } @@ -293,7 +293,7 @@ void loop() { static bool ledOn = false; if (ledNum == 0) { - ledNum = ag.ledBar.getNumberOfLed(); + ledNum = ag.ledBar.getNumberOfLeds(); log_i("Get number of led: %d", ledNum); if (ledNum) { ag.ledBar.setBrighness(0xff); diff --git a/examples/TestESP8266/TestESP8266.ino b/examples/TestESP8266/TestESP8266.ino index 78ad5eb..dbe4dfe 100644 --- a/examples/TestESP8266/TestESP8266.ino +++ b/examples/TestESP8266/TestESP8266.ino @@ -4,23 +4,23 @@ /** * @brief Define test board */ -#define TEST_BOARD_DIY_BASIC_KIT 0 -#define TEST_BOARD_DIY_PRO_INDOOR_V4_2 1 +#define TEST_DIY_BASIC 1 /** * @brief Define test sensor */ -#define TEST_SENSOR_SenseAirS8 0 +#define TEST_SENSOR_SenseAirS8 1 +// #define S8_BASELINE_CALIB #define TEST_SENSOR_PMS5003 0 #define TEST_SENSOR_SHT4x 0 -#define TEST_SENSOR_SGP4x 1 +#define TEST_SENSOR_SGP4x 0 #define TEST_SWITCH 0 #define TEST_OLED 0 -#if TEST_BOARD_DIY_BASIC_KIT -AirGradient ag(BOARD_DIY_BASIC_KIT); -#elif TEST_BOARD_DIY_PRO_INDOOR_V4_2 -AirGradient ag(BOARD_DIY_PRO_INDOOR_V4_2); +#if TEST_DIY_BASIC +AirGradient ag(DIY_BASIC); +#elif TEST_DIY_PRO_INDOOR_V4_2 +AirGradient ag(DIY_PRO_INDOOR_V4_2); #else #error "Board test not defined" #endif @@ -38,11 +38,19 @@ void setup() { Serial.println("CO2S8 sensor init failure"); } +#ifdef S8_BASELINE_CALIB if (ag.s8.setBaselineCalibration()) { Serial.println("Manual calib success"); } else { Serial.println("Manual calib failure"); } +#else + if (ag.s8.setAutoCalib(8)) { + Serial.println("Set auto calib success"); + } else { + Serial.println("Set auto calib failure"); + } +#endif delay(5000); #endif @@ -59,7 +67,7 @@ void setup() { #endif #if TEST_SENSOR_SHT4x - if (ag.sht.begin(Wire, Serial)) { + if (ag.sht4x.begin(Wire, Serial)) { Serial.println("SHT init success"); } else { Serial.println("SHT init failed"); @@ -132,7 +140,7 @@ void loop() { shtTime = millis(); float temperature, humidity; Serial.printf("SHT Temperature: %f, Humidity: %f\r\n", - ag.sht.getTemperature(), ag.sht.getRelativeHumidity()); + ag.sht4x.getTemperature(), ag.sht4x.getRelativeHumidity()); } #endif @@ -142,7 +150,7 @@ void loop() { /*** * Must call this task on loop and avoid delay on loop over 1000 ms - */ + */ ag.sgp41.handle(); if (ms >= 1000) { diff --git a/examples/TestPM/TestPM.ino b/examples/TestPM/TestPM.ino index 2ee53b7..556d1cf 100644 --- a/examples/TestPM/TestPM.ino +++ b/examples/TestPM/TestPM.ino @@ -1,5 +1,6 @@ /* -This is sample code for the AirGradient library with a minimal implementation to read PM values from the Plantower sensor. +This is sample code for the AirGradient library with a minimal implementation +to read PM values from the Plantower sensor. CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License */ @@ -7,11 +8,10 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License #include #ifdef ESP8266 -AirGradient ag = AirGradient(BOARD_DIY_PRO_INDOOR_V4_2); -// AirGradient ag = AirGradient(BOARD_DIY_BASIC_KIT); +AirGradient ag = AirGradient(DIY_BASIC); #else -// AirGradient ag = AirGradient(BOARD_ONE_INDOOR_MONITOR_V9_0); -AirGradient ag = AirGradient(BOARD_OUTDOOR_MONITOR_V1_3); +// AirGradient ag = AirGradient(ONE_INDOOR); +AirGradient ag = AirGradient(OPEN_AIR_OUTDOOR); #endif void failedHandler(String msg); @@ -19,16 +19,16 @@ void failedHandler(String msg); void setup() { Serial.begin(115200); #ifdef ESP8266 - if(ag.pms5003.begin(&Serial) == false) { + if (ag.pms5003.begin(&Serial) == false) { failedHandler("Init PMS5003 failed"); } #else - if (ag.getBoardType() == BOARD_OUTDOOR_MONITOR_V1_3) { - if(ag.pms5003t_1.begin(Serial0) == false) { + if (ag.getBoardType() == OPEN_AIR_OUTDOOR) { + if (ag.pms5003t_1.begin(Serial0) == false) { failedHandler("Init PMS5003T failed"); } } else { - if(ag.pms5003.begin(Serial0) == false) { + if (ag.pms5003.begin(Serial0) == false) { failedHandler("Init PMS5003T failed"); } } @@ -37,25 +37,37 @@ void setup() { void loop() { int PM2; + bool readResul = false; #ifdef ESP8266 - PM2 = ag.pms5003.getPm25Ae(); - Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2); - Serial.printf("PM2.5 in US AQI: %d\r\n", ag.pms5003.convertPm25ToUsAqi(PM2)); -#else - if (ag.getBoardType() == BOARD_OUTDOOR_MONITOR_V1_3) { - PM2 = ag.pms5003t_1.getPm25Ae(); - } else { + if (ag.pms5003.readData()) { PM2 = ag.pms5003.getPm25Ae(); - } - - Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2); - if (ag.getBoardType() == BOARD_OUTDOOR_MONITOR_V1_3) { - Serial.printf("PM2.5 in US AQI: %d\r\n", - ag.pms5003t_1.convertPm25ToUsAqi(PM2)); - } else { + Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2); Serial.printf("PM2.5 in US AQI: %d\r\n", ag.pms5003.convertPm25ToUsAqi(PM2)); } +#else + if (ag.getBoardType() == OPEN_AIR_OUTDOOR) { + if (ag.pms5003t_1.readData()) { + PM2 = ag.pms5003t_1.getPm25Ae(); + readResul = true; + } + } else { + if (ag.pms5003.readData()) { + PM2 = ag.pms5003.getPm25Ae(); + readResul = true; + } + } + + if (readResul) { + Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2); + if (ag.getBoardType() == OPEN_AIR_OUTDOOR) { + Serial.printf("PM2.5 in US AQI: %d\r\n", + ag.pms5003t_1.convertPm25ToUsAqi(PM2)); + } else { + Serial.printf("PM2.5 in US AQI: %d\r\n", + ag.pms5003.convertPm25ToUsAqi(PM2)); + } + } #endif delay(5000); diff --git a/examples/TestSht3x/TestSht3x.ino b/examples/TestSht3x/TestSht3x.ino new file mode 100644 index 0000000..7684fdc --- /dev/null +++ b/examples/TestSht3x/TestSht3x.ino @@ -0,0 +1,39 @@ +#include + +AirGradient ag(DIY_BASIC); + +void failedHandler(String msg); + +void setup() { + Serial.begin(115200); + Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin()); + + if (ag.sht3x.begin(Wire, Serial) == false) { + failedHandler("SHT3x init failed"); + } +} + +void loop() { + float temp = ag.sht3x.getTemperature(); + if (temp <= -256.0f) { + Serial.println("Get temperature failed"); + } else { + Serial.printf("Get temperature: %f\r\n", temp); + } + + float hum = ag.sht3x.getRelativeHumidity(); + if (hum < 0) { + Serial.println("Get humidity failed"); + } else { + Serial.printf("Get humidity: %f\r\n", hum); + } + + delay(1000); +} + +void failedHandler(String msg) { + while (true) { + Serial.println(msg); + delay(1000); + } +} diff --git a/src/AirGradient.cpp b/src/AirGradient.cpp index 913b461..81743eb 100644 --- a/src/AirGradient.cpp +++ b/src/AirGradient.cpp @@ -3,9 +3,9 @@ #define AG_LIB_VER "3.0.0" AirGradient::AirGradient(BoardType type) - : pms5003(type), pms5003t_1(type), pms5003t_2(type), s8(type), sht(type), sgp41(type), - display(type), boardType(type), button(type), statusLed(type), - ledBar(type), watchdog(type) {} + : pms5003(type), pms5003t_1(type), pms5003t_2(type), s8(type), sht4x(type), + sht3x(type), sgp41(type), display(type), boardType(type), button(type), + statusLed(type), ledBar(type), watchdog(type) {} /** * @brief Get pin number for I2C SDA diff --git a/src/AirGradient.h b/src/AirGradient.h index a3fc632..8034f60 100644 --- a/src/AirGradient.h +++ b/src/AirGradient.h @@ -1,18 +1,23 @@ #ifndef _AIR_GRADIENT_H_ #define _AIR_GRADIENT_H_ -#include "bsp/BoardDef.h" -#include "bsp/LedBar.h" -#include "bsp/PushButton.h" -#include "bsp/StatusLed.h" -#include "bsp/HardwareWatchdog.h" -#include "co2/s8.h" #include "display/oled.h" -#include "pm/pms5003.h" -#include "pm/pms5003t.h" -#include "sgp/sgp41.h" +#include "main/BoardDef.h" +#include "main/HardwareWatchdog.h" +#include "main/LedBar.h" +#include "main/PushButton.h" +#include "main/StatusLed.h" +#include "pms/pms5003.h" +#include "pms/pms5003t.h" +#include "s8/s8.h" +#include "sgp41/sgp41.h" #include "sht/sht4x.h" +#include "sht/sht3x.h" +/** + * @brief Class with define all the sensor has supported by Airgradient. Each + * sensor usage must be init before use. + */ class AirGradient { public: AirGradient(BoardType type); @@ -21,7 +26,15 @@ public: * @brief Plantower PMS5003 sensor */ PMS5003 pms5003; + /** + * @brief Plantower PMS5003T sensor: connect to PM1 connector on + * OPEN_AIR_OUTDOOR. + */ PMS5003T pms5003t_1; + /** + * @brief Plantower PMS5003T sensor: connect to PM2 connector on + * OPEN_AIR_OUTDOOR. + */ PMS5003T pms5003t_2; /** @@ -30,18 +43,24 @@ public: S8 s8; /** - * @brief Temperature and humidity sensor + * @brief SHT41 Temperature and humidity sensor */ - Sht sht; + Sht4x sht4x; /** - * @brief TVOC and NOx sensor + * @brief SHT3x Temperature and humidity sensor + * + */ + Sht3x sht3x; + + /** + * @brief SGP41 TVOC and NOx sensor * */ Sgp41 sgp41; /** - * @brief Display + * @brief OLED Display * */ Display display; @@ -55,18 +74,43 @@ public: * @brief LED */ StatusLed statusLed; + + /** + * @brief RGB LED array + * + */ LedBar ledBar; /** - * @brief Hardware watchdog + * @brief External hardware watchdog */ HardwareWatchdog watchdog; + /** + * @brief Get I2C SDA pin has of board supported + * + * @return int Pin number if -1 invalid + */ int getI2cSdaPin(void); + /** + * @brief Get I2C SCL pin has of board supported + * + * @return int Pin number if -1 invalid + */ int getI2cSclPin(void); + /** + * @brief Get the Board Type + * + * @return BoardType @ref BoardType + */ BoardType getBoardType(void); + /** + * @brief Get the library version string + * + * @return String + */ String getVersion(void); private: diff --git a/src/display/oled.cpp b/src/display/oled.cpp index 9c52597..24d1a44 100644 --- a/src/display/oled.cpp +++ b/src/display/oled.cpp @@ -3,7 +3,7 @@ #include "../library/Adafruit_SSD1306_Wemos_OLED/Adafruit_SSD1306.h" #define disp(func) \ - if (this->_boardType == BOARD_DIY_BASIC_KIT) { \ + if (this->_boardType == DIY_BASIC) { \ ((Adafruit_SSD1306 *)(this->oled))->func; \ } else { \ ((Adafruit_SH110X *)(this->oled))->func; \ @@ -19,7 +19,18 @@ void Display::begin(TwoWire &wire, Stream &debugStream) { Display::Display(BoardType type) : _boardType(type) {} +/** + * @brief Initialize display, should be call this function before call of ther, + * if not it's always return failure. + * + * @param wire TwoWire instance, Must be initialized + */ void Display::begin(TwoWire &wire) { + if (_isBegin) { + AgLog("Initialized, call end() then try again"); + return; + } + this->_bsp = getBoardDef(this->_boardType); if ((this->_bsp == nullptr) || (this->_bsp->I2C.supported == false) || (this->_bsp->OLED.supported == false)) { @@ -28,67 +39,106 @@ void Display::begin(TwoWire &wire) { } /** Init OLED */ - if (this->_boardType == BOARD_DIY_BASIC_KIT) { + if (this->_boardType == DIY_BASIC) { AgLog("Init Adafruit_SSD1306"); Adafruit_SSD1306 *_oled = new Adafruit_SSD1306(); _oled->begin(wire, SSD1306_SWITCHCAPVCC, this->_bsp->OLED.addr); this->oled = _oled; } else { AgLog("Init Adafruit_SH1106G"); - Adafruit_SH1106G *_oled = new Adafruit_SH1106G(this->_bsp->OLED.width, this->_bsp->OLED.height, &wire); + Adafruit_SH1106G *_oled = new Adafruit_SH1106G( + this->_bsp->OLED.width, this->_bsp->OLED.height, &wire); _oled->begin(this->_bsp->OLED.addr, false); this->oled = _oled; } - this->_isInit = true; + this->_isBegin = true; disp(clearDisplay()); - AgLog("Init"); + AgLog("Initialize"); } +/** + * @brief Clear display buffer + * + */ void Display::clear(void) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } disp(clearDisplay()); } +/** + * @brief Invert display color + * + * @param i 0: black, other is white + */ void Display::invertDisplay(uint8_t i) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } disp(invertDisplay(i)); } +/** + * @brief Send display frame buffer to OLED + * + */ void Display::show() { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } disp(display()); } +/** + * @brief Set display contract + * + * @param value Contract (0;255); + */ void Display::setContrast(uint8_t value) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } disp(setContrast(value)); } +/** + * @brief Draw pixel into display frame buffer, call show to draw to + * display(OLED) + * + * @param x X Position + * @param y Y Position + * @param color Color (0: black, other white) + */ void Display::drawPixel(int16_t x, int16_t y, uint16_t color) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } disp(drawPixel(x, y, color)); } +/** + * @brief Set text size, it's scale default font instead of point to multiple + * font has define for special size + * + * @param size Size of text (default = 1) + */ void Display::setTextSize(int size) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } disp(setTextSize(size)); } +/** + * @brief Move draw cursor into new position + * + * @param x X Position + * @param y Y Position + */ void Display::setCursor(int16_t x, int16_t y) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } disp(setCursor(x, y)); @@ -100,7 +150,7 @@ void Display::setCursor(int16_t x, int16_t y) { * @param color 0:black, 1: While */ void Display::setTextColor(uint16_t color) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } disp(setTextColor(color)); @@ -113,66 +163,120 @@ void Display::setTextColor(uint16_t color) { * @param backGroundColor Text background color */ void Display::setTextColor(uint16_t foreGroundColor, uint16_t backGroundColor) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } disp(setTextColor(foreGroundColor, backGroundColor)); } +/** + * @brief Draw text to display framebuffer, call show() to draw to display + * (OLED) + * + * @param text String + */ void Display::setText(String text) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } disp(print(text)); } +/** + * @brief Draw bitmap into display framebuffer, call show() to draw to display + * (OLED) + * + * @param x X Position + * @param y Y Position + * @param bitmap Bitmap buffer + * @param w Bitmap width + * @param h Bitmap hight + * @param color Bitmap color + */ void Display::drawBitmap(int16_t x, int16_t y, const uint8_t bitmap[], int16_t w, int16_t h, uint16_t color) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } disp(drawBitmap(x, y, bitmap, w, h, color)); } +/** + * @brief Set text to display framebuffer, call show() to draw into to display + * (OLED) + * + * @param text Character buffer + */ void Display::setText(const char text[]) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } disp(print(text)); } +/** + * @brief Draw line to display framebuffer, call show() to draw to + * display(OLED) + * + * @param x0 Start X position + * @param y0 Start Y position + * @param x1 End X Position + * @param y1 End Y Position + * @param color Color (0: black, otherwise white) + */ void Display::drawLine(int x0, int y0, int x1, int y1, uint16_t color) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } disp(drawLine(x0, y0, x1, y1, color)); } +/** + * @brief Draw circle to display framebuffer, + * + * @param x + * @param y + * @param r + * @param color + */ void Display::drawCircle(int x, int y, int r, uint16_t color) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } disp(drawCircle(x, y, r, color)); } void Display::drawRect(int x0, int y0, int x1, int y1, uint16_t color) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } disp(drawRect(x0, y0, x1, y1, color)); } -bool Display::checkInit(void) { - if (this->_isInit) { +bool Display::isBegin(void) { + if (this->_isBegin) { return true; } - AgLog("OLED is not init"); + AgLog("Display not-initialized"); return false; } void Display::setRotation(uint8_t r) { - if (checkInit() == false) { + if (isBegin() == false) { return; } disp(setRotation(r)); } + +void Display::end(void) { + if (this->_isBegin == false) { + return; + } + _isBegin = false; + if (this->_boardType == DIY_BASIC) { + delete ((Adafruit_SSD1306 *)(this->oled)); + } else { + delete ((Adafruit_SH110X *)(this->oled)); + } + AgLog("De-initialize"); +} diff --git a/src/display/oled.h b/src/display/oled.h index 70739ee..56a9d8e 100644 --- a/src/display/oled.h +++ b/src/display/oled.h @@ -1,10 +1,14 @@ #ifndef _AIR_GRADIENT_OLED_H_ #define _AIR_GRADIENT_OLED_H_ -#include "../bsp/BoardDef.h" +#include "../main/BoardDef.h" #include #include +/** + * @brief The class define how to handle the OLED display on Airgradient has + * attached or support OLED display like: ONE-V9, Basic-V4 + */ class Display { public: const uint16_t COLOR_WHILTE = 1; @@ -15,10 +19,11 @@ public: #endif Display(BoardType type); void begin(TwoWire &wire); + void end(void); - void clear(void); // .clear + void clear(void); void invertDisplay(uint8_t i); - void show(); // .show() + void show(); void setContrast(uint8_t value); void drawPixel(int16_t x, int16_t y, uint16_t color); @@ -39,14 +44,14 @@ private: BoardType _boardType; const BoardDef *_bsp = nullptr; void *oled; - bool _isInit = false; + bool _isBegin = false; #if defined(ESP8266) const char *TAG = "oled"; Stream *_debugStream = nullptr; #else #endif - bool checkInit(void); + bool isBegin(void); }; #endif /** _AIR_GRADIENT_OLED_H_ */ diff --git a/src/library/arduino-i2c-sht3x/.clang-format b/src/library/arduino-i2c-sht3x/.clang-format new file mode 100644 index 0000000..047f2ad --- /dev/null +++ b/src/library/arduino-i2c-sht3x/.clang-format @@ -0,0 +1,14 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +IndentWidth: 4 +AlignAfterOpenBracket: Align +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +IndentCaseLabels: true +SpacesBeforeTrailingComments: 2 +PointerAlignment: Left +AlignEscapedNewlines: Left +ForEachMacros: ['TEST_GROUP', 'TEST'] +... diff --git a/src/library/arduino-i2c-sht3x/.gitlab-ci.yml b/src/library/arduino-i2c-sht3x/.gitlab-ci.yml new file mode 100644 index 0000000..1b859e0 --- /dev/null +++ b/src/library/arduino-i2c-sht3x/.gitlab-ci.yml @@ -0,0 +1,110 @@ +stages: + - validate + - test + +variables: + YQ_URL: https://github.com/mikefarah/yq/releases/download/v4.33.3/yq_linux_amd64 + +compile_test: + stage: test + image: + name: registry.gitlab.sensirion.lokal/sensirion/docker/docker-arduino:0.4.0 + tags: [docker, linux] + before_script: + - rm -rf ../sensirion-core-arduino-library + script: + - git clone --depth 1 --branch 0.5.2 https://github.com/Sensirion/arduino-core.git ../sensirion-core-arduino-library + - arduino-cli compile --libraries=".." --warnings all --fqbn arduino:samd:mkrzero ./examples/exampleUsageSingleShot/exampleUsageSingleShot.ino + - arduino-cli compile --libraries=".." --warnings all --fqbn arduino:avr:mega ./examples/exampleUsageSingleShot/exampleUsageSingleShot.ino + - arduino-cli compile --libraries=".." --warnings all --fqbn arduino:avr:nano ./examples/exampleUsageSingleShot/exampleUsageSingleShot.ino + - arduino-cli compile --libraries=".." --warnings all --fqbn arduino:avr:uno ./examples/exampleUsageSingleShot/exampleUsageSingleShot.ino + - arduino-cli compile --libraries=".." --warnings all --fqbn esp32:esp32:esp32 ./examples/exampleUsageSingleShot/exampleUsageSingleShot.ino + - arduino-cli compile --libraries=".." --warnings all --fqbn esp8266:esp8266:generic ./examples/exampleUsageSingleShot/exampleUsageSingleShot.ino + + - arduino-cli compile --libraries=".." --warnings all --fqbn arduino:samd:mkrzero ./examples/exampleUsage/exampleUsage.ino + - arduino-cli compile --libraries=".." --warnings all --fqbn arduino:avr:mega ./examples/exampleUsage/exampleUsage.ino + - arduino-cli compile --libraries=".." --warnings all --fqbn arduino:avr:nano ./examples/exampleUsage/exampleUsage.ino + - arduino-cli compile --libraries=".." --warnings all --fqbn arduino:avr:uno ./examples/exampleUsage/exampleUsage.ino + - arduino-cli compile --libraries=".." --warnings all --fqbn esp32:esp32:esp32 ./examples/exampleUsage/exampleUsage.ino + - arduino-cli compile --libraries=".." --warnings all --fqbn esp8266:esp8266:generic ./examples/exampleUsage/exampleUsage.ino + +arduino_lint: + stage: validate + image: + name: registry.gitlab.sensirion.lokal/mso-sw/drivers/docker-driver-generator:0.2.0 + tags: [linux, docker] + script: + - mkdir ~/arlint + - PATH=~/arlint:$PATH + - curl -fsSL https://raw.githubusercontent.com/arduino/arduino-lint/main/etc/install.sh | BINDIR=~/arlint sh + - arduino-lint --library-manager false + +syntax_check: + stage: validate + image: + name: registry.gitlab.sensirion.lokal/mso-sw/drivers/docker-driver-generator:0.2.0 + tags: [linux, docker] + script: + - find . -type f -iregex ".*\.\(c\|h\|cpp\|ino\)" -exec clang-format-6.0 -i -style=file {} \; && git diff --exit-code + + +cppcheck: + stage: validate + image: + name: registry.gitlab.sensirion.lokal/mso-sw/drivers/docker-driver-generator:0.2.0 + tags: [linux, docker] + script: + - cppcheck --std=c++11 --language=c++ --error-exitcode=1 --enable=warning,style,performance,portability --suppress=unreadVariable src/* + +TODO_check: + stage: validate + image: + name: registry.gitlab.sensirion.lokal/mso-sw/drivers/docker-driver-generator:0.2.0 + tags: [linux, docker] + script: + - '! grep -rnw --exclude=.gitlab-ci.yml --exclude-dir=.git . -e "TODO"' + +metadata_check: + stage: validate + image: + name: registry.gitlab.sensirion.lokal/mso-sw/drivers/docker-driver-generator:0.2.0 + tags: [linux, docker] + before_script: + - apt-get -qq update && apt-get -qq install -y wget + - if ! [ -d downloads/ ]; then mkdir downloads; fi + - if ! [ -e downloads/yq ]; then wget --no-verbose $YQ_URL -O downloads/yq; fi + - cp downloads/yq /usr/local/bin/yq && chmod +x /usr/local/bin/yq + script: + # check if metadata.yml exists + - > + if ! [ -f "metadata.yml" ]; then + echo "metadata.yml file not found" + exit 1 + fi + # check that dg_status is 'released' + - export DG_STATUS=$(yq ".dg_status.[]" ./metadata.yml) + - > + if [ $DG_STATUS != "released" ]; then + echo "dg_status in metadata.yml has to be 'released', not '$DG_STATUS'" + exit 1 + fi + # check that last_generated is not older than timestamp of last non-merge commit (+ 3 minutes) + - export IS_MANUALLY_MODIFIED=$(yq ".is_manually_modified" ./metadata.yml) + - > + if [ $IS_MANUALLY_MODIFIED = false ]; then + export LAST_GENERATED_TS=$(yq ".last_generated" ./metadata.yml) + export LAST_GENERATED_TS_EPOCH=$(date -d "$LAST_GENERATED_TS" +%s) + export LAST_NON_MERGE_COMMIT_TS=$(git log --format=%ad --date=iso-strict --no-merges -1) + export COMMIT_TS_EPOCH=$(date -d "$LAST_NON_MERGE_COMMIT_TS" +%s) + if [ $(($LAST_GENERATED_TS_EPOCH + 180)) -lt $COMMIT_TS_EPOCH ]; then + echo "'last_generated' timestamp in metadata.yml is older than commit timestamp ($LAST_GENERATED_TS vs $LAST_NON_MERGE_COMMIT_TS)" + exit 1 + fi + fi + # check that 'is_manually_modified' is set to true if commit is not from driver generator + - export LAST_NON_MERGE_COMMIT_AUTHOR=$(git log --format=%an --no-merges -1) + - > + if ! [ "$LAST_NON_MERGE_COMMIT_AUTHOR" = "Driver Generator 2" ] && [ "$IS_MANUALLY_MODIFIED" = false ]; then + echo "Last commit is not from Driver Generator. Please update 'is_manually_modified' in metadata.yml" + exit 1 + fi \ No newline at end of file diff --git a/src/library/arduino-i2c-sht3x/CHANGELOG.md b/src/library/arduino-i2c-sht3x/CHANGELOG.md new file mode 100644 index 0000000..5baa52d --- /dev/null +++ b/src/library/arduino-i2c-sht3x/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## [Unreleased] + +## [1.0.0] - 2023-10-27 + +Initial release + diff --git a/src/library/arduino-i2c-sht3x/LICENSE b/src/library/arduino-i2c-sht3x/LICENSE new file mode 100644 index 0000000..f95f5fe --- /dev/null +++ b/src/library/arduino-i2c-sht3x/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2023, Sensirion AG +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/library/arduino-i2c-sht3x/README.md b/src/library/arduino-i2c-sht3x/README.md new file mode 100644 index 0000000..b77edaa --- /dev/null +++ b/src/library/arduino-i2c-sht3x/README.md @@ -0,0 +1,217 @@ +# Sensirion I²C SHT3X Arduino Library + +This is the Sensirion SHT3X library for Arduino allowing you to +communicate with a sensor of the SHT3X family over I²C. + + + +Click [here](https://sensirion.com/products/catalog/SHT30-DIS-B) to learn more about the Sensirion SHT3X sensor family. + + +Not all sensors of this driver family support all measurements. +In case a measurement is not supported by all sensors, the products that +support it are listed in the API description. + + + +## Supported sensor types + +| Sensor name | I²C Addresses | +| ------------- | -------------- | +|[SHT30A](https://sensirion.com/products/catalog/SHT30A-DIS-B)| **0x44**, 0x45| +|[SHT30](https://sensirion.com/products/catalog/SHT30-DIS-B)| **0x44**, 0x45| +|[SHT31A](https://sensirion.com/products/catalog/SHT31A-DIS-B)| **0x44**, 0x45| +|[SHT31](https://sensirion.com/products/catalog/SHT31-DIS-B)| **0x44**, 0x45| +|[SHT33](https://sensirion.com/products/catalog/SHT33-DIS)| **0x44**, 0x45| +|[SHT35A](https://sensirion.com/products/catalog/SHT35A-DIS-B)| **0x44**, 0x45| +|[SHT35](https://sensirion.com/products/catalog/SHT35-DIS-B)| **0x44**, 0x45| +|[SHT85](https://sensirion.com/sht85)| **0x44** | + +The following instructions and examples use a *SHT30*. + + + +## Installation of the library + +This library can be installed using the Arduino Library manager: +Start the [Arduino IDE](http://www.arduino.cc/en/main/software) and open +the Library Manager via + +`Sketch` ➔ `Include Library` ➔ `Manage Libraries...` + +Search for the `Sensirion I2C SHT3X` library in the `Filter +your search...` field and install it by clicking the `install` button. + +If you cannot find it in the library manager, download the latest release as .zip file +and add it to your [Arduino IDE](http://www.arduino.cc/en/main/software) via + +`Sketch` ➔ `Include Library` ➔ `Add .ZIP Library...` + +Don't forget to **install the dependencies** listed below the same way via library +manager or `Add .ZIP Library` + +#### Dependencies +* [Sensirion Core](https://github.com/Sensirion/arduino-core) + +## Sensor wiring + +Use the following pin description to connect your SHT3X to the standard I²C bus of your Arduino board: + + + +| *Pin* | *Cable Color* | *Name* | *Description* | *Comments* | +|-------|---------------|:------:|----------------|------------| +| 1 | green | SDA | I2C: Serial data input / output | +| 2 | black | GND | Ground | +| 3 | yellow | SCL | I2C: Serial clock input | +| 4 | red | VDD | Supply Voltage | 2.15V to 5.5V + + + + +The recommended voltage is 3.3V. + +### Board specific wiring +You will find pinout schematics for recommended board models below: + + + +
Arduino Uno +

+ +| *SHT3X* | *SHT3X Pin* | *Cable Color* | *Board Pin* | +| :---: | --- | --- | --- | +| SDA | 1 | green | D18/SDA | +| GND | 2 | black | GND | +| SCL | 3 | yellow | D19/SCL | +| VDD | 4 | red | 3.3V | + + + + +

+
+ + + +
Arduino Nano +

+ +| *SHT3X* | *SHT3X Pin* | *Cable Color* | *Board Pin* | +| :---: | --- | --- | --- | +| SDA | 1 | green | A4 | +| GND | 2 | black | GND | +| SCL | 3 | yellow | A5 | +| VDD | 4 | red | 3.3V | + + + + +

+
+ + + +
Arduino Micro +

+ +| *SHT3X* | *SHT3X Pin* | *Cable Color* | *Board Pin* | +| :---: | --- | --- | --- | +| SDA | 1 | green | D2/SDA | +| GND | 2 | black | GND | +| SCL | 3 | yellow | ~D3/SCL | +| VDD | 4 | red | 3.3V | + + + + +

+
+ + + +
Arduino Mega 2560 +

+ +| *SHT3X* | *SHT3X Pin* | *Cable Color* | *Board Pin* | +| :---: | --- | --- | --- | +| SDA | 1 | green | D20/SDA | +| GND | 2 | black | GND | +| SCL | 3 | yellow | D21/SCL | +| VDD | 4 | red | 3.3V | + + + + +

+
+ + + +
ESP32 DevKitC +

+ +| *SHT3X* | *SHT3X Pin* | *Cable Color* | *Board Pin* | +| :---: | --- | --- | --- | +| SDA | 1 | green | GPIO 21 | +| GND | 2 | black | GND | +| SCL | 3 | yellow | GPIO 22 | +| VDD | 4 | red | 3V3 | + + + + +

+
+ + +## Quick Start + +1. Install the libraries and dependencies according to [Installation of the library](#installation-of-the-library) + +2. Connect the SHT3X sensor to your Arduino as explained in [Sensor wiring](#sensor-wiring) + +3. Open the `exampleUsage` sample project within the Arduino IDE: + + `File` ➔ `Examples` ➔ `Sensirion I2C SHT3X` ➔ `exampleUsage` + + + The provided example is working with a SHT30, I²C address 0x44. + In order to use the code with another product or I²C address you need to change it in the code of `exampleUsage`. + You find the list with pre-defined addresses in `src/SensirionI2CSht3x.h`. + + +5. Click the `Upload` button in the Arduino IDE or `Sketch` ➔ `Upload` + +4. When the upload process has finished, open the `Serial Monitor` or `Serial + Plotter` via the `Tools` menu to observe the measurement values. Note that + the `Baud Rate` in the used tool has to be set to `115200 baud`. + +## Contributing + +**Contributions are welcome!** + +We develop and test this driver using our company internal tools (version +control, continuous integration, code review etc.) and automatically +synchronize the master branch with GitHub. But this doesn't mean that we don't +respond to issues or don't accept pull requests on GitHub. In fact, you're very +welcome to open issues or create pull requests :) + +This Sensirion library uses +[`clang-format`](https://releases.llvm.org/download.html) to standardize the +formatting of all our `.cpp` and `.h` files. Make sure your contributions are +formatted accordingly: + +The `-i` flag will apply the format changes to the files listed. + +```bash +clang-format -i src/*.cpp src/*.h +``` + +Note that differences from this formatting will result in a failed build until +they are fixed. + + +## License + +See [LICENSE](LICENSE). diff --git a/src/library/arduino-i2c-sht3x/examples/exampleUsage/exampleUsage.ino b/src/library/arduino-i2c-sht3x/examples/exampleUsage/exampleUsage.ino new file mode 100644 index 0000000..06a6945 --- /dev/null +++ b/src/library/arduino-i2c-sht3x/examples/exampleUsage/exampleUsage.ino @@ -0,0 +1,98 @@ +/* + * THIS FILE IS AUTOMATICALLY GENERATED + * + * Generator: sensirion-driver-generator 0.33.0 + * Product: sht3x + * Model-Version: 1.0.0 + */ +/* + * Copyright (c) 2023, Sensirion AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Sensirion AG nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include + +SensirionI2cSht3x sensor; + +static char errorMessage[64]; +static int16_t error; + +void setup() { + + Serial.begin(115200); + while (!Serial) { + delay(100); + } + Wire.begin(); + sensor.begin(Wire, SHT30_I2C_ADDR_44); + + sensor.stopMeasurement(); + delay(1); + sensor.softReset(); + delay(100); + uint16_t aStatusRegister = 0u; + error = sensor.readStatusRegister(aStatusRegister); + if (error != NO_ERROR) { + Serial.print("Error trying to execute readStatusRegister(): "); + errorToString(error, errorMessage, sizeof errorMessage); + Serial.println(errorMessage); + return; + } + Serial.print("aStatusRegister: "); + Serial.print(aStatusRegister); + Serial.println(); + error = sensor.startPeriodicMeasurement(REPEATABILITY_MEDIUM, + MPS_ONE_PER_SECOND); + if (error != NO_ERROR) { + Serial.print("Error trying to execute startPeriodicMeasurement(): "); + errorToString(error, errorMessage, sizeof errorMessage); + Serial.println(errorMessage); + return; + } +} + +void loop() { + + float aTemperature = 0.0; + float aHumidity = 0.0; + error = sensor.blockingReadMeasurement(aTemperature, aHumidity); + if (error != NO_ERROR) { + Serial.print("Error trying to execute blockingReadMeasurement(): "); + errorToString(error, errorMessage, sizeof errorMessage); + Serial.println(errorMessage); + return; + } + Serial.print("aTemperature: "); + Serial.print(aTemperature); + Serial.print("\t"); + Serial.print("aHumidity: "); + Serial.print(aHumidity); + Serial.println(); +} diff --git a/src/library/arduino-i2c-sht3x/examples/exampleUsageSingleShot/exampleUsageSingleShot.ino b/src/library/arduino-i2c-sht3x/examples/exampleUsageSingleShot/exampleUsageSingleShot.ino new file mode 100644 index 0000000..0d65fc5 --- /dev/null +++ b/src/library/arduino-i2c-sht3x/examples/exampleUsageSingleShot/exampleUsageSingleShot.ino @@ -0,0 +1,91 @@ +/* + * THIS FILE IS AUTOMATICALLY GENERATED + * + * Generator: sensirion-driver-generator 0.33.0 + * Product: sht3x + * Model-Version: 1.0.0 + */ +/* + * Copyright (c) 2023, Sensirion AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Sensirion AG nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include + +SensirionI2cSht3x sensor; + +static char errorMessage[64]; +static int16_t error; + +void setup() { + + Serial.begin(115200); + while (!Serial) { + delay(100); + } + Wire.begin(); + sensor.begin(Wire, SHT30_I2C_ADDR_44); + + sensor.stopMeasurement(); + delay(1); + sensor.softReset(); + delay(100); + uint16_t aStatusRegister = 0u; + error = sensor.readStatusRegister(aStatusRegister); + if (error != NO_ERROR) { + Serial.print("Error trying to execute readStatusRegister(): "); + errorToString(error, errorMessage, sizeof errorMessage); + Serial.println(errorMessage); + return; + } + Serial.print("aStatusRegister: "); + Serial.print(aStatusRegister); + Serial.println(); +} + +void loop() { + + float aTemperature = 0.0; + float aHumidity = 0.0; + error = sensor.measureSingleShot(REPEATABILITY_MEDIUM, false, aTemperature, + aHumidity); + if (error != NO_ERROR) { + Serial.print("Error trying to execute measureSingleShot(): "); + errorToString(error, errorMessage, sizeof errorMessage); + Serial.println(errorMessage); + return; + } + Serial.print("aTemperature: "); + Serial.print(aTemperature); + Serial.print("\t"); + Serial.print("aHumidity: "); + Serial.print(aHumidity); + Serial.println(); +} diff --git a/src/library/arduino-i2c-sht3x/images/Arduino-Mega-2560-Rev3-i2c-pinout-3.3V.png b/src/library/arduino-i2c-sht3x/images/Arduino-Mega-2560-Rev3-i2c-pinout-3.3V.png new file mode 100644 index 0000000..942173d Binary files /dev/null and b/src/library/arduino-i2c-sht3x/images/Arduino-Mega-2560-Rev3-i2c-pinout-3.3V.png differ diff --git a/src/library/arduino-i2c-sht3x/images/Arduino-Micro-i2c-pinout-3.3V.png b/src/library/arduino-i2c-sht3x/images/Arduino-Micro-i2c-pinout-3.3V.png new file mode 100644 index 0000000..ca9ba6c Binary files /dev/null and b/src/library/arduino-i2c-sht3x/images/Arduino-Micro-i2c-pinout-3.3V.png differ diff --git a/src/library/arduino-i2c-sht3x/images/Arduino-Nano-i2c-pinout-3.3V.png b/src/library/arduino-i2c-sht3x/images/Arduino-Nano-i2c-pinout-3.3V.png new file mode 100644 index 0000000..55fd2ff Binary files /dev/null and b/src/library/arduino-i2c-sht3x/images/Arduino-Nano-i2c-pinout-3.3V.png differ diff --git a/src/library/arduino-i2c-sht3x/images/Arduino-Uno-Rev3-i2c-pinout-3.3V.png b/src/library/arduino-i2c-sht3x/images/Arduino-Uno-Rev3-i2c-pinout-3.3V.png new file mode 100644 index 0000000..a6daad7 Binary files /dev/null and b/src/library/arduino-i2c-sht3x/images/Arduino-Uno-Rev3-i2c-pinout-3.3V.png differ diff --git a/src/library/arduino-i2c-sht3x/images/SHT3x.png b/src/library/arduino-i2c-sht3x/images/SHT3x.png new file mode 100644 index 0000000..795f29d Binary files /dev/null and b/src/library/arduino-i2c-sht3x/images/SHT3x.png differ diff --git a/src/library/arduino-i2c-sht3x/images/SHT3x_pinout.png b/src/library/arduino-i2c-sht3x/images/SHT3x_pinout.png new file mode 100644 index 0000000..d8b293b Binary files /dev/null and b/src/library/arduino-i2c-sht3x/images/SHT3x_pinout.png differ diff --git a/src/library/arduino-i2c-sht3x/images/esp32-devkitc-i2c-pinout-3.3V.png b/src/library/arduino-i2c-sht3x/images/esp32-devkitc-i2c-pinout-3.3V.png new file mode 100644 index 0000000..319b021 Binary files /dev/null and b/src/library/arduino-i2c-sht3x/images/esp32-devkitc-i2c-pinout-3.3V.png differ diff --git a/src/library/arduino-i2c-sht3x/keywords.txt b/src/library/arduino-i2c-sht3x/keywords.txt new file mode 100644 index 0000000..388e59e --- /dev/null +++ b/src/library/arduino-i2c-sht3x/keywords.txt @@ -0,0 +1,59 @@ +####################################### +# Syntax Coloring Map +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +SensirionI2cSht3x KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +measureSingleShot KEYWORD2 +startPeriodicMeasurement KEYWORD2 +blockingReadMeasurement KEYWORD2 +readStatusRegister KEYWORD2 +measureSingleShotHighRepeatability KEYWORD2 +measureSingleShotHighRepeatabilityClockStretching KEYWORD2 +measureSingleShotMediumRepeatability KEYWORD2 +measureSingleShotMediumRepeatabilityClockStretching KEYWORD2 +measureSingleShotLowRepeatability KEYWORD2 +measureSingleShotLowRepeatabilityClockStretching KEYWORD2 +startMeasurement05MpsHighRepeatability KEYWORD2 +startMeasurement05MpsMediumRepeatability KEYWORD2 +startMeasurement05MpsLowRepeatability KEYWORD2 +startMeasurement1MpsHighRepeatability KEYWORD2 +startMeasurement1MpsMediumRepeatability KEYWORD2 +startMeasurement1MpsLowRepeatability KEYWORD2 +startMeasurement2MpsHighRepeatability KEYWORD2 +startMeasurement2MpsMediumRepeatability KEYWORD2 +startMeasurement2MpsLowRepeatability KEYWORD2 +startMeasurement4MpsHighRepeatability KEYWORD2 +startMeasurement4MpsMediumRepeatability KEYWORD2 +startMeasurement4MpsLowRepeatability KEYWORD2 +startMeasurement10MpsHighRepeatability KEYWORD2 +startMeasurement10MpsMediumRepeatability KEYWORD2 +startMeasurement10MpsLowRepeatability KEYWORD2 +startArtMeasurement KEYWORD2 +readMeasurement KEYWORD2 +stopMeasurement KEYWORD2 +enableHeater KEYWORD2 +disableHeater KEYWORD2 +llreadStatusRegister KEYWORD2 +clearStatusRegister KEYWORD2 +softReset KEYWORD2 +signalTemperature KEYWORD2 +signalHumidity KEYWORD2 + +####################################### +# Instances (KEYWORD2) +####################################### + +sensor KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### \ No newline at end of file diff --git a/src/library/arduino-i2c-sht3x/library.properties b/src/library/arduino-i2c-sht3x/library.properties new file mode 100644 index 0000000..0a86b30 --- /dev/null +++ b/src/library/arduino-i2c-sht3x/library.properties @@ -0,0 +1,11 @@ +name=SensirionI2cSht3x +version=1.0.0 +author=Sensirion +maintainer=Sensirion +sentence=Library for the SHT3X sensor family by Sensirion +paragraph=Enables you to use the SHT3X sensor family via I2C. +url=https://github.com/Sensirion/arduino-i2c-sht3x +category=Sensors +architectures=* +depends=Sensirion Core +includes=SensirionI2cSht3x.h diff --git a/src/library/arduino-i2c-sht3x/metadata.yml b/src/library/arduino-i2c-sht3x/metadata.yml new file mode 100644 index 0000000..c438e43 --- /dev/null +++ b/src/library/arduino-i2c-sht3x/metadata.yml @@ -0,0 +1,7 @@ +generator_version: 0.33.0 +model_version: 1.0.0 +dg_status: +- released +is_manually_modified: true +first_generated: '2023-10-27 14:38' +last_generated: '2023-10-27 14:38' diff --git a/src/library/arduino-i2c-sht3x/src/SensirionI2cSht3x.cpp b/src/library/arduino-i2c-sht3x/src/SensirionI2cSht3x.cpp new file mode 100644 index 0000000..8d7d6fe --- /dev/null +++ b/src/library/arduino-i2c-sht3x/src/SensirionI2cSht3x.cpp @@ -0,0 +1,720 @@ +/* + * THIS FILE IS AUTOMATICALLY GENERATED + * + * Generator: sensirion-driver-generator 0.33.0 + * Product: sht3x + * Model-Version: 1.0.0 + */ +/* + * Copyright (c) 2023, Sensirion AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Sensirion AG nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "SensirionI2cSht3x.h" +#include + +static uint8_t communication_buffer[6] = {0}; + +SensirionI2cSht3x::SensirionI2cSht3x() { +} + +float SensirionI2cSht3x::signalTemperature(uint16_t temperatureTicks) { + float temperature = 0.0; + temperature = (float)(temperatureTicks); + temperature = -45 + ((temperature * 175.0) / 65535.0); + return temperature; +} + +float SensirionI2cSht3x::signalHumidity(uint16_t humidityTicks) { + float humidity = 0.0; + humidity = (float)(humidityTicks); + humidity = (100 * humidity) / 65535.0; + return humidity; +} + +int16_t +SensirionI2cSht3x::measureSingleShot(Repeatability measurementRepeatability, + bool isClockStretching, + float& aTemperature, float& aHumidity) { + uint16_t rawTemp = 0; + uint16_t rawHumi = 0; + int16_t localError = 0; + if (isClockStretching) { + if (measurementRepeatability == REPEATABILITY_HIGH) { + localError = measureSingleShotHighRepeatabilityClockStretching( + rawTemp, rawHumi); + if (localError != NO_ERROR) { + return localError; + } + } else if (measurementRepeatability == REPEATABILITY_MEDIUM) { + localError = measureSingleShotMediumRepeatabilityClockStretching( + rawTemp, rawHumi); + if (localError != NO_ERROR) { + return localError; + } + } else if (measurementRepeatability == REPEATABILITY_LOW) { + localError = measureSingleShotLowRepeatabilityClockStretching( + rawTemp, rawHumi); + if (localError != NO_ERROR) { + return localError; + } + } + } else if (measurementRepeatability == REPEATABILITY_HIGH) { + localError = measureSingleShotHighRepeatability(rawTemp, rawHumi); + if (localError != NO_ERROR) { + return localError; + } + } else if (measurementRepeatability == REPEATABILITY_MEDIUM) { + localError = measureSingleShotMediumRepeatability(rawTemp, rawHumi); + if (localError != NO_ERROR) { + return localError; + } + } else if (measurementRepeatability == REPEATABILITY_LOW) { + localError = measureSingleShotLowRepeatability(rawTemp, rawHumi); + if (localError != NO_ERROR) { + return localError; + } + } + aTemperature = signalTemperature(rawTemp); + aHumidity = signalHumidity(rawHumi); + return localError; +} + +int16_t SensirionI2cSht3x::startPeriodicMeasurement( + Repeatability measurementRepeatability, Mps messagesPerSecond) { + int16_t localError = 0; + if (messagesPerSecond == MPS_EVERY_TWO_SECONDS) { + if (measurementRepeatability == REPEATABILITY_HIGH) { + localError = startMeasurement05MpsHighRepeatability(); + if (localError != NO_ERROR) { + return localError; + } + } else if (measurementRepeatability == REPEATABILITY_MEDIUM) { + localError = startMeasurement05MpsMediumRepeatability(); + if (localError != NO_ERROR) { + return localError; + } + } else if (measurementRepeatability == REPEATABILITY_LOW) { + localError = startMeasurement05MpsLowRepeatability(); + if (localError != NO_ERROR) { + return localError; + } + } + } else if (messagesPerSecond == MPS_ONE_PER_SECOND) { + if (measurementRepeatability == REPEATABILITY_HIGH) { + localError = startMeasurement1MpsHighRepeatability(); + if (localError != NO_ERROR) { + return localError; + } + } else if (measurementRepeatability == REPEATABILITY_MEDIUM) { + localError = startMeasurement1MpsMediumRepeatability(); + if (localError != NO_ERROR) { + return localError; + } + } else if (measurementRepeatability == REPEATABILITY_LOW) { + localError = startMeasurement1MpsLowRepeatability(); + if (localError != NO_ERROR) { + return localError; + } + } + } else if (messagesPerSecond == MPS_TWO_PER_SECOND) { + if (measurementRepeatability == REPEATABILITY_HIGH) { + localError = startMeasurement2MpsHighRepeatability(); + if (localError != NO_ERROR) { + return localError; + } + } else if (measurementRepeatability == REPEATABILITY_MEDIUM) { + localError = startMeasurement2MpsMediumRepeatability(); + if (localError != NO_ERROR) { + return localError; + } + } else if (measurementRepeatability == REPEATABILITY_LOW) { + localError = startMeasurement2MpsLowRepeatability(); + if (localError != NO_ERROR) { + return localError; + } + } + } else if (messagesPerSecond == MPS_FOUR_PER_SECOND) { + if (measurementRepeatability == REPEATABILITY_HIGH) { + localError = startMeasurement4MpsHighRepeatability(); + if (localError != NO_ERROR) { + return localError; + } + } else if (measurementRepeatability == REPEATABILITY_MEDIUM) { + localError = startMeasurement4MpsMediumRepeatability(); + if (localError != NO_ERROR) { + return localError; + } + } else if (measurementRepeatability == REPEATABILITY_LOW) { + localError = startMeasurement4MpsLowRepeatability(); + if (localError != NO_ERROR) { + return localError; + } + } + } else if (messagesPerSecond == MPS_TEN_PER_SECOND) { + if (measurementRepeatability == REPEATABILITY_HIGH) { + localError = startMeasurement10MpsHighRepeatability(); + if (localError != NO_ERROR) { + return localError; + } + } else if (measurementRepeatability == REPEATABILITY_MEDIUM) { + localError = startMeasurement10MpsMediumRepeatability(); + if (localError != NO_ERROR) { + return localError; + } + } else if (measurementRepeatability == REPEATABILITY_LOW) { + localError = startMeasurement10MpsLowRepeatability(); + if (localError != NO_ERROR) { + return localError; + } + } + } + return localError; +} + +int16_t SensirionI2cSht3x::blockingReadMeasurement(float& aTemperature, + float& aHumidity) { + uint16_t status = 0u; + uint16_t dataReadyFlag = 0u; + uint16_t rawTemp = 0; + uint16_t rawHumi = 0; + int16_t localError = 0; + localError = llreadStatusRegister(status); + if (localError != NO_ERROR) { + return localError; + } + dataReadyFlag = (status >> 6) & 15; + while (dataReadyFlag == 0) { + delay(100); + localError = llreadStatusRegister(status); + if (localError != NO_ERROR) { + return localError; + } + dataReadyFlag = (status >> 6) & 15; + } + localError = readMeasurement(rawTemp, rawHumi); + if (localError != NO_ERROR) { + return localError; + } + aTemperature = signalTemperature(rawTemp); + aHumidity = signalHumidity(rawHumi); + return localError; +} + +int16_t SensirionI2cSht3x::readStatusRegister(uint16_t& aStatusRegister) { + uint16_t status = 0u; + int16_t localError = 0; + localError = llreadStatusRegister(status); + if (localError != NO_ERROR) { + return localError; + } + aStatusRegister = static_cast(status); + + return localError; +} + +int16_t SensirionI2cSht3x::measureSingleShotHighRepeatability( + uint16_t& temperatureTicks, uint16_t& humidityTicks) { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2400, buffer_ptr, 6); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(16); + SensirionI2CRxFrame rxFrame(buffer_ptr, 6); + localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 6, + rxFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + localError |= rxFrame.getUInt16(temperatureTicks); + localError |= rxFrame.getUInt16(humidityTicks); + return localError; +} + +int16_t SensirionI2cSht3x::measureSingleShotHighRepeatabilityClockStretching( + uint16_t& temperatureTicks, uint16_t& humidityTicks) { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2c06, buffer_ptr, 6); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(16); + SensirionI2CRxFrame rxFrame(buffer_ptr, 6); + localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 6, + rxFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + localError |= rxFrame.getUInt16(temperatureTicks); + localError |= rxFrame.getUInt16(humidityTicks); + return localError; +} + +int16_t SensirionI2cSht3x::measureSingleShotMediumRepeatability( + uint16_t& temperatureTicks, uint16_t& humidityTicks) { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x240b, buffer_ptr, 6); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(7); + SensirionI2CRxFrame rxFrame(buffer_ptr, 6); + localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 6, + rxFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + localError |= rxFrame.getUInt16(temperatureTicks); + localError |= rxFrame.getUInt16(humidityTicks); + return localError; +} + +int16_t SensirionI2cSht3x::measureSingleShotMediumRepeatabilityClockStretching( + uint16_t& temperatureTicks, uint16_t& humidityTicks) { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2c0d, buffer_ptr, 6); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(7); + SensirionI2CRxFrame rxFrame(buffer_ptr, 6); + localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 6, + rxFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + localError |= rxFrame.getUInt16(temperatureTicks); + localError |= rxFrame.getUInt16(humidityTicks); + return localError; +} + +int16_t +SensirionI2cSht3x::measureSingleShotLowRepeatability(uint16_t& temperatureTicks, + uint16_t& humidityTicks) { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2416, buffer_ptr, 6); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(5); + SensirionI2CRxFrame rxFrame(buffer_ptr, 6); + localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 6, + rxFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + localError |= rxFrame.getUInt16(temperatureTicks); + localError |= rxFrame.getUInt16(humidityTicks); + return localError; +} + +int16_t SensirionI2cSht3x::measureSingleShotLowRepeatabilityClockStretching( + uint16_t& temperatureTicks, uint16_t& humidityTicks) { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2c10, buffer_ptr, 6); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(5); + SensirionI2CRxFrame rxFrame(buffer_ptr, 6); + localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 6, + rxFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + localError |= rxFrame.getUInt16(temperatureTicks); + localError |= rxFrame.getUInt16(humidityTicks); + return localError; +} + +int16_t SensirionI2cSht3x::startMeasurement05MpsHighRepeatability() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2032, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(16); + return localError; +} + +int16_t SensirionI2cSht3x::startMeasurement05MpsMediumRepeatability() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2024, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(7); + return localError; +} + +int16_t SensirionI2cSht3x::startMeasurement05MpsLowRepeatability() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x202f, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(5); + return localError; +} + +int16_t SensirionI2cSht3x::startMeasurement1MpsHighRepeatability() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2130, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(16); + return localError; +} + +int16_t SensirionI2cSht3x::startMeasurement1MpsMediumRepeatability() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2126, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(7); + return localError; +} + +int16_t SensirionI2cSht3x::startMeasurement1MpsLowRepeatability() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x212d, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(5); + return localError; +} + +int16_t SensirionI2cSht3x::startMeasurement2MpsHighRepeatability() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2236, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(16); + return localError; +} + +int16_t SensirionI2cSht3x::startMeasurement2MpsMediumRepeatability() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2220, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(7); + return localError; +} + +int16_t SensirionI2cSht3x::startMeasurement2MpsLowRepeatability() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x222b, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(5); + return localError; +} + +int16_t SensirionI2cSht3x::startMeasurement4MpsHighRepeatability() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2334, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(16); + return localError; +} + +int16_t SensirionI2cSht3x::startMeasurement4MpsMediumRepeatability() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2322, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(7); + return localError; +} + +int16_t SensirionI2cSht3x::startMeasurement4MpsLowRepeatability() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2329, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(5); + return localError; +} + +int16_t SensirionI2cSht3x::startMeasurement10MpsHighRepeatability() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2737, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(16); + return localError; +} + +int16_t SensirionI2cSht3x::startMeasurement10MpsMediumRepeatability() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2721, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(7); + return localError; +} + +int16_t SensirionI2cSht3x::startMeasurement10MpsLowRepeatability() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x273a, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(5); + return localError; +} + +int16_t SensirionI2cSht3x::startArtMeasurement() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x2b32, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + return localError; +} + +int16_t SensirionI2cSht3x::readMeasurement(uint16_t& temperatureTicks, + uint16_t& humidityTicks) { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0xe000, buffer_ptr, 6); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + SensirionI2CRxFrame rxFrame(buffer_ptr, 6); + localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 6, + rxFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + localError |= rxFrame.getUInt16(temperatureTicks); + localError |= rxFrame.getUInt16(humidityTicks); + return localError; +} + +int16_t SensirionI2cSht3x::stopMeasurement() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x3093, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(1); + return localError; +} + +int16_t SensirionI2cSht3x::enableHeater() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x306d, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(10); + return localError; +} + +int16_t SensirionI2cSht3x::disableHeater() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x3066, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(10); + return localError; +} + +int16_t SensirionI2cSht3x::llreadStatusRegister(uint16_t& statusRegister) { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0xf32d, buffer_ptr, 3); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(10); + SensirionI2CRxFrame rxFrame(buffer_ptr, 3); + localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3, + rxFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + localError |= rxFrame.getUInt16(statusRegister); + return localError; +} + +int16_t SensirionI2cSht3x::clearStatusRegister() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x3041, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(10); + return localError; +} + +int16_t SensirionI2cSht3x::softReset() { + int16_t localError = NO_ERROR; + uint8_t* buffer_ptr = communication_buffer; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x30a2, buffer_ptr, 2); + localError = + SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus); + if (localError != NO_ERROR) { + return localError; + } + delay(2); + return localError; +} + +void SensirionI2cSht3x::begin(TwoWire& i2cBus, uint8_t i2cAddress) { + _i2cBus = &i2cBus; + _i2cAddress = i2cAddress; +} diff --git a/src/library/arduino-i2c-sht3x/src/SensirionI2cSht3x.h b/src/library/arduino-i2c-sht3x/src/SensirionI2cSht3x.h new file mode 100644 index 0000000..e9ff789 --- /dev/null +++ b/src/library/arduino-i2c-sht3x/src/SensirionI2cSht3x.h @@ -0,0 +1,548 @@ +/* + * THIS FILE IS AUTOMATICALLY GENERATED + * + * Generator: sensirion-driver-generator 0.33.0 + * Product: sht3x + * Model-Version: 1.0.0 + */ +/* + * Copyright (c) 2023, Sensirion AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Sensirion AG nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SENSIRIONI2CSHT3X_H +#define SENSIRIONI2CSHT3X_H + +#include "../../SensirionCore/src/SensirionCore.h" +#include + +#define NO_ERROR 0 +#define SHT30A_I2C_ADDR_44 0x44 +#define SHT30A_I2C_ADDR_45 0x45 +#define SHT30_I2C_ADDR_44 0x44 +#define SHT30_I2C_ADDR_45 0x45 +#define SHT31A_I2C_ADDR_44 0x44 +#define SHT31A_I2C_ADDR_45 0x45 +#define SHT31_I2C_ADDR_44 0x44 +#define SHT31_I2C_ADDR_45 0x45 +#define SHT33_I2C_ADDR_44 0x44 +#define SHT33_I2C_ADDR_45 0x45 +#define SHT35A_I2C_ADDR_44 0x44 +#define SHT35A_I2C_ADDR_45 0x45 +#define SHT35_I2C_ADDR_44 0x44 +#define SHT35_I2C_ADDR_45 0x45 + +typedef enum { + MEASURE_SINGLE_SHOT_HIGH_REPEATABILITY_CMD_ID = 0x2400, + MEASURE_SINGLE_SHOT_HIGH_REPEATABILITY_CLOCK_STRETCHING_CMD_ID = 0x2c06, + MEASURE_SINGLE_SHOT_MEDIUM_REPEATABILITY_CMD_ID = 0x240b, + MEASURE_SINGLE_SHOT_MEDIUM_REPEATABILITY_CLOCK_STRETCHING_CMD_ID = 0x2c0d, + MEASURE_SINGLE_SHOT_LOW_REPEATABILITY_CMD_ID = 0x2416, + MEASURE_SINGLE_SHOT_LOW_REPEATABILITY_CLOCK_STRETCHING_CMD_ID = 0x2c10, + START_MEASUREMENT_0_5_MPS_HIGH_REPEATABILITY_CMD_ID = 0x2032, + START_MEASUREMENT_0_5_MPS_MEDIUM_REPEATABILITY_CMD_ID = 0x2024, + START_MEASUREMENT_0_5_MPS_LOW_REPEATABILITY_CMD_ID = 0x202f, + START_MEASUREMENT_1_MPS_HIGH_REPEATABILITY_CMD_ID = 0x2130, + START_MEASUREMENT_1_MPS_MEDIUM_REPEATABILITY_CMD_ID = 0x2126, + START_MEASUREMENT_1_MPS_LOW_REPEATABILITY_CMD_ID = 0x212d, + START_MEASUREMENT_2_MPS_HIGH_REPEATABILITY_CMD_ID = 0x2236, + START_MEASUREMENT_2_MPS_MEDIUM_REPEATABILITY_CMD_ID = 0x2220, + START_MEASUREMENT_2_MPS_LOW_REPEATABILITY_CMD_ID = 0x222b, + START_MEASUREMENT_4_MPS_HIGH_REPEATABILITY_CMD_ID = 0x2334, + START_MEASUREMENT_4_MPS_MEDIUM_REPEATABILITY_CMD_ID = 0x2322, + START_MEASUREMENT_4_MPS_LOW_REPEATABILITY_CMD_ID = 0x2329, + START_MEASUREMENT_10_MPS_HIGH_REPEATABILITY_CMD_ID = 0x2737, + START_MEASUREMENT_10_MPS_MEDIUM_REPEATABILITY_CMD_ID = 0x2721, + START_MEASUREMENT_10_MPS_LOW_REPEATABILITY_CMD_ID = 0x273a, + START_ART_MEASUREMENT_CMD_ID = 0x2b32, + READ_MEASUREMENT_CMD_ID = 0xe000, + STOP_MEASUREMENT_CMD_ID = 0x3093, + ENABLE_HEATER_CMD_ID = 0x306d, + DISABLE_HEATER_CMD_ID = 0x3066, + READ_STATUS_REGISTER_CMD_ID = 0xf32d, + CLEAR_STATUS_REGISTER_CMD_ID = 0x3041, + SOFT_RESET_CMD_ID = 0x30a2, +} CmdId; + +typedef enum { + REPEATABILITY_LOW = 0, + REPEATABILITY_MEDIUM = 1, + REPEATABILITY_HIGH = 2, +} Repeatability; + +typedef enum { + MPS_EVERY_TWO_SECONDS = 0, + MPS_ONE_PER_SECOND = 1, + MPS_TWO_PER_SECOND = 2, + MPS_FOUR_PER_SECOND = 4, + MPS_TEN_PER_SECOND = 10, +} Mps; + +class SensirionI2cSht3x { + public: + SensirionI2cSht3x(); + /** + * @brief Initializes the SHT3x class. + * + * @param i2cBus Arduino stream object to be used for communication. + */ + void begin(TwoWire& i2cBus, uint8_t i2cAddress); + + /** + * @brief Single shot measurement with the specified properties + * + * @param[in] measurementRepeatability The repeatability of the periodic + * measurement + * @param[in] isClockStretching Toggle clock stretching + * @param[out] aTemperature Converted from ticks to degrees celsius by -45 + + * (175 * value / 65535) + * @param[out] aHumidity Converted from ticks to relative humidity by 100 * + * value / 65535 + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t measureSingleShot(Repeatability measurementRepeatability, + bool isClockStretching, float& aTemperature, + float& aHumidity); + + /** + * @brief startPeriodicMeasurement + * + * Start the periodic measurement measurement mode. + * + * This is a convenience method that selects the correct measurement command + * based on the provided arguments. + * + * @param[in] measurementRepeatability The repeatability of the periodic + * measurement + * @param[in] messagesPerSecond The messages per second of the periodic + * measurement + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startPeriodicMeasurement(Repeatability measurementRepeatability, + Mps messagesPerSecond); + + /** + * @brief blockingReadMeasurement + * + * This is a convenience method that combines polling the data ready flag + * and reading out the data. As the minimal measurement interval is 2s and + * we sleep for 100ms we iterate at most 200 times. Note that this is + * blocking the system for a considerable amount of time! + * + * @param[out] aTemperature Converted from ticks to degrees celsius by -45 + + * (175 * value / 65535) + * @param[out] aHumidity Converted from ticks to relative humidity by 100 * + * value / 65535 + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t blockingReadMeasurement(float& aTemperature, float& aHumidity); + + /** + * @brief Read the contents of the status register + * + * @param[out] aStatusRegister + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t readStatusRegister(uint16_t& aStatusRegister); + + /** + * @brief measureSingleShotHighRepeatability + * + * Single shot measurement with high repeatability + * + * @param[out] temperatureTicks Temperature ticks. Convert to degrees + * celsius by -45 + 175 * value / 65535 + * @param[out] humidityTicks Humidity ticks. Convert to relative humidity by + * 100 + * * value / 65535 + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t measureSingleShotHighRepeatability(uint16_t& temperatureTicks, + uint16_t& humidityTicks); + + /** + * @brief measureSingleShotHighRepeatabilityClockStretching + * + * Single shot measurement with high repeatability and clock stretching + * enabled + * + * @param[out] temperatureTicks Temperature ticks. Convert to degrees + * celsius by -45 + 175 * value / 65535 + * @param[out] humidityTicks Humidity ticks. Convert to relative humidity by + * 100 + * * value / 65535 + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t measureSingleShotHighRepeatabilityClockStretching( + uint16_t& temperatureTicks, uint16_t& humidityTicks); + + /** + * @brief measureSingleShotMediumRepeatability + * + * Single shot measurement with medium repeatability + * + * @param[out] temperatureTicks Temperature ticks. Convert to degrees + * celsius by -45 + 175 * value / 65535 + * @param[out] humidityTicks Humidity ticks. Convert to relative humidity by + * 100 + * * value / 65535 + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t measureSingleShotMediumRepeatability(uint16_t& temperatureTicks, + uint16_t& humidityTicks); + + /** + * @brief measureSingleShotMediumRepeatabilityClockStretching + * + * Single shot measurement with medium repeatability and clock stretching + * enabled + * + * @param[out] temperatureTicks Temperature ticks. Convert to degrees + * celsius by -45 + 175 * value / 65535 + * @param[out] humidityTicks Humidity ticks. Convert to relative humidity by + * 100 + * * value / 65535 + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t measureSingleShotMediumRepeatabilityClockStretching( + uint16_t& temperatureTicks, uint16_t& humidityTicks); + + /** + * @brief measureSingleShotLowRepeatability + * + * Single shot measurement with low repeatability + * + * @param[out] temperatureTicks Temperature ticks. Convert to degrees + * celsius by -45 + 175 * value / 65535 + * @param[out] humidityTicks Humidity ticks. Convert to relative humidity by + * 100 + * * value / 65535 + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t measureSingleShotLowRepeatability(uint16_t& temperatureTicks, + uint16_t& humidityTicks); + + /** + * @brief measureSingleShotLowRepeatabilityClockStretching + * + * Single shot measurement with low repeatability and clock stretching + * enabled + * + * @param[out] temperatureTicks Temperature ticks. Convert to degrees + * celsius by -45 + 175 * value / 65535 + * @param[out] humidityTicks Humidity ticks. Convert to relative humidity by + * 100 + * * value / 65535 + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t + measureSingleShotLowRepeatabilityClockStretching(uint16_t& temperatureTicks, + uint16_t& humidityTicks); + + /** + * @brief startMeasurement05MpsHighRepeatability + * + * Start periodic measurement mode with 0.5 mps and high repeatability. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startMeasurement05MpsHighRepeatability(); + + /** + * @brief startMeasurement05MpsMediumRepeatability + * + * Start periodic measurement mode with 0.5 mps and medium repeatability. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startMeasurement05MpsMediumRepeatability(); + + /** + * @brief startMeasurement05MpsLowRepeatability + * + * Start periodic measurement mode with 0.5 mps and low repeatability. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startMeasurement05MpsLowRepeatability(); + + /** + * @brief startMeasurement1MpsHighRepeatability + * + * Start periodic measurement mode with 1 mps and high repeatability. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startMeasurement1MpsHighRepeatability(); + + /** + * @brief startMeasurement1MpsMediumRepeatability + * + * Start periodic measurement mode with 1 mps and medium repeatability. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startMeasurement1MpsMediumRepeatability(); + + /** + * @brief startMeasurement1MpsLowRepeatability + * + * Start periodic measurement mode with 1 mps and low repeatability. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startMeasurement1MpsLowRepeatability(); + + /** + * @brief startMeasurement2MpsHighRepeatability + * + * Start periodic measurement mode with 2 mps and high repeatability. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startMeasurement2MpsHighRepeatability(); + + /** + * @brief startMeasurement2MpsMediumRepeatability + * + * Start periodic measurement mode with 2 mps and medium repeatability. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startMeasurement2MpsMediumRepeatability(); + + /** + * @brief startMeasurement2MpsLowRepeatability + * + * Start periodic measurement mode with 2 mps and low repeatability. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startMeasurement2MpsLowRepeatability(); + + /** + * @brief startMeasurement4MpsHighRepeatability + * + * Start periodic measurement mode with 4 mps and high repeatability. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startMeasurement4MpsHighRepeatability(); + + /** + * @brief startMeasurement4MpsMediumRepeatability + * + * Start periodic measurement mode with 4 mps and medium repeatability. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startMeasurement4MpsMediumRepeatability(); + + /** + * @brief startMeasurement4MpsLowRepeatability + * + * Start periodic measurement mode with 4 mps and low repeatability. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startMeasurement4MpsLowRepeatability(); + + /** + * @brief startMeasurement10MpsHighRepeatability + * + * Start periodic measurement mode with 10 mps and high repeatability. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startMeasurement10MpsHighRepeatability(); + + /** + * @brief startMeasurement10MpsMediumRepeatability + * + * Start periodic measurement mode with 10 mps and medium repeatability. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startMeasurement10MpsMediumRepeatability(); + + /** + * @brief startMeasurement10MpsLowRepeatability + * + * Start periodic measurement mode with 10 mps and low repeatability. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startMeasurement10MpsLowRepeatability(); + + /** + * @brief startArtMeasurement + * + * Start ART (accelerated response time) measurement + * + * @note After issuing the ART command the sensor will start acquiring data + * with a frequency of 4Hz. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t startArtMeasurement(); + + /** + * @brief readMeasurement + * + * Read out data after a "start measurement" or "start art measurement" + * command has been issued. + * + * @param[out] temperatureTicks Temperature ticks. Convert to degrees + * celsius by -45 + 175 * value / 65535 + * @param[out] humidityTicks Humidity ticks. Convert to relative humidity by + * 100 + * * value / 65535 + * + * @note After the read out command fetch data has been issued, the data + * memory is cleared, i.e. no measurement data is present. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t readMeasurement(uint16_t& temperatureTicks, + uint16_t& humidityTicks); + + /** + * @brief stopMeasurement + * + * Stop the periodic measurement mode. + * + * @note Upon reception of this command the sensor will abort the ongoing + * measurement and enter the single shot mode. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t stopMeasurement(); + + /** + * @brief enableHeater + * + * Enable the heater + * + * @note The SHT3x is equipped with an internal heater, which is meant for + * plausibility checking only. The temperature increase achieved by the + * heater depends on various parameters and lies in the range of a few + * degrees centigrade. + * + * After a reset the heater is disabled (default condition). + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t enableHeater(); + + /** + * @brief disableHeater + * + * Disable the heater + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t disableHeater(); + + /** + * @brief llreadStatusRegister + * + * Read out the status register + * + * @param[out] statusRegister The contents of the status register + * + * @note The status register contains information on the operational status + * of the heater, the alert mode and on the execution status of the last + * command and the last write sequence. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t llreadStatusRegister(uint16_t& statusRegister); + + /** + * @brief clearStatusRegister + * + * Clear (set to zero) all flags (Bit 4) in the status register. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t clearStatusRegister(); + + /** + * @brief softReset + * + * Perform a soft reset. + * + * @note A system reset of the SHT3x can be achieved in multiple ways: - + * Soft reset: use this command - I2C general call: all devices on the I2C + * bus are reset by sending the command 0x06 to the I2C address 0x00 - Reset + * pin: send a pulse to the dedicated nReset pin. The nReset pin has to be + * pulled low for a minimum of 1 µs to generate a reset of the sensor. - + * Hard reset: Power down (incl. pulling SDA, SCL and ADDR low) + * + * During the reset procedure the sensor will not process commands. + * + * @return error_code 0 on success, an error code otherwise. + */ + int16_t softReset(); + + private: + TwoWire* _i2cBus = nullptr; + uint8_t _i2cAddress = 0; + + /** + * @brief signalTemperature + * + * @param[in] temperatureTicks + * + * @return Converted from ticks to degrees celsius by -45 + (175 * value / + * 65535) + */ + float signalTemperature(uint16_t temperatureTicks); + + /** + * @brief signalHumidity + * + * @param[in] humidityTicks + * + * @return Converted from ticks to relative humidity by 100 * value / 65535 + */ + float signalHumidity(uint16_t humidityTicks); +}; + +#endif // SENSIRIONI2CSHT3X_H diff --git a/src/bsp/BoardDef.cpp b/src/main/BoardDef.cpp similarity index 92% rename from src/bsp/BoardDef.cpp rename to src/main/BoardDef.cpp index 4c42f1f..23fe603 100644 --- a/src/bsp/BoardDef.cpp +++ b/src/main/BoardDef.cpp @@ -3,9 +3,9 @@ #include "esp32-hal-log.h" #endif -const BoardDef bsps[BOARD_DEF_MAX] = { - /** BOARD_DIY_BASIC_KIT */ - [BOARD_DIY_BASIC_KIT] = +const BoardDef bsps[_BOARD_MAX] = { + /** DIY_BASIC */ + [DIY_BASIC] = { .SenseAirS8 = { @@ -17,7 +17,7 @@ const BoardDef bsps[BOARD_DEF_MAX] = { .supported = false, #endif }, - .PMS5003 = + .Pms5003 = { .uart_tx_pin = 14, .uart_rx_pin = 12, @@ -70,10 +70,10 @@ const BoardDef bsps[BOARD_DEF_MAX] = { .resetPin = -1, .supported = false, }, - .name = "BOARD_DIY_BASIC_KIT", + .name = "DIY_BASIC", }, - /** BOARD_DIY_PRO_INDOOR_V4_2 */ - [BOARD_DIY_PRO_INDOOR_V4_2] = + /** DIY_PRO_INDOOR_V4_2 */ + [DIY_PRO_INDOOR_V4_2] = { .SenseAirS8 = { @@ -85,7 +85,7 @@ const BoardDef bsps[BOARD_DEF_MAX] = { .supported = false, #endif }, - .PMS5003 = + .Pms5003 = { .uart_tx_pin = 14, .uart_rx_pin = 12, @@ -144,10 +144,10 @@ const BoardDef bsps[BOARD_DEF_MAX] = { .resetPin = -1, .supported = false, }, - .name = "BOARD_DIY_PRO_INDOOR_V4_2", + .name = "DIY_PRO_INDOOR_V4_2", }, - /** BOARD_ONE_INDOOR_MONITOR_V9_0 */ - [BOARD_ONE_INDOOR_MONITOR_V9_0] = + /** ONE_INDOOR */ + [ONE_INDOOR] = { .SenseAirS8 = { @@ -160,7 +160,7 @@ const BoardDef bsps[BOARD_DEF_MAX] = { #endif }, /** Use UART0 don't use define pin number */ - .PMS5003 = + .Pms5003 = { .uart_tx_pin = -1, .uart_rx_pin = -1, @@ -232,10 +232,10 @@ const BoardDef bsps[BOARD_DEF_MAX] = { .supported = true, #endif }, - .name = "BOARD_ONE_INDOOR_MONITOR_V9_0", + .name = "ONE_INDOOR", }, - /** BOARD_OUTDOOR_MONITOR_V1_3 */ - [BOARD_OUTDOOR_MONITOR_V1_3] = { + /** OPEN_AIR_OUTDOOR */ + [OPEN_AIR_OUTDOOR] = { .SenseAirS8 = { .uart_tx_pin = 1, @@ -247,7 +247,7 @@ const BoardDef bsps[BOARD_DEF_MAX] = { #endif }, /** Use UART0 don't use define pin number */ - .PMS5003 = + .Pms5003 = { .uart_tx_pin = -1, .uart_rx_pin = -1, @@ -319,7 +319,7 @@ const BoardDef bsps[BOARD_DEF_MAX] = { .supported = true, #endif }, - .name = "BOARD_OUTDOOR_MONITOR_V1_3", + .name = "OPEN_AIR_OUTDOOR", }}; /** @@ -329,7 +329,7 @@ const BoardDef bsps[BOARD_DEF_MAX] = { * @return const BoardDef* */ const BoardDef *getBoardDef(BoardType def) { - if (def >= BOARD_DEF_MAX) { + if (def >= _BOARD_MAX) { return NULL; } return &bsps[def]; @@ -356,7 +356,7 @@ void printBoardDef(Stream *_debug) { } #endif - for (int i = 0; i < BOARD_DEF_MAX; i++) { + for (int i = 0; i < _BOARD_MAX; i++) { bspPrintf("Board name: %s", bsps[i].name); bspPrintf("\tSensor CO2 S8:"); bspPrintf("\t\tSupported: %d", bsps[i].SenseAirS8.supported); @@ -366,10 +366,10 @@ void printBoardDef(Stream *_debug) { } bspPrintf("\tSensor PMS5003:"); - bspPrintf("\t\tSupported: %d", bsps[i].PMS5003.supported); - if (bsps[i].PMS5003.supported) { - bspPrintf("\t\tUART Tx: %d", bsps[i].PMS5003.uart_tx_pin); - bspPrintf("\t\tUART Rx: %d", bsps[i].PMS5003.uart_rx_pin); + bspPrintf("\t\tSupported: %d", bsps[i].Pms5003.supported); + if (bsps[i].Pms5003.supported) { + bspPrintf("\t\tUART Tx: %d", bsps[i].Pms5003.uart_tx_pin); + bspPrintf("\t\tUART Rx: %d", bsps[i].Pms5003.uart_rx_pin); } bspPrintf("\tI2C"); diff --git a/src/bsp/BoardDef.h b/src/main/BoardDef.h similarity index 90% rename from src/bsp/BoardDef.h rename to src/main/BoardDef.h index 7b62449..b445993 100644 --- a/src/bsp/BoardDef.h +++ b/src/main/BoardDef.h @@ -13,14 +13,21 @@ #define AgLog(c, ...) log_i(c, ##__VA_ARGS__) #endif +/** + * @brief Define Airgradient supported board type + */ enum BoardType { - BOARD_DIY_BASIC_KIT = 0x00, - BOARD_DIY_PRO_INDOOR_V4_2 = 0x01, - BOARD_ONE_INDOOR_MONITOR_V9_0 = 0x02, - BOARD_OUTDOOR_MONITOR_V1_3 = 0x03, - BOARD_DEF_MAX + DIY_BASIC = 0x00, + DIY_PRO_INDOOR_V4_2 = 0x01, + ONE_INDOOR = 0x02, + OPEN_AIR_OUTDOOR = 0x03, + _BOARD_MAX }; +/** + * @brief Board definitions + * + */ struct BoardDef { /** Board Support CO2 SenseS8 */ struct { @@ -34,7 +41,7 @@ struct BoardDef { const int uart_tx_pin; /** UART tx pin */ const int uart_rx_pin; /** UART rx pin */ const bool supported; /** Is BSP supported for this sensor */ - } PMS5003; + } Pms5003; /** I2C Bus */ struct { diff --git a/src/bsp/HardwareWatchdog.cpp b/src/main/HardwareWatchdog.cpp similarity index 100% rename from src/bsp/HardwareWatchdog.cpp rename to src/main/HardwareWatchdog.cpp diff --git a/src/bsp/HardwareWatchdog.h b/src/main/HardwareWatchdog.h similarity index 86% rename from src/bsp/HardwareWatchdog.h rename to src/main/HardwareWatchdog.h index 77953ca..367ff91 100644 --- a/src/bsp/HardwareWatchdog.h +++ b/src/main/HardwareWatchdog.h @@ -5,6 +5,10 @@ #include "BoardDef.h" +/** + * @brief The class define how to control external watchdog on ONE-V9 and + * Outdoor + */ class HardwareWatchdog { public: HardwareWatchdog(BoardType type); diff --git a/src/bsp/LedBar.cpp b/src/main/LedBar.cpp similarity index 87% rename from src/bsp/LedBar.cpp rename to src/main/LedBar.cpp index b6747ba..1b1f5ef 100644 --- a/src/bsp/LedBar.cpp +++ b/src/main/LedBar.cpp @@ -18,7 +18,7 @@ LedBar::LedBar(BoardType type) : _boardType(type) {} * */ void LedBar::begin(void) { - if (this->_isInit) { + if (this->_isBegin) { return; } @@ -36,9 +36,9 @@ void LedBar::begin(void) { pixel()->begin(); pixel()->clear(); - this->_isInit = true; + this->_isBegin = true; - AgLog("Init"); + AgLog("Initialize"); } /** @@ -48,7 +48,7 @@ void LedBar::begin(void) { * @param red Color Red (0 - 255) * @param green Color Green (0 - 255) * @param blue Color Blue (0 - 255) - * @param ledNum Index of LED from 0 to getNumberOfLed() - 1 + * @param ledNum Index of LED from 0 to getNumberOfLeds() - 1 */ void LedBar::setColor(uint8_t red, uint8_t green, uint8_t blue, int ledNum) { if (this->ledNumInvalid(ledNum)) { @@ -64,7 +64,7 @@ void LedBar::setColor(uint8_t red, uint8_t green, uint8_t blue, int ledNum) { * @param brightness Brightness (0 - 255) */ void LedBar::setBrighness(uint8_t brightness) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } pixel()->setBrightness(brightness); @@ -75,16 +75,16 @@ void LedBar::setBrighness(uint8_t brightness) { * * @return int Number of LED */ -int LedBar::getNumberOfLed(void) { - if (this->checkInit() == false) { +int LedBar::getNumberOfLeds(void) { + if (this->isBegin() == false) { return 0; } return this->_bsp->LED.rgbNum; } -bool LedBar::checkInit(void) { - if (this->_isInit) { +bool LedBar::isBegin(void) { + if (this->_isBegin) { return true; } AgLog("LED is not initialized"); @@ -92,7 +92,7 @@ bool LedBar::checkInit(void) { } bool LedBar::ledNumInvalid(int ledNum) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return true; } diff --git a/src/bsp/LedBar.h b/src/main/LedBar.h similarity index 83% rename from src/bsp/LedBar.h rename to src/main/LedBar.h index a682a06..b2431e3 100644 --- a/src/bsp/LedBar.h +++ b/src/main/LedBar.h @@ -5,6 +5,10 @@ #include "BoardDef.h" +/** + * @brief The class define how to handle the RGB LED bar + * + */ class LedBar { public: #if defined(ESP8266) @@ -16,13 +20,13 @@ public: void setColor(uint8_t red, uint8_t green, uint8_t blue, int ledNum); void setColor(uint8_t red, uint8_t green, uint8_t blue); void setBrighness(uint8_t brightness); - int getNumberOfLed(void); + int getNumberOfLeds(void); void show(void); void clear(void); private: const BoardDef *_bsp; - bool _isInit = false; + bool _isBegin = false; uint8_t _ledState = 0; BoardType _boardType; void *pixels = nullptr; @@ -31,7 +35,7 @@ private: const char *TAG = "LED"; #else #endif - bool checkInit(void); + bool isBegin(void); bool ledNumInvalid(int ledNum); }; diff --git a/src/bsp/PushButton.cpp b/src/main/PushButton.cpp similarity index 83% rename from src/bsp/PushButton.cpp rename to src/main/PushButton.cpp index 627219c..d81f81b 100644 --- a/src/bsp/PushButton.cpp +++ b/src/main/PushButton.cpp @@ -15,7 +15,8 @@ void PushButton::begin(Stream &debugStream) { * */ void PushButton::begin(void) { - if (this->_isInit) { + if (this->_isBegin) { + AgLog("Initialized, call end() then try again"); return; } @@ -25,14 +26,14 @@ void PushButton::begin(void) { return; } - if (this->_boardType == BOARD_DIY_PRO_INDOOR_V4_2) { + if (this->_boardType == DIY_PRO_INDOOR_V4_2) { pinMode(this->_bsp->SW.pin, INPUT_PULLUP); } else { pinMode(this->_bsp->SW.pin, INPUT); } - this->_isInit = true; - AgLog("Init"); + this->_isBegin = true; + AgLog("Initialize"); } /** @@ -41,7 +42,7 @@ void PushButton::begin(void) { * @return PushButton::State */ PushButton::State PushButton::getState(void) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return State::BUTTON_RELEASED; } @@ -64,8 +65,8 @@ String PushButton::toString(PushButton::State state) { return "Released"; } -bool PushButton::checkInit(void) { - if (this->_isInit) { +bool PushButton::isBegin(void) { + if (this->_isBegin) { return true; } AgLog("Switch not initialized"); diff --git a/src/bsp/PushButton.h b/src/main/PushButton.h similarity index 86% rename from src/bsp/PushButton.h rename to src/main/PushButton.h index b59dbf3..4b2f6d4 100644 --- a/src/bsp/PushButton.h +++ b/src/main/PushButton.h @@ -4,6 +4,10 @@ #include "BoardDef.h" #include +/** + * @brief The class define how to handle the Push button + * + */ class PushButton { public: /** @@ -26,7 +30,7 @@ private: /** Board type */ BoardType _boardType; /** Is inititalize flag */ - bool _isInit = false; + bool _isBegin = false; /** Special variable for ESP8266 */ #if defined(ESP8266) @@ -37,7 +41,7 @@ private: /** Method */ - bool checkInit(void); + bool isBegin(void); }; #endif /** _AIR_GRADIENT_SW_H_ */ diff --git a/src/bsp/StatusLed.cpp b/src/main/StatusLed.cpp similarity index 74% rename from src/bsp/StatusLed.cpp rename to src/main/StatusLed.cpp index 9c90045..d0451e8 100644 --- a/src/bsp/StatusLed.cpp +++ b/src/main/StatusLed.cpp @@ -15,6 +15,10 @@ void StatusLed::begin(Stream &debugStream) { * */ void StatusLed::begin(void) { + if (this->_isBegin) { + AgLog("Initialized, call end() then try again"); + return; + } bsp = getBoardDef(this->boardType); if ((bsp == nullptr) || (bsp->LED.supported == false)) { AgLog("Board not support StatusLed"); @@ -25,9 +29,9 @@ void StatusLed::begin(void) { digitalWrite(bsp->LED.pin, !bsp->LED.onState); this->state = LED_OFF; - this->isInit = true; + this->_isBegin = true; - AgLog("Init"); + AgLog("Initialize"); } /** @@ -35,7 +39,7 @@ void StatusLed::begin(void) { * */ void StatusLed::setOn(void) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } digitalWrite(bsp->LED.pin, bsp->LED.onState); @@ -48,7 +52,7 @@ void StatusLed::setOn(void) { * */ void StatusLed::setOff(void) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return; } digitalWrite(bsp->LED.pin, !bsp->LED.onState); @@ -88,11 +92,24 @@ String StatusLed::toString(StatusLed::State state) { return "Off"; } -bool StatusLed::checkInit(void) { - if (this->isInit == false) { - AgLog("No-Initialized"); +bool StatusLed::isBegin(void) { + if (this->_isBegin == false) { + AgLog("Not-Initialized"); return false; } return true; } + +void StatusLed::end(void) { + if (_isBegin == false) { + return; + } + +#if defined(ESP8266) + _debugStream = nullptr; +#endif + setOff(); + _isBegin = false; + AgLog("De-initialize"); +} diff --git a/src/bsp/StatusLed.h b/src/main/StatusLed.h similarity index 83% rename from src/bsp/StatusLed.h rename to src/main/StatusLed.h index ee271a8..3890243 100644 --- a/src/bsp/StatusLed.h +++ b/src/main/StatusLed.h @@ -4,6 +4,10 @@ #include "BoardDef.h" #include +/** + * @brief The class define how to handle the LED + * + */ class StatusLed { public: enum State { @@ -17,6 +21,7 @@ public: #else #endif void begin(void); + void end(void); void setOn(void); void setOff(void); void setToggle(void); @@ -26,7 +31,7 @@ public: private: const BoardDef *bsp = nullptr; BoardType boardType; - bool isInit = false; + bool _isBegin = false; State state; #if defined(ESP8266) Stream *_debugStream; @@ -34,7 +39,7 @@ private: #else #endif - bool checkInit(void); + bool isBegin(void); }; #endif /** _STATUS_LED_H_ */ diff --git a/src/pm/PMS.cpp b/src/pms/PMS.cpp similarity index 98% rename from src/pm/PMS.cpp rename to src/pms/PMS.cpp index 3554fd9..be7e919 100644 --- a/src/pm/PMS.cpp +++ b/src/pms/PMS.cpp @@ -1,7 +1,5 @@ #include "PMS.h" -// PMS::PMS(Stream &stream) { this->_stream = &stream; } - bool PMS::begin(Stream *stream) { _stream = stream; diff --git a/src/pm/PMS.h b/src/pms/PMS.h similarity index 84% rename from src/pm/PMS.h rename to src/pms/PMS.h index b393d8a..f1f32dc 100644 --- a/src/pm/PMS.h +++ b/src/pms/PMS.h @@ -3,10 +3,15 @@ #include +/** + * @brief Class define how to handle plantower PMS sensor it's upport for + * PMS5003 and PMS5003T series. The data @ref AMB_TMP and @ref AMB_HUM only + * valid on PMS5003T + */ class PMS { public: static const uint16_t SINGLE_RESPONSE_TIME = 1000; - static const uint16_t TOTAL_RESPONSE_TIME = 1000 * 10; + static const uint16_t TOTAL_RESPONSE_TIME = 1000 * 10; static const uint16_t STEADY_RESPONSE_TIME = 1000 * 30; // static const uint16_t BAUD_RATE = 9600; @@ -38,7 +43,7 @@ public: uint16_t AMB_HUM; }; - bool begin(Stream* stream); + bool begin(Stream *stream); void sleep(); void wakeUp(); void activeMode(); diff --git a/src/pm/pms5003.cpp b/src/pms/pms5003.cpp similarity index 84% rename from src/pm/pms5003.cpp rename to src/pms/pms5003.cpp index e258c3e..9f4fe81 100644 --- a/src/pm/pms5003.cpp +++ b/src/pms/pms5003.cpp @@ -43,32 +43,27 @@ PMS5003::PMS5003(BoardType def) : _boardDef(def) {} * @return false Failure */ bool PMS5003::begin(void) { - if (this->_isInit) { + if (this->_isBegin) { + AgLog("Initialized, call end() then try again"); return true; } #if defined(ESP32) - // if (this->_serial != &Serial) { - // AgLog("Hardware serial must be Serial(0)"); - // return false; - // } #endif - this->bsp = getBoardDef(this->_boardDef); if (bsp == NULL) { AgLog("Board [%d] not supported", this->_boardDef); return false; } - if (bsp->PMS5003.supported == false) { + if (bsp->Pms5003.supported == false) { AgLog("Board [%d] PMS50035003 not supported", this->_boardDef); return false; } #if defined(ESP8266) - bsp->PMS5003.uart_tx_pin; - SoftwareSerial *uart = - new SoftwareSerial(bsp->PMS5003.uart_tx_pin, bsp->PMS5003.uart_rx_pin); + bsp->Pms5003.uart_tx_pin; + SoftwareSerial *uart = new SoftwareSerial(bsp->Pms5003.uart_tx_pin, bsp->Pms5003.uart_rx_pin); uart->begin(9600); if (pms.begin(uart) == false) { AgLog("PMS failed"); @@ -82,7 +77,7 @@ bool PMS5003::begin(void) { } #endif - this->_isInit = true; + this->_isBegin = true; return true; } @@ -119,7 +114,7 @@ int PMS5003::pm25ToAQI(int pm02) { * @return false Failure */ bool PMS5003::readData(void) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return false; } @@ -168,10 +163,26 @@ int PMS5003::convertPm25ToUsAqi(int pm25) { return this->pm25ToAQI(pm25); } * @return true Initialized * @return false No-initialized */ -bool PMS5003::checkInit(void) { - if (this->_isInit == false) { - AgLog("No initialized"); +bool PMS5003::isBegin(void) { + if (this->_isBegin == false) { + AgLog("Not-initialized"); return false; } return true; } + +/** + * @brief De-initialize sensor + */ +void PMS5003::end(void) { + if (_isBegin == false) { + return; + } + _isBegin = false; +#if defined(ESP8266) + _debugStream = NULL; +#else + delete _serial; +#endif + AgLog("De-initialize"); +} diff --git a/src/pm/pms5003.h b/src/pms/pms5003.h similarity index 80% rename from src/pm/pms5003.h rename to src/pms/pms5003.h index c56038f..3ba47aa 100644 --- a/src/pm/pms5003.h +++ b/src/pms/pms5003.h @@ -1,10 +1,13 @@ #ifndef _AIR_GRADIENT_PMS5003_H_ #define _AIR_GRADIENT_PMS5003_H_ -#include "../bsp/BoardDef.h" +#include "../main/BoardDef.h" #include "Stream.h" #include "PMS.h" +/** + * @brief The class define how to handle PMS5003 sensor bas on @ref PMS class + */ class PMS5003 { public: PMS5003(BoardType def); @@ -13,6 +16,7 @@ public: #else bool begin(HardwareSerial &serial); #endif + void end(void); bool readData(void); int getPm01Ae(void); @@ -22,7 +26,7 @@ public: int convertPm25ToUsAqi(int pm25); private: - bool _isInit = false; + bool _isBegin = false; BoardType _boardDef; PMS pms; const BoardDef *bsp; @@ -36,7 +40,7 @@ private: PMS::DATA pmsData; bool begin(void); - bool checkInit(void); + bool isBegin(void); int pm25ToAQI(int pm02); }; #endif /** _AIR_GRADIENT_PMS5003_H_ */ diff --git a/src/pm/pms5003t.cpp b/src/pms/pms5003t.cpp similarity index 88% rename from src/pm/pms5003t.cpp rename to src/pms/pms5003t.cpp index e784f76..a229229 100644 --- a/src/pm/pms5003t.cpp +++ b/src/pms/pms5003t.cpp @@ -43,7 +43,7 @@ PMS5003T::PMS5003T(BoardType def) : _boardDef(def) {} * @return false Failure */ bool PMS5003T::begin(void) { - if (this->_isInit) { + if (this->_isBegin) { return true; } @@ -60,15 +60,15 @@ bool PMS5003T::begin(void) { return false; } - if (bsp->PMS5003.supported == false) { + if (bsp->Pms5003.supported == false) { AgLog("Board [%d] PMS5003 not supported", this->_boardDef); return false; } #if defined(ESP8266) - bsp->PMS5003.uart_tx_pin; + bsp->Pms5003.uart_tx_pin; SoftwareSerial *uart = - new SoftwareSerial(bsp->PMS5003.uart_tx_pin, bsp->PMS5003.uart_rx_pin); + new SoftwareSerial(bsp->Pms5003.uart_tx_pin, bsp->Pms5003.uart_rx_pin); uart->begin(9600); if (pms.begin(uart) == false) { AgLog("PMS failed"); @@ -82,8 +82,8 @@ bool PMS5003T::begin(void) { if (this->_serial == &Serial) { #endif AgLog("Init Serial"); - this->_serial->begin(9600, SERIAL_8N1, bsp->PMS5003.uart_rx_pin, - bsp->PMS5003.uart_tx_pin); + this->_serial->begin(9600, SERIAL_8N1, bsp->Pms5003.uart_rx_pin, + bsp->Pms5003.uart_tx_pin); } else { if (bsp->SenseAirS8.supported == false) { AgLog("Board [%d] PMS5003T_2 not supported", this->_boardDef); @@ -101,7 +101,7 @@ bool PMS5003T::begin(void) { } #endif - this->_isInit = true; + this->_isBegin = true; return true; } @@ -138,7 +138,7 @@ int PMS5003T::pm25ToAQI(int pm02) { * @return false Failure */ bool PMS5003T::readData(void) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return false; } @@ -207,9 +207,9 @@ float PMS5003T::getRelativeHumidity(void) { * @return true Initialized * @return false No-initialized */ -bool PMS5003T::checkInit(void) { - if (this->_isInit == false) { - AgLog("No initialized"); +bool PMS5003T::isBegin(void) { + if (this->_isBegin == false) { + AgLog("Not-initialized"); return false; } return true; @@ -221,3 +221,16 @@ float PMS5003T::correctionTemperature(float inTemp) { } return inTemp * 1.181f - 5.113f; } + +void PMS5003T::end(void) { + if (_isBegin == false) { + return; + } + _isBegin = false; +#if defined(ESP8266) + _debugStream = NULL; +#else + delete _serial; +#endif + AgLog("De-initialize"); +} diff --git a/src/pm/pms5003t.h b/src/pms/pms5003t.h similarity index 82% rename from src/pm/pms5003t.h rename to src/pms/pms5003t.h index d2ca595..f5a19f5 100644 --- a/src/pm/pms5003t.h +++ b/src/pms/pms5003t.h @@ -2,10 +2,13 @@ #define _PMS5003T_H_ #include -#include "../bsp/BoardDef.h" +#include "../main/BoardDef.h" #include "PMS.h" #include "Stream.h" +/** + * @brief The class define how to handle PMS5003T sensor bas on @ref PMS class + */ class PMS5003T { public: PMS5003T(BoardType def); @@ -14,6 +17,7 @@ public: #else bool begin(HardwareSerial &serial); #endif + void end(void); bool readData(void); int getPm01Ae(void); @@ -25,7 +29,7 @@ public: float getRelativeHumidity(void); private: - bool _isInit = false; + bool _isBegin = false; bool _isSleep = false; BoardType _boardDef; @@ -41,7 +45,7 @@ private: int pm25ToAQI(int pm02); PMS pms; PMS::DATA pmsData; - bool checkInit(void); + bool isBegin(void); float correctionTemperature(float inTemp); }; diff --git a/src/co2/mb_crc.cpp b/src/s8/mb_crc.cpp similarity index 100% rename from src/co2/mb_crc.cpp rename to src/s8/mb_crc.cpp diff --git a/src/co2/mb_crc.h b/src/s8/mb_crc.h similarity index 100% rename from src/co2/mb_crc.h rename to src/s8/mb_crc.h diff --git a/src/co2/s8.cpp b/src/s8/s8.cpp similarity index 92% rename from src/co2/s8.cpp rename to src/s8/s8.cpp index 53d14e9..628cc5b 100644 --- a/src/co2/s8.cpp +++ b/src/s8/s8.cpp @@ -6,7 +6,7 @@ #endif /** - * @brief Construct a new Sense Air S 8:: Sense Air S 8 object + * @brief Construct a new Sense Air S8:: Sense Air S8 object * * @param def */ @@ -18,7 +18,7 @@ S8::S8(BoardType def) : _boardDef(def) {} * @return true = success, otherwise is failure */ bool S8::begin(void) { - if (this->_isInit) { + if (this->_isBegin) { AgLog("Initialized, Call end() then try again"); return true; } @@ -27,8 +27,8 @@ bool S8::begin(void) { } /** - * @brief Init sensor has print debug log, if class create without serial debug - * before it's override last define + * @brief Init S8 sensor, this methos should be call before other, if not it's + * always return the failure status * * @param _debugStream Serial print debug log, NULL if don't use * @return true = success, otherwise is failure @@ -56,13 +56,12 @@ bool S8::begin(HardwareSerial &serial) { * */ void S8::end(void) { - if (this->_isInit == false) { - AgLog("Senor is not initialized"); + if (this->_isBegin == false) { return; } // Deinit - AgLog("De-Inititlized"); + AgLog("De-Inititlize"); } /** @@ -71,7 +70,7 @@ void S8::end(void) { * @param firmver String buffer, len = 10 char */ void S8::getFirmwareVersion(char firmver[]) { - if (this->isInit() == false) { + if (this->isBegin() == false) { return; } @@ -103,7 +102,7 @@ void S8::getFirmwareVersion(char firmver[]) { * @return int32_t Return ID */ int32_t S8::getSensorTypeId(void) { - if (this->isInit() == false) { + if (this->isBegin() == false) { return -1; } @@ -149,7 +148,7 @@ int32_t S8::getSensorTypeId(void) { * @return int32_t ID */ int32_t S8::getSensorId(void) { - if (this->isInit() == false) { + if (this->isBegin() == false) { return -1; } @@ -195,7 +194,7 @@ int32_t S8::getSensorId(void) { * @return int16_t */ int16_t S8::getMemoryMapVersion(void) { - if (this->isInit() == false) { + if (this->isBegin() == false) { return -1; } @@ -225,7 +224,7 @@ int16_t S8::getMemoryMapVersion(void) { * @return int16_t (PPM), -1 if invalid. */ int16_t S8::getCo2(void) { - if (this->isInit() == false) { + if (this->isBegin() == false) { return -1; } @@ -266,7 +265,7 @@ bool S8::setBaselineCalibration(void) { /** * @brief Wait for background calibration done - * + * * @return true Done * @return false On calib */ @@ -286,7 +285,7 @@ bool S8::isBaseLineCalibrationDone(void) { * @return int16_t PWM */ int16_t S8::getOutputPWM(void) { - if (this->isInit() == false) { + if (this->isBegin() == false) { return -1; } @@ -318,7 +317,7 @@ int16_t S8::getOutputPWM(void) { * @return int16_t Hour */ int16_t S8::getCalibPeriodABC(void) { - if (this->isInit() == false) { + if (this->isBegin() == false) { return -1; } @@ -350,7 +349,7 @@ int16_t S8::getCalibPeriodABC(void) { * @return false Failure */ bool S8::setCalibPeriodABC(int16_t period) { - if (this->isInit() == false) { + if (this->isBegin() == false) { return false; } @@ -391,7 +390,7 @@ bool S8::setCalibPeriodABC(int16_t period) { * @return false Failure */ bool S8::manualCalib(void) { - if (this->isInit() == false) { + if (this->isBegin() == false) { return false; } @@ -416,7 +415,7 @@ bool S8::manualCalib(void) { * @return int16_t Flags */ int16_t S8::getAcknowledgement(void) { - if (this->isInit() == false) { + if (this->isBegin() == false) { return -1; } @@ -446,7 +445,7 @@ int16_t S8::getAcknowledgement(void) { * @return false Failure */ bool S8::clearAcknowledgement(void) { - if (this->isInit() == false) { + if (this->isBegin() == false) { return false; } @@ -480,7 +479,7 @@ bool S8::clearAcknowledgement(void) { * @return int16_t Alarm status */ int16_t S8::getAlarmStatus(void) { - if (this->isInit() == false) { + if (this->isBegin() == false) { return -1; } @@ -509,7 +508,7 @@ int16_t S8::getAlarmStatus(void) { * @return S8::Status Sensor status */ S8::Status S8::getStatus(void) { - if (this->isInit() == false) { + if (this->isBegin() == false) { return (Status)0; } @@ -538,7 +537,7 @@ S8::Status S8::getStatus(void) { * @return int16_t Output status */ int16_t S8::getOutputStatus(void) { - if (this->isInit() == false) { + if (this->isBegin() == false) { return -1; } @@ -569,7 +568,7 @@ int16_t S8::getOutputStatus(void) { * @return false Failure */ bool S8::sendSpecialCommand(CalibrationSpecialComamnd command) { - if (this->isInit() == false) { + if (this->isBegin() == false) { return false; } @@ -656,16 +655,16 @@ bool S8::init(int txPin, int rxPin, uint32_t baud) { /** Check communication by get firmware version */ char fwVers[11]; - this->_isInit = true; + this->_isBegin = true; this->getFirmwareVersion(fwVers); if (strlen(fwVers) == 0) { - this->_isInit = false; + this->_isBegin = false; return false; } AgLog("Firmware version: %s", fwVers); AgLog("Sensor successfully initialized. Heating up for 10s"); - this->_isInit = true; + this->_isBegin = true; this->_lastInitTime = millis(); return true; } @@ -676,11 +675,11 @@ bool S8::init(int txPin, int rxPin, uint32_t baud) { * @return true Initialized * @return false No-Initialized */ -bool S8::isInit(void) { - if (this->_isInit) { +bool S8::isBegin(void) { + if (this->_isBegin) { return true; } - AgLog("Sensor no-initialized"); + AgLog("Sensor not-initialized"); return false; } @@ -794,3 +793,23 @@ void S8::sendCommand(uint8_t func, uint16_t reg, uint16_t value) { uartWriteBytes(8); } } + +/** + * @brief Set Auto calib baseline period + * + * @param hours Number of hour will trigger auto calib: [0, 4800], 0: disable + * @return true Success + * @return false Failure + */ +bool S8::setAutoCalib(int hours) { + if (isBegin() == false) { + return false; + } + + int curCalib = getCalibPeriodABC(); + if (curCalib == hours) { + return true; + } + + return setCalibPeriodABC(hours); +} diff --git a/src/co2/s8.h b/src/s8/s8.h similarity index 95% rename from src/co2/s8.h rename to src/s8/s8.h index a0a72db..b967a9e 100644 --- a/src/co2/s8.h +++ b/src/s8/s8.h @@ -1,9 +1,12 @@ #ifndef _S8_H_ #define _S8_H_ -#include "../bsp/BoardDef.h" +#include "../main/BoardDef.h" #include "Arduino.h" +/** + * @brief The class define how to handle the senseair S8 sensor (CO2 sensor) + */ class S8 { public: const int S8_BAUDRATE = @@ -75,6 +78,7 @@ public: int16_t getCo2(void); bool setBaselineCalibration(void); bool isBaseLineCalibrationDone(void); + bool setAutoCalib(int hours); private: /** Variables */ @@ -86,7 +90,7 @@ private: #if defined(ESP32) HardwareSerial *_serial; #endif - bool _isInit = false; + bool _isBegin = false; uint32_t _lastInitTime; bool isCalib = false; @@ -95,7 +99,7 @@ private: bool init(const BoardDef *bsp); bool init(int txPin, int rxPin); bool init(int txPin, int rxPin, uint32_t baud); - bool isInit(void); + bool isBegin(void); void uartWriteBytes(uint8_t size); // Send bytes to sensor uint8_t diff --git a/src/sgp/sgp41.cpp b/src/sgp41/sgp41.cpp similarity index 76% rename from src/sgp/sgp41.cpp rename to src/sgp41/sgp41.cpp index 558769c..b6ae3aa 100644 --- a/src/sgp/sgp41.cpp +++ b/src/sgp41/sgp41.cpp @@ -7,6 +7,11 @@ #define vocAlgorithm() ((VOCGasIndexAlgorithm *)(this->_vocAlgorithm)) #define noxAlgorithm() ((NOxGasIndexAlgorithm *)(this->_noxAlgorithm)) +/** + * @brief Construct a new Sgp 4 1:: Sgp 4 1 object + * + * @param type Board type @ref BoardType + */ Sgp41::Sgp41(BoardType type) : _boardType(type) {} /** @@ -18,9 +23,13 @@ Sgp41::Sgp41(BoardType type) : _boardType(type) {} * @return false Failure */ bool Sgp41::begin(TwoWire &wire) { - if (this->_isInit) { + /** Ignore next step if initialized */ + if (this->_isBegin) { + AgLog("Initialized, call end() then try again"); return true; } + + /** Check that board has supported this sensor */ if (this->boardSupported() == false) { return false; } @@ -55,8 +64,8 @@ bool Sgp41::begin(TwoWire &wire) { conditioningCount = 0; #endif - this->_isInit = true; - AgLog("Init"); + this->_isBegin = true; + AgLog("Initialize"); return true; } @@ -88,7 +97,11 @@ void Sgp41::handle(void) { } } -#else +#else +/** + * @brief Handle the sensor conditioning and run time udpate value, This method + * must not call, it's called on private task + */ void Sgp41::_handle(void) { /** NOx conditionning */ uint16_t err; @@ -115,18 +128,26 @@ void Sgp41::_handle(void) { } #endif +/** + * @brief De-Initialize sensor + */ void Sgp41::end(void) { - if (this->_isInit == false) { + if (this->_isBegin == false) { return; } #ifdef ESP32 vTaskDelete(pollTask); +#else + _debugStream = nullptr; #endif - this->_isInit = false; + bsp = NULL; + this->_isBegin = false; delete sgpSensor(); + delete vocAlgorithm(); + delete noxAlgorithm(); - AgLog("De-Init"); + AgLog("De-initialize"); } /** @@ -153,6 +174,12 @@ int Sgp41::getNoxIndex(void) { return nox; } +/** + * @brief Check that board has supported sensor + * + * @return true Supported + * @return false Not-supported + */ bool Sgp41::boardSupported(void) { if (this->bsp == nullptr) { this->bsp = getBoardDef(this->_boardType); @@ -165,25 +192,19 @@ bool Sgp41::boardSupported(void) { return true; } -int Sgp41::sdaPin(void) { - if (this->bsp) { - return this->bsp->I2C.sda_pin; - } - AgLog("sdaPin(): board not supported I2C"); - return -1; -} - -int Sgp41::sclPin(void) { - if (this->bsp) { - return this->bsp->I2C.scl_pin; - } - AgLog("sdlPin(): board not supported I2C"); - return -1; -} - +/** + * @brief Get raw signal + * + * @param raw_voc Raw VOC output + * @param row_nox Raw NOx output + * @param defaultRh + * @param defaultT + * @return true Success + * @return false Failure + */ bool Sgp41::getRawSignal(uint16_t &raw_voc, uint16_t &row_nox, uint16_t defaultRh, uint16_t defaultT) { - if (this->checkInit() == false) { + if (this->isBegin() == false) { return false; } @@ -195,43 +216,25 @@ bool Sgp41::getRawSignal(uint16_t &raw_voc, uint16_t &row_nox, } /** - * @brief This command turns the hotplate off and stops the measurement. - * Subsequently, the sensor enters the idle mode. + * @brief Check that sensor is initialized + * + * @return true Initialized + * @return false Not-initialized + */ +bool Sgp41::isBegin(void) { + if (this->_isBegin) { + return true; + } + AgLog("Sensor not-initialized"); + return false; +} + +/** + * @brief Handle nox conditioning process * * @return true Success * @return false Failure */ -bool Sgp41::turnHeaterOff(void) { - if (this->checkInit() == false) { - return false; - } - - if (sgpSensor()->turnHeaterOff() == 0) { - return true; - } - return false; -} - -bool Sgp41::getSerialNumber(uint16_t serialNumbers[], - uint8_t serialNumberSize) { - if (this->checkInit() == false) { - return false; - } - - if (sgpSensor()->getSerialNumber(serialNumbers) == 0) { - return true; - } - return false; -} - -bool Sgp41::checkInit(void) { - if (this->_isInit) { - return true; - } - AgLog("Sensor no-initialized"); - return false; -} - bool Sgp41::_noxConditioning(void) { uint16_t err; uint16_t srawVoc; diff --git a/src/sgp/sgp41.h b/src/sgp41/sgp41.h similarity index 82% rename from src/sgp/sgp41.h rename to src/sgp41/sgp41.h index f0f4183..f26b343 100644 --- a/src/sgp/sgp41.h +++ b/src/sgp41/sgp41.h @@ -1,10 +1,15 @@ #ifndef _AIR_GRADIENT_SGP4X_H_ #define _AIR_GRADIENT_SGP4X_H_ -#include "../bsp/BoardDef.h" +#include "../main/BoardDef.h" #include #include +/** + * @brief The class define how to handle Sensirion sensor SGP41 (VOC and NOx + * sensor) + * + */ class Sgp41 { public: Sgp41(BoardType type); @@ -12,7 +17,7 @@ public: #if defined(ESP8266) bool begin(TwoWire &wire, Stream &stream); void handle(void); -#else +#else void _handle(void); #endif void end(void); @@ -22,7 +27,7 @@ public: private: bool onConditioning = true; bool ready = false; - bool _isInit = false; + bool _isBegin = false; void *_sensor; void *_vocAlgorithm; void *_noxAlgorithm; @@ -40,14 +45,10 @@ private: #else TaskHandle_t pollTask; #endif - bool checkInit(void); + bool isBegin(void); bool boardSupported(void); - int sdaPin(void); - int sclPin(void); bool getRawSignal(uint16_t &raw_voc, uint16_t &raw_nox, uint16_t defaultRh = 0x8000, uint16_t defaultT = 0x6666); - bool turnHeaterOff(void); - bool getSerialNumber(uint16_t serialNumbers[], uint8_t serialNumberSize); bool _noxConditioning(void); }; diff --git a/src/sht/sht3x.cpp b/src/sht/sht3x.cpp new file mode 100644 index 0000000..62aa5e3 --- /dev/null +++ b/src/sht/sht3x.cpp @@ -0,0 +1,163 @@ +#include "sht3x.h" +#include "../library/arduino-i2c-sht3x/src/SensirionI2cSht3x.h" + +#define sht3x() ((SensirionI2cSht3x *)(this->_sensor)) + +/** + * @brief Check that sensor has initialized + * + * @return true Initialized + * @return false Not-initialized + */ +bool Sht3x::isBegin(void) { + if (_isBegin) { + return true; + } + AgLog("Sensor not-initialized"); + return false; +} + +/** + * @brief Check sensor has supported by board + * + * @return true Supported + * @return false Not-supported + */ +bool Sht3x::boardSupported(void) { + if (_bsp == NULL) { + _bsp = getBoardDef(_boarType); + } + + if ((_bsp == NULL) || (_bsp->I2C.supported == false)) { + AgLog("Board not supported"); + return false; + } + return true; +} + +/** + * @brief Get temperature and humidity data + * + * @param temp Tempreature read out + * @param hum Humidity read out + * @return true Success + * @return false Failure + */ +bool Sht3x::measure(float &temp, float &hum) { + if (isBegin() == false) { + return false; + } + + if (sht3x()->measureSingleShot(REPEATABILITY_MEDIUM, false, temp, hum) == + NO_ERROR) { + return true; + } + return false; +} + +/** + * @brief Construct a new Sht 3x:: Sht 3x object + * + * @param type + */ +Sht3x::Sht3x(BoardType type) : _boarType(type) {} + +/** + * @brief Destroy the Sht 3x:: Sht 3x object + * + */ +Sht3x::~Sht3x() { end(); } + +#ifdef ESP8266 +/** + * @brief Initialized sensor + * + * @param wire TwoWire instance, must be initialized + * @param debugStream Point to debug Serial to print debug log + * @return true Success + * @return false Failure + */ +bool Sht3x::begin(TwoWire &wire, Stream &debugStream) { + _debugStream = &debugStream; + return begin(wire); +} +#else +#endif + +/** + * @brief Init sensor, should init before use sensor, if not call other method + * always return invalid + * + * @param wire TwoWire instance, must be initialized + * @return true Success + * @return false Failure + */ +bool Sht3x::begin(TwoWire &wire) { + if (_isBegin) { + AgLog("Initialized, call end() then try again"); + return true; + } + + /** Check sensor has supported on board */ + if (boardSupported() == false) { + return false; + } + + /** Create sensor and init */ + _sensor = new SensirionI2cSht3x(); + sht3x()->begin(wire, SHT30_I2C_ADDR_44); + if (sht3x()->softReset() != NO_ERROR) { + AgLog("Reset sensor fail, look like sensor is not on I2C bus"); + return false; + } + + _isBegin = true; + AgLog("Initialize"); + return true; +} + +/** + * @brief De-initialize sensor + * + */ +void Sht3x::end(void) { + if (_isBegin == false) { + return; + } + _isBegin = false; + _bsp = NULL; + delete sht3x(); +#ifdef ESP8266 + _debugStream = nullptr; +#endif + AgLog("De-initialize"); +} + +/** + * @brief Get temperature degree celsius + * + * @return float value <= 256.0f is invalid, that mean sensor has issue or + * communication to sensor not worked as well + */ +float Sht3x::getTemperature(void) { + float temp; + float hum; + if (measure(temp, hum)) { + return temp; + } + return -256.0f; +} + +/** + * @brief Get humidity + * + * @return float Percent(0 - 100), value < 0 is invalid. + */ +float Sht3x::getRelativeHumidity(void) { + float temp; + float hum; + if (measure(temp, hum)) { + return hum; + } + return -1.0f; +} diff --git a/src/sht/sht3x.h b/src/sht/sht3x.h new file mode 100644 index 0000000..971aaa6 --- /dev/null +++ b/src/sht/sht3x.h @@ -0,0 +1,42 @@ +#ifndef _AIR_GRADIENT_SHT3X_H_ +#define _AIR_GRADIENT_SHT3X_H_ + +#include "../main/BoardDef.h" +#include +#include + +/** + * @brief The class with define how to handle the Sensirion sensor SHT3x + * (temperature and humidity sensor). + */ +class Sht3x { +private: + BoardType _boarType; + bool _isBegin = false; + const BoardDef *_bsp = NULL; + void *_sensor; +#ifdef ESP8266 + Stream *_debugStream = nullptr; + const char *TAG = "SHT3x"; +#else +#endif + + bool isBegin(void); + bool boardSupported(void); + bool measure(float &temp, float &hum); + +public: + Sht3x(BoardType type); + ~Sht3x(); + +#ifdef ESP8266 + bool begin(TwoWire &wire, Stream &debugStream); +#else +#endif + bool begin(TwoWire &wire); + void end(void); + float getTemperature(void); + float getRelativeHumidity(void); +}; + +#endif /** _AIR_GRADIENT_SHT3X_H_ */ diff --git a/src/sht/sht4x.cpp b/src/sht/sht4x.cpp index cdd1d6d..df49b7e 100644 --- a/src/sht/sht4x.cpp +++ b/src/sht/sht4x.cpp @@ -1,18 +1,32 @@ #include "sht4x.h" #include "../library/SensirionSHT4x/src/SensirionI2CSht4x.h" +/** Cast _sensor to SensirionI2CSht4x */ #define shtSensor() ((SensirionI2CSht4x *)(this->_sensor)) #if defined(ESP8266) -bool Sht::begin(TwoWire &wire, Stream &debugStream) { +/** + * @brief Init sensor, Ifthis funciton not call the other funtion call will + * always return false or value invalid + * + * @param wire wire TwoWire instance, Must be initialized + * @param debugStream Point to debug Serial to print debug log + * @return true Sucecss + * @return false Failure + */ +bool Sht4x::begin(TwoWire &wire, Stream &debugStream) { this->_debugStream = &debugStream; return this->begin(wire); } #else - #endif -Sht::Sht(BoardType type) : _boardType(type) {} +/** + * @brief Construct a new Sht4x:: Sht4x object + * + * @param type Board type @ref BoardType + */ +Sht4x::Sht4x(BoardType type) : _boardType(type) {} /** * @brief Init sensor, Ifthis funciton not call the other funtion call will @@ -22,14 +36,19 @@ Sht::Sht(BoardType type) : _boardType(type) {} * @return true Success * @return false Failure */ -bool Sht::begin(TwoWire &wire) { - if (this->_isInit) { +bool Sht4x::begin(TwoWire &wire) { + /** Ignore next step if sensor has intiialized */ + if (this->_isBegin) { + AgLog("Initialized, call end() then try again"); return true; } + /** Check sensor has supported on board */ if (this->boardSupported() == false) { return false; } + + /** Create new SensirionI2CSht4x and init */ this->_sensor = new SensirionI2CSht4x(); shtSensor()->begin(wire, SHT40_I2C_ADDR_44); if (shtSensor()->softReset() != 0) { @@ -39,18 +58,27 @@ bool Sht::begin(TwoWire &wire) { delay(10); - this->_isInit = true; - AgLog("Init"); + this->_isBegin = true; + AgLog("Initialize"); return true; } -void Sht::end(void) { - if (this->_isInit == false) { +/** + * @brief De-initialize SHT41 sensor + * + */ +void Sht4x::end(void) { + if (this->_isBegin == false) { return; } - this->_isInit = false; + this->_isBegin = false; + _bsp = NULL; delete shtSensor(); +#if defined(ESP8266) + _debugStream = nullptr; +#endif +AgLog("De-initialize"); } /** @@ -59,7 +87,7 @@ void Sht::end(void) { * @return float value <= 256.0f is invalid, That mean sensor has issue or * communication to sensor not worked as well. */ -float Sht::getTemperature(void) { +float Sht4x::getTemperature(void) { float temperature; float humidity; if (this->measureMediumPrecision(temperature, humidity)) { @@ -74,7 +102,7 @@ float Sht::getTemperature(void) { * * @return float Percent(0 - 100), value < 0 is invalid. */ -float Sht::getRelativeHumidity(void) { +float Sht4x::getRelativeHumidity(void) { float temperature; float humidity; if (this->measureMediumPrecision(temperature, humidity)) { @@ -84,7 +112,13 @@ float Sht::getRelativeHumidity(void) { return -1.0f; } -bool Sht::boardSupported(void) { +/** + * @brief Check sensor has supported by board + * + * @return true Supported + * @return false Not supported + */ +bool Sht4x::boardSupported(void) { if (this->_bsp == NULL) { this->_bsp = getBoardDef(this->_boardType); } @@ -96,43 +130,30 @@ bool Sht::boardSupported(void) { return true; } -int Sht::sdaPin(void) { - if (this->_bsp) { - return this->_bsp->I2C.sda_pin; - } - AgLog("sdaPin(): board not supported I2C"); - return -1; -} - -int Sht::sclPin(void) { - if (this->_bsp) { - return this->_bsp->I2C.scl_pin; - } - AgLog("sdlPin(): board not supported I2C"); - return -1; -} - -bool Sht::checkInit(void) { - if (this->_isInit) { +/** + * @brief Check that sensor has initialized + * + * @return true Initialized + * @return false Not-initialized + */ +bool Sht4x::isBegin(void) { + if (this->_isBegin) { return true; } - AgLog("Sensor no-initialized"); + AgLog("Sensor not-initialized"); return false; } -bool Sht::measureHighPrecision(float &temperature, float &humidity) { - if (this->checkInit() == false) { - return false; - } - - if (shtSensor()->measureHighPrecision(temperature, humidity) == 0) { - return true; - } - return false; -} - -bool Sht::measureMediumPrecision(float &temperature, float &humidity) { - if (this->checkInit() == false) { +/** + * @brief Ge SHT41 temperature and humidity value with medium meaure precision + * + * @param temperature Read out temperarure + * @param humidity Read humidity + * @return true Success + * @return false Failure + */ +bool Sht4x::measureMediumPrecision(float &temperature, float &humidity) { + if (this->isBegin() == false) { return false; } @@ -142,69 +163,3 @@ bool Sht::measureMediumPrecision(float &temperature, float &humidity) { return false; } -bool Sht::measureLowestPrecision(float &temperature, float &humidity) { - if (this->checkInit() == false) { - return false; - } - - if (shtSensor()->measureLowestPrecision(temperature, humidity) == 0) { - return true; - } - return false; -} - -bool Sht::activateHighestHeaterPowerShort(float &temperature, float &humidity) { - if (this->checkInit() == false) { - return false; - } - - if (shtSensor()->activateHighestHeaterPowerShort(temperature, humidity) == - 0) { - return true; - } - return false; -} - -bool Sht::activateMediumHeaterPowerLong(float &temperature, float &humidity) { - if (this->checkInit() == false) { - return false; - } - - if (shtSensor()->activateMediumHeaterPowerLong(temperature, humidity) == 0) { - return true; - } - return false; -} - -bool Sht::activateLowestHeaterPowerLong(float &temperature, float &humidity) { - if (this->checkInit() == false) { - return false; - } - - if (shtSensor()->activateLowestHeaterPowerLong(temperature, humidity) == 0) { - return true; - } - return false; -} - -bool Sht::getSerialNumber(uint32_t &serialNumber) { - if (this->checkInit() == false) { - return false; - } - - if (shtSensor()->serialNumber(serialNumber) == 0) { - return true; - } - return false; -} - -bool Sht::softReset(void) { - if (this->checkInit() == false) { - return false; - } - - if (shtSensor()->softReset() == 0) { - return true; - } - return false; -} diff --git a/src/sht/sht4x.h b/src/sht/sht4x.h index 83a40b5..b7a3b11 100644 --- a/src/sht/sht4x.h +++ b/src/sht/sht4x.h @@ -4,24 +4,27 @@ #include #include -#include "../bsp/BoardDef.h" +#include "../main/BoardDef.h" -class Sht { +/** + * @brief The class with define how to handle the Sensirion sensor SHT41 + * (temperature and humidity sensor). + */ +class Sht4x { public: #if defined(ESP8266) bool begin(TwoWire &wire, Stream &debugStream); #else #endif - Sht(BoardType type); + Sht4x(BoardType type); bool begin(TwoWire &wire); void end(void); - float getTemperature(void); float getRelativeHumidity(void); private: BoardType _boardType; - bool _isInit = false; + bool _isBegin = false; /** Flag indicate that sensor initialized or not */ void *_sensor; const BoardDef *_bsp = NULL; #if defined(ESP8266) @@ -29,21 +32,9 @@ private: const char *TAG = "SHT4x"; #else #endif - bool checkInit(void); + bool isBegin(void); bool boardSupported(void); - int sdaPin(void); - int sclPin(void); - - bool measureHighPrecision(float &temperature, float &humidity); bool measureMediumPrecision(float &temperature, float &humidity); - bool measureLowestPrecision(float &temperature, float &humidity); - - bool activateHighestHeaterPowerShort(float &temperature, float &humidity); - bool activateMediumHeaterPowerLong(float &temperature, float &humidity); - bool activateLowestHeaterPowerLong(float &temperature, float &humidity); - - bool getSerialNumber(uint32_t &serialNumber); - bool softReset(void); }; #endif /** _AIR_GRADIENT_SHT_H_ */