Compare commits

..

32 Commits
3.0.0 ... 3.0.3

Author SHA1 Message Date
5667279cf1 Merge pull request #53 from airgradienthq/feature/update-sht-for-all-examples
Feature/update sht for all examples
2024-02-16 21:57:52 +07:00
2941bb2d5d next version 3.0.3 2024-02-16 21:56:43 +07:00
a0044ad0ac Update example to use sht support for sht3x and sht4x 2024-02-16 13:39:33 +07:00
fc5c0a1d6e Merge branch 'hotfix/sht30' into feature/update-sht-for-all-examples 2024-02-16 13:29:03 +07:00
b28719b7a5 Improved LED test on startup. Set for version 3.0.2 2024-02-16 12:11:17 +07:00
87a3b6e409 Merge commit 'f17afd932ebef7d0d2e49c130e076dfc5462c086' 2024-02-15 20:07:42 +07:00
dd62a10ed5 Update arduino library version 3.0.1 2024-02-15 20:01:02 +07:00
225d079d48 Merge pull request #52 from airgradienthq/feature/led-test-with-button-on-power-up
Feature/led test with button on power up
2024-02-15 19:58:40 +07:00
7ea43fdc7d Update lib version string: 3.0.1 2024-02-15 19:56:46 +07:00
67ad912a71 update DISPLAY_DELAY_SHOW_CONTENT_MS from 3000 to 6000 2024-02-15 19:55:17 +07:00
5602a456a7 fix: wrong config key for PM Standard 2024-02-15 19:48:58 +07:00
28e5aa4e69 LED test update: support country TH and Show display Press now for LED test 2024-02-15 13:40:42 +07:00
e55f3b6e74 LED Test on Button 2024-02-15 11:21:04 +07:00
f17afd932e Auto detect 1PST, 1PPT and 1PP 2024-02-15 10:56:53 +07:00
7a6cc8caef use "arduino-sht" library for sht3x and sht4x 2024-02-10 21:14:27 +07:00
94ead3751b Merge pull request #48 from airgradienthq/feature/Basic_V4-show-full-device-id-on-display
Feature/basic v4 show full device id on display
2024-02-07 21:01:22 +07:00
ab600e014a Update content and line space of display show serial number value 2024-02-07 21:00:13 +07:00
60d02d88b5 Update show device id on 4 line of display 2024-02-07 09:43:52 +07:00
05594441b8 Update sht3x 2024-02-07 09:18:02 +07:00
9e461a9036 Merge pull request #47 from airgradienthq/hotfix/ledbar-test
updated `ledBarTestRequested`
2024-02-06 13:25:14 +07:00
ce6bee19af updated ledBarTestRequested 2024-02-06 13:24:10 +07:00
0354c6e634 Merge pull request #46 from airgradienthq/optimize-and-clean-code
Optimize firmware and bug fix
2024-02-06 10:56:39 +07:00
ac9efccd94 Fix: missing handle serverConfig ledBarTestRequested test 2024-02-06 10:52:01 +07:00
4df0fc5d5c Set S8 Automatic Baseline Period 2024-02-06 10:41:10 +07:00
4c180fedbd Support SHT3x 2024-02-06 09:38:37 +07:00
8b73ac77f9 Update TVOC missing call to polling data 2024-02-04 16:30:50 +07:00
cbb444f1bc Change WiFi connection screen to four lines 2024-02-04 16:20:37 +07:00
b2762a3b6c [Lib] Change LedBar function name getNumberOfLed to getNumberOfLeds 2024-02-04 15:48:06 +07:00
512420f5a2 Update: Explain difference between PMS5003 and PMS5003T 2024-02-04 15:22:06 +07:00
e94a625072 Remove .DS_Store 2024-02-04 15:19:39 +07:00
7bbe81ad1d Fix: TestPM (before PM Simple) only shows nulls 2024-02-04 15:10:46 +07:00
335fad3f0d Clean code and add comments 2024-02-04 15:04:38 +07:00
71 changed files with 4084 additions and 4004 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.vscode
*.DS_Store

View File

@ -34,7 +34,7 @@ If you have any questions or problems, check out [our forum](https://forum.airgr
- [Sensirion Gas Index Algorithm](https://github.com/Sensirion/arduino-gas-index-algorithm)
- [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 SHT](https://github.com/Sensirion/arduino-sht)
- [PMS](https://github.com/fu-hsi/pms)
## License

BIN
examples/.DS_Store vendored

Binary file not shown.

View File

@ -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,156 +36,425 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
#include <Arduino_JSON.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h>
#include <U8g2lib.h>
#include <WiFiClient.h>
#include <WiFiManager.h>
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 2000 /** 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 "pmStandard" */
if (JSON.typeof_(root["pmStandard"]) == "string") {
String standard = root["pmStandard"];
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);
/** Init I2C */
Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin());
delay(1000);
/** 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);
/** Show serial number display */
ag.display.clear();
ag.display.setCursor(1, 1);
ag.display.setText("Warm Up");
ag.display.setCursor(1, 15);
ag.display.setText("Serial#");
ag.display.setCursor(1, 29);
String id = getNormalizedMac();
Serial.println("Device id: " + id);
String id1 = id.substring(0, 9);
String id2 = id.substring(9, 12);
ag.display.setText("\'" + id1);
ag.display.setCursor(1, 40);
ag.display.setText(id2 + "\'");
ag.display.show();
getServerConfig();
delay(5000);
}
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 +471,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,18 +500,11 @@ 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) {
failedHandler("SHT init failed");
@ -293,152 +525,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 +550,118 @@ 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() {
if (ag.sht.measure()) {
temp = ag.sht.getTemperature();
hum = ag.sht.getRelativeHumidity();
Serial.printf("Temperature: %0.2f\r\n", temp);
Serial.printf(" Humidity: %d\r\n", hum);
} else {
Serial.println("Meaure SHT failed");
}
}
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;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,687 +0,0 @@
/*
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 <AirGradient.h>
#include <Arduino_JSON.h>
#include <HTTPClient.h>
#include <HardwareSerial.h>
#include <WiFiManager.h>
#include <Wire.h>
/**
*
* @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 DEBUG true
#define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */
#define WIFI_HOTSPOT_PASSWORD_DEFAULT "cleanair"
AirGradient ag(BOARD_OUTDOOR_MONITOR_V1_3);
// time in seconds needed for NOx conditioning
uint16_t conditioning_s = 10;
String APIROOT = "http://hw.airgradient.com/";
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;
// 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;
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 */
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 pm25 = -1;
int pm01 = -1;
int pm10 = -1;
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);
void setup() {
if (DEBUG) {
Serial.begin(115200);
}
/** Board init */
boardInit();
delay(500);
countdown(3);
if (connectWIFI) {
connectToWifi();
}
if (WiFi.status() == WL_CONNECTED) {
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);
}
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();
}
}
}
void sendPing() {
String payload =
"{\"wifi\":" + String(WiFi.RSSI()) + ", \"boot\":" + loopCount + "}";
if (postToServer(payload)) {
ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNNECTED);
} else {
ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNECT_FAILED);
}
delay(5000);
}
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");
}
}
}
void countdown(int from) {
debug("\n");
while (from > 0) {
debug(String(from--));
debug(" ");
delay(1000);
}
debug("\n");
}
void resetWatchdog() {
Serial.println("Watchdog reset");
ag.watchdog.reset();
}
bool wifiMangerClientConnected(void) {
return WiFi.softAPgetStationNum() ? true : false;
}
// Wifi Manager
void connectToWifi() {
HOTSPOT = "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(HOTSPOT.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 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(":", "");
mac.toLowerCase();
return mac;
}
void boardInit(void) {
if (Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin()) == false) {
failedHandler("Init I2C failed");
}
ag.watchdog.begin();
ag.button.begin();
ag.statusLed.begin();
if (ag.pms5003t_1.begin(Serial0) == false) {
failedHandler("Init PMS5003T failed");
}
if (ag.s8.begin(Serial1) == false) {
failedHandler("Init SenseAirS8 failed");
}
if (ag.sgp41.begin(Wire) == false) {
failedHandler("Init SGP41 failed");
}
}
void failedHandler(String msg) {
while (true) {
Serial.println(msg);
vTaskDelay(1000);
}
}
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);
delay(1000);
}
if (ag.s8.setBaselineCalibration()) {
Serial.println("Calibration success");
delay(1000);
Serial.println("Wait for calib finish...");
int count = 0;
while (ag.s8.isBaseLineCalibrationDone() == false) {
delay(1000);
count++;
}
Serial.printf("Calib finish after %d sec\r\n", count);
delay(2000);
} else {
Serial.println("Calibration failure!!!");
delay(2000);
}
}
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();
ag.statusLed.setOn();
delay(50);
ag.statusLed.setOff();
delay(950);
ag.statusLed.setOn();
delay(50);
ag.statusLed.setOff();
break;
}
case APP_SM_WIFI_MANAGER_CONNECT_FAILED: {
ag.statusLed.setOff();
for (int j = 0; j < 3; j++) {
for (int i = 0; i < 3 * 2; i++) {
ag.statusLed.setToggle();
delay(100);
}
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 * 2; i++) {
ag.statusLed.setToggle();
delay(100);
}
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 * 2; i++) {
ag.statusLed.setToggle();
delay(100);
}
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;
}
}

View File

@ -0,0 +1,974 @@
/*
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 <AirGradient.h>
#include <Arduino_JSON.h>
#include <HTTPClient.h>
#include <HardwareSerial.h>
#include <WiFiManager.h>
#include <Wire.h>
/**
*
* @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 2000 /** 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();
}
}
void setPeriod(int period) { this->period = period; }
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 "pmStandard" */
if (JSON.typeof_(root["pmStandard"]) == "string") {
String standard = root["pmStandard"];
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;
int loopCount = 0;
WiFiManager wifiManager; /** wifi manager instance */
static bool wifiHasConfig = false;
static String wifiSSID = "";
int tvocIndex = -1;
int noxIndex = -1;
int co2Ppm = 0;
int pm25_1 = -1;
int pm01_1 = -1;
int pm10_1 = -1;
int pm03PCount_1 = -1;
float temp_1;
int hum_1;
int pm25_2 = -1;
int pm01_2 = -1;
int pm10_2 = -1;
int pm03PCount_2 = -1;
float temp_2;
int hum_2;
int pm1Value01;
int pm1Value25;
int pm1Value10;
int pm1PCount;
int pm1temp;
int pm1hum;
int pm2Value01;
int pm2Value25;
int pm2Value10;
int pm2PCount;
int pm2temp;
int pm2hum;
int countPosition;
const int targetCount = 20;
enum {
FW_MODE_PST, /** PMS5003T, S8 and SGP41 */
FW_MODE_PPT, /** PMS5003T_1, PMS5003T_2, SGP41 */
FW_MODE_PP /** PMS5003T_1, PMS5003T_2 */
};
int fw_mode = FW_MODE_PST;
void boardInit(void);
void failedHandler(String msg);
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);
static const char *getFwMode(int mode);
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() {
Serial.begin(115200);
/** Board init */
boardInit();
/** Server init */
agServer.begin();
/** WiFi connect */
connectToWifi();
if (WiFi.isConnected()) {
wifiHasConfig = true;
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() {
configSchedule.run();
serverSchedule.run();
if (fw_mode == FW_MODE_PST) {
co2Schedule.run();
}
pmsSchedule.run();
if (fw_mode == FW_MODE_PST || fw_mode == FW_MODE_PPT) {
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);
}
static void sendDataToServer(void) {
JSONVar root;
root["wifi"] = WiFi.RSSI();
root["boot"] = loopCount;
if (fw_mode == FW_MODE_PST) {
if (co2Ppm >= 0) {
root["rco2"] = co2Ppm;
}
if (pm01_1 >= 0) {
root["pm01"] = pm01_1;
}
if (pm25_1 >= 0) {
root["pm02"] = pm25_1;
}
if (pm10_1 >= 0) {
root["pm10"] = pm10_1;
}
if (pm03PCount_1 >= 0) {
root["pm003_count"] = pm03PCount_1;
}
if (tvocIndex >= 0) {
root["tvoc_index"] = tvocIndex;
}
if (noxIndex >= 0) {
root["noxIndex"] = noxIndex;
}
if (temp_1 >= 0) {
root["atmp"] = temp_1;
}
if (hum_1 >= 0) {
root["rhum"] = hum_1;
}
} else if (fw_mode == FW_MODE_PPT) {
if (tvocIndex > 0) {
root["tvoc_index"] = loopCount;
}
if (noxIndex > 0) {
root["nox_index"] = loopCount;
}
}
if (fw_mode == FW_MODE_PP || FW_MODE_PPT) {
root["pm01"] = (int)((pm01_1 + pm01_2) / 2);
root["pm02"] = (int)((pm25_1 + pm25_2) / 2);
root["pm003_count"] = (int)((pm03PCount_1 + pm03PCount_2) / 2);
root["atmp"] = (int)((temp_1 + temp_2) / 2);
root["rhum"] = (int)((hum_1 + hum_2) / 2);
root["channels"]["1"]["pm01"] = pm01_1;
root["channels"]["1"]["pm02"] = pm25_1;
root["channels"]["1"]["pm10"] = pm10_1;
root["channels"]["1"]["pm003_count"] = pm03PCount_1;
root["channels"]["1"]["atmp"] = temp_1;
root["channels"]["1"]["rhum"] = hum_1;
root["channels"]["2"]["pm01"] = pm01_2;
root["channels"]["2"]["pm02"] = pm25_2;
root["channels"]["2"]["pm10"] = pm10_2;
root["channels"]["2"]["pm003_count"] = pm03PCount_2;
root["channels"]["2"]["atmp"] = temp_2;
root["channels"]["2"]["rhum"] = hum_2;
}
/** Send data to sensor */
if (agServer.postToServer(getDevId(), JSON.stringify(root))) {
resetWatchdog();
}
loopCount++;
}
void resetWatchdog() {
Serial.println("Watchdog reset");
ag.watchdog.reset();
}
bool wifiMangerClientConnected(void) {
return WiFi.softAPgetStationNum() ? true : false;
}
// 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 */
ag.statusLed.setOff();
delay(2000);
if (WiFi.isConnected() == false) {
ledSmHandler(APP_SM_WIFI_MANAGER_CONNECT_FAILED);
}
}
String getNormalizedMac() {
String mac = WiFi.macAddress();
mac.replace(":", "");
mac.toLowerCase();
return mac;
}
void boardInit(void) {
if (Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin()) == false) {
failedHandler("Init I2C failed");
}
ag.watchdog.begin();
ag.button.begin();
ag.statusLed.begin();
/** detect sensor: PMS5003, PMS5003T, SGP41 and S8 */
if (ag.s8.begin(Serial1) == false) {
Serial.println("S8 not detect run mode 'PPT'");
fw_mode = FW_MODE_PPT;
/** De-initialize Serial1 */
Serial1.end();
}
if (ag.sgp41.begin(Wire) == false) {
if (fw_mode == FW_MODE_PST) {
failedHandler("Init SGP41 failed");
} else {
Serial.println("SGP41 not detect run mode 'PP'");
fw_mode = FW_MODE_PP;
}
}
if (ag.pms5003t_1.begin(Serial0) == false) {
failedHandler("Init PMS5003T_1 failed");
}
if (fw_mode != FW_MODE_PST) {
if (ag.pms5003t_2.begin(Serial1) == false) {
failedHandler("Init PMS5003T_2 failed");
}
}
if (fw_mode != FW_MODE_PST) {
pmsSchedule.setPeriod(2000);
}
Serial.printf("Firmware node: %s\r\n", getFwMode(fw_mode));
}
void failedHandler(String msg) {
while (true) {
Serial.println(msg);
vTaskDelay(1000);
}
}
void co2Calibration(void) {
/** Count down for co2CalibCountdown secs */
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);
}
if (ag.s8.setBaselineCalibration()) {
Serial.println("Calibration success");
delay(1000);
Serial.println("Wait for calib finish...");
int count = 0;
while (ag.s8.isBaseLineCalibrationDone() == false) {
delay(1000);
count++;
}
Serial.printf("Calib finish after %d sec\r\n", count);
delay(2000);
} else {
Serial.println("Calibration failure!!!");
delay(2000);
}
}
/**
* @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 (fw_mode == FW_MODE_PST) {
if (ag.pms5003t_1.readData()) {
pm01_1 = ag.pms5003t_1.getPm01Ae();
pm25_1 = ag.pms5003t_1.getPm25Ae();
pm25_1 = ag.pms5003t_1.getPm10Ae();
pm03PCount_1 = ag.pms5003t_1.getPm03ParticleCount();
temp_1 = ag.pms5003t_1.getTemperature();
hum_1 = ag.pms5003t_1.getRelativeHumidity();
}
} else {
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) {
pm01_1 = pm1Value01 / targetCount;
pm25_1 = pm1Value25 / targetCount;
pm10_1 = pm1Value10 / targetCount;
pm03PCount_1 = pm1PCount / targetCount;
temp_1 = pm1temp / targetCount;
hum_1 = pm1hum / targetCount;
pm01_2 = pm2Value01 / targetCount;
pm25_2 = pm2Value25 / targetCount;
pm10_2 = pm2Value10 / targetCount;
pm03PCount_2 = pm2PCount / targetCount;
temp_2 = pm2temp / targetCount;
hum_2 = pm2hum / targetCount;
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 co2Poll(void) {
co2Ppm = ag.s8.getCo2();
Serial.printf("CO2 index: %d\r\n", co2Ppm);
}
static void serverConfigPoll(void) {
if (agServer.pollServerConfig(getDevId())) {
/** Only support CO2 S8 sensor on FW_MODE_PST */
if (fw_mode == FW_MODE_PST) {
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;
}
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;
}
}
static const char *getFwMode(int mode) {
switch (mode) {
case FW_MODE_PST:
return "FW_MODE_PST";
case FW_MODE_PPT:
return "FW_MODE_PPT";
case FW_MODE_PP:
return "FW_MODE_PP";
default:
break;
}
return "FW_MODE_UNKNOW";
}

View File

@ -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 <AirGradient.h>
#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);
}

View File

@ -1,369 +0,0 @@
#include <AirGradient.h>
#include <HardwareSerial.h>
#include <Wire.h>
/**
* AirGradient use ESP32C3 has default Serial0 use for PMS5003, to print log
* should use esp-hal-log instead.
*/
#include <esp32-hal-log.h>
/**
* @brief Define test board
*/
#define TEST_BOARD_OUTDOOR_MONITOR_V1_3 0
#define TEST_BOARD_ONE_INDOOR_MONITOR_V9_0 1
/**
* @brief Define test sensor
*/
#define TEST_SENSOR_SenseAirS8 0
#define TEST_SENSOR_SHT4x 0
#define TEST_SENSOR_SGP4x 0
#define TEST_SWITCH 0
#define TEST_OLED 0
#if TEST_BOARD_OUTDOOR_MONITOR_V1_3
#define TEST_STATUS_LED 0
#define TEST_PMS5003T 1
#endif
#define TEST_WATCHDOG 1
#if TEST_BOARD_ONE_INDOOR_MONITOR_V9_0
#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);
#else
#error "Must enable board test
#endif
void setup() {
/** Print All AirGradient board define */
printBoardDef(NULL);
#if TEST_SENSOR_SenseAirS8
/** Cause Serial is use default for PMS, CO2S8 should be use Serial 1
* Serial 1 will be init by SenseAirS8 don't need to init any more on user
* code
*/
if (ag.s8.begin(Serial1)) {
log_i("CO2S8 sensor init success");
} else {
log_i("CO2S8 sensor init failure");
}
log_i("Start baseline calib");
if (ag.s8.setBaselineCalibration()) {
log_i("Calib success");
} else {
log_e("Calib failure");
}
delay(5000); // Wait for calib done
#endif
#if TEST_SENSOR_PMS5003
if (ag.pms5003.begin(Serial0)) {
log_i("PMS5003 sensor init success");
} else {
log_i("PMS5003 sensor init failure");
}
#endif
#if TEST_PMS5003T
/**
* @brief PMS5003T_1 alway connect to Serial (esp32c3 RXD0, RXD0)
*/
if (ag.pms5003t_1.begin(Serial)) {
log_i("PMS5003T_1 sensor init success");
} else {
log_i("PMS5003T_1 sensor init failure");
}
// TODO Only test without senseair s8 because it's share the UART bus
#if TEST_SENSOR_SenseAirS8 == 0
if (ag.pms5003t_2.begin(Serial1)) {
log_i("PMS5003T_2 sensor init success");
} else {
log_i("PMS5003T_2 sensor init failure");
}
#endif
#endif
#if TEST_SENSOR_SHT4x || TEST_SENSOR_SGP4x || TEST_OLED
Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin());
#endif
#if TEST_SENSOR_SHT4x
if (ag.sht.begin(Wire)) {
log_i("SHT init success");
} else {
log_i("SHT init failed");
}
#endif
#if TEST_SENSOR_SGP4x
if (ag.sgp41.begin(Wire)) {
log_i("SGP init success");
} else {
log_e("SGP init failure");
}
#endif
#if TEST_LED
led.begin();
#endif
#if TEST_SWITCH
ag.button.begin();
#endif
#if TEST_OLED
ag.display.begin(Wire);
ag.display.setTextSize(1);
ag.display.setCursor(0, 0);
ag.display.setTextColor(1);
ag.display.setText("180s to connect to wifi hostpost AC-xxxxx");
ag.display.show();
#endif
#if TEST_STATUS_LED
ag.statusLed.begin();
#endif
#if TEST_LED_BAR
ag.ledBar.begin();
#endif
#if TEST_WATCHDOG
ag.watchdog.begin();
#endif
}
void loop() {
uint32_t ms;
#if TEST_SENSOR_SenseAirS8
static uint32_t lastTime = 0;
/** Wait for sensor ready */
ms = (uint32_t)(millis() - lastTime);
if (ms >= 1000) {
lastTime = millis();
log_i("CO2: %d (PPM)", ag.s8.getCo2());
}
#endif
#if TEST_SENSOR_PMS5003
static uint32_t pmsTime = 0;
ms = (uint32_t)(millis() - pmsTime);
if (ms >= 1000) {
pmsTime = millis();
if (ag.pms5003.readData()) {
log_i("Passive mode PM 1.0 (ug/m3): %d", ag.pms5003.getPm10Ae());
log_i("Passive mode PM 2.5 (ug/m3): %d", ag.pms5003.getPm25Ae());
log_i("Passive mode PM 10.0 (ug/m3): %d", ag.pms5003.getPm10Ae());
} else {
log_i("PMS sensor read failure");
}
}
#endif
#if TEST_PMS5003T
static uint32_t pmsTime = 0;
ms = (uint32_t)(millis() - pmsTime);
if (ms >= 1000) {
pmsTime = millis();
if (ag.pms5003t_1.readData()) {
log_i("PMS5003_1 PM 1.0 (ug/m3): %d", ag.pms5003t_1.getPm10Ae());
log_i("PMS5003_1 PM 2.5 (ug/m3): %d", ag.pms5003t_1.getPm25Ae());
log_i("PMS5003_1 PM 10.0 (ug/m3): %d", ag.pms5003t_1.getPm10Ae());
log_i("PMS5003_1 PM 3.0 (ug/m3): %d",
ag.pms5003t_1.getPm03ParticleCount());
log_i("Temperature : %02f °C",
ag.pms5003t_1.getTemperature());
log_i("Humidity : %02f %%",
ag.pms5003t_1.getRelativeHumidity());
} else {
log_i("PMS5003_1 sensor read failure");
}
if (ag.pms5003t_2.readData()) {
log_i("PMS5003_2 PM 1.0 (ug/m3): %d", ag.pms5003t_2.getPm10Ae());
log_i("PMS5003_2 PM 2.5 (ug/m3): %d", ag.pms5003t_2.getPm25Ae());
log_i("PMS5003_2 PM 10.0 (ug/m3): %d", ag.pms5003t_2.getPm10Ae());
log_i("PMS5003_2 PM 3.0 (ug/m3): %d",
ag.pms5003t_2.getPm03ParticleCount());
// log_i("Temperature : %02f °C",
// ag.pms5003t_1.getTemperature());
// log_i("Humidity : %02f %%",
// ag.pms5003t_1.getRelativeHumidity());
} else {
log_i("PMS5003_2 sensor read failure");
}
}
#endif
#if TEST_SENSOR_SHT4x
/**
* @brief Get SHT sensor data each 1sec
*
*/
static uint32_t shtTime = 0;
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());
}
#endif
#if TEST_SENSOR_SGP4x
static uint32_t sgpTime;
ms = (uint32_t)(millis() - sgpTime);
if (ms >= 1000) {
sgpTime = millis();
uint16_t rawVOC;
log_i("Get TVOC: %d", ag.sgp41.getTvocIndex());
log_i("Get NOx: %d", ag.sgp41.getNoxIndex());
}
#endif
#if TEST_LED
static uint32_t ledTime;
#if TEST_BOARD_OUTDOOR_MONITOR_V1_3
// ms = (uint32_t)(millis() - ledTime);
// if(ms >= 500)
// {
// ledTime = millis();
// led.ledToggle();
// }
#elif TEST_BOARD_ONE_INDOOR_MONITOR_V9_0
static int ledIndex;
static int ledIndexOld;
ms = (uint32_t)(millis() - ledTime);
if (ms >= 50) {
ledTime = millis();
if (ledIndex == ledIndexOld) {
led.ledOff();
} else {
// Turn last LED off
led.ledSetColor(0, 0, 0, ledIndexOld);
}
// Turn new led ON
led.ledSetColor(255, 0, 0, ledIndex);
ledIndexOld = ledIndex;
ledIndex++;
if (ledIndex >= led.getNumberOfLed()) {
ledIndex = 0;
}
}
#else
#endif
#endif
#if TEST_SWITCH
static PushButton::State stateOld = PushButton::State::BUTTON_RELEASED;
PushButton::State state = ag.button.getState();
if (state != stateOld) {
stateOld = state;
log_i("Button state changed: %s", ag.button.toString(state).c_str());
if (state == PushButton::State::BUTTON_PRESSED) {
ag.statusLed.setOn();
} else {
ag.statusLed.setOff();
}
}
#endif
#if TEST_LED_BAR
static uint32_t ledTime;
static uint8_t ledNum = 0;
static uint8_t ledIndex = 0;
static uint8_t ledStep = 0;
static bool ledOn = false;
if (ledNum == 0) {
ledNum = ag.ledBar.getNumberOfLed();
log_i("Get number of led: %d", ledNum);
if (ledNum) {
ag.ledBar.setBrighness(0xff);
for (int i = 0; i < ledNum; i++) {
// ag.ledBar.setColor(0xff, 0xff, 0xff, i);
// ag.ledBar.setColor(204, 136, 153, i);
// ag.ledBar.setColor(204, 0, 0, i);
// ag.ledBar.setColor(204, 100, 153, i);
ag.ledBar.setColor(0, 136, 255, i);
}
ag.ledBar.show();
}
} else {
ms = (uint32_t)(millis() - ledTime);
if (ms >= 500) {
ledTime = millis();
switch (ledStep) {
case 0: {
ag.ledBar.setColor(255, 0, 0, ledIndex);
ledIndex++;
if (ledIndex >= ledNum) {
ag.ledBar.setColor(0, 0, 0);
ledIndex = 0;
ledStep = 1;
}
ag.ledBar.show();
break;
}
case 1: {
ledIndex++;
if (ledIndex >= ledNum) {
ag.ledBar.setColor(255, 0, 0);
ag.ledBar.show();
ledIndex = ledNum - 1;
ledStep = 2;
}
break;
}
case 2: {
if (ledOn) {
ag.ledBar.setColor(255, 0, 0);
} else {
ag.ledBar.setColor(0, 0, 0);
}
ledOn = !ledOn;
ag.ledBar.show();
ledIndex--;
if (ledIndex == 0) {
ag.ledBar.setColor(0, 0, 0);
ag.ledBar.show();
ledStep = 0;
ledIndex = 0;
}
break;
}
default:
break;
}
}
}
#endif
#if TEST_WATCHDOG
static uint32_t wdgTime;
ms = (uint32_t)(millis() - wdgTime);
if (ms >= (1000 * 60)) {
wdgTime = millis();
/** Reset watchdog reach 1 minutes */
ag.watchdog.reset();
}
#endif
}

View File

@ -1,164 +0,0 @@
#include <AirGradient.h>
#include <Wire.h>
/**
* @brief Define test board
*/
#define TEST_BOARD_DIY_BASIC_KIT 0
#define TEST_BOARD_DIY_PRO_INDOOR_V4_2 1
/**
* @brief Define test sensor
*/
#define TEST_SENSOR_SenseAirS8 0
#define TEST_SENSOR_PMS5003 0
#define TEST_SENSOR_SHT4x 0
#define TEST_SENSOR_SGP4x 1
#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);
#else
#error "Board test not defined"
#endif
void setup() {
Serial.begin(115200);
/** Print All AirGradient board define */
printBoardDef(&Serial);
#if TEST_SENSOR_SenseAirS8
if (ag.s8.begin(&Serial) == true) {
Serial.println("CO2S8 sensor init success");
} else {
Serial.println("CO2S8 sensor init failure");
}
if (ag.s8.setBaselineCalibration()) {
Serial.println("Manual calib success");
} else {
Serial.println("Manual calib failure");
}
delay(5000);
#endif
#if TEST_SENSOR_PMS5003
if (ag.pms5003.begin(&Serial) == true) {
Serial.println("PMS5003 sensor init success");
} else {
Serial.println("PMS5003 sensor init failure");
}
#endif
#if TEST_SENSOR_SHT4x || TEST_SENSOR_SGP4x || TEST_OLED
Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin());
#endif
#if TEST_SENSOR_SHT4x
if (ag.sht.begin(Wire, Serial)) {
Serial.println("SHT init success");
} else {
Serial.println("SHT init failed");
}
#endif
#if TEST_SENSOR_SGP4x
if (ag.sgp41.begin(Wire, Serial)) {
Serial.println("SGP init succses");
} else {
Serial.println("SGP init failure");
}
#endif
#if TEST_SWITCH
ag.button.begin(Serial);
#endif
#if TEST_OLED
ag.display.begin(Wire, Serial);
ag.display.setTextSize(1);
ag.display.setCursor(0, 0);
ag.display.setTextColor(1);
ag.display.setText("Hello");
ag.display.show();
#endif
}
void loop() {
uint32_t ms;
#if TEST_SENSOR_SenseAirS8
static uint32_t lastTime = 0;
/** Wait for sensor ready */
// if(co2s8.isReady())
// {
// Get sensor data each 1sec
ms = (uint32_t)(millis() - lastTime);
if (ms >= 1000) {
lastTime = millis();
Serial.printf("CO2: %d (PMM)\r\n", ag.s8.getCo2());
}
// }
#endif
#if TEST_SENSOR_PMS5003
static uint32_t pmsTime = 0;
ms = (uint32_t)(millis() - pmsTime);
if (ms >= 1000) {
pmsTime = millis();
if (ag.pms5003.readData()) {
Serial.printf("Passive mode PM 1.0 (ug/m3): %d\r\n",
ag.pms5003.getPm01Ae());
Serial.printf("Passive mode PM 2.5 (ug/m3): %d\r\n",
ag.pms5003.getPm25Ae());
Serial.printf("Passive mode PM 10.5 (ug/m3): %d\r\n",
ag.pms5003.getPm10Ae());
}
}
#endif
#if TEST_SENSOR_SHT4x
/**
* @brief Get SHT sensor data each 1sec
*
*/
static uint32_t shtTime = 0;
ms = (uint32_t)(millis() - shtTime);
if (ms >= 1000) {
shtTime = millis();
float temperature, humidity;
Serial.printf("SHT Temperature: %f, Humidity: %f\r\n",
ag.sht.getTemperature(), ag.sht.getRelativeHumidity());
}
#endif
#if TEST_SENSOR_SGP4x
static uint32_t sgpTime;
ms = (uint32_t)(millis() - sgpTime);
/***
* Must call this task on loop and avoid delay on loop over 1000 ms
*/
ag.sgp41.handle();
if (ms >= 1000) {
sgpTime = millis();
Serial.printf("SGP TVOC: %d, NOx: %d\r\n", ag.sgp41.getTvocIndex(),
ag.sgp41.getNoxIndex());
}
#endif
#if TEST_SWITCH
static PushButton::State stateOld = PushButton::State::BUTTON_RELEASED;
PushButton::State state = ag.button.getState();
if (state != stateOld) {
stateOld = state;
Serial.printf("Button state changed: %s\r\n",
ag.button.toString(state).c_str());
}
#endif
}

View File

@ -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 <AirGradient.h>
#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);

View File

@ -0,0 +1,39 @@
#include <AirGradient.h>
#if defined(ESP8266)
AirGradient ag(DIY_BASIC);
#else
AirGradient ag(ONE_INDOOR);
#endif
void failedHandler(String msg);
void setup() {
Serial.begin(115200);
Serial.println("Hello");
Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin());
delay(1000);
if (ag.sht.begin(Wire) == false) {
failedHandler("SHT init failed");
}
}
void loop() {
if (ag.sht.measure()) {
float hum = ag.sht.getRelativeHumidity();
float temp = ag.sht.getTemperature();
Serial.printf("Get temperature: %f\r\n", temp);
Serial.printf(" Get humidity: %f\r\n", hum);
} else {
Serial.println("Measure failed");
}
delay(1000);
}
void failedHandler(String msg) {
while (true) {
Serial.println(msg);
delay(1000);
}
}

View File

@ -1,5 +1,5 @@
name=AirGradient Air Quality Sensor
version=3.0.0
version=3.0.3
author=AirGradient <support@airgradient.com>
maintainer=AirGradient <support@airgradient.com>
sentence=ESP32-C3 / ESP8266 library for air quality monitor measuring PM, CO2, Temperature, TVOC and Humidity with OLED display.

View File

@ -1,11 +1,11 @@
#include "AirGradient.h"
#define AG_LIB_VER "3.0.0"
#define AG_LIB_VER "3.0.3"
AirGradient::AirGradient(BoardType type)
: pms5003(type), pms5003t_1(type), pms5003t_2(type), s8(type), sht(type), sgp41(type),
: pms5003(type), pms5003t_1(type), pms5003t_2(type), s8(type), sgp41(type),
display(type), boardType(type), button(type), statusLed(type),
ledBar(type), watchdog(type) {}
ledBar(type), watchdog(type), sht(type) {}
/**
* @brief Get pin number for I2C SDA

View File

@ -1,18 +1,22 @@
#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 "sht/sht4x.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/sht.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 +25,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 +42,19 @@ public:
S8 s8;
/**
* @brief Temperature and humidity sensor
* @brief Temperature and humidity sensor supported SHT3x and SHT4x
*
*/
Sht sht;
/**
* @brief TVOC and NOx sensor
* @brief SGP41 TVOC and NOx sensor
*
*/
Sgp41 sgp41;
/**
* @brief Display
* @brief OLED Display
*
*/
Display display;
@ -55,18 +68,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:

View File

@ -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");
}

View File

@ -1,10 +1,14 @@
#ifndef _AIR_GRADIENT_OLED_H_
#define _AIR_GRADIENT_OLED_H_
#include "../bsp/BoardDef.h"
#include "../main/BoardDef.h"
#include <Arduino.h>
#include <Wire.h>
/**
* @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_ */

View File

@ -1,14 +0,0 @@
---
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']
...

View File

@ -1,70 +0,0 @@
app/bin/
app/pde.jar
build/macosx/work/
arduino-core/bin/
arduino-core/arduino-core.jar
hardware/arduino/bootloaders/caterina_LUFA/Descriptors.o
hardware/arduino/bootloaders/caterina_LUFA/Descriptors.lst
hardware/arduino/bootloaders/caterina_LUFA/Caterina.sym
hardware/arduino/bootloaders/caterina_LUFA/Caterina.o
hardware/arduino/bootloaders/caterina_LUFA/Caterina.map
hardware/arduino/bootloaders/caterina_LUFA/Caterina.lst
hardware/arduino/bootloaders/caterina_LUFA/Caterina.lss
hardware/arduino/bootloaders/caterina_LUFA/Caterina.elf
hardware/arduino/bootloaders/caterina_LUFA/Caterina.eep
hardware/arduino/bootloaders/caterina_LUFA/.dep/
build/*.zip
build/*.tar.bz2
build/windows/work/
build/windows/*.zip
build/windows/*.tgz
build/windows/*.tar.bz2
build/windows/libastylej*
build/windows/liblistSerials*
build/windows/arduino-*.zip
build/windows/dist/*.tar.gz
build/windows/dist/*.tar.bz2
build/windows/launch4j-*.tgz
build/windows/launch4j-*.zip
build/windows/launcher/launch4j
build/windows/WinAVR-*.zip
build/macosx/arduino-*.zip
build/macosx/dist/*.tar.gz
build/macosx/dist/*.tar.bz2
build/macosx/*.tar.bz2
build/macosx/libastylej*
build/macosx/appbundler*.jar
build/macosx/appbundler*.zip
build/macosx/appbundler
build/macosx/appbundler-1.0ea-arduino?
build/macosx/appbundler-1.0ea-arduino*.zip
build/macosx/appbundler-1.0ea-upstream*.zip
build/linux/work/
build/linux/dist/*.tar.gz
build/linux/dist/*.tar.bz2
build/linux/*.tgz
build/linux/*.tar.xz
build/linux/*.tar.bz2
build/linux/*.zip
build/linux/libastylej*
build/linux/liblistSerials*
build/shared/arduino-examples*
build/shared/reference*.zip
build/shared/Edison*.zip
build/shared/Galileo*.zip
build/shared/WiFi101-Updater-ArduinoIDE-Plugin*.zip
test-bin
*.iml
.idea
.DS_Store
.directory
hardware/arduino/avr/libraries/Bridge/examples/XivelyClient/passwords.h
avr-toolchain-*.zip
/app/nbproject/private/
/arduino-core/nbproject/private/
/app/build/
/arduino-core/build/
manifest.mf
nbbuild.xml
nbproject

View File

@ -1,103 +0,0 @@
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/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

View File

@ -1,19 +0,0 @@
# 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]
Defined I2C addresses for different sensor models.
You must now pass the I2C address to use in the begin() method of SensirionI2cSht4x
Improved README
## [0.1.0] - 2021-07-27
Initial release
[0.1.0]: https://github.com/Sensirion/arduino-i2c-sht4x/releases/tag/0.1.0

View File

@ -1,206 +0,0 @@
# Sensirion I²C SHT4X Arduino Library
This is the Sensirion SHT4X library for Arduino allowing you to
communicate with a sensor of the SHT4X family over I²C.
<img src="images/SHT4x.png" width="300px">
Click [here](https://sensirion.com/products/catalog/SEK-SHT40/) to learn more about the Sensirion SHT4X sensor family.
## Supported sensor types
| Sensor name | I²C Addresses |
| ------------- | -------------- |
|[SHT40](https://sensirion.com/products/catalog/SHT40/)| **0x44**, 0x45|
|[SHT41](https://sensirion.com/products/catalog/SHT41/)| **0x44**, 0x45|
|[SHT45](https://sensirion.com/products/catalog/SHT45/)| **0x44**, 0x45|
The following instructions and examples use a *SHT40*.
## 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 SHT4X` 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 SHT4X to the standard I²C bus of your Arduino board:
<img src="images/SHT40_pinout.png" width="300px">
| *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 | 1.1V to 3.6V
The recommended voltage is 3.3V.
Please refer to the datasheet for proper circuit setup. There are 3rd party boards for easy connection of the SHT4x sensor to your Arduino Board.
### Board specific wiring
You will find pinout schematics for recommended board models below:
<details><summary>Arduino Uno</summary>
<p>
| *SHT4X* | *SHT4X Pin* | *Cable Color* | *Board Pin* |
| :---: | --- | --- | --- |
| SDA | 1 | green | D18/SDA |
| GND | 2 | black | GND |
| SCL | 3 | yellow | D19/SCL |
| VDD | 4 | red | 3.3V |
<img src="images/Arduino-Uno-Rev3-i2c-pinout-3.3V.png" width="600px">
</p>
</details>
<details><summary>Arduino Nano</summary>
<p>
| *SHT4X* | *SHT4X Pin* | *Cable Color* | *Board Pin* |
| :---: | --- | --- | --- |
| SDA | 1 | green | A4 |
| GND | 2 | black | GND |
| SCL | 3 | yellow | A5 |
| VDD | 4 | red | 3.3V |
<img src="images/Arduino-Nano-i2c-pinout-3.3V.png" width="600px">
</p>
</details>
<details><summary>Arduino Micro</summary>
<p>
| *SHT4X* | *SHT4X Pin* | *Cable Color* | *Board Pin* |
| :---: | --- | --- | --- |
| SDA | 1 | green | D2/SDA |
| GND | 2 | black | GND |
| SCL | 3 | yellow | ~D3/SCL |
| VDD | 4 | red | 3.3V |
<img src="images/Arduino-Micro-i2c-pinout-3.3V.png" width="600px">
</p>
</details>
<details><summary>Arduino Mega 2560</summary>
<p>
| *SHT4X* | *SHT4X Pin* | *Cable Color* | *Board Pin* |
| :---: | --- | --- | --- |
| SDA | 1 | green | D20/SDA |
| GND | 2 | black | GND |
| SCL | 3 | yellow | D21/SCL |
| VDD | 4 | red | 3.3V |
<img src="images/Arduino-Mega-2560-Rev3-i2c-pinout-3.3V.png" width="600px">
</p>
</details>
<details><summary>ESP32 DevKitC</summary>
<p>
| *SHT4X* | *SHT4X Pin* | *Cable Color* | *Board Pin* |
| :---: | --- | --- | --- |
| SDA | 1 | green | GPIO 21 |
| GND | 2 | black | GND |
| SCL | 3 | yellow | GPIO 22 |
| VDD | 4 | red | 3V3 |
<img src="images/esp32-devkitc-i2c-pinout-3.3V.png" width="600px">
</p>
</details>
## Quick Start
1. Install the libraries and dependencies according to [Installation of the library](#installation-of-the-library)
2. Connect the SHT4X 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 SHT4X``exampleUsage`
The provided example is working with a SHT40, 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/SensirionI2CSht4x.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).

View File

@ -1,89 +0,0 @@
/*
* THIS FILE IS AUTOMATICALLY GENERATED
*
* Generator: sensirion-driver-generator 0.32.0
* Product: sht4x
* Model-Version: 2.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 <Arduino.h>
#include <SensirionI2CSht4x.h>
#include <Wire.h>
SensirionI2CSht4x sensor;
static char errorMessage[64];
static int16_t error;
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(100);
}
Wire.begin();
sensor.begin(Wire, SHT40_I2C_ADDR_44);
sensor.softReset();
delay(10);
uint32_t serialNumber = 0;
error = sensor.serialNumber(serialNumber);
if (error != NO_ERROR) {
Serial.print("Error trying to execute serialNumber(): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
Serial.print("serialNumber: ");
Serial.print(serialNumber);
Serial.println();
}
void loop() {
float aTemperature = 0.0;
float aHumidity = 0.0;
delay(20);
error = sensor.measureLowestPrecision(aTemperature, aHumidity);
if (error != NO_ERROR) {
Serial.print("Error trying to execute measureLowestPrecision(): ");
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();
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

View File

@ -1,46 +0,0 @@
#######################################
# Syntax Coloring Map
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
SensirionI2CSht4x KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
measureHighPrecision KEYWORD2
measureMediumPrecision KEYWORD2
measureLowestPrecision KEYWORD2
activateHighestHeaterPowerLong KEYWORD2
activateHighestHeaterPowerShort KEYWORD2
activateMediumHeaterPowerLong KEYWORD2
activateMediumHeaterPowerShort KEYWORD2
activateLowestHeaterPowerLong KEYWORD2
activateLowestHeaterPowerShort KEYWORD2
measureHighPrecisionTicks KEYWORD2
measureMediumPrecisionTicks KEYWORD2
measureLowestPrecisionTicks KEYWORD2
activateHighestHeaterPowerLongTicks KEYWORD2
activateHighestHeaterPowerShortTicks KEYWORD2
activateMediumHeaterPowerLongTicks KEYWORD2
activateMediumHeaterPowerShortTicks KEYWORD2
activateLowestHeaterPowerLongTicks KEYWORD2
activateLowestHeaterPowerShortTicks KEYWORD2
serialNumber KEYWORD2
softReset KEYWORD2
signalTemperature KEYWORD2
signalHumidity KEYWORD2
#######################################
# Instances (KEYWORD2)
#######################################
sensor KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View File

@ -1,11 +0,0 @@
name=Sensirion I2C SHT4x
version=1.0.0
author=Sensirion
maintainer=Sensirion
sentence=Library for the SHT4X sensor family by Sensirion
paragraph=Enables you to use the SHT4X sensor family via I2C.
url=https://github.com/Sensirion/arduino-i2c-sht4x
category=Sensors
architectures=*
depends=Sensirion Core
includes=SensirionI2CSht4x.h

View File

@ -1,7 +0,0 @@
generator_version: 0.32.0
model_version: 2.0.0
dg_status:
- released
is_manually_modified: true
first_generated: '2021-07-06 15:05'
last_generated: '2023-10-18 07:36'

View File

@ -1,436 +0,0 @@
/*
* THIS FILE IS AUTOMATICALLY GENERATED
*
* Generator: sensirion-driver-generator 0.32.0
* Product: sht4x
* Model-Version: 2.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 "SensirionI2CSht4x.h"
#include "../../SensirionCore/src/SensirionCore.h"
#include <Arduino.h>
static uint8_t communication_buffer[6] = {0};
SensirionI2CSht4x::SensirionI2CSht4x() {
}
float SensirionI2CSht4x::signalTemperature(uint16_t temperatureTicks) {
float temperature = 0.0;
temperature = (float)(temperatureTicks);
temperature = (float)((temperature * 175.0) / 65535.0) - 45.0;
return temperature;
}
float SensirionI2CSht4x::signalHumidity(uint16_t humidityTicks) {
float humidity = 0.0;
humidity = (float)(humidityTicks);
humidity = (float)((humidity * 125.0) / 65535.0) - 6.0;
return humidity;
}
int16_t SensirionI2CSht4x::measureHighPrecision(float& aTemperature,
float& aHumidity) {
uint16_t tempTicks = 0;
uint16_t humiTicks = 0;
int16_t localError = 0;
localError = measureHighPrecisionTicks(tempTicks, humiTicks);
if (localError != NO_ERROR) {
return localError;
}
aTemperature = signalTemperature(tempTicks);
aHumidity = signalHumidity(humiTicks);
return localError;
}
int16_t SensirionI2CSht4x::measureMediumPrecision(float& aTemperature,
float& aHumidity) {
uint16_t tempTicks = 0;
uint16_t humiTicks = 0;
int16_t localError = 0;
localError = measureMediumPrecisionTicks(tempTicks, humiTicks);
if (localError != NO_ERROR) {
return localError;
}
aTemperature = signalTemperature(tempTicks);
aHumidity = signalHumidity(humiTicks);
return localError;
}
int16_t SensirionI2CSht4x::measureLowestPrecision(float& aTemperature,
float& aHumidity) {
uint16_t tempTicks = 0;
uint16_t humiTicks = 0;
int16_t localError = 0;
localError = measureLowestPrecisionTicks(tempTicks, humiTicks);
if (localError != NO_ERROR) {
return localError;
}
aTemperature = signalTemperature(tempTicks);
aHumidity = signalHumidity(humiTicks);
return localError;
}
int16_t SensirionI2CSht4x::activateHighestHeaterPowerLong(float& aTemperature,
float& aHumidity) {
uint16_t tempTicks = 0;
uint16_t humiTicks = 0;
int16_t localError = 0;
localError = activateHighestHeaterPowerLongTicks(tempTicks, humiTicks);
if (localError != NO_ERROR) {
return localError;
}
aTemperature = signalTemperature(tempTicks);
aHumidity = signalHumidity(humiTicks);
return localError;
}
int16_t SensirionI2CSht4x::activateHighestHeaterPowerShort(float& aTemperature,
float& aHumidity) {
uint16_t tempTicks = 0;
uint16_t humiTicks = 0;
int16_t localError = 0;
localError = activateHighestHeaterPowerShortTicks(tempTicks, humiTicks);
if (localError != NO_ERROR) {
return localError;
}
aTemperature = signalTemperature(tempTicks);
aHumidity = signalHumidity(humiTicks);
return localError;
}
int16_t SensirionI2CSht4x::activateMediumHeaterPowerLong(float& aTemperature,
float& aHumidity) {
uint16_t tempTicks = 0;
uint16_t humiTicks = 0;
int16_t localError = 0;
localError = activateMediumHeaterPowerLongTicks(tempTicks, humiTicks);
if (localError != NO_ERROR) {
return localError;
}
aTemperature = signalTemperature(tempTicks);
aHumidity = signalHumidity(humiTicks);
return localError;
}
int16_t SensirionI2CSht4x::activateMediumHeaterPowerShort(float& aTemperature,
float& aHumidity) {
uint16_t tempTicks = 0;
uint16_t humiTicks = 0;
int16_t localError = 0;
localError = activateMediumHeaterPowerShortTicks(tempTicks, humiTicks);
if (localError != NO_ERROR) {
return localError;
}
aTemperature = signalTemperature(tempTicks);
aHumidity = signalHumidity(humiTicks);
return localError;
}
int16_t SensirionI2CSht4x::activateLowestHeaterPowerLong(float& aTemperature,
float& aHumidity) {
uint16_t tempTicks = 0;
uint16_t humiTicks = 0;
int16_t localError = 0;
localError = activateLowestHeaterPowerLongTicks(tempTicks, humiTicks);
if (localError != NO_ERROR) {
return localError;
}
aTemperature = signalTemperature(tempTicks);
aHumidity = signalHumidity(humiTicks);
return localError;
}
int16_t SensirionI2CSht4x::activateLowestHeaterPowerShort(float& aTemperature,
float& aHumidity) {
uint16_t tempTicks = 0;
uint16_t humiTicks = 0;
int16_t localError = 0;
localError = activateLowestHeaterPowerShortTicks(tempTicks, humiTicks);
if (localError != NO_ERROR) {
return localError;
}
aTemperature = signalTemperature(tempTicks);
aHumidity = signalHumidity(humiTicks);
return localError;
}
int16_t SensirionI2CSht4x::measureHighPrecisionTicks(uint16_t& temperatureTicks,
uint16_t& humidityTicks) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt8Command(0xfd, buffer_ptr, 6);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
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
SensirionI2CSht4x::measureMediumPrecisionTicks(uint16_t& temperatureTicks,
uint16_t& humidityTicks) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt8Command(0xf6, 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
SensirionI2CSht4x::measureLowestPrecisionTicks(uint16_t& temperatureTicks,
uint16_t& humidityTicks) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt8Command(0xe0, buffer_ptr, 6);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(2);
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 SensirionI2CSht4x::activateHighestHeaterPowerLongTicks(
uint16_t& temperatureTicks, uint16_t& humidityTicks) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt8Command(0x39, buffer_ptr, 6);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(1100);
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 SensirionI2CSht4x::activateHighestHeaterPowerShortTicks(
uint16_t& temperatureTicks, uint16_t& humidityTicks) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt8Command(0x32, buffer_ptr, 6);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(110);
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 SensirionI2CSht4x::activateMediumHeaterPowerLongTicks(
uint16_t& temperatureTicks, uint16_t& humidityTicks) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt8Command(0x2f, buffer_ptr, 6);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(1100);
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 SensirionI2CSht4x::activateMediumHeaterPowerShortTicks(
uint16_t& temperatureTicks, uint16_t& humidityTicks) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt8Command(0x24, buffer_ptr, 6);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(110);
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 SensirionI2CSht4x::activateLowestHeaterPowerLongTicks(
uint16_t& temperatureTicks, uint16_t& humidityTicks) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt8Command(0x1e, buffer_ptr, 6);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(1100);
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 SensirionI2CSht4x::activateLowestHeaterPowerShortTicks(
uint16_t& temperatureTicks, uint16_t& humidityTicks) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt8Command(0x15, buffer_ptr, 6);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(110);
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 SensirionI2CSht4x::serialNumber(uint32_t& serialNumber) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt8Command(0x89, buffer_ptr, 6);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
SensirionI2CRxFrame rxFrame(buffer_ptr, 6);
localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 6,
rxFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
localError |= rxFrame.getUInt32(serialNumber);
return localError;
}
int16_t SensirionI2CSht4x::softReset() {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt8Command(0x94, buffer_ptr, 2);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
return localError;
}
void SensirionI2CSht4x::begin(TwoWire& i2cBus, uint8_t i2cAddress) {
_i2cBus = &i2cBus;
_i2cAddress = i2cAddress;
}

View File

@ -1,424 +0,0 @@
/*
* THIS FILE IS AUTOMATICALLY GENERATED
*
* Generator: sensirion-driver-generator 0.32.0
* Product: sht4x
* Model-Version: 2.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 SENSIRIONI2CSHT4X_H
#define SENSIRIONI2CSHT4X_H
#include <Wire.h>
#define NO_ERROR 0
#define SHT40_I2C_ADDR_44 0x44
#define SHT40_I2C_ADDR_45 0x45
#define SHT41_I2C_ADDR_44 0x44
#define SHT41_I2C_ADDR_45 0x45
#define SHT45_I2C_ADDR_44 0x44
#define SHT45_I2C_ADDR_45 0x45
typedef enum {
MEASURE_HIGH_PRECISION_TICKS_CMD_ID = 0xfd,
MEASURE_MEDIUM_PRECISION_TICKS_CMD_ID = 0xf6,
MEASURE_LOWEST_PRECISION_TICKS_CMD_ID = 0xe0,
ACTIVATE_HIGHEST_HEATER_POWER_LONG_TICKS_CMD_ID = 0x39,
ACTIVATE_HIGHEST_HEATER_POWER_SHORT_TICKS_CMD_ID = 0x32,
ACTIVATE_MEDIUM_HEATER_POWER_LONG_TICKS_CMD_ID = 0x2f,
ACTIVATE_MEDIUM_HEATER_POWER_SHORT_TICKS_CMD_ID = 0x24,
ACTIVATE_LOWEST_HEATER_POWER_LONG_TICKS_CMD_ID = 0x1e,
ACTIVATE_LOWEST_HEATER_POWER_SHORT_TICKS_CMD_ID = 0x15,
SERIAL_NUMBER_CMD_ID = 0x89,
SOFT_RESET_CMD_ID = 0x94,
} CmdId;
class SensirionI2CSht4x {
public:
SensirionI2CSht4x();
/**
* @brief Initializes the SHT4x class.
*
* @param i2cBus Arduino stream object to be used for communication.
*/
void begin(TwoWire& i2cBus, uint8_t i2cAddress);
/**
* @brief Run measurement with high precision
*
* SHT4x command for a single shot measurement with high repeatability.
*
* @param[out] aTemperature Converted from ticks to degrees celsius by (175
* * ticks_value / 65535) - 45
* @param[out] aHumidity Converted from ticks to percent relative humdity by
* (125 * ticks_value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t measureHighPrecision(float& aTemperature, float& aHumidity);
/**
* @brief Run measurement with medium precision
*
* SHT4x command for a single shot measurement with medium repeatability.
*
* @param[out] aTemperature Converted from ticks to degrees celsius by (175
* * ticks_value / 65535) - 45
* @param[out] aHumidity Converted from ticks to percent relative humdity by
* (125 * ticks_value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t measureMediumPrecision(float& aTemperature, float& aHumidity);
/**
* @brief Run measurement with low precision
*
* SHT4x command for a single shot measurement with low repeatability.
*
* @param[out] aTemperature Converted from ticks to degrees celsius by (175
* * ticks_value / 65535) - 45
* @param[out] aHumidity Converted from ticks to percent relative humdity by
* (125 * ticks_value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t measureLowestPrecision(float& aTemperature, float& aHumidity);
/**
* @brief Run measurement with high precision and the highest power heater
* for 1s
*
* SHT4x command to activate highest heater power and perform a single shot
* high precision measurement for 1s.
*
* @param[out] aTemperature Converted from ticks to degrees celsius by (175
* * ticks_value / 65535) - 45
* @param[out] aHumidity Converted from ticks to percent relative humdity by
* (125 * ticks_value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t activateHighestHeaterPowerLong(float& aTemperature,
float& aHumidity);
/**
* @brief Run measurement with high precision and the highest power heater
* for 0.1s
*
* SHT4x command to activate highest heater power and perform a single shot
* high precision measurement for 0.1s.
*
* @param[out] aTemperature Converted from ticks to degrees celsius by (175
* * ticks_value / 65535) - 45
* @param[out] aHumidity Converted from ticks to percent relative humdity by
* (125 * ticks_value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t activateHighestHeaterPowerShort(float& aTemperature,
float& aHumidity);
/**
* @brief Run measurement with high precision and the medium power heater
* for 1s
*
* SHT4x command to activate medium heater power and perform a single shot
* high precision measurement for 1s.
*
* @param[out] aTemperature Converted from ticks to degrees celsius by (175
* * ticks_value / 65535) - 45
* @param[out] aHumidity Converted from ticks to percent relative humdity by
* (125 * ticks_value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t activateMediumHeaterPowerLong(float& aTemperature,
float& aHumidity);
/**
* @brief Run measurement with high precision and the medium power heater
* for 0.1s
*
* SHT4x command to activate medium heater power and perform a single shot
* high precision measurement for 0.1s.
*
* @param[out] aTemperature Converted from ticks to degrees celsius by (175
* * ticks_value / 65535) - 45
* @param[out] aHumidity Converted from ticks to percent relative humdity by
* (125 * ticks_value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t activateMediumHeaterPowerShort(float& aTemperature,
float& aHumidity);
/**
* @brief Run measurement with high precision and the lowest power heater
* for 1s
*
* SHT4x command to activate lowest heater power and perform a single shot
* high precision measurement for 1s.
*
* @param[out] aTemperature Converted from ticks to degrees celsius by (175
* * ticks_value / 65535) - 45
* @param[out] aHumidity Converted from ticks to percent relative humdity by
* (125 * ticks_value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t activateLowestHeaterPowerLong(float& aTemperature,
float& aHumidity);
/**
* @brief Run measurement with high precision and the lowest power heater
* for 0.1s
*
* SHT4x command to activate lowest heater power and perform a single shot
* high precision measurement for 0.1s.
*
* @param[out] aTemperature Converted from ticks to degrees celsius by (175
* * ticks_value / 65535) - 45
* @param[out] aHumidity Converted from ticks to percent relative humdity by
* (125 * ticks_value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t activateLowestHeaterPowerShort(float& aTemperature,
float& aHumidity);
/**
* @brief measureHighPrecisionTicks
*
* SHT4x command for a single shot measurement with high repeatability.
*
* @param[out] temperatureTicks Temperature ticks. Convert to degrees
* celsius by (175 * value / 65535) - 45
* @param[out] humidityTicks Humidity ticks. Convert to degrees celsius by
* (125
* * value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t measureHighPrecisionTicks(uint16_t& temperatureTicks,
uint16_t& humidityTicks);
/**
* @brief measureMediumPrecisionTicks
*
* SHT4x command for a single shot measurement with medium repeatability.
*
* @param[out] temperatureTicks Temperature ticks. Convert to degrees
* celsius by (175 * value / 65535) - 45
* @param[out] humidityTicks Humidity ticks. Convert to degrees celsius by
* (125
* * value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t measureMediumPrecisionTicks(uint16_t& temperatureTicks,
uint16_t& humidityTicks);
/**
* @brief measureLowestPrecisionTicks
*
* SHT4x command for a single shot measurement with lowest repeatability.
*
* @param[out] temperatureTicks Temperature ticks. Convert to degrees
* celsius by (175 * value / 65535) - 45
* @param[out] humidityTicks Humidity ticks. Convert to degrees celsius by
* (125
* * value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t measureLowestPrecisionTicks(uint16_t& temperatureTicks,
uint16_t& humidityTicks);
/**
* @brief activateHighestHeaterPowerLongTicks
*
* SHT4x command to activate highest heater power and perform a single shot
* high precision measurement for 1s.
*
* @param[out] temperatureTicks Temperature ticks. Convert to degrees
* celsius by (175 * value / 65535) - 45
* @param[out] humidityTicks Humidity ticks. Convert to degrees celsius by
* (125
* * value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t activateHighestHeaterPowerLongTicks(uint16_t& temperatureTicks,
uint16_t& humidityTicks);
/**
* @brief activateHighestHeaterPowerShortTicks
*
* SHT4x command to activate highest heater power and perform a single shot
* high precision measurement for 0.1s.
*
* @param[out] temperatureTicks Temperature ticks. Convert to degrees
* celsius by (175 * value / 65535) - 45
* @param[out] humidityTicks Humidity ticks. Convert to degrees celsius by
* (125
* * value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t activateHighestHeaterPowerShortTicks(uint16_t& temperatureTicks,
uint16_t& humidityTicks);
/**
* @brief activateMediumHeaterPowerLongTicks
*
* SHT4x command to activate medium heater power and perform a single shot
* high precision measurement for 1s.
*
* @param[out] temperatureTicks Temperature ticks. Convert to degrees
* celsius by (175 * value / 65535) - 45
* @param[out] humidityTicks Humidity ticks. Convert to degrees celsius by
* (125
* * value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t activateMediumHeaterPowerLongTicks(uint16_t& temperatureTicks,
uint16_t& humidityTicks);
/**
* @brief activateMediumHeaterPowerShortTicks
*
* SHT4x command to activate medium heater power and perform a single shot
* high precision measurement for 0.1s.
*
* @param[out] temperatureTicks Temperature ticks. Convert to degrees
* celsius by (175 * value / 65535) - 45
* @param[out] humidityTicks Humidity ticks. Convert to degrees celsius by
* (125
* * value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t activateMediumHeaterPowerShortTicks(uint16_t& temperatureTicks,
uint16_t& humidityTicks);
/**
* @brief activateLowestHeaterPowerLongTicks
*
* SHT4x command to activate lowest heater power and perform a single shot
* high precision measurement for 1s.
*
* @param[out] temperatureTicks Temperature ticks. Convert to degrees
* celsius by (175 * value / 65535) - 45
* @param[out] humidityTicks Humidity ticks. Convert to degrees celsius by
* (125
* * value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t activateLowestHeaterPowerLongTicks(uint16_t& temperatureTicks,
uint16_t& humidityTicks);
/**
* @brief activateLowestHeaterPowerShortTicks
*
* SHT4x command to activate lowest heater power and perform a single shot
* high precision measurement for 0.1s.
*
* @param[out] temperatureTicks Temperature ticks. Convert to degrees
* celsius by (175 * value / 65535) - 45
* @param[out] humidityTicks Humidity ticks. Convert to degrees celsius by
* (125
* * value / 65535) - 6
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t activateLowestHeaterPowerShortTicks(uint16_t& temperatureTicks,
uint16_t& humidityTicks);
/**
* @brief serialNumber
*
* Read out the serial number
*
* @param[out] serialNumber Unique serial number
*
* @note Each sensor has a unique serial number that is assigned by
* Sensirion during production.It is stored in the one-time-programmable
* memory and cannot be manipulated after production.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t serialNumber(uint32_t& serialNumber);
/**
* @brief softReset
*
* Perform a soft reset.
*
* @note A reset of the sensor can be achieved in three ways: • Soft reset:
* use this function • I2C general call: all devices on the I2C bus are
* reset by sending the command 0x06 to the I2C address 0x00. • Power down
* (incl.pulling SCL and SDA low)
*
* @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 (175 * ticks_value /
* 65535) - 45
*/
float signalTemperature(uint16_t temperatureTicks);
/**
* @brief signalHumidity
*
* @param[in] humidityTicks
*
* @return Converted from ticks to percent relative humdity by (125 *
* ticks_value / 65535) - 6
*/
float signalHumidity(uint16_t humidityTicks);
};
#endif // SENSIRIONI2CSHT4X_H

View File

@ -1,21 +1,19 @@
BSD 3-Clause License
Copyright (c) 2023, Sensirion AG
Copyright (c) 2018, 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.
* 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.
* 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.
* 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

View File

@ -0,0 +1,72 @@
# arduino-sht
Repository for Sensirion humidity and temperature sensor support on Arduino
## Supported sensors:
- SHTC1
- SHTC3
- SHTW1
- SHTW2
- SHT2x (SHT20, SHT21, SHT25)
- SHT3x-DIS (I2C)
- SHT3x-ARP (ratiometric analog voltage output)
- SHT85
- SHT4x
For <code><a href="https://github.com/Sensirion/arduino-i2c-sht3x">sht3x</a></code> and <code><a href="https://github.com/Sensirion/arduino-i2c-sht4x">sht4x</a></code> there are specific drivers available in separate repositories.
## Installation
The recommended way to install ```arduino-sht``` is through the Library
Manager of the Arduino IDE. To access it, go to the ```Tools``` menu and
select ```Manage Libraries...```, and search for the library name there.
If you prefer to install it manually, you can download either via git or from
the releases page and place it in your Arduino/libraries directory. After
restarting the Arduino IDE, you will see the new SHTSensor menu items under
libraries and examples.
## Integrating it into your sketch
Assuming you installed the library as described above, the following steps are
necessary:
1. Import the Wire library like this: From the menu bar, select Sketch > Import
Library > Wire
1. Import the arduino-sht library: From the menu bar, select Sketch >
Import Library > arduino-sht
1. Create an instance of the `SHTSensor` class (`SHTSensor sht;`)
2. In `setup()`, make sure to init the Wire library with `Wire.begin()`
3. Also in `setup()`, call `sht.init()`
5. If you want to use the serial console, remember to initialize the Serial
library with `Serial.begin(9600)`
1. Call `sht.readSample()` in the `loop()` function, which reads a temperature
and humidity sample from the sensor
2. Use `sht.getHumidity()` and `sht.getTemperature()` to get the values from
the last sample
*Important:* `getHumidity()` and `getTemperature()` do *not* read a new sample
from the sensor, but return the values read last. To read a new sample, make
sure to call `readSample()`
### Using an custom or alternative I2C port/Wire instance
Some Arduino boards have multiple predefined I2C ports; generally, the second port will be called `Wire1`.
The `arduino-sht` library allows to use an alternative interface; to do so, pass the port you want to use as an argument to `sht.init()`, like this:
```
if (sht.init(Wire1)) {
Serial.print("init(): success\n");
} else {
Serial.print("init(): failed\n");
}
```
## Example projects
See example project
[sht-autodetect](examples/sht-autodetect/sht-autodetect.ino)
### Usage with multiple SHT31 sensors
See example project
[multiple-sht-sensors](examples/multiple-sht-sensors/multiple-sht-sensors.ino)

View File

@ -0,0 +1,442 @@
/*
* Copyright (c) 2018, Sensirion AG <andreas.brauchli@sensirion.com>
* Copyright (c) 2015-2016, Johannes Winkelmann <jw@smts.ch>
* 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 the 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 <COPYRIGHT HOLDER> 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 <inttypes.h>
#include <Arduino.h>
#include "SHTSensor.h"
//
// class SHTSensorDriver
//
SHTSensorDriver::~SHTSensorDriver()
{
}
bool SHTSensorDriver::readSample()
{
return false;
}
//
// class SHTI2cSensor
//
const uint8_t SHTI2cSensor::EXPECTED_DATA_SIZE = 6;
bool SHTI2cSensor::readFromI2c(TwoWire & wire,
uint8_t i2cAddress,
const uint8_t *i2cCommand,
uint8_t commandLength, uint8_t *data,
uint8_t dataLength,
uint8_t duration)
{
wire.beginTransmission(i2cAddress);
for (int i = 0; i < commandLength; ++i) {
if (wire.write(i2cCommand[i]) != 1) {
return false;
}
}
if (wire.endTransmission() != 0) {
return false;
}
delay(duration);
wire.requestFrom(i2cAddress, dataLength);
// check if the same number of bytes are received that are requested.
if (wire.available() != dataLength) {
return false;
}
for (int i = 0; i < dataLength; ++i) {
data[i] = wire.read();
}
return true;
}
uint8_t SHTI2cSensor::crc8(const uint8_t *data, uint8_t len, uint8_t crcInit)
{
// adapted from SHT21 sample code from
// http://www.sensirion.com/en/products/humidity-temperature/download-center/
uint8_t crc = crcInit;
uint8_t byteCtr;
for (byteCtr = 0; byteCtr < len; ++byteCtr) {
crc ^= data[byteCtr];
for (uint8_t bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x31;
} else {
crc = (crc << 1);
}
}
}
return crc;
}
bool SHTI2cSensor::readSample()
{
uint8_t data[EXPECTED_DATA_SIZE];
uint8_t cmd[mCmd_Size];
cmd[0] = mI2cCommand >> 8;
//is omitted for SHT4x Sensors
cmd[1] = mI2cCommand & 0xff;
if (!readFromI2c(mWire, mI2cAddress, cmd, mCmd_Size, data,
EXPECTED_DATA_SIZE, mDuration)) {
return false;
}
// -- Important: assuming each 2 byte of data is followed by 1 byte of CRC
// check CRC for both RH and T
if (crc8(&data[0], 2) != data[2] || crc8(&data[3], 2) != data[5]) {
return false;
}
// convert to Temperature/Humidity
uint16_t val;
val = (data[0] << 8) + data[1];
mTemperature = mA + mB * (val / mC);
val = (data[3] << 8) + data[4];
mHumidity = mX + mY * (val / mZ);
return true;
}
//
// class SHTC1Sensor
//
class SHTC1Sensor : public SHTI2cSensor
{
public:
SHTC1Sensor(TwoWire & wire)
// clock stretching disabled, high precision, T first
: SHTI2cSensor(0x70, 0x7866, 15, -45, 175, 65535, 0, 100, 65535, 2, wire)
{
}
};
//
// class SHT2xSensor (SHT20, SHT21, SHT25)
//
class SHT2xSensor : public SHTI2cSensor
{
public:
SHT2xSensor(TwoWire &wire)
// clock stretching disabled
: SHTI2cSensor(0x40, // i2cAddress
0xF3F5, // i2cCommand Hi: T, Lo: RH
85, // duration
-46.85, // a (sht_t_poly1)
175.72, // b (sht_t_poly2)
65536.0, // c (sht_t_poly3)
-6.0, // x (sht_h_poly1)
125.0, // y (sht_h_poly2)
65536.0, // z (sht_h_poly3)
1, // cmd_Size
wire)
{
}
bool readSample() override
{
uint8_t data[EXPECTED_DATA_SIZE];
uint8_t cmd[mCmd_Size];
// SHT2x sends T and RH in two separate commands (different to other sensors)
// so we have to spit the command into two bytes and
// have to read from I2C two times with EXPECTED_DATA_SIZE / 2
// Upper byte is T for SHT2x Sensors
cmd[0] = mI2cCommand >> 8;
// Lower byte is RH for SHT2x Sensors
cmd[1] = mI2cCommand & 0xff;
// read T from SHT2x Sensor
if (!readFromI2c(mWire, mI2cAddress, cmd, mCmd_Size, data,
EXPECTED_DATA_SIZE / 2, mDuration)) {
DEBUG_SHT("SHT2x readFromI2c(T) false\n");
return false;
}
// read RH from SHT2x Sensor
if (!readFromI2c(mWire, mI2cAddress, &cmd[1], mCmd_Size, &data[3],
EXPECTED_DATA_SIZE / 2, mDuration)) {
DEBUG_SHT("SHT2x readFromI2c(RH) false\n");
return false;
}
// -- Important: assuming each 2 byte of data is followed by 1 byte of CRC
// check CRC for both RH and T with a crc init value of 0
if (crc8(&data[0], 2, 0) != data[2] || crc8(&data[3], 2, 0) != data[5]) {
DEBUG_SHT("SHT2x crc8 false\n");
return false;
}
// check status bits [1..0] (see datasheet)
// bit 0: not used, bit 1: measurement type (0: temperature, 1 humidity)
if (((data[1] & 0x02) != 0x00) || ((data[4] & 0x02) != 0x02)) {
DEBUG_SHT("SHT2x status bits false\n");
return false;
}
// convert to Temperature/Humidity
uint16_t val;
val = (data[0] << 8) + (data[1] & ~0x03); // get value and clear status bits [1..0]
mTemperature = mA + mB * (val / mC);
val = (data[3] << 8) + (data[4] & ~0x03); // get value and clear status bits [1..0]
mHumidity = mX + mY * (val / mZ);
return true;
}
};
//
// class SHT3xSensor
//
class SHT3xSensor : public SHTI2cSensor
{
private:
static const uint16_t SHT3X_ACCURACY_HIGH = 0x2400;
static const uint16_t SHT3X_ACCURACY_MEDIUM = 0x240b;
static const uint16_t SHT3X_ACCURACY_LOW = 0x2416;
static const uint8_t SHT3X_ACCURACY_HIGH_DURATION = 15;
static const uint8_t SHT3X_ACCURACY_MEDIUM_DURATION = 6;
static const uint8_t SHT3X_ACCURACY_LOW_DURATION = 4;
public:
static const uint8_t SHT3X_I2C_ADDRESS_44 = 0x44;
static const uint8_t SHT3X_I2C_ADDRESS_45 = 0x45;
SHT3xSensor(TwoWire & wire, uint8_t i2cAddress = SHT3X_I2C_ADDRESS_44)
: SHTI2cSensor(i2cAddress, SHT3X_ACCURACY_HIGH,
SHT3X_ACCURACY_HIGH_DURATION,
-45, 175, 65535, 0, 100, 65535, 2, wire)
{
}
virtual bool setAccuracy(SHTSensor::SHTAccuracy newAccuracy)
{
switch (newAccuracy) {
case SHTSensor::SHT_ACCURACY_HIGH:
mI2cCommand = SHT3X_ACCURACY_HIGH;
mDuration = SHT3X_ACCURACY_HIGH_DURATION;
break;
case SHTSensor::SHT_ACCURACY_MEDIUM:
mI2cCommand = SHT3X_ACCURACY_MEDIUM;
mDuration = SHT3X_ACCURACY_MEDIUM_DURATION;
break;
case SHTSensor::SHT_ACCURACY_LOW:
mI2cCommand = SHT3X_ACCURACY_LOW;
mDuration = SHT3X_ACCURACY_LOW_DURATION;
break;
default:
return false;
}
return true;
}
};
//
// class SHT4xSensor
//
class SHT4xSensor : public SHTI2cSensor
{
private:
static const uint16_t SHT4X_ACCURACY_HIGH = 0xFD00;
static const uint16_t SHT4X_ACCURACY_MEDIUM = 0xF600;
static const uint16_t SHT4X_ACCURACY_LOW = 0xE000;
static const uint8_t SHT4X_ACCURACY_HIGH_DURATION = 10;
static const uint8_t SHT4X_ACCURACY_MEDIUM_DURATION = 4;
static const uint8_t SHT4X_ACCURACY_LOW_DURATION = 2;
public:
static const uint8_t SHT4X_I2C_ADDRESS_44 = 0x44;
static const uint8_t SHT4X_I2C_ADDRESS_45 = 0x45;
SHT4xSensor(TwoWire & wire, uint8_t i2cAddress = SHT4X_I2C_ADDRESS_44)
: SHTI2cSensor(i2cAddress, SHT4X_ACCURACY_HIGH,
SHT4X_ACCURACY_HIGH_DURATION,
-45, 175, 65535, -6, 125, 65535, 1, wire)
{
}
virtual bool setAccuracy(SHTSensor::SHTAccuracy newAccuracy)
{
switch (newAccuracy) {
case SHTSensor::SHT_ACCURACY_HIGH:
mI2cCommand = SHT4X_ACCURACY_HIGH;
mDuration = SHT4X_ACCURACY_HIGH_DURATION;
break;
case SHTSensor::SHT_ACCURACY_MEDIUM:
mI2cCommand = SHT4X_ACCURACY_MEDIUM;
mDuration = SHT4X_ACCURACY_MEDIUM_DURATION;
break;
case SHTSensor::SHT_ACCURACY_LOW:
mI2cCommand = SHT4X_ACCURACY_LOW;
mDuration = SHT4X_ACCURACY_LOW_DURATION;
break;
default:
return false;
}
return true;
}
};
//
// class SHT3xAnalogSensor
//
float SHT3xAnalogSensor::readHumidity()
{
float max_adc = (float)((1 << mReadResolutionBits) - 1);
return -12.5f + 125 * (analogRead(mHumidityAdcPin) / max_adc);
}
float SHT3xAnalogSensor::readTemperature()
{
float max_adc = (float)((1 << mReadResolutionBits) - 1);
return -66.875f + 218.75f * (analogRead(mTemperatureAdcPin) / max_adc);
}
//
// class SHTSensor
//
const SHTSensor::SHTSensorType SHTSensor::AUTO_DETECT_SENSORS[] = {
SHT4X, // IMPORTANT: SHT4x needs to be probed before the SHT3x, since they
// share their I2C address, and probing for an SHT3x can cause the
// first reading of and SHT4x to be off.
// see https://github.com/Sensirion/arduino-sht/issues/27
SHT2X,
SHT3X,
SHT3X_ALT,
SHTC1
};
const float SHTSensor::TEMPERATURE_INVALID = NAN;
const float SHTSensor::HUMIDITY_INVALID = NAN;
bool SHTSensor::init(TwoWire & wire)
{
if (mSensor != NULL) {
cleanup();
}
switch(mSensorType) {
case SHT2X:
mSensor = new SHT2xSensor(wire);
break;
case SHT3X:
case SHT85:
mSensor = new SHT3xSensor(wire);
break;
case SHT3X_ALT:
mSensor = new SHT3xSensor(wire, SHT3xSensor::SHT3X_I2C_ADDRESS_45);
break;
case SHTW1:
case SHTW2:
case SHTC1:
case SHTC3:
mSensor = new SHTC1Sensor(wire);
break;
case SHT4X:
mSensor = new SHT4xSensor(wire);
break;
case AUTO_DETECT:
{
bool detected = false;
for (unsigned int i = 0;
i < sizeof(AUTO_DETECT_SENSORS) / sizeof(AUTO_DETECT_SENSORS[0]);
++i) {
mSensorType = AUTO_DETECT_SENSORS[i];
delay(40); // TODO: this was necessary to make SHT4x autodetect work; revisit to find root cause
if (init(wire)) {
detected = true;
break;
}
}
if (!detected) {
cleanup();
}
break;
}
}
// to finish the initialization, attempt to read to make sure the communication works
// Note: readSample() will check for a NULL mSensor in case auto detect failed
return readSample();
}
bool SHTSensor::readSample()
{
if (!mSensor || !mSensor->readSample())
return false;
mTemperature = mSensor->mTemperature;
mHumidity = mSensor->mHumidity;
return true;
}
bool SHTSensor::setAccuracy(SHTAccuracy newAccuracy)
{
if (!mSensor)
return false;
return mSensor->setAccuracy(newAccuracy);
}
void SHTSensor::cleanup()
{
if (mSensor) {
delete mSensor;
mSensor = NULL;
}
}

View File

@ -0,0 +1,309 @@
/*
* Copyright (c) 2018, Sensirion AG <andreas.brauchli@sensirion.com>
* Copyright (c) 2015-2016, Johannes Winkelmann <jw@smts.ch>
* 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 the 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 <COPYRIGHT HOLDER> 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 SHTSENSOR_H
#define SHTSENSOR_H
#include <inttypes.h>
#include <Wire.h>
//#define DEBUG_SHT_SENSOR
#ifdef DEBUG_SHT_SENSOR
#ifdef DEBUG_ESP_PORT
#define DEBUG_SHT(f) do { DEBUG_ESP_PORT.print(PSTR(f)); } while (0)
#else
#define DEBUG_SHT(f) do { Serial.print(PSTR(f)); } while (0)
#endif
#else
#define DEBUG_SHT(x...) do { (void)0; } while (0)
#endif
// Forward declaration
class SHTSensorDriver;
/**
* Official interface for Sensirion SHT Sensors
*/
class SHTSensor
{
public:
/**
* Enum of the supported Digital Sensirion SHT Sensors.
* For analog sensors, see SHT3xAnalogSensor.
* Using the special AUTO_DETECT sensor causes all i2c sensors to be
* probed. The first matching sensor will then be used.
*/
enum SHTSensorType {
/** Automatically detect the sensor type (only i2c sensors listed above) */
AUTO_DETECT,
// i2c Sensors:
/** SHT3x-DIS with ADDR (sensor pin 2) connected to VSS (default) */
SHT3X,
SHT85,
/** SHT3x-DIS with ADDR (sensor pin 2) connected to VDD */
SHT3X_ALT,
SHTC1,
SHTC3,
SHTW1,
SHTW2,
SHT4X,
SHT2X
};
/**
* Accuracy setting of measurement.
* Not all sensors support changing the sampling accuracy.
*/
enum SHTAccuracy {
/** Highest repeatability at the cost of slower measurement */
SHT_ACCURACY_HIGH,
/** Balanced repeatability and speed of measurement */
SHT_ACCURACY_MEDIUM,
/** Fastest measurement but lowest repeatability */
SHT_ACCURACY_LOW
};
/** Value reported by getHumidity() when the sensor is not initialized */
static const float HUMIDITY_INVALID;
/** Value reported by getTemperature() when the sensor is not initialized */
static const float TEMPERATURE_INVALID;
/**
* Auto-detectable sensor types.
* Note that the SHTC3, SHTW1 and SHTW2 share exactly the same driver as the SHTC1
* and are thus not listed individually.
*/
static const SHTSensorType AUTO_DETECT_SENSORS[];
/**
* Instantiate a new SHTSensor
* By default, the i2c bus is queried for known SHT Sensors. To address
* a specific sensor, set the `sensorType'.
*/
SHTSensor(SHTSensorType sensorType = AUTO_DETECT)
: mSensorType(sensorType),
mSensor(NULL),
mTemperature(SHTSensor::TEMPERATURE_INVALID),
mHumidity(SHTSensor::HUMIDITY_INVALID)
{
}
virtual ~SHTSensor() {
cleanup();
}
/**
* Initialize the sensor driver, and probe for the sensor on the bus
*
* If SHTSensor() was created with an empty constructor or with 'sensorType'
* AUTO_DETECT, init() will also try to automatically detect a sensor.
* Auto detection will stop as soon as the first sensor was found; if you have
* multiple sensor types on the bus, use the 'sensorType' argument of the
* constructor to control which sensor type will be instantiated.
*
* To read out the sensor use readSample(), followed by getTemperature() and
* getHumidity() to retrieve the values from the sample
*
* Returns true if communication with a sensor on the bus was successful, false otherwise
*/
bool init(TwoWire & wire = Wire);
/**
* Read new values from the sensor
* After the call, use getTemperature() and getHumidity() to retrieve the
* values
* Returns true if the sample was read and the values are cached
*/
bool readSample();
/**
* Get the relative humidity in percent read from the last sample
* Use readSample() to trigger a new sensor reading
*/
float getHumidity() const {
return mHumidity;
}
/**
* Get the temperature in Celsius read from the last sample
* Use readSample() to trigger a new sensor reading
*/
float getTemperature() const {
return mTemperature;
}
/**
* Change the sensor accurancy, if supported by the sensor
* Returns true if the accuracy was changed
*/
bool setAccuracy(SHTAccuracy newAccuracy);
SHTSensorType mSensorType;
private:
void cleanup();
SHTSensorDriver *mSensor;
float mTemperature;
float mHumidity;
};
/** Abstract class for a digital SHT Sensor driver */
class SHTSensorDriver
{
public:
virtual ~SHTSensorDriver() = 0;
/**
* Set the sensor accuracy.
* Returns false if the sensor does not support changing the accuracy
*/
virtual bool setAccuracy(SHTSensor::SHTAccuracy /* newAccuracy */) {
return false;
}
/** Returns true if the next sample was read and the values are cached */
virtual bool readSample();
/**
* Get the relative humidity in percent read from the last sample
* Use readSample() to trigger a new sensor reading
*/
float getHumidity() const {
return mHumidity;
}
/**
* Get the humidity in percent read from the last sample
* Use readSample() to trigger a new sensor reading
*/
float getTemperature() const {
return mTemperature;
}
float mTemperature;
float mHumidity;
};
/** Base class for i2c SHT Sensor drivers */
class SHTI2cSensor : public SHTSensorDriver {
public:
/** Size of i2c commands to send */
/** Size of i2c replies to expect */
static const uint8_t EXPECTED_DATA_SIZE;
/**
* Constructor for i2c SHT Sensors
* Takes the `i2cAddress' to read, the `i2cCommand' issues when sampling
* the sensor and the values `a', `b', `c' to convert the fixed-point
* temperature value received by the sensor to a floating point value using
* the formula: temperature = a + b * (rawTemperature / c)
* and the values `x' and `y' to convert the fixed-point humidity value
* received by the sensor to a floating point value using the formula:
* humidity = x + y * (rawHumidity / z)
* duration is the duration in milliseconds of one measurement
*/
SHTI2cSensor(uint8_t i2cAddress, uint16_t i2cCommand, uint8_t duration,
float a, float b, float c,
float x, float y, float z, uint8_t cmd_Size,
TwoWire & wire = Wire)
: mI2cAddress(i2cAddress), mI2cCommand(i2cCommand), mDuration(duration),
mA(a), mB(b), mC(c), mX(x), mY(y), mZ(z), mCmd_Size(cmd_Size),
mWire(wire)
{
}
virtual ~SHTI2cSensor()
{
}
virtual bool readSample();
uint8_t mI2cAddress;
uint16_t mI2cCommand;
uint8_t mDuration;
float mA;
float mB;
float mC;
float mX;
float mY;
float mZ;
uint8_t mCmd_Size;
TwoWire & mWire;
private:
protected:
static uint8_t crc8(const uint8_t *data, uint8_t len, uint8_t crcInit = 0xff);
static bool readFromI2c(TwoWire & wire,
uint8_t i2cAddress,
const uint8_t *i2cCommand,
uint8_t commandLength, uint8_t *data,
uint8_t dataLength, uint8_t duration);
};
class SHT3xAnalogSensor
{
public:
/**
* Instantiate a new Sensirion SHT3x Analog sensor driver instance.
* The required paramters are `humidityPin` and `temperaturePin`
* An optional `readResolutionBits' can be set since the Arduino/Genuino Zero
* support 12bit precision analog readings. By default, 10 bit precision is
* used.
*
* Example usage:
* SHT3xAnalogSensor sht3xAnalog(HUMIDITY_PIN, TEMPERATURE_PIN);
* float humidity = sht.readHumidity();
* float temperature = sht.readTemperature();
*/
SHT3xAnalogSensor(uint8_t humidityPin, uint8_t temperaturePin,
uint8_t readResolutionBits = 10)
: mHumidityAdcPin(humidityPin), mTemperatureAdcPin(temperaturePin),
mReadResolutionBits(readResolutionBits)
{
}
virtual ~SHT3xAnalogSensor()
{
}
float readHumidity();
float readTemperature();
uint8_t mHumidityAdcPin;
uint8_t mTemperatureAdcPin;
uint8_t mReadResolutionBits;
};
#endif /* SHTSENSOR_H */

View File

@ -0,0 +1,6 @@
#ifndef ARDUINO_SHT_H
#define ARDUINO_SHT_H
#include "SHTSensor.h"
#endif

View File

@ -0,0 +1,62 @@
#include <Wire.h>
#include "SHTSensor.h"
// Note that all i2c devices sharing one bus must have distinct addresses. Thus
// this example only works with sensors that have distinct i2c addresses (e.g.
// SHT3x and SHT3x_alt, or SHT3x and SHTC3).
// Make sure not to use auto-detection as it will only pick up one sensor.
// Sensor with normal i2c address
// Sensor 1 with address pin pulled to GND
SHTSensor sht1(SHTSensor::SHT3X);
// Sensor with alternative i2c address
// Sensor 2 with address pin pulled to Vdd
SHTSensor sht2(SHTSensor::SHT3X_ALT);
void setup() {
// put your setup code here, to run once:
Wire.begin();
Serial.begin(9600);
delay(1000); // let serial console settle
// init on a specific sensor type (i.e. without auto detecting),
// does not check if the sensor is responding and will thus always succeed.
// initialize sensor with normal i2c-address
sht1.init();
// initialize sensor with alternative i2c-address
sht2.init();
}
void loop() {
// put your main code here, to run repeatedly:
// read from first sensor
if (sht1.readSample()) {
Serial.print("SHT1 :\n");
Serial.print(" RH: ");
Serial.print(sht1.getHumidity(), 2);
Serial.print("\n");
Serial.print(" T: ");
Serial.print(sht1.getTemperature(), 2);
Serial.print("\n");
} else {
Serial.print("Sensor 1: Error in readSample()\n");
}
// read from second sensor
if (sht2.readSample()) {
Serial.print("SHT2:\n");
Serial.print(" RH: ");
Serial.print(sht2.getHumidity(), 2);
Serial.print("\n");
Serial.print(" T: ");
Serial.print(sht2.getTemperature(), 2);
Serial.print("\n");
} else {
Serial.print("Sensor 2: Error in readSample()\n");
}
delay(1000);
}

View File

@ -0,0 +1,41 @@
#include <Wire.h>
#include "SHTSensor.h"
SHTSensor sht;
// To use a specific sensor instead of probing the bus use this command:
// SHTSensor sht(SHTSensor::SHT3X);
void setup() {
// put your setup code here, to run once:
Wire.begin();
Serial.begin(9600);
delay(1000); // let serial console settle
if (sht.init()) {
Serial.print("init(): success\n");
} else {
Serial.print("init(): failed\n");
}
sht.setAccuracy(SHTSensor::SHT_ACCURACY_MEDIUM); // only supported by SHT3x
}
void loop() {
// put your main code here, to run repeatedly:
if (sht.readSample()) {
Serial.print("SHT:\n");
Serial.print(" RH: ");
Serial.print(sht.getHumidity(), 2);
Serial.print("\n");
Serial.print(" T: ");
Serial.print(sht.getTemperature(), 2);
Serial.print("\n");
} else {
Serial.print("Error in readSample()\n");
}
delay(1000);
}

View File

@ -0,0 +1,27 @@
#include <Arduino.h>
#include <Wire.h>
#include "SHTSensor.h"
SHT3xAnalogSensor sht3xAnalog(A0, A1);
void setup() {
// put your setup code here, to run once:
Wire.begin();
Serial.begin(9600);
delay(1000); // let serial console settle
}
void loop() {
Serial.print("SHT3x Analog:\n");
Serial.print(" RH: ");
Serial.print(sht3xAnalog.readHumidity(), 2);
Serial.print("\n");
Serial.print(" T: ");
Serial.print(sht3xAnalog.readTemperature(), 2);
Serial.print("\n");
delay(1000);
}

View File

@ -0,0 +1,29 @@
#######################################
# Syntax Coloring Map For arduino-sht
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
SHTSensorType KEYWORD1
SHTAccuracy KEYWORD1
SHTSensor KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
init KEYWORD2
readSample KEYWORD2
getHumidity KEYWORD2
getTemperature KEYWORD2
setAccuracy KEYWORD2
#######################################
# Instances (KEYWORD2)
#######################################
#######################################
# Constants (LITERAL1)
#######################################

View File

@ -0,0 +1,9 @@
name=arduino-sht
version=1.2.3
author=Johannes Winkelmann, Andreas Brauchli
maintainer=Johannes Winkelmann <jwi@sensirion.com>
sentence=Support for Sensirion's humidity and temperature sensors.
paragraph=Supported sensors: SHTC1, SHTC3, SHTW1, SHTW2, SHT3x-DIS (I2C), SHT2x, SHT85, SHT3x-ARP, SHT4x
category=Sensors
url=https://developer.sensirion.com
architectures=*

View File

@ -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");

View File

@ -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 {

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
};

View File

@ -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");

View File

@ -4,6 +4,10 @@
#include "BoardDef.h"
#include <Arduino.h>
/**
* @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_ */

View File

@ -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");
}

View File

@ -4,6 +4,10 @@
#include "BoardDef.h"
#include <Arduino.h>
/**
* @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_ */

View File

@ -1,7 +1,5 @@
#include "PMS.h"
// PMS::PMS(Stream &stream) { this->_stream = &stream; }
bool PMS::begin(Stream *stream) {
_stream = stream;

View File

@ -3,10 +3,15 @@
#include <Arduino.h>
/**
* @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();

View File

@ -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");
}

View File

@ -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_ */

View File

@ -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");
}

View File

@ -2,10 +2,13 @@
#define _PMS5003T_H_
#include <HardwareSerial.h>
#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);
};

View File

@ -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);
}

View File

@ -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

View File

@ -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;

View File

@ -1,10 +1,15 @@
#ifndef _AIR_GRADIENT_SGP4X_H_
#define _AIR_GRADIENT_SGP4X_H_
#include "../bsp/BoardDef.h"
#include "../main/BoardDef.h"
#include <Arduino.h>
#include <Wire.h>
/**
* @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);
};

149
src/sht/sht.cpp Normal file
View File

@ -0,0 +1,149 @@
#include "sht.h"
#include "../library/arduino-sht/SHTSensor.h"
/** Cast _sensor to SHTSensor */
#define shtSensor() ((SHTSensor *)(this->_sensor))
/**
* @brief Check that is sensor initialized
*
* @return true Initialized
* @return false Not-initialized
*/
bool Sht::isBegin(void) {
if (this->_isBegin) {
return true;
}
AgLog("Sensor not-initialized");
return false;
}
/**
* @brief Check board is support I2C to work with this sensor
*
* @return true Supported
* @return false Not supported
*/
bool Sht::boardSupported(void) {
if (this->_bsp == NULL) {
this->_bsp = getBoardDef(this->_boardType);
}
if ((this->_bsp == NULL) || (this->_bsp->I2C.supported == false)) {
AgLog("Board not supported");
return false;
}
return true;
}
/**
* @brief Construct a new Sht:: Sht object
*
* @param type
*/
Sht::Sht(BoardType type) : _boardType(type) {}
/**
* @brief Destroy the Sht:: Sht object
*
*/
Sht::~Sht() {}
#if defined(ESP8266)
/**
* @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 Sht::begin(TwoWire &wire, Stream &debugStream) {
_debugStream = &debugStream;
return begin(wire);
}
#else
#endif
/**
* @brief Initialize sensor, should be init sensor before use other API, if not
* return result always failed
*
* @param wire TwoWire instance, Must be initialized
* @return true Success
* @return false Failure
*/
bool Sht::begin(TwoWire &wire) {
if (_isBegin) {
AgLog("Initialized, call end() then try again");
return true;
}
if (boardSupported() == false) {
return false;
}
/** Create new sensor */
_sensor = new SHTSensor();
if (_sensor == nullptr) {
AgLog("Create SHTSensor failed");
return false;
}
/** Initialize sensor */
if (shtSensor()->init(wire) == false) {
AgLog("Initialize SHTSensor failed");
return false;
}
// Only supported by SHT3x
if (shtSensor()->setAccuracy(SHTSensor::SHT_ACCURACY_MEDIUM) == false) {
AgLog("Configure sensor failed");
return false;
}
AgLog("Initialize");
_isBegin = true;
return true;
}
/**
* @brief De-initialize sht sensor
*
*/
void Sht::end(void) {
if (_isBegin == false) {
return;
}
delete shtSensor();
_isBegin = false;
#if defined(ESP8266)
_debugStream = nullptr;
#else
#endif
_isBegin = false;
AgLog("De-Initialize");
}
/**
* @brief Get temprature degree celcius
*
* @return float
*/
float Sht::getTemperature(void) { return shtSensor()->getTemperature(); }
/**
* @brief Get humidity
*
* @return float
*/
float Sht::getRelativeHumidity(void) { return shtSensor()->getHumidity(); }
/**
* @brief Measure temperature and humidity
*
* @return true Success
* @return false Failure
*/
bool Sht::measure(void) { return shtSensor()->readSample(); }

42
src/sht/sht.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef _SHT_H_
#define _SHT_H_
#include "../main/BoardDef.h"
#include <Arduino.h>
#include <Wire.h>
/**
* @brief This class with define how to handlet sensirion sensor sht4x and
* sht3x(Temperature and humidity sensor)
*
*/
class Sht {
private:
BoardType _boardType;
bool _isBegin = false;
void *_sensor;
const BoardDef *_bsp = NULL;
#if defined(ESP8266)
Stream *_debugStream = nullptr;
const char *TAG = "SHT";
#else
#endif
bool isBegin(void);
bool boardSupported(void);
public:
Sht(BoardType type);
~Sht();
#if defined(ESP8266)
bool begin(TwoWire &wire, Stream &debugStream);
#else
#endif
bool begin(TwoWire &wire);
void end(void);
bool measure(void);
float getTemperature(void);
float getRelativeHumidity(void);
};
#endif /** _SHT_H_ */

View File

@ -1,210 +0,0 @@
#include "sht4x.h"
#include "../library/SensirionSHT4x/src/SensirionI2CSht4x.h"
#define shtSensor() ((SensirionI2CSht4x *)(this->_sensor))
#if defined(ESP8266)
bool Sht::begin(TwoWire &wire, Stream &debugStream) {
this->_debugStream = &debugStream;
return this->begin(wire);
}
#else
#endif
Sht::Sht(BoardType type) : _boardType(type) {}
/**
* @brief Init sensor, Ifthis funciton not call the other funtion call will
* always return false or value invalid
*
* @param wire TwoWire instance, Must be initialized
* @return true Success
* @return false Failure
*/
bool Sht::begin(TwoWire &wire) {
if (this->_isInit) {
return true;
}
if (this->boardSupported() == false) {
return false;
}
this->_sensor = new SensirionI2CSht4x();
shtSensor()->begin(wire, SHT40_I2C_ADDR_44);
if (shtSensor()->softReset() != 0) {
AgLog("Reset sensor fail, look like sensor is not on I2C bus");
return false;
}
delay(10);
this->_isInit = true;
AgLog("Init");
return true;
}
void Sht::end(void) {
if (this->_isInit == false) {
return;
}
this->_isInit = false;
delete shtSensor();
}
/**
* @brief Get temperature degrees celsius
*
* @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 temperature;
float humidity;
if (this->measureMediumPrecision(temperature, humidity)) {
return temperature;
}
return -256.0f;
}
/**
* @brief Get humidity
*
* @return float Percent(0 - 100), value < 0 is invalid.
*/
float Sht::getRelativeHumidity(void) {
float temperature;
float humidity;
if (this->measureMediumPrecision(temperature, humidity)) {
return humidity;
}
return -1.0f;
}
bool Sht::boardSupported(void) {
if (this->_bsp == NULL) {
this->_bsp = getBoardDef(this->_boardType);
}
if ((this->_bsp == NULL) || (this->_bsp->I2C.supported == false)) {
AgLog("Board not supported");
return false;
}
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) {
return true;
}
AgLog("Sensor no-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) {
return false;
}
if (shtSensor()->measureMediumPrecision(temperature, humidity) == 0) {
return true;
}
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;
}

View File

@ -1,49 +0,0 @@
#ifndef _AIR_GRADIENT_SHT_H_
#define _AIR_GRADIENT_SHT_H_
#include <Arduino.h>
#include <Wire.h>
#include "../bsp/BoardDef.h"
class Sht {
public:
#if defined(ESP8266)
bool begin(TwoWire &wire, Stream &debugStream);
#else
#endif
Sht(BoardType type);
bool begin(TwoWire &wire);
void end(void);
float getTemperature(void);
float getRelativeHumidity(void);
private:
BoardType _boardType;
bool _isInit = false;
void *_sensor;
const BoardDef *_bsp = NULL;
#if defined(ESP8266)
Stream *_debugStream = nullptr;
const char *TAG = "SHT4x";
#else
#endif
bool checkInit(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_ */