Compare commits

...

93 Commits
3.0.4 ... 3.0.8

Author SHA1 Message Date
f46c66a77f Merge pull request #87 from airgradienthq/develop
fix: After factory reset LEDBar is off
2024-03-18 08:48:28 +07:00
9c8ae315a0 fix: After factory reset LEDBar is off 2024-03-18 08:45:16 +07:00
3ef438412f Merge pull request #85 from airgradienthq/develop
Develop: optimize and bugfix
2024-03-16 12:00:20 +07:00
ce1373141a Updated version number 2024-03-16 11:13:04 +07:00
aceecde7b6 Format code 2024-03-16 10:02:59 +07:00
6926abd6f7 Standardize result of /measures/current for OpenAir 2024-03-16 10:02:46 +07:00
15dec40dfc Merge remote-tracking branch 'origin/master' into develop 2024-03-16 09:11:25 +07:00
4a36cf0c13 Merge pull request #83 from austvik/master
Standardize result of /measures/current
2024-03-16 09:08:53 +07:00
ecc92a6824 Merge pull request #81 from dechamps/celsius
Fix OpenMetrics typo: celcius -> celsius
2024-03-16 09:07:49 +07:00
3d243cb8ca Revert: version 3.0.7 2024-03-16 08:52:50 +07:00
471448a0f1 Prevent reboot in offline mode 2024-03-16 08:50:43 +07:00
ea3e976232 update next version 3.0.7 2024-03-16 08:21:44 +07:00
87f2463233 Update comment 2024-03-16 08:21:14 +07:00
49c7877ec3 Standardize result of /measures/current
Makes it easier for integrations which talks both to the cloud and to the
Cloud API to support the same format for reading current measures.

In practice this adds the firmware version and led mode to the output, and
changes the writing of pm003Count, tvocIndex and noxIndex to use the same
spelling as for the documented cloud API.
2024-03-15 19:22:00 +01:00
be1a9778e6 Fix: PMS Read Failed 2024-03-14 21:17:43 +07:00
ed1d45cea1 Fix OpenMetrics typo: celcius -> celsius
See #78
2024-03-10 11:02:04 +00:00
db31b39ce2 Merge pull request #80 from airgradienthq/develop
Develop
2024-03-10 12:01:35 +07:00
d92d312b0c add openmetrics 2024-03-10 11:57:52 +07:00
6837529096 add compensation temperature and humidity for SGP41 2024-03-10 11:20:52 +07:00
b94ae9eff0 Merge remote-tracking branch 'origin/master' into develop 2024-03-10 10:10:58 +07:00
1810c0f355 Merge pull request #78 from gouthamve/better-openmetrics-names
[ONE/Prometheus] Use full unit in temperature metric
2024-03-10 09:58:09 +07:00
eb0f45750d Merge pull request #79 from austvik/master
Fix MDNS Service Discovery:
2024-03-10 09:53:25 +07:00
9ae8fb2355 fix: Completely turn off LEDbar 2024-03-10 09:34:04 +07:00
512509c2e2 Print HTTP Response in logs in case of error 2024-03-10 08:44:49 +07:00
66815f590c Merge remote-tracking branch 'origin/develop' into develop 2024-03-10 08:27:40 +07:00
f60e9bbe3e Fix MDNS Service Discovery:
- Underscore before names per https://github.com/espressif/arduino-esp32/issues/962
- Only one service per port

The combination of both changes is needed to make the service discoverable in OpenHAB

The removal of the published http service is maybe something you don't want,
but as long as it doesn't serve web pages it is maybe OK?
2024-03-08 23:37:12 +01:00
f361e3c9a9 [ONE/Prometheus] Use full unit in temperature metric
Please see: https://prometheus.io/docs/practices/naming/#base-units

Also, thanks a lot for this support, I had my own exporter that I can now delete :)

Signed-off-by: gouthamve <gouthamve@gmail.com>
2024-03-08 19:02:19 +01:00
e76dcf07c8 Updated log texts 2024-03-08 13:47:28 +07:00
e6fe489be7 Updated display texts 2024-03-08 13:29:17 +07:00
9ddb606a00 Merge pull request #77 from airgradienthq/develop
Turn all LED on while init
2024-03-07 21:57:16 +07:00
cd5ee2da18 Turn all LED on while init 2024-03-07 21:54:22 +07:00
4c42a9ddc8 Merge pull request #76 from airgradienthq/develop
Develop
2024-03-07 21:49:10 +07:00
78b1b0975c update: Connect to Dashboard show also S/N, remove test code 2024-03-07 21:48:13 +07:00
d99881aa46 update: Connect to Dashboard show also S/N 2024-03-07 21:44:46 +07:00
df937fe65f update mDNS servicce and attribute 2024-03-07 21:30:42 +07:00
dc742d3c92 Updated log messages and version number 2024-03-07 16:11:36 +07:00
a7b2ad526f Merge pull request #74 from airgradienthq/develop
fix: `O-1PS` not recognize without `SGP` sensor
2024-03-06 18:13:34 +07:00
bb804b9f6a fix: O-1PS not recognize without SGP sensor 2024-03-06 18:02:32 +07:00
1a00073cf6 Merge pull request #73 from airgradienthq/develop
Develop
2024-03-06 17:24:52 +07:00
469d07a2d6 fix: O-1PS not recognized 2024-03-06 17:20:55 +07:00
6cf5e31843 add nox_index to payload 2024-03-03 22:24:58 +07:00
3f1da6387b Update mDNS service model attribute 2024-03-03 22:03:23 +07:00
99b4858f1d Merge pull request #72 from airgradienthq/develop
Develop: update workflows build configure
2024-03-03 21:57:41 +07:00
4374c980ec Update workflows example build configure 2024-03-03 21:51:53 +07:00
ded7637b06 Update workflows example build configure 2024-03-03 21:47:50 +07:00
6a79ab6b5b Update workflows example build configure 2024-03-03 21:46:07 +07:00
7baff75524 Merge pull request #71 from dechamps/checkpr
Fix check workflow failing on pull requests
2024-03-03 21:38:01 +07:00
d421c94647 Merge branch 'master' into develop 2024-03-03 21:35:25 +07:00
d78205aa20 Changed measurement and update interval for Open Air. Added fw version to logs. 2024-03-02 15:04:30 +07:00
c1228bbd06 Changed measurement and update intervalls 2024-03-02 14:05:00 +07:00
1eb43f684b Fix SHT read error. 2024-03-02 13:41:08 +07:00
4798e44cb7 MDNS replace board with model 2024-03-01 21:56:21 +07:00
a867e9af38 revert SENSOR_TEMP_HUM_UPDATE_INTERVAL value 2024-03-01 19:51:58 +07:00
8fcf257726 Fix check workflow failing on pull requests
This fixes the following check GitHub Actions workflow failure that
would otherwise occur on pull requests (but not on pushes/branches):

  Error installing Git Library: Library install failed: object not found
2024-02-29 23:52:36 +00:00
a59d5a1bb8 Update check.yml
Adjusted new name for example files
2024-02-29 20:05:16 +07:00
b4d6006678 Changed PM polling frequency for Open Air to 2s 2024-02-29 18:30:52 +07:00
236c5bab84 Added offline mode after LED test. 2024-02-29 18:15:22 +07:00
852fdc4360 Renamed example file. 2024-02-29 17:58:58 +07:00
f7e85a92e8 Uped version Nr. Renamed examples. 2024-02-29 17:58:14 +07:00
e99fc2ecdc Merge pull request #69 from airgradienthq/develop
Update API naming
2024-02-29 15:26:53 +07:00
67785ed99b Update API naming 2024-02-29 15:20:19 +07:00
45ac4f116b Merge pull request #68 from airgradienthq/develop
Develop
2024-02-29 15:12:28 +07:00
173e3caf2f Update API naming 2024-02-29 15:07:23 +07:00
351af57591 move pm25ToAQI into PMSUtils 2024-02-29 14:45:44 +07:00
0bda7a1c4b Change pmPoll interval from 5s to 2s 2024-02-29 14:00:19 +07:00
9a31c107fa add get BoardName from AirGradient library 2024-02-29 13:58:48 +07:00
5449fa15ea Merge branch 'master' into develop 2024-02-29 13:50:46 +07:00
3e4e2affa8 Merge pull request #59 from dechamps/prometheus
Add support for Prometheus/OpenMetrics to One V9
2024-02-29 13:47:49 +07:00
d3a242a0b7 Merge branch 'master' into develop 2024-02-29 13:45:45 +07:00
e5e2887c4d Merge pull request #58 from dechamps/githubactions
Add compile check GitHub Actions workflow
2024-02-29 13:41:50 +07:00
4eda2e4cb5 Merge pull request #67 from airgradienthq/develop
Develop
2024-02-29 10:57:10 +07:00
fcee721d58 Merge pull request #66 from airgradienthq/feature/add-mdns-attributes
Add mDNS attribute
2024-02-29 10:56:48 +07:00
7d12e63e34 Merge pull request #65 from airgradienthq/feature/relative-humidity-pms5003t-formula
Feature/relative humidity pms5003t formula
2024-02-29 10:50:21 +07:00
8ff8b7929e Update correction relative humdity formula 2024-02-29 10:49:38 +07:00
66c53daed6 Implement relative humidity correction 2024-02-29 10:39:17 +07:00
5de3a34dd0 Add mDNS attribute 2024-02-29 10:22:05 +07:00
b94112e22a Merge pull request #64 from airgradienthq/feature/led-bar-color-for-pms-and-co2
Feature/led bar color for pms and co2
2024-02-29 09:38:13 +07:00
99e925e7bd Merge pull request #63 from airgradienthq/hotfix/build-fail-if-set-core-debug-level-to-verbose
fix: build fail if set `Core Debug Level` to `Verbose`, #57
2024-02-29 09:34:26 +07:00
d8cba0d346 fix: build fail if set Core Debug Level to Verbose, #57 2024-02-29 09:33:33 +07:00
39de897621 Merge pull request #56 from dechamps/includecap
Fix path capitalization
2024-02-29 09:05:30 +07:00
9f1a793848 Merge pull request #62 from airgradienthq/hotfix/compile-fail-cause-value-type
fix: BoardDef pin number invalid type, #51
2024-02-29 08:49:03 +07:00
be9ba88d52 fix: BoardDef pin number invalid type, #51 2024-02-29 08:45:38 +07:00
0084b6fb91 fix: update cloud sync json noxIndex by nox_index 2024-02-29 08:39:05 +07:00
75b579bafa Merge pull request #61 from airgradienthq/feature/factory-config-reset
add Factory RESET
2024-02-26 17:51:17 +07:00
cf5ff99d8a add Factory RESET 2024-02-26 17:48:22 +07:00
ea204d90b1 Merge pull request #60 from airgradienthq/bugfix/mqtt-sending-interval
fix: Mqtt sending interval
2024-02-26 15:56:30 +07:00
13f6c2c747 fix: Mqtt sending interval 2024-02-26 15:55:33 +07:00
760f827d0d Add support for Prometheus/OpenMetrics to One V9
This commit adds a new feature to the One V9 (ONE_I-9PSL) firmware:
support for exposing metrics to Prometheus (or any other ingestor
compatible with the OpenMetrics format).

With this change, the AirGradient device will make metrics available
on the standard HTTP /metrics endpoint, out-of-the-box, with no need to
do anything else. All the user has to do is add their device address as
a target to their scrape config on their Prometheus server.

For more information on Prometheus and OpenMetrics, see:

- https://prometheus.io/docs/instrumenting/exposition_formats/
- https://openmetrics.io/
- https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md

This obsoletes projects such as:

- ebfa8d0ac6/AirGradient-DIY
- https://forum.airgradient.com/t/prometheus-integration/1504
2024-02-25 17:51:58 +00:00
8c8e0d4dea Add compile check GitHub Actions workflow
This commit adds a GitHub Actions workflow that, on every push/pull
request, will check that every single example successfully compiles on
every board it supports. That is, it it will check compilation on:

- ESP8266 for BASIC_v4, TestCO2, TestPM and TestSht
- ESP32 for ONE_I-9PSL, Open_Air, TestCO2, TestPM and TestSht

This provides the first building block towards a Continuous Integration
(CI) pipeline and prevents build breakages from making it to master.

Ideally this should also run tests on the examples (i.e. verifying that
the example boots successfully and sends metrics), but for now this will
at least ensure the build is not obviously broken.
2024-02-25 12:12:16 +00:00
b749495bf4 Fix path capitalization
This fixes build breakage on case-sensitive filesystems (e.g. Linux)
with errors like:

  src/Display/Display.h:4:10: fatal error: ../main/BoardDef.h: No such file or directory
2024-02-24 21:43:03 +00:00
7e3eabf09f Remove old color set process for PM 2024-02-21 21:18:44 +07:00
e636876c9b Update .gitignore 2024-02-21 21:16:12 +07:00
68953d7390 Update LED for PM and CO2 2024-02-21 21:16:01 +07:00
27 changed files with 1822 additions and 628 deletions

58
.github/workflows/check.yml vendored Normal file
View File

@ -0,0 +1,58 @@
on: [push, pull_request]
jobs:
compile:
strategy:
fail-fast: false
matrix:
example:
- "BASIC"
- "ONE"
- "Open_Air"
- "TestCO2"
- "TestPM"
- "TestSht"
fqbn:
- "esp8266:esp8266:d1_mini"
- "esp32:esp32:esp32c3"
include:
- fqbn: "esp8266:esp8266:d1_mini"
core: "esp8266:esp8266@3.1.2"
core_url: "https://arduino.esp8266.com/stable/package_esp8266com_index.json"
- fqbn: "esp32:esp32:esp32c3"
board_options: "JTAGAdapter=default,CDCOnBoot=cdc,PartitionScheme=default,CPUFreq=160,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=verbose,EraseFlash=none"
core: "esp32:esp32@2.0.11"
exclude:
- example: "BASIC"
fqbn: "esp32:esp32:esp32c3"
- example: "ONE"
fqbn: "esp8266:esp8266:d1_mini"
- example: "Open_Air"
fqbn: "esp8266:esp8266:d1_mini"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run:
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh |
sh -s 0.35.3
- run: bin/arduino-cli --verbose core install '${{ matrix.core }}'
--additional-urls '${{ matrix.core_url }}'
- run: bin/arduino-cli --verbose lib install
WiFiManager@2.0.16-rc.2
Arduino_JSON@0.2.0
U8g2@2.34.22
# In some cases, actions/checkout@v4 will check out a detached HEAD; for
# example, this happens on pull request events, where an hypothetical
# PR merge commit is checked out. This tends to confuse
# `arduino-cli lib install --git-url`, making it fail with errors such as:
# Error installing Git Library: Library install failed: object not found
# Create and check out a dummy branch to work around this issue.
- run: git checkout -b check
- run: bin/arduino-cli --verbose lib install --git-url .
env:
ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL: "true"
- run: bin/arduino-cli --verbose compile 'examples/${{ matrix.example }}'
--fqbn '${{ matrix.fqbn }}' --board-options '${{ matrix.board_options }}'
# TODO: at this point it would be a good idea to run some smoke tests on
# the resulting image (e.g. that it boots successfully and sends metrics)
# but that would either require a high fidelity device emulator, or a
# "hardware lab" runner that is directly connected to a relevant device.

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
.vscode
*.DS_Store *.DS_Store
build
.vscode

View File

@ -35,7 +35,6 @@ If you have any questions or problems, check out [our forum](https://forum.airgr
- [Sensirion Core](https://github.com/Sensirion/arduino-core/) - [Sensirion Core](https://github.com/Sensirion/arduino-core/)
- [Sensirion I2C SGP41](https://github.com/Sensirion/arduino-i2c-sgp41) - [Sensirion I2C SGP41](https://github.com/Sensirion/arduino-i2c-sgp41)
- [Sensirion I2C SHT](https://github.com/Sensirion/arduino-sht) - [Sensirion I2C SHT](https://github.com/Sensirion/arduino-sht)
- [PMS](https://github.com/fu-hsi/pms)
## License ## License
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License

View File

@ -50,7 +50,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */ #define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
#define SENSOR_CO2_UPDATE_INTERVAL 5000 /** ms */ #define SENSOR_CO2_UPDATE_INTERVAL 5000 /** ms */
#define SENSOR_PM_UPDATE_INTERVAL 5000 /** ms */ #define SENSOR_PM_UPDATE_INTERVAL 5000 /** ms */
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 5000 /** ms */ #define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */ #define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
#define WIFI_HOTSPOT_PASSWORD_DEFAULT \ #define WIFI_HOTSPOT_PASSWORD_DEFAULT \
"cleanair" /** default WiFi AP password \ "cleanair" /** default WiFi AP password \
@ -115,7 +115,7 @@ public:
* @return true Success * @return true Success
* @return false Failure * @return false Failure
*/ */
bool pollServerConfig(String id) { bool fetchServerConfiguration(String id) {
String uri = String uri =
"http://hw.airgradient.com/sensors/airgradient:" + id + "/one/config"; "http://hw.airgradient.com/sensors/airgradient:" + id + "/one/config";
@ -249,6 +249,8 @@ public:
if ((retCode == 200) || (retCode == 429)) { if ((retCode == 200) || (retCode == 429)) {
serverFailed = false; serverFailed = false;
return true; return true;
} else {
Serial.printf("Post response failed code: %d\r\n", retCode);
} }
serverFailed = true; serverFailed = true;
return false; return false;
@ -368,10 +370,10 @@ static bool wifiHasConfig = false; /** */
static void boardInit(void); static void boardInit(void);
static void failedHandler(String msg); static void failedHandler(String msg);
static void co2Calibration(void); static void co2Calibration(void);
static void serverConfigPoll(void); static void updateServerConfiguration(void);
static void co2Poll(void); static void co2Update(void);
static void pmPoll(void); static void pmUpdate(void);
static void tempHumPoll(void); static void tempHumUpdate(void);
static void sendDataToServer(void); static void sendDataToServer(void);
static void dispHandler(void); static void dispHandler(void);
static String getDevId(void); static String getDevId(void);
@ -382,12 +384,14 @@ bool hasSensorS8 = true;
bool hasSensorPMS = true; bool hasSensorPMS = true;
bool hasSensorSHT = true; bool hasSensorSHT = true;
int pmFailCount = 0; int pmFailCount = 0;
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, serverConfigPoll); int getCO2FailCount = 0;
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL,
updateServerConfiguration);
AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer); AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
AgSchedule dispSchedule(DISP_UPDATE_INTERVAL, dispHandler); AgSchedule dispSchedule(DISP_UPDATE_INTERVAL, dispHandler);
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Poll); AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmPoll); AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmUpdate);
AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumPoll); AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate);
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
@ -413,7 +417,7 @@ void setup() {
wifiHasConfig = true; wifiHasConfig = true;
sendPing(); sendPing();
agServer.pollServerConfig(getDevId()); agServer.fetchServerConfiguration(getDevId());
if (agServer.isCo2Calib()) { if (agServer.isCo2Calib()) {
co2Calibration(); co2Calibration();
} }
@ -453,6 +457,9 @@ void loop() {
} }
updateWiFiConnect(); updateWiFiConnect();
/** Read PMS on loop */
ag.pms5003.handle();
} }
static void sendPing() { static void sendPing() {
@ -576,8 +583,8 @@ static void co2Calibration(void) {
} }
} }
static void serverConfigPoll(void) { static void updateServerConfiguration(void) {
if (agServer.pollServerConfig(getDevId())) { if (agServer.fetchServerConfiguration(getDevId())) {
if (agServer.isCo2Calib()) { if (agServer.isCo2Calib()) {
if (hasSensorS8) { if (hasSensorS8) {
co2Calibration(); co2Calibration();
@ -591,7 +598,7 @@ static void serverConfigPoll(void) {
Serial.printf("abcDays config: %d days(%d hours)\r\n", Serial.printf("abcDays config: %d days(%d hours)\r\n",
agServer.getCo2AbcDaysConfig(), newHour); agServer.getCo2AbcDaysConfig(), newHour);
int curHour = ag.s8.getAbcPeriod(); int curHour = ag.s8.getAbcPeriod();
Serial.printf("Current config: %d (hours)\r\n", ag.s8.getAbcPeriod()); Serial.printf("Current config: %d (hours)\r\n", curHour);
if (curHour == newHour) { if (curHour == newHour) {
Serial.println("set 'abcDays' ignored"); Serial.println("set 'abcDays' ignored");
} else { } else {
@ -609,13 +616,23 @@ static void serverConfigPoll(void) {
} }
} }
static void co2Poll() { static void co2Update() {
co2Ppm = ag.s8.getCo2(); int value = ag.s8.getCo2();
Serial.printf("CO2 index: %d\r\n", co2Ppm); if (value >= 0) {
co2Ppm = value;
getCO2FailCount = 0;
Serial.printf("CO2 index: %d\r\n", co2Ppm);
} else {
getCO2FailCount++;
Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
if (getCO2FailCount >= 3) {
co2Ppm = -1;
}
}
} }
void pmPoll() { void pmUpdate() {
if (ag.pms5003.readData()) { if (ag.pms5003.isFailed() == false) {
pm25 = ag.pms5003.getPm25Ae(); pm25 = ag.pms5003.getPm25Ae();
Serial.printf("PMS2.5: %d\r\n", pm25); Serial.printf("PMS2.5: %d\r\n", pm25);
pmFailCount = 0; pmFailCount = 0;
@ -628,7 +645,7 @@ void pmPoll() {
} }
} }
static void tempHumPoll() { static void tempHumUpdate() {
if (ag.sht.measure()) { if (ag.sht.measure()) {
temp = ag.sht.getTemperature(); temp = ag.sht.getTemperature();
hum = ag.sht.getRelativeHumidity(); hum = ag.sht.getRelativeHumidity();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,7 @@ void setup()
if (ag.s8.begin(&Serial) == false) if (ag.s8.begin(&Serial) == false)
{ {
#else #else
if (ag.s8.begin(Serial1) == false) if (ag.s8.begin(Serial0) == false)
{ {
#endif #endif
failedHandler("SenseAir S8 init failed"); failedHandler("SenseAir S8 init failed");

View File

@ -10,8 +10,8 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
#ifdef ESP8266 #ifdef ESP8266
AirGradient ag = AirGradient(DIY_BASIC); AirGradient ag = AirGradient(DIY_BASIC);
#else #else
// AirGradient ag = AirGradient(ONE_INDOOR); AirGradient ag = AirGradient(ONE_INDOOR);
AirGradient ag = AirGradient(OPEN_AIR_OUTDOOR); // AirGradient ag = AirGradient(OPEN_AIR_OUTDOOR);
#endif #endif
void failedHandler(String msg); void failedHandler(String msg);
@ -35,42 +35,56 @@ void setup() {
#endif #endif
} }
uint32_t lastRead = 0;
void loop() { void loop() {
int PM2; int PM2;
bool readResul = false; bool readResul = false;
#ifdef ESP8266
if (ag.pms5003.readData()) {
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() == 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) { uint32_t ms = (uint32_t)(millis() - lastRead);
Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2); if (ms >= 5000) {
if (ag.getBoardType() == OPEN_AIR_OUTDOOR) { lastRead = millis();
Serial.printf("PM2.5 in US AQI: %d\r\n", #ifdef ESP8266
ag.pms5003t_1.convertPm25ToUsAqi(PM2)); if (ag.pms5003.isFailed() == false) {
} else { 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", Serial.printf("PM2.5 in US AQI: %d\r\n",
ag.pms5003.convertPm25ToUsAqi(PM2)); ag.pms5003.convertPm25ToUsAqi(PM2));
} else {
Serial.println("PMS sensor failed");
}
#else
if (ag.getBoardType() == OPEN_AIR_OUTDOOR) {
if (ag.pms5003t_1.isFailed() == false) {
PM2 = ag.pms5003t_1.getPm25Ae();
readResul = true;
}
} else {
if (ag.pms5003.isFailed() == false) {
PM2 = ag.pms5003.getPm25Ae();
readResul = true;
}
} }
}
#endif
delay(5000); 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));
}
} else {
Serial.println("PMS sensor failed");
}
#endif
}
if (ag.getBoardType() == OPEN_AIR_OUTDOOR) {
ag.pms5003t_1.handle();
} else {
ag.pms5003.handle();
}
} }
void failedHandler(String msg) { void failedHandler(String msg) {

View File

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

View File

@ -1,6 +1,6 @@
#include "AirGradient.h" #include "AirGradient.h"
#define AG_LIB_VER "3.0.4" #define AG_LIB_VER "3.0.8"
AirGradient::AirGradient(BoardType type) AirGradient::AirGradient(BoardType type)
: pms5003(type), pms5003t_1(type), pms5003t_2(type), s8(type), sgp41(type), : pms5003(type), pms5003t_1(type), pms5003t_2(type), s8(type), sgp41(type),
@ -40,3 +40,7 @@ BoardType AirGradient::getBoardType(void) { return boardType; }
double AirGradient::round2(double value) { double AirGradient::round2(double value) {
return (int)(value * 100 + 0.5) / 100.0; return (int)(value * 100 + 0.5) / 100.0;
} }
String AirGradient::getBoardName(void) {
return String(getBoardDefName(boardType));
}

View File

@ -107,6 +107,13 @@ public:
*/ */
String getVersion(void); String getVersion(void);
/**
* @brief Get the Board Name object
*
* @return String
*/
String getBoardName(void);
/** /**
* @brief Round double value with for 2 decimal * @brief Round double value with for 2 decimal
* *

View File

@ -1,7 +1,7 @@
#ifndef _AIR_GRADIENT_OLED_H_ #ifndef _AIR_GRADIENT_OLED_H_
#define _AIR_GRADIENT_OLED_H_ #define _AIR_GRADIENT_OLED_H_
#include "../main/BoardDef.h" #include "../Main/BoardDef.h"
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h> #include <Wire.h>

View File

@ -335,6 +335,19 @@ const BoardDef *getBoardDef(BoardType def) {
return &bsps[def]; return &bsps[def];
} }
/**
* @brief Get the Board Name
*
* @param type BoarType
* @return const char*
*/
const char *getBoardDefName(BoardType type) {
if (type >= _BOARD_MAX) {
return NULL;
}
return bsps[type].name;
}
#if defined(ESP8266) #if defined(ESP8266)
#define bspPrintf(c, ...) \ #define bspPrintf(c, ...) \
if (_debug != nullptr) { \ if (_debug != nullptr) { \

View File

@ -76,13 +76,14 @@ struct BoardDef {
/** Watchdog */ /** Watchdog */
struct { struct {
const uint8_t resetPin; const int resetPin;
const bool supported; const bool supported;
} WDG; } WDG;
const char *name; const char *name;
}; };
const BoardDef *getBoardDef(BoardType def); const BoardDef *getBoardDef(BoardType def);
const char *getBoardDefName(BoardType type);
void printBoardDef(Stream *_debug); void printBoardDef(Stream *_debug);
#endif /** _AIR_GRADIENT_BOARD_DEF_H_ */ #endif /** _AIR_GRADIENT_BOARD_DEF_H_ */

View File

@ -35,6 +35,7 @@ void LedBar::begin(void) {
this->_bsp->LED.rgbNum, this->_bsp->LED.pin, NEO_GRB + NEO_KHZ800); this->_bsp->LED.rgbNum, this->_bsp->LED.pin, NEO_GRB + NEO_KHZ800);
pixel()->begin(); pixel()->begin();
pixel()->clear(); pixel()->clear();
pixel()->show();
this->_isBegin = true; this->_isBegin = true;
@ -114,6 +115,10 @@ void LedBar::setColor(uint8_t red, uint8_t green, uint8_t blue) {
* *
*/ */
void LedBar::show(void) { void LedBar::show(void) {
// Ignore update the LED if LED bar disabled
if (enabled == false) {
return;
}
if (pixel()->canShow()) { if (pixel()->canShow()) {
pixel()->show(); pixel()->show();
} }
@ -124,3 +129,15 @@ void LedBar::show(void) {
* *
*/ */
void LedBar::clear(void) { pixel()->clear(); } void LedBar::clear(void) { pixel()->clear(); }
void LedBar::setEnable(bool enable) {
if (this->enabled != enable) {
if (enable == false) {
pixel()->clear();
pixel()->show();
}
}
this->enabled = enable;
}
bool LedBar::isEnabled(void) { return enabled; }

View File

@ -23,8 +23,11 @@ public:
int getNumberOfLeds(void); int getNumberOfLeds(void);
void show(void); void show(void);
void clear(void); void clear(void);
void setEnable(bool enable);
bool isEnabled(void);
private: private:
bool enabled = true;
const BoardDef *_bsp; const BoardDef *_bsp;
bool _isBegin = false; bool _isBegin = false;
uint8_t _ledState = 0; uint8_t _ledState = 0;

View File

@ -1,164 +1,300 @@
#include "PMS.h" #include "PMS.h"
#include "../Main/BoardDef.h"
bool PMS::begin(Stream *stream) { /**
_stream = stream; * @brief Init and check that sensor has connected
*
* @param stream UART stream
* @return true Sucecss
* @return false Failure
*/
bool PMSBase::begin(Stream *stream) {
this->stream = stream;
DATA data; failed = true;
if (readUntil(data, 5000)) { lastRead = 0; // To read buffer on handle without wait after 1.5sec
return true;
this->stream->flush();
// Run and check sensor data for 4sec
while (1) {
handle();
if (failed == false) {
return true;
}
delay(1);
uint32_t ms = (uint32_t)(millis() - lastRead);
if (ms >= 4000) {
break;
}
} }
return false; return false;
} }
// Standby mode. For low power consumption and prolong the life of the sensor. /**
void PMS::sleep() { * @brief Check and read sensor data then update variable.
uint8_t command[] = {0x42, 0x4D, 0xE4, 0x00, 0x00, 0x01, 0x73}; * Check result from method @isFailed before get value
_stream->write(command, sizeof(command)); */
} void PMSBase::handle() {
uint32_t ms;
// Operating mode. Stable data should be got at least 30 seconds after the if (lastRead == 0) {
// sensor wakeup from the sleep mode because of the fan's performance. lastRead = millis();
void PMS::wakeUp() { if (lastRead == 0) {
uint8_t command[] = {0x42, 0x4D, 0xE4, 0x00, 0x01, 0x01, 0x74}; lastRead = 1;
_stream->write(command, sizeof(command)); }
} } else {
ms = (uint32_t)(millis() - lastRead);
// Active mode. Default mode after power up. In this mode sensor would send /**
// serial data to the host automatically. * The PMS in Active mode sends an update data every 1 second. If we read
void PMS::activeMode() { * exactly every 1 sec then we may or may not get an update (depending on
uint8_t command[] = {0x42, 0x4D, 0xE1, 0x00, 0x01, 0x01, 0x71}; * timing tolerances). Hence we read every 2.5 seconds and expect 2 ..3
_stream->write(command, sizeof(command)); * updates,
_mode = MODE_ACTIVE; */
} if (ms < 2500) {
return;
// Passive mode. In this mode sensor would send serial data to the host only for }
// request.
void PMS::passiveMode() {
uint8_t command[] = {0x42, 0x4D, 0xE1, 0x00, 0x00, 0x01, 0x70};
_stream->write(command, sizeof(command));
_mode = MODE_PASSIVE;
}
// Request read in Passive Mode.
void PMS::requestRead() {
if (_mode == MODE_PASSIVE) {
uint8_t command[] = {0x42, 0x4D, 0xE2, 0x00, 0x00, 0x01, 0x71};
_stream->write(command, sizeof(command));
} }
} bool result = false;
char buf[32];
int bufIndex;
int step = 0;
int len = 0;
int bcount = 0;
// Non-blocking function for parse response. while (stream->available()) {
bool PMS::read(DATA &data) { char value = stream->read();
_data = &data; switch (step) {
loop(); case 0: {
if (value == 0x42) {
return _status == STATUS_OK; step = 1;
} bufIndex = 0;
buf[bufIndex++] = value;
// Blocking function for parse response. Default timeout is 1s. }
bool PMS::readUntil(DATA &data, uint16_t timeout) {
_data = &data;
uint32_t start = millis();
do {
loop();
if (_status == STATUS_OK) {
break; break;
} }
case 1: {
/** Relax task to avoid watchdog reset */ if (value == 0x4d) {
delay(1); step = 2;
} while (millis() - start < timeout); buf[bufIndex++] = value;
// Serial.println("Got 0x4d");
return _status == STATUS_OK;
}
void PMS::loop() {
_status = STATUS_WAITING;
if (_stream->available()) {
uint8_t ch = _stream->read();
switch (_index) {
case 0:
if (ch != 0x42) {
return;
}
_calculatedChecksum = ch;
break;
case 1:
if (ch != 0x4D) {
_index = 0;
return;
}
_calculatedChecksum += ch;
break;
case 2:
_calculatedChecksum += ch;
_frameLen = ch << 8;
break;
case 3:
_frameLen |= ch;
// Unsupported sensor, different frame length, transmission error e.t.c.
if (_frameLen != 2 * 9 + 2 && _frameLen != 2 * 13 + 2) {
_index = 0;
return;
}
_calculatedChecksum += ch;
break;
default:
if (_index == _frameLen + 2) {
_checksum = ch << 8;
} else if (_index == _frameLen + 2 + 1) {
_checksum |= ch;
if (_calculatedChecksum == _checksum) {
_status = STATUS_OK;
// Standard Particles, CF=1.
_data->PM_SP_UG_1_0 = makeWord(_payload[0], _payload[1]);
_data->PM_SP_UG_2_5 = makeWord(_payload[2], _payload[3]);
_data->PM_SP_UG_10_0 = makeWord(_payload[4], _payload[5]);
// Atmospheric Environment.
_data->PM_AE_UG_1_0 = makeWord(_payload[6], _payload[7]);
_data->PM_AE_UG_2_5 = makeWord(_payload[8], _payload[9]);
_data->PM_AE_UG_10_0 = makeWord(_payload[10], _payload[11]);
// Total particles count per 100ml air
_data->PM_RAW_0_3 = makeWord(_payload[12], _payload[13]);
_data->PM_RAW_0_5 = makeWord(_payload[14], _payload[15]);
_data->PM_RAW_1_0 = makeWord(_payload[16], _payload[17]);
_data->PM_RAW_2_5 = makeWord(_payload[18], _payload[19]);
_data->PM_RAW_5_0 = makeWord(_payload[20], _payload[21]);
_data->PM_RAW_10_0 = makeWord(_payload[22], _payload[23]);
// Formaldehyde concentration (PMSxxxxST units only)
_data->AMB_HCHO = makeWord(_payload[24], _payload[25]) / 1000;
// Temperature & humidity (PMSxxxxST units only)
_data->AMB_TMP = makeWord(_payload[20], _payload[21]);
_data->AMB_HUM = makeWord(_payload[22], _payload[23]);
}
_index = 0;
return;
} else { } else {
_calculatedChecksum += ch; step = 0;
uint8_t payloadIndex = _index - 4; }
break;
// Payload is common to all sensors (first 2x6 bytes). }
if (payloadIndex < sizeof(_payload)) { case 2: {
_payload[payloadIndex] = ch; buf[bufIndex++] = value;
if (bufIndex >= 4) {
len = toValue(&buf[2]);
if (len != 28) {
// Serial.printf("Got good bad len %d\r\n", len);
len += 4;
step = 3;
} else {
// Serial.println("Got good len");
step = 4;
} }
} }
break;
}
case 3: {
bufIndex++;
if (bufIndex >= len) {
step = 0;
// Serial.println("Bad lengh read all buffer");
}
break;
}
case 4: {
buf[bufIndex++] = value;
if (bufIndex >= 32) {
result |= validate(buf);
step = 0;
// Serial.println("Got data");
}
break;
}
default:
break; break;
} }
_index++; // Reduce core panic: delay 1 ms each 32bytes data
bcount++;
if ((bcount % 32) == 0) {
delay(1);
}
}
if (result) {
lastRead = millis();
if (lastRead == 0) {
lastRead = 1;
}
failed = false;
} else {
if (ms > 5000) {
failed = true;
}
} }
} }
/**
* @brief Check that PMS send is failed or disconnected
*
* @return true Failed
* @return false No problem
*/
bool PMSBase::isFailed(void) { return failed; }
/**
* @brief Read PMS 0.1 ug/m3 with CF = 1 PM estimates
*
* @return uint16_t
*/
uint16_t PMSBase::getRaw0_1(void) { return toValue(&package[4]); }
/**
* @brief Read PMS 2.5 ug/m3 with CF = 1 PM estimates
*
* @return uint16_t
*/
uint16_t PMSBase::getRaw2_5(void) { return toValue(&package[6]); }
/**
* @brief Read PMS 10 ug/m3 with CF = 1 PM estimates
*
* @return uint16_t
*/
uint16_t PMSBase::getRaw10(void) { return toValue(&package[8]); }
/**
* @brief Read PMS 0.1 ug/m3
*
* @return uint16_t
*/
uint16_t PMSBase::getPM0_1(void) { return toValue(&package[10]); }
/**
* @brief Read PMS 2.5 ug/m3
*
* @return uint16_t
*/
uint16_t PMSBase::getPM2_5(void) { return toValue(&package[12]); }
/**
* @brief Read PMS 10 ug/m3
*
* @return uint16_t
*/
uint16_t PMSBase::getPM10(void) { return toValue(&package[14]); }
/**
* @brief Get numnber concentrations over 0.3 um/0.1L
*
* @return uint16_t
*/
uint16_t PMSBase::getCount0_3(void) { return toValue(&package[16]); }
/**
* @brief Get numnber concentrations over 0.5 um/0.1L
*
* @return uint16_t
*/
uint16_t PMSBase::getCount0_5(void) { return toValue(&package[18]); }
/**
* @brief Get numnber concentrations over 1.0 um/0.1L
*
* @return uint16_t
*/
uint16_t PMSBase::getCount1_0(void) { return toValue(&package[20]); }
/**
* @brief Get numnber concentrations over 2.5 um/0.1L
*
* @return uint16_t
*/
uint16_t PMSBase::getCount2_5(void) { return toValue(&package[22]); }
/**
* @brief Get numnber concentrations over 5.0 um/0.1L (only PMS5003)
*
* @return uint16_t
*/
uint16_t PMSBase::getCount5_0(void) { return toValue(&package[24]); }
/**
* @brief Get numnber concentrations over 10.0 um/0.1L (only PMS5003)
*
* @return uint16_t
*/
uint16_t PMSBase::getCount10(void) { return toValue(&package[26]); }
/**
* @brief Get temperature (only PMS5003T)
*
* @return uint16_t
*/
uint16_t PMSBase::getTemp(void) { return toValue(&package[24]); }
/**
* @brief Get humidity (only PMS5003T)
*
* @return uint16_t
*/
uint16_t PMSBase::getHum(void) { return toValue(&package[26]); }
/**
* @brief Convert PMS2.5 to US AQI unit
*
* @param pm02
* @return int
*/
int PMSBase::pm25ToAQI(int pm02) {
if (pm02 <= 12.0)
return ((50 - 0) / (12.0 - .0) * (pm02 - .0) + 0);
else if (pm02 <= 35.4)
return ((100 - 50) / (35.4 - 12.0) * (pm02 - 12.0) + 50);
else if (pm02 <= 55.4)
return ((150 - 100) / (55.4 - 35.4) * (pm02 - 35.4) + 100);
else if (pm02 <= 150.4)
return ((200 - 150) / (150.4 - 55.4) * (pm02 - 55.4) + 150);
else if (pm02 <= 250.4)
return ((300 - 200) / (250.4 - 150.4) * (pm02 - 150.4) + 200);
else if (pm02 <= 350.4)
return ((400 - 300) / (350.4 - 250.4) * (pm02 - 250.4) + 300);
else if (pm02 <= 500.4)
return ((500 - 400) / (500.4 - 350.4) * (pm02 - 350.4) + 400);
else
return 500;
}
/**
* @brief Convert two byte value to uint16_t value
*
* @param buf bytes array (must be >= 2)
* @return uint16_t
*/
uint16_t PMSBase::toValue(char *buf) { return (buf[0] << 8) | buf[1]; }
/**
* @brief Validate package data
*
* @param buf Package buffer
* @return true Success
* @return false Failed
*/
bool PMSBase::validate(char *buf) {
uint16_t sum = 0;
for (int i = 0; i < 30; i++) {
sum += buf[i];
}
if (sum == toValue(&buf[30])) {
for (int i = 0; i < 32; i++) {
package[i] = buf[i];
}
return true;
}
return false;
}

View File

@ -1,75 +1,43 @@
#ifndef _PMS_BASE_H_ #ifndef _PMS5003_BASE_H_
#define _PMS_BASE_H_ #define _PMS5003_BASE_H_
#include <Arduino.h> #include <Arduino.h>
/** class PMSBase {
* @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: public:
static const uint16_t SINGLE_RESPONSE_TIME = 1000;
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;
struct DATA {
// Standard Particles, CF=1
uint16_t PM_SP_UG_1_0;
uint16_t PM_SP_UG_2_5;
uint16_t PM_SP_UG_10_0;
// Atmospheric environment
uint16_t PM_AE_UG_1_0;
uint16_t PM_AE_UG_2_5;
uint16_t PM_AE_UG_10_0;
// Raw particles count (number of particles in 0.1l of air
uint16_t PM_RAW_0_3;
uint16_t PM_RAW_0_5;
uint16_t PM_RAW_1_0;
uint16_t PM_RAW_2_5;
uint16_t PM_RAW_5_0;
uint16_t PM_RAW_10_0;
// Formaldehyde (HCHO) concentration in mg/m^3 - PMSxxxxST units only
uint16_t AMB_HCHO;
// Temperature & humidity - PMSxxxxST units only
int16_t AMB_TMP;
uint16_t AMB_HUM;
};
bool begin(Stream *stream); bool begin(Stream *stream);
void sleep(); void handle();
void wakeUp(); bool isFailed(void);
void activeMode(); uint16_t getRaw0_1(void);
void passiveMode(); uint16_t getRaw2_5(void);
uint16_t getRaw10(void);
uint16_t getPM0_1(void);
uint16_t getPM2_5(void);
uint16_t getPM10(void);
uint16_t getCount0_3(void);
uint16_t getCount0_5(void);
uint16_t getCount1_0(void);
uint16_t getCount2_5(void);
void requestRead(); /** For PMS5003 */
bool read(DATA &data); uint16_t getCount5_0(void);
bool readUntil(DATA &data, uint16_t timeout = SINGLE_RESPONSE_TIME); uint16_t getCount10(void);
/** For PMS5003T*/
uint16_t getTemp(void);
uint16_t getHum(void);
int pm25ToAQI(int pm02);
private: private:
enum STATUS { STATUS_WAITING, STATUS_OK }; Stream *stream;
enum MODE { MODE_ACTIVE, MODE_PASSIVE }; char package[32];
int packageIndex;
bool failed = false;
uint32_t lastRead;
uint8_t _payload[50]; uint16_t toValue(char *buf);
Stream *_stream; bool validate(char *buf);
DATA *_data;
STATUS _status;
MODE _mode = MODE_ACTIVE;
uint8_t _index = 0;
uint16_t _frameLen;
uint16_t _checksum;
uint16_t _calculatedChecksum;
void loop();
char Char_PM2[10];
}; };
#endif #endif /** _PMS5003_BASE_H_ */

View File

@ -63,7 +63,8 @@ bool PMS5003::begin(void) {
#if defined(ESP8266) #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); SoftwareSerial *uart =
new SoftwareSerial(bsp->Pms5003.uart_tx_pin, bsp->Pms5003.uart_rx_pin);
uart->begin(9600); uart->begin(9600);
if (pms.begin(uart) == false) { if (pms.begin(uart) == false) {
AgLog("PMS failed"); AgLog("PMS failed");
@ -81,73 +82,33 @@ bool PMS5003::begin(void) {
return true; return true;
} }
/**
* @brief Convert PM2.5 to US AQI
*
* @param pm02
* @return int
*/
int PMS5003::pm25ToAQI(int pm02) {
if (pm02 <= 12.0)
return ((50 - 0) / (12.0 - .0) * (pm02 - .0) + 0);
else if (pm02 <= 35.4)
return ((100 - 50) / (35.4 - 12.0) * (pm02 - 12.0) + 50);
else if (pm02 <= 55.4)
return ((150 - 100) / (55.4 - 35.4) * (pm02 - 35.4) + 100);
else if (pm02 <= 150.4)
return ((200 - 150) / (150.4 - 55.4) * (pm02 - 55.4) + 150);
else if (pm02 <= 250.4)
return ((300 - 200) / (250.4 - 150.4) * (pm02 - 150.4) + 200);
else if (pm02 <= 350.4)
return ((400 - 300) / (350.4 - 250.4) * (pm02 - 250.4) + 300);
else if (pm02 <= 500.4)
return ((500 - 400) / (500.4 - 350.4) * (pm02 - 350.4) + 400);
else
return 500;
}
/**
* @brief Read all package data then call to @ref getPMxxx to get the target
* data
*
* @return true Success
* @return false Failure
*/
bool PMS5003::readData(void) {
if (this->isBegin() == false) {
return false;
}
return pms.readUntil(pmsData);
}
/** /**
* @brief Read PM1.0 must call this function after @ref readData success * @brief Read PM1.0 must call this function after @ref readData success
* *
* @return int PM1.0 index * @return int PM1.0 index
*/ */
int PMS5003::getPm01Ae(void) { return pmsData.PM_AE_UG_1_0; } int PMS5003::getPm01Ae(void) { return pms.getPM0_1(); }
/** /**
* @brief Read PM2.5 must call this function after @ref readData success * @brief Read PM2.5 must call this function after @ref readData success
* *
* @return int PM2.5 index * @return int PM2.5 index
*/ */
int PMS5003::getPm25Ae(void) { return pmsData.PM_AE_UG_2_5; } int PMS5003::getPm25Ae(void) { return pms.getPM2_5(); }
/** /**
* @brief Read PM10.0 must call this function after @ref readData success * @brief Read PM10.0 must call this function after @ref readData success
* *
* @return int PM10.0 index * @return int PM10.0 index
*/ */
int PMS5003::getPm10Ae(void) { return pmsData.PM_AE_UG_10_0; } int PMS5003::getPm10Ae(void) { return pms.getPM10(); }
/** /**
* @brief Read PM3.0 must call this function after @ref readData success * @brief Read PM0.3 must call this function after @ref readData success
* *
* @return int PM3.0 index * @return int PM0.3 index
*/ */
int PMS5003::getPm03ParticleCount(void) { return pmsData.PM_RAW_0_3; } int PMS5003::getPm03ParticleCount(void) { return pms.getCount0_3(); }
/** /**
* @brief Convert PM2.5 to US AQI * @brief Convert PM2.5 to US AQI
@ -155,7 +116,7 @@ int PMS5003::getPm03ParticleCount(void) { return pmsData.PM_RAW_0_3; }
* @param pm25 PM2.5 index * @param pm25 PM2.5 index
* @return int PM2.5 US AQI * @return int PM2.5 US AQI
*/ */
int PMS5003::convertPm25ToUsAqi(int pm25) { return this->pm25ToAQI(pm25); } int PMS5003::convertPm25ToUsAqi(int pm25) { return pms.pm25ToAQI(pm25); }
/** /**
* @brief Check device initialized or not * @brief Check device initialized or not
@ -186,3 +147,17 @@ void PMS5003::end(void) {
#endif #endif
AgLog("De-initialize"); AgLog("De-initialize");
} }
/**
* @brief Check and read PMS sensor data. This method should be callack from
* loop process to continoue check sensor data if it's available
*/
void PMS5003::handle(void) { pms.handle(); }
/**
* @brief Get sensor status
*
* @return true No problem
* @return false Communication timeout or sensor has removed
*/
bool PMS5003::isFailed(void) { return pms.isFailed(); }

View File

@ -2,8 +2,8 @@
#define _AIR_GRADIENT_PMS5003_H_ #define _AIR_GRADIENT_PMS5003_H_
#include "../Main/BoardDef.h" #include "../Main/BoardDef.h"
#include "Stream.h"
#include "PMS.h" #include "PMS.h"
#include "Stream.h"
/** /**
* @brief The class define how to handle PMS5003 sensor bas on @ref PMS class * @brief The class define how to handle PMS5003 sensor bas on @ref PMS class
@ -17,8 +17,8 @@ public:
bool begin(HardwareSerial &serial); bool begin(HardwareSerial &serial);
#endif #endif
void end(void); void end(void);
void handle(void);
bool readData(void); bool isFailed(void);
int getPm01Ae(void); int getPm01Ae(void);
int getPm25Ae(void); int getPm25Ae(void);
int getPm10Ae(void); int getPm10Ae(void);
@ -28,7 +28,7 @@ public:
private: private:
bool _isBegin = false; bool _isBegin = false;
BoardType _boardDef; BoardType _boardDef;
PMS pms; PMSBase pms;
const BoardDef *bsp; const BoardDef *bsp;
#if defined(ESP8266) #if defined(ESP8266)
Stream *_debugStream; Stream *_debugStream;
@ -36,11 +36,7 @@ private:
#else #else
HardwareSerial *_serial; HardwareSerial *_serial;
#endif #endif
// Conplug_PMS5003T *pms;
PMS::DATA pmsData;
bool begin(void); bool begin(void);
bool isBegin(void); bool isBegin(void);
int pm25ToAQI(int pm02);
}; };
#endif /** _AIR_GRADIENT_PMS5003_H_ */ #endif /** _AIR_GRADIENT_PMS5003_H_ */

View File

@ -78,19 +78,21 @@ bool PMS5003T::begin(void) {
#if ARDUINO_USB_CDC_ON_BOOT // Serial used for USB CDC #if ARDUINO_USB_CDC_ON_BOOT // Serial used for USB CDC
if (this->_serial == &Serial0) { if (this->_serial == &Serial0) {
AgLog("Init Serial0");
_serial->begin(9600, SERIAL_8N1);
#else #else
if (this->_serial == &Serial) { if (this->_serial == &Serial) {
#endif
AgLog("Init Serial"); AgLog("Init Serial");
this->_serial->begin(9600, SERIAL_8N1, bsp->Pms5003.uart_rx_pin, this->_serial->begin(9600, SERIAL_8N1, bsp->Pms5003.uart_rx_pin,
bsp->Pms5003.uart_tx_pin); bsp->Pms5003.uart_tx_pin);
#endif
} else { } else {
/** Share with sensor air s8*/
if (bsp->SenseAirS8.supported == false) { if (bsp->SenseAirS8.supported == false) {
AgLog("Board [%d] PMS5003T_2 not supported", this->_boardDef); AgLog("Board [%d] PMS5003T_2 not supported", this->_boardDef);
return false; return false;
} }
/** Share with sensor air s8*/
AgLog("Init Serialx"); AgLog("Init Serialx");
this->_serial->begin(9600, SERIAL_8N1, bsp->SenseAirS8.uart_rx_pin, this->_serial->begin(9600, SERIAL_8N1, bsp->SenseAirS8.uart_rx_pin,
bsp->SenseAirS8.uart_tx_pin); bsp->SenseAirS8.uart_tx_pin);
@ -105,73 +107,33 @@ bool PMS5003T::begin(void) {
return true; return true;
} }
/**
* @brief Convert PM2.5 to US AQI
*
* @param pm02
* @return int
*/
int PMS5003T::pm25ToAQI(int pm02) {
if (pm02 <= 12.0)
return ((50 - 0) / (12.0 - .0) * (pm02 - .0) + 0);
else if (pm02 <= 35.4)
return ((100 - 50) / (35.4 - 12.0) * (pm02 - 12.0) + 50);
else if (pm02 <= 55.4)
return ((150 - 100) / (55.4 - 35.4) * (pm02 - 35.4) + 100);
else if (pm02 <= 150.4)
return ((200 - 150) / (150.4 - 55.4) * (pm02 - 55.4) + 150);
else if (pm02 <= 250.4)
return ((300 - 200) / (250.4 - 150.4) * (pm02 - 150.4) + 200);
else if (pm02 <= 350.4)
return ((400 - 300) / (350.4 - 250.4) * (pm02 - 250.4) + 300);
else if (pm02 <= 500.4)
return ((500 - 400) / (500.4 - 350.4) * (pm02 - 350.4) + 400);
else
return 500;
}
/**
* @brief Read all package data then call to @ref getPMxxx to get the target
* data
*
* @return true Success
* @return false Failure
*/
bool PMS5003T::readData(void) {
if (this->isBegin() == false) {
return false;
}
return pms.readUntil(pmsData);
}
/** /**
* @brief Read PM1.0 must call this function after @ref readData success * @brief Read PM1.0 must call this function after @ref readData success
* *
* @return int PM1.0 index * @return int PM1.0 index
*/ */
int PMS5003T::getPm01Ae(void) { return pmsData.PM_AE_UG_1_0; } int PMS5003T::getPm01Ae(void) { return pms.getPM0_1(); }
/** /**
* @brief Read PM2.5 must call this function after @ref readData success * @brief Read PM2.5 must call this function after @ref readData success
* *
* @return int PM2.5 index * @return int PM2.5 index
*/ */
int PMS5003T::getPm25Ae(void) { return pmsData.PM_AE_UG_2_5; } int PMS5003T::getPm25Ae(void) { return pms.getPM2_5(); }
/** /**
* @brief Read PM10.0 must call this function after @ref readData success * @brief Read PM10.0 must call this function after @ref readData success
* *
* @return int PM10.0 index * @return int PM10.0 index
*/ */
int PMS5003T::getPm10Ae(void) { return pmsData.PM_AE_UG_10_0; } int PMS5003T::getPm10Ae(void) { return pms.getPM10(); }
/** /**
* @brief Read PM3.0 must call this function after @ref readData success * @brief Read PM 0.3 Count must call this function after @ref readData success
* *
* @return int PM3.0 index * @return int PM 0.3 Count index
*/ */
int PMS5003T::getPm03ParticleCount(void) { return pmsData.PM_RAW_0_3; } int PMS5003T::getPm03ParticleCount(void) { return pms.getCount0_3(); }
/** /**
* @brief Convert PM2.5 to US AQI * @brief Convert PM2.5 to US AQI
@ -179,7 +141,7 @@ int PMS5003T::getPm03ParticleCount(void) { return pmsData.PM_RAW_0_3; }
* @param pm25 PM2.5 index * @param pm25 PM2.5 index
* @return int PM2.5 US AQI * @return int PM2.5 US AQI
*/ */
int PMS5003T::convertPm25ToUsAqi(int pm25) { return this->pm25ToAQI(pm25); } int PMS5003T::convertPm25ToUsAqi(int pm25) { return pms.pm25ToAQI(pm25); }
/** /**
* @brief Get temperature, Must call this method after @ref readData() success * @brief Get temperature, Must call this method after @ref readData() success
@ -187,7 +149,7 @@ int PMS5003T::convertPm25ToUsAqi(int pm25) { return this->pm25ToAQI(pm25); }
* @return float Degree Celcius * @return float Degree Celcius
*/ */
float PMS5003T::getTemperature(void) { float PMS5003T::getTemperature(void) {
float temp = pmsData.AMB_TMP; float temp = pms.getTemp();
return correctionTemperature(temp / 10.0f); return correctionTemperature(temp / 10.0f);
} }
@ -197,8 +159,8 @@ float PMS5003T::getTemperature(void) {
* @return float Percent (%) * @return float Percent (%)
*/ */
float PMS5003T::getRelativeHumidity(void) { float PMS5003T::getRelativeHumidity(void) {
float temp = pmsData.AMB_HUM; float hum = pms.getHum();
return temp / 10.0f; return correctionRelativeHumidity(hum / 10.0f);
} }
/** /**
@ -234,3 +196,31 @@ void PMS5003T::end(void) {
#endif #endif
AgLog("De-initialize"); AgLog("De-initialize");
} }
/**
* @brief Check and read PMS sensor data. This method should be callack from
* loop process to continoue check sensor data if it's available
*/
void PMS5003T::handle(void) { pms.handle(); }
/**
* @brief Get sensor status
*
* @return true No problem
* @return false Communication timeout or sensor has removed
*/
bool PMS5003T::isFailed(void) { return pms.isFailed(); }
/**
* @brief Correct the PMS5003T relactive humidity
*
* @param inHum Input humidity
* @return float Corrected humidity
*/
float PMS5003T::correctionRelativeHumidity(float inHum) {
float hum = inHum * 1.259 + 7.34;
if (hum > 100.0f) {
hum = 100.0f;
}
return hum;
}

View File

@ -1,10 +1,10 @@
#ifndef _PMS5003T_H_ #ifndef _PMS5003T_H_
#define _PMS5003T_H_ #define _PMS5003T_H_
#include <HardwareSerial.h>
#include "../Main/BoardDef.h" #include "../Main/BoardDef.h"
#include "PMS.h" #include "PMS.h"
#include "Stream.h" #include "Stream.h"
#include <HardwareSerial.h>
/** /**
* @brief The class define how to handle PMS5003T sensor bas on @ref PMS class * @brief The class define how to handle PMS5003T sensor bas on @ref PMS class
@ -19,7 +19,8 @@ public:
#endif #endif
void end(void); void end(void);
bool readData(void); void handle(void);
bool isFailed(void);
int getPm01Ae(void); int getPm01Ae(void);
int getPm25Ae(void); int getPm25Ae(void);
int getPm10Ae(void); int getPm10Ae(void);
@ -40,13 +41,11 @@ private:
#else #else
HardwareSerial *_serial; HardwareSerial *_serial;
#endif #endif
PMSBase pms;
bool begin(void); bool begin(void);
int pm25ToAQI(int pm02);
PMS pms;
PMS::DATA pmsData;
bool isBegin(void); bool isBegin(void);
float correctionTemperature(float inTemp); float correctionTemperature(float inTemp);
float correctionRelativeHumidity(float inHum);
}; };
#endif /** _PMS5003T_H_ */ #endif /** _PMS5003T_H_ */

View File

@ -228,7 +228,7 @@ int16_t S8::getCo2(void) {
return -1; return -1;
} }
int16_t co2 = 0; int16_t co2 = -1;
// Ask CO2 value // Ask CO2 value
sendCommand(MODBUS_FUNC_READ_INPUT_REGISTERS, MODBUS_IR4, 0x0001); sendCommand(MODBUS_FUNC_READ_INPUT_REGISTERS, MODBUS_IR4, 0x0001);
@ -649,11 +649,25 @@ bool S8::init(int txPin, int rxPin, uint32_t baud) {
uart->begin(baud); uart->begin(baud);
this->_uartStream = uart; this->_uartStream = uart;
#else #else
#if ARDUINO_USB_CDC_ON_BOOT
/** The 'Serial0' can ont configure tx, rx pin, only use as default */
if (_serial == &Serial0) {
AgLog("Init on 'Serial0'");
_serial->begin(baud, SERIAL_8N1);
} else {
AgLog("Init on 'Serialx'");
this->_serial->begin(baud, SERIAL_8N1, rxPin, txPin);
}
this->_uartStream = this->_serial;
#else
AgLog("Init on 'Serialx'");
this->_serial->begin(baud, SERIAL_8N1, rxPin, txPin); this->_serial->begin(baud, SERIAL_8N1, rxPin, txPin);
this->_uartStream = this->_serial; this->_uartStream = this->_serial;
#endif
#endif #endif
/** Check communication by get firmware version */ /** Check communication by get firmware version */
delay(100);
char fwVers[11]; char fwVers[11];
this->_isBegin = true; this->_isBegin = true;
this->getFirmwareVersion(fwVers); this->getFirmwareVersion(fwVers);
@ -712,7 +726,7 @@ uint8_t S8::uartReadBytes(uint8_t max_bytes, uint32_t timeout_ms) {
#if defined(ESP32) #if defined(ESP32)
// Relax 5ms to avoid watchdog reset // Relax 5ms to avoid watchdog reset
vTaskDelay(pdMS_TO_TICKS(5)); vTaskDelay(pdMS_TO_TICKS(1));
#endif #endif
} }
return nb; return nb;

View File

@ -1,7 +1,7 @@
#ifndef _S8_H_ #ifndef _S8_H_
#define _S8_H_ #define _S8_H_
#include "../main/BoardDef.h" #include "../Main/BoardDef.h"
#include "Arduino.h" #include "Arduino.h"
/** /**

View File

@ -91,7 +91,7 @@ void Sgp41::handle(void) {
if (getRawSignal(srawVoc, srawNox)) { if (getRawSignal(srawVoc, srawNox)) {
nox = noxAlgorithm()->process(srawNox); nox = noxAlgorithm()->process(srawNox);
tvoc = vocAlgorithm()->process(srawVoc); tvoc = vocAlgorithm()->process(srawVoc);
AgLog("Polling SGP41 success: tvoc: %d, nox: %d", tvoc, nox); // AgLog("Polling SGP41 success: tvoc: %d, nox: %d", tvoc, nox);
} }
} }
} }
@ -121,9 +121,10 @@ void Sgp41::_handle(void) {
vTaskDelay(pdMS_TO_TICKS(1000)); vTaskDelay(pdMS_TO_TICKS(1000));
if (getRawSignal(srawVoc, srawNox)) { if (getRawSignal(srawVoc, srawNox)) {
tvocRaw = srawVoc; tvocRaw = srawVoc;
noxRaw = srawNox;
nox = noxAlgorithm()->process(srawNox); nox = noxAlgorithm()->process(srawNox);
tvoc = vocAlgorithm()->process(srawVoc); tvoc = vocAlgorithm()->process(srawVoc);
AgLog("Polling SGP41 success: tvoc: %d, nox: %d", tvoc, nox); // AgLog("Polling SGP41 success: tvoc: %d, nox: %d", tvoc, nox);
} }
} }
} }
@ -249,3 +250,23 @@ bool Sgp41::_noxConditioning(void) {
* @return int * @return int
*/ */
int Sgp41::getTvocRaw(void) { return tvocRaw; } int Sgp41::getTvocRaw(void) { return tvocRaw; }
/**
* @brief Get NOX raw value
*
* @return int
*/
int Sgp41::getNoxRaw(void) { return noxRaw; }
/**
* @brief Set compasation temperature and humidity to calculate TVOC and NOx
* index
*
* @param temp Temperature
* @param hum Humidity
*/
void Sgp41::setCompensationTemperatureHumidity(float temp, float hum) {
defaultT = static_cast<uint16_t>((temp + 45) * 65535 / 175);
defaultRh = static_cast<uint16_t>(hum * 65535 / 100);
AgLog("Update: defaultT: %d, defaultRh: %d", defaultT, defaultRh);
}

View File

@ -1,7 +1,7 @@
#ifndef _AIR_GRADIENT_SGP4X_H_ #ifndef _AIR_GRADIENT_SGP4X_H_
#define _AIR_GRADIENT_SGP4X_H_ #define _AIR_GRADIENT_SGP4X_H_
#include "../main/BoardDef.h" #include "../Main/BoardDef.h"
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h> #include <Wire.h>
@ -24,6 +24,8 @@ public:
int getTvocIndex(void); int getTvocIndex(void);
int getNoxIndex(void); int getNoxIndex(void);
int getTvocRaw(void); int getTvocRaw(void);
int getNoxRaw(void);
void setCompensationTemperatureHumidity(float temp, float hum);
private: private:
bool onConditioning = true; bool onConditioning = true;
@ -39,6 +41,7 @@ private:
int tvoc = 0; int tvoc = 0;
int tvocRaw; int tvocRaw;
int nox = 0; int nox = 0;
int noxRaw;
#if defined(ESP8266) #if defined(ESP8266)
uint32_t conditioningPeriod; uint32_t conditioningPeriod;
uint8_t conditioningCount; uint8_t conditioningCount;

View File

@ -1,7 +1,7 @@
#ifndef _SHT_H_ #ifndef _SHT_H_
#define _SHT_H_ #define _SHT_H_
#include "../main/BoardDef.h" #include "../Main/BoardDef.h"
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h> #include <Wire.h>