From 96e174bcc67af843d11fc33e2f90d9063dfe7af4 Mon Sep 17 00:00:00 2001 From: Akshaylal S <40035782+Akshaylal@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:10:42 +0530 Subject: [PATCH] Add support for AGS02MA TVOC Sensor (#24109) * Add ags02ma library * Initial commit * fix object init * fix bugs * Update I2C Driver number Device disabled by default * refactoring for consistency --- I2CDEVICES.md | 1 + lib/lib_i2c/AGS02MA-0.4.3/.arduino-ci.yml | 28 ++ lib/lib_i2c/AGS02MA-0.4.3/AGS02MA.cpp | 386 +++++++++++++++++ lib/lib_i2c/AGS02MA-0.4.3/AGS02MA.h | 145 +++++++ lib/lib_i2c/AGS02MA-0.4.3/CHANGELOG.md | 65 +++ lib/lib_i2c/AGS02MA-0.4.3/LICENSE | 21 + lib/lib_i2c/AGS02MA-0.4.3/README.md | 400 ++++++++++++++++++ .../examples/AGS02MA_PPB/AGS02MA_PPB.ino | 74 ++++ .../AGS02MA_PPB_TIMING/AGS02MA_PPB_TIMING.ino | 75 ++++ .../AGS02MA_PPB_TIMING/performance_0.3.0.txt | 29 ++ .../performance_0.3.1_10khz.txt | 27 ++ .../performance_0.3.1_25khz.txt | 32 ++ .../examples/AGS02MA_PPM/AGS02MA_PPM.ino | 71 ++++ .../examples/AGS02MA_UGM3/AGS02MA_UGM3.ino | 71 ++++ .../AGS02MA_calibrate/AGS02MA_calibrate.ino | 164 +++++++ .../AGS02MA_calibrate_manual.ino | 172 ++++++++ .../AGS02MA_get_registers/.arduino-ci.yml | 28 ++ .../AGS02MA_get_registers.ino | 108 +++++ .../AGS02MA_minimal/AGS02MA_minimal.ino | 59 +++ .../AGS02MA_minimal_plotter.ino | 69 +++ .../AGS02MA_readRegister.ino | 123 ++++++ .../AGS02MA_setAddress/AGS02MA_setAddress.ino | 60 +++ .../examples/AGS02MA_test/AGS02MA_test.ino | 55 +++ .../AGS02MA_test_CRC8/.arduino-ci.yml | 7 + .../AGS02MA_test_CRC8/AGS02MA_test_CRC8.ino | 76 ++++ .../AGS02MA-0.4.3/examples/issue/issue.ino | 105 +++++ .../examples/test_CRC8/.arduino-ci.yml | 7 + .../examples/test_CRC8/test_CRC8.ino | 83 ++++ lib/lib_i2c/AGS02MA-0.4.3/keywords.txt | 60 +++ lib/lib_i2c/AGS02MA-0.4.3/library.json | 29 ++ lib/lib_i2c/AGS02MA-0.4.3/library.properties | 11 + .../AGS02MA-0.4.3/test/unit_test_001.cpp | 102 +++++ tasmota/my_user_config.h | 2 + .../tasmota_xsns_sensor/xsns_118_ags02ma.ino | 199 +++++++++ 34 files changed, 2944 insertions(+) create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/.arduino-ci.yml create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/AGS02MA.cpp create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/AGS02MA.h create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/CHANGELOG.md create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/LICENSE create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/README.md create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB/AGS02MA_PPB.ino create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB_TIMING/AGS02MA_PPB_TIMING.ino create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB_TIMING/performance_0.3.0.txt create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB_TIMING/performance_0.3.1_10khz.txt create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB_TIMING/performance_0.3.1_25khz.txt create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPM/AGS02MA_PPM.ino create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_UGM3/AGS02MA_UGM3.ino create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_calibrate/AGS02MA_calibrate.ino create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_calibrate_manual/AGS02MA_calibrate_manual.ino create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_get_registers/.arduino-ci.yml create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_get_registers/AGS02MA_get_registers.ino create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_minimal/AGS02MA_minimal.ino create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_minimal_plotter/AGS02MA_minimal_plotter.ino create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_readRegister/AGS02MA_readRegister.ino create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_setAddress/AGS02MA_setAddress.ino create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_test/AGS02MA_test.ino create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_test_CRC8/.arduino-ci.yml create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_test_CRC8/AGS02MA_test_CRC8.ino create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/issue/issue.ino create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/test_CRC8/.arduino-ci.yml create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/examples/test_CRC8/test_CRC8.ino create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/keywords.txt create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/library.json create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/library.properties create mode 100644 lib/lib_i2c/AGS02MA-0.4.3/test/unit_test_001.cpp create mode 100644 tasmota/tasmota_xsns_sensor/xsns_118_ags02ma.ino diff --git a/I2CDEVICES.md b/I2CDEVICES.md index b317edf83..9eba84417 100644 --- a/I2CDEVICES.md +++ b/I2CDEVICES.md @@ -133,5 +133,6 @@ Index | Define | Driver | Device | Address(es) | Bus2 | Descrip 92 | USE_PCF85063 | xdrv_56 | PCF85063 | 0x51 | | PCF85063 Real time clock 93 | USE_AS33772S | xdrv_119 | AS33772S | 0x52 | Yes | AS33772S USB PD Sink Controller 94 | USE_RV3028 | xdrv_56 | RV3028 | 0x52 | Yes | RV-3028-C7 RTC Controller + 95 | USE_AGS02MA | xsns_118 | AGS02MA | 0x1A | | TVOC Gas sensor NOTE: Bus2 supported on ESP32 only. diff --git a/lib/lib_i2c/AGS02MA-0.4.3/.arduino-ci.yml b/lib/lib_i2c/AGS02MA-0.4.3/.arduino-ci.yml new file mode 100644 index 000000000..77a333f93 --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/.arduino-ci.yml @@ -0,0 +1,28 @@ +platforms: + rpipico: + board: rp2040:rp2040:rpipico + package: rp2040:rp2040 + gcc: + features: + defines: + - ARDUINO_ARCH_RP2040 + warnings: + flags: + +packages: + rp2040:rp2040: + url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json + +compile: + # Choosing to run compilation tests on 2 different Arduino platforms + platforms: + - uno + # - due + # - zero + # - leonardo + - m4 + - esp32 + - esp8266 + # - mega2560 + - rpipico + diff --git a/lib/lib_i2c/AGS02MA-0.4.3/AGS02MA.cpp b/lib/lib_i2c/AGS02MA-0.4.3/AGS02MA.cpp new file mode 100644 index 000000000..8bfebfc1f --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/AGS02MA.cpp @@ -0,0 +1,386 @@ +// +// FILE: AGS02MA.cpp +// AUTHOR: Rob Tillaart, Viktor Balint, Beanow +// DATE: 2021-08-12 +// VERSION: 0.4.3 +// PURPOSE: Arduino library for AGS02MA TVOC sensor +// URL: https://github.com/RobTillaart/AGS02MA + + +#include "AGS02MA.h" + + +// REGISTERS +#define AGS02MA_DATA 0x00 +#define AGS02MA_CALIBRATION 0x01 +#define AGS02MA_VERSION 0x11 +#define AGS02MA_SLAVE_ADDRESS 0x21 + + + +AGS02MA::AGS02MA(const uint8_t deviceAddress, TwoWire *wire) +{ + _address = deviceAddress; + _wire = wire; + reset(); +} + + +bool AGS02MA::begin() +{ + _startTime = millis(); // PREHEAT TIMING + return isConnected(); +} + + +bool AGS02MA::isConnected() +{ + _setI2CLowSpeed(); + _wire->beginTransmission(_address); + bool rv = ( _wire->endTransmission(true) == 0); + _setI2CHighSpeed(); + return rv; +} + + +void AGS02MA::reset() +{ + _I2CResetSpeed = 100000; + _startTime = millis(); + _lastRead = 0; + _lastPPB = 0; + _mode = 255; + _status = AGS02MA_OK; + _error = AGS02MA_OK; +} + + +bool AGS02MA::setAddress(const uint8_t deviceAddress) +{ + if ((deviceAddress < 10) or (deviceAddress > 119)) return false; + _buffer[2] = _buffer[0] = deviceAddress; + _buffer[3] = _buffer[1] = 0xFF ^ deviceAddress; + _buffer[4] = _CRC8(_buffer, 4); + if (_writeRegister(AGS02MA_SLAVE_ADDRESS)) + { + _address = deviceAddress; + } + return isConnected(); +} + + +uint8_t AGS02MA::getSensorVersion() +{ + uint8_t version = 0xFF; + if (_readRegister(AGS02MA_VERSION)) + { + // for (int i = 0; i < 5; i++) + // { + // Serial.print(_buffer[i]); + // Serial.print('\t'); + // } + // Serial.println(); + // unclear what the other bytes have for information. + // datasheet names these 3 bytes as KEEP. + // BUFFER VALUE MEANING + // buffer [0] == 20 year ? + // buffer [1] == 07 month ? + // buffer [2] == 28 day ? + // buffer [3] == 117 VERSION + // buffer [4] == CRC + version = _buffer[3]; + if (_CRC8(_buffer, 5) != 0) + { + _error = AGS02MA_ERROR_CRC; + } + } + return version; +} + + +uint32_t AGS02MA::getSensorDate() +{ + uint32_t date = 0xFFFFFFFF; + if (_readRegister(AGS02MA_VERSION)) + { + date = 0x20; + date <<= 8; + date += _bin2bcd(_buffer[0]); + date <<= 8; + date += _bin2bcd(_buffer[1]); + date <<= 8; + date += _bin2bcd(_buffer[2]); + // version = _buffer[3]; + if (_CRC8(_buffer, 5) != 0) + { + _error = AGS02MA_ERROR_CRC; + } + } + return date; +} + + +bool AGS02MA::setPPBMode() +{ + _buffer[0] = 0x00; + _buffer[1] = 0xFF; + _buffer[2] = 0x00; + _buffer[3] = 0xFF; + _buffer[4] = 0x30; + if (_writeRegister(AGS02MA_DATA)) + { + _mode = 0; + return true; + } + return false; +} + + +bool AGS02MA::setUGM3Mode() +{ + _buffer[0] = 0x02; + _buffer[1] = 0xFD; + _buffer[2] = 0x02; + _buffer[3] = 0xFD; + _buffer[4] = 0x00; + if (_writeRegister(AGS02MA_DATA)) + { + _mode = 1; + return true; + } + return false; +} + + +uint32_t AGS02MA::readPPB() +{ + uint32_t value = _readSensor(); + if (_error == AGS02MA_OK) + { + _lastRead = millis(); + _lastPPB = value; + } + else + { + value = _lastPPB; + } + return value; +} + + +uint32_t AGS02MA::readUGM3() +{ + uint32_t value = _readSensor(); + if (_error == AGS02MA_OK) + { + _lastRead = millis(); + _lastUGM3 = value; + } + else + { + value = _lastUGM3; + } + return value; +} + + +bool AGS02MA::manualZeroCalibration(uint16_t value) +{ + _buffer[0] = 0x00; + _buffer[1] = 0x0C; + _buffer[2] = (uint8_t) (value >> 8); + _buffer[3] = (uint8_t) (value & 0x00FF); + _buffer[4] = _CRC8(_buffer, 4); + return _writeRegister(AGS02MA_CALIBRATION); +} + + +bool AGS02MA::getZeroCalibrationData(AGS02MA::ZeroCalibrationData &data) { + if (!_readRegister(AGS02MA_CALIBRATION)) + { + return false; + } + + if (_CRC8(_buffer, 5) != 0) + { + _error = AGS02MA_ERROR_CRC; + return false; + } + + _error = AGS02MA_OK; + // Don't pollute the struct given to us, until we've handled all error cases. + data.status = _getDataMSB(); + data.value = _getDataLSB(); + return true; +} + + +int AGS02MA::lastError() +{ + int e = _error; + _error = AGS02MA_OK; // reset error after read + return e; +} + + +bool AGS02MA::readRegister(uint8_t address, AGS02MA::RegisterData ®) { + if (!_readRegister(address)) + { + return false; + } + + if (_CRC8(_buffer, 5) != 0) + { + _error = AGS02MA_ERROR_CRC; + return false; + } + + _error = AGS02MA_OK; + // Don't pollute the struct given to us, until we've handled all error cases. + reg.data[0] = _buffer[0]; + reg.data[1] = _buffer[1]; + reg.data[2] = _buffer[2]; + reg.data[3] = _buffer[3]; + reg.crc = _buffer[4]; + reg.crcValid = true; // checked above. + return true; +} + + +///////////////////////////////////////////////////////// +// +// PRIVATE +// +uint32_t AGS02MA::_readSensor() +{ + uint32_t value = 0; + if (_readRegister(AGS02MA_DATA)) + { + _error = AGS02MA_OK; + _status = _buffer[0]; + if (_status & 0x01) + { + _error = AGS02MA_ERROR_NOT_READY; + } + value = _buffer[1] * 65536UL; + value += _buffer[2] * 256; + value += _buffer[3]; + if (_CRC8(_buffer, 5) != 0) + { + _error = AGS02MA_ERROR_CRC; + } + } + return value; +} + + +bool AGS02MA::_readRegister(uint8_t reg) +{ + while (millis() - _lastRegTime < 30) yield(); + + _setI2CLowSpeed(); + + _wire->beginTransmission(_address); + _wire->write(reg); + _error = _wire->endTransmission(true); + if (_error != 0) + { + // _error will be I2C error code + _setI2CHighSpeed(); + return false; + } + // TODO investigate async interface + delay(30); + + if (_wire->requestFrom(_address, (uint8_t)5) != 5) + { + _error = AGS02MA_ERROR_READ; + _setI2CHighSpeed(); + return false; + } + for (uint8_t i = 0; i < 5; i++) + { + _buffer[i] = _wire->read(); + } + _error = AGS02MA_OK; + _setI2CHighSpeed(); + return true; +} + + +bool AGS02MA::_writeRegister(uint8_t reg) +{ + while (millis() - _lastRegTime < 30) yield(); + _lastRegTime = millis(); + + _setI2CLowSpeed(); + + _wire->beginTransmission(_address); + _wire->write(reg); + for (uint8_t i = 0; i < 5; i++) + { + _wire->write(_buffer[i]); + } + _error = _wire->endTransmission(true); + _setI2CHighSpeed(); + return (_error == 0); +} + + +void AGS02MA::_setI2CLowSpeed() +{ +#if defined (__AVR__) + // TWBR = 255; // == 30.4 KHz with TWSR = 0x00 + TWBR = 78; // == 25.0 KHZ + TWSR = 0x01; // pre-scaler = 4 +#else + _wire->setClock(AGS02MA_I2C_CLOCK); +#endif +} + + +void AGS02MA::_setI2CHighSpeed() +{ +#if defined (__AVR__) + TWSR = 0x00; +#endif + _wire->setClock(_I2CResetSpeed); +} + + +uint16_t AGS02MA::_getDataMSB() +{ + return (_buffer[0] << 8) + _buffer[1]; +} + + +uint16_t AGS02MA::_getDataLSB() +{ + return (_buffer[2] << 8) + _buffer[3]; +} + + +uint8_t AGS02MA::_CRC8(uint8_t * buf, uint8_t size) +{ + uint8_t crc = 0xFF; // start value + for (uint8_t b = 0; b < size; b++) + { + crc ^= buf[b]; + for (uint8_t i = 0; i < 8; i++) + { + if (crc & 0x80) crc = (crc << 1) ^ 0x31; + else crc = (crc << 1); + } + } + return crc; +} + + +uint8_t AGS02MA::_bin2bcd (uint8_t value) +{ + return value + 6 * (value / 10); +} + + +// -- END OF FILE -- + diff --git a/lib/lib_i2c/AGS02MA-0.4.3/AGS02MA.h b/lib/lib_i2c/AGS02MA-0.4.3/AGS02MA.h new file mode 100644 index 000000000..5888cef8c --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/AGS02MA.h @@ -0,0 +1,145 @@ +#pragma once +// +// FILE: AGS02MA.h +// AUTHOR: Rob Tillaart, Viktor Balint, Beanow +// DATE: 2021-08-12 +// VERSION: 0.4.3 +// PURPOSE: Arduino library for AGS02MA TVOC sensor +// URL: https://github.com/RobTillaart/AGS02MA + + +#include "Arduino.h" +#include "Wire.h" + + +#define AGS02MA_LIB_VERSION (F("0.4.3")) + +#define AGS02MA_OK 0 +#define AGS02MA_ERROR -10 +#define AGS02MA_ERROR_CRC -11 +#define AGS02MA_ERROR_READ -12 +#define AGS02MA_ERROR_NOT_READY -13 +#define AGS02MA_ERROR_REQUEST -14 + + +#define AGS02MA_I2C_CLOCK 25000 // max 30000 + + +class AGS02MA +{ +public: + struct RegisterData + { + uint8_t data[4]; + uint8_t crc; + bool crcValid; + }; + + struct ZeroCalibrationData + { + /** + * Warning, the exact meaning of this status is not fully documented. + * It seems like it's a bit mask: + * 0000 1100 | 0x0C | 12 | Typical value + * 0000 1101 | 0x0D | 13 | Sometimes seen on v117 + * 0111 1101 | 0x7D | 125 | Seen on v118, after power-off (gives different data than 12!) + */ + uint16_t status; + uint16_t value; + }; + + // address 26 = 0x1A + explicit AGS02MA(const uint8_t deviceAddress = 26, TwoWire *wire = &Wire); + + bool begin(); + bool isConnected(); + void reset(); + + bool isHeated() { return (millis() - _startTime) > 120000UL; }; + + + // CONFIGURATION + bool setAddress(const uint8_t deviceAddress); + uint8_t getAddress() { return _address; }; + uint8_t getSensorVersion(); + uint32_t getSensorDate(); + + // to set the speed the I2C bus should return to + // as the device operates at very low bus speed of 30 kHz. + void setI2CResetSpeed(uint32_t speed) { _I2CResetSpeed = speed; }; + uint32_t getI2CResetSpeed() { return _I2CResetSpeed; }; + + // to be called after at least 5 minutes in fresh air. + bool zeroCalibration() { return manualZeroCalibration(0); }; + + /** + * Set the zero calibration value manually. + * To be called after at least 5 minutes in fresh air. + * For v117: 0-65535 = automatic calibration. + * For v118: 0 = automatic calibration, 1-65535 manual calibration. + */ + bool manualZeroCalibration(uint16_t value = 0); + bool getZeroCalibrationData(ZeroCalibrationData &data); + + + // MODE + bool setPPBMode(); + bool setUGM3Mode(); + uint8_t getMode() { return _mode; }; + + + // READ functions + uint32_t readPPB(); // parts per billion 10^9 + uint32_t readUGM3(); // microgram per cubic meter + + // derived read functions + float readPPM() { return readPPB() * 0.001; }; // parts per million + float readMGM3() { return readUGM3() * 0.001; }; // milligram per cubic meter + float readUGF3() { return readUGM3() * 0.0283168466; }; // microgram per cubic feet + + float lastPPM() { return _lastPPB * 0.001; }; + uint32_t lastPPB() { return _lastPPB; }; // fetch last PPB measurement + uint32_t lastUGM3() { return _lastUGM3; }; // fetch last UGM3 measurement + + + // STATUS + uint32_t lastRead() { return _lastRead; }; // timestamp last measurement + int lastError(); + uint8_t lastStatus() { return _status; }; + uint8_t dataReady() { return _status & 0x01; }; + + // Reading registers + bool readRegister(uint8_t address, RegisterData ®); + + +private: + uint32_t _readSensor(); + bool _readRegister(uint8_t reg); + bool _writeRegister(uint8_t reg); + + uint32_t _I2CResetSpeed = 100000; + uint32_t _startTime = 0; + uint32_t _lastRead = 0; + uint32_t _lastRegTime = 0; + uint32_t _lastPPB = 0; + uint32_t _lastUGM3 = 0; + uint8_t _address = 0; + uint8_t _mode = 255; + uint8_t _status = 0; + uint8_t _buffer[5]; + + void _setI2CLowSpeed(); + void _setI2CHighSpeed(); + uint16_t _getDataMSB(); + uint16_t _getDataLSB(); + uint8_t _CRC8(uint8_t * buf, uint8_t size); + uint8_t _bin2bcd(uint8_t val); + + int _error = AGS02MA_OK; + + TwoWire* _wire; +}; + + +// -- END OF FILE -- + diff --git a/lib/lib_i2c/AGS02MA-0.4.3/CHANGELOG.md b/lib/lib_i2c/AGS02MA-0.4.3/CHANGELOG.md new file mode 100644 index 000000000..f4c036b68 --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/CHANGELOG.md @@ -0,0 +1,65 @@ +# Change Log AGS02MA + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + + + +## [0.4.3] - 2025-08-15 +- update readme.md +- update license +- minor edits + +## [0.4.2] - 2024-02-03 +- update readme.md + - added multiplexer section + - extended PPB "health" table +- clean up examples +- refactor conditional code + - added **setI2CLowSpeed()** and **setI2CHighSpeed()** +- improved error handling a bit. + - added **AGS02MA_ERROR_REQUEST** + - redo **readRegister()** +- minor edits + +## [0.4.1] - 2023-12-10 +- fix #26, error in readme. + +## [0.4.0] - 2023-12-06 +- refactor API, begin() +- update readme.md + +---- + +## [0.3.4] - 2023-09-25 +- add Wire1 support for ESP32 +- update readme.md +- minor edits + +## [0.3.3] - 2023-01-21 +- update GitHub actions +- update license 2023 +- update keywords +- minor edit readme.md +- minor edit code +- add CHANGELOG.md (for real) + +## [0.3.2] - 2022-10-26 +- add CHANGELOG.md +- add RP2040 in build + +---- + +## no info + +- 0.3.1 +- 0.3.0 +- 0.2.0 +- 0.1.4 +- 0.1.3 +- 0.1.2 +- 0.1.1 +- 0.1.0 + diff --git a/lib/lib_i2c/AGS02MA-0.4.3/LICENSE b/lib/lib_i2c/AGS02MA-0.4.3/LICENSE new file mode 100644 index 000000000..6e5129e5e --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-2025 Rob Tillaart + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/lib_i2c/AGS02MA-0.4.3/README.md b/lib/lib_i2c/AGS02MA-0.4.3/README.md new file mode 100644 index 000000000..d8c165d40 --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/README.md @@ -0,0 +1,400 @@ + +[![Arduino CI](https://github.com/RobTillaart/AGS02MA/workflows/Arduino%20CI/badge.svg)](https://github.com/marketplace/actions/arduino_ci) +[![Arduino-lint](https://github.com/RobTillaart/AGS02MA/actions/workflows/arduino-lint.yml/badge.svg)](https://github.com/RobTillaart/AGS02MA/actions/workflows/arduino-lint.yml) +[![JSON check](https://github.com/RobTillaart/AGS02MA/actions/workflows/jsoncheck.yml/badge.svg)](https://github.com/RobTillaart/AGS02MA/actions/workflows/jsoncheck.yml) +[![GitHub issues](https://img.shields.io/github/issues/RobTillaart/AGS02MA.svg)](https://github.com/RobTillaart/AGS02MA/issues) + +[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/RobTillaart/AGS02MA/blob/master/LICENSE) +[![GitHub release](https://img.shields.io/github/release/RobTillaart/AGS02MA.svg?maxAge=3600)](https://github.com/RobTillaart/AGS02MA/releases) +[![PlatformIO Registry](https://badges.registry.platformio.org/packages/robtillaart/library/AGS02MA.svg)](https://registry.platformio.org/libraries/robtillaart/AGS02MA) + + +# AGS02MA + +Arduino library for AGS02MA TVOC sensor. + + +### Description + +**Experimental** + +This library is experimental, so please use with care. + +The AGS02MA is a sensor that measures the TVOC = Total Volatile Organic Compounds +in the air. It does not measure a specific gas, but several. + + +Note the warning about the I2C low speed, the device works at max 30 KHz. +Since 0.3.1 this library uses 25 KHz. + +Feedback as always, is welcome. Please open an issue. + +Note this library is **not** meant to replace professional monitoring systems. + + +### 0.4.0 Breaking change + +Version 0.4.0 introduced a breaking change. +You cannot set the pins in **begin()** any more. +This reduces the dependency of processor dependent Wire implementations. +The user has to call **Wire.begin()** and can optionally set the Wire pins +before calling **begin()**. + + +### Related + +- https://github.com/RobTillaart/AGS02MA TVOC sensor +- https://github.com/RobTillaart/AGS2616 H2 sensor +- https://github.com/RobTillaart/AGS3870 CH4 sensor +- https://github.com/RobTillaart/AGS3871 CO sensor +- https://www.renesas.com/us/en/document/whp/overview-tvoc-and-indoor-air-quality +- https://github.com/RobTillaart/SGP30 (experimental) +- https://github.com/RobTillaart/AtomicWeight (determine Mass from chemical formula) +- https://github.com/RobTillaart/map2colour + + +## I2C + +### Pin layout from left to right + +Always check datasheet! + +| Front L->R | Description | +|:------------:|:--------------| +| pin 1 | VDD +5V | +| pin 2 | SDA data | +| pin 3 | GND | +| pin 4 | SCL clock | + + +### Address + +The device has a fixed address of 26 or 0x1A. + +The device works at 100 kHz I2C bus speed (datasheet). + +Note: several AGS devices use the same I2C address 0x1A. +Known are the AGS2616 (H2), AGS3870 (CH4), AGS3871 (CO), AGS02MA (TVOC). +If you want to use them on one I2C bus, you need multiplexing. +See section below. + + +### WARNING - LOW SPEED + +The sensor uses I2C at very low speed <= 30 KHz. +For an Arduino UNO the lowest speed supported is about 30.4KHz (TWBR = 255) which works +in my testruns so far. +First runs with Arduino UNO indicate 2 failed reads in > 500 Reads, so less than 1% +failure rate. + +Tests with ESP32 / ESP8266 at 30 KHz look good, +tests with ESP32 at lower clock speeds are to be done but expected to work. + +The library sets the clock speed to 30 KHz (for non AVR) during operation +and resets the I2C clock speed default to 100 KHz after operation. +This is done to minimize interference with the communication of other devices. +The "reset clock speed" can be changed with **setI2CResetSpeed(speed)** e.g. to 200 or 400 KHz. + + +### 0.3.1 fix. + +Version 0.3.1 sets the **I2C prescaler TWSR** register of the Arduino UNO to 4 so the lowest +speed possible is reduced to about 8 KHz. +A test run 4 hours with 6000++ reads on an UNO at 25 KHz gave 0 errors. +So the communication speed will be set to 25 KHz, also for other boards, for stability. +After communication the I2C clock (+ prescaler) is reset again as before. + + +### I2C multiplexing + +Sometimes you need to control more devices than possible with the default +address range the device provides. +This is possible with an I2C multiplexer e.g. TCA9548 which creates up +to eight channels (think of it as I2C subnets) which can use the complete +address range of the device. + +Drawback of using a multiplexer is that it takes more administration in +your code e.g. which device is on which channel. +This will slow down the access, which must be taken into account when +deciding which devices are on which channel. +Also note that switching between channels will slow down other devices +too if they are behind the multiplexer. + +- https://github.com/RobTillaart/TCA9548 + + +## Version 118 problems + +The library can request the version with **getSensorVersion()**. +My devices all report version 117 and this version is used to develop / test this library. +There are devices reported with version 118 which behave differently. + + +### ugM3 not supported + +See - https://github.com/RobTillaart/AGS02MA/issues/11 + +The version 118 seems only to support the **PPB** and not the **ugM3** mode. +It is unclear if this is an incident, bug or a structural change in the firmware. + +If you encounter similar problems with setting the mode (any version), please let me know. +That will help indicating if this is a "structural change" or incident. + + +### Calibrate problem! + +See - https://github.com/RobTillaart/AGS02MA/issues/13 + +In this issue a problem is reported with a version 118 sensor. +The problem exposed itself after running the calibration sketch (command). +The problem has been confirmed by a 2nd version 118 sensor. +Additional calibration runs did not fix the problem. +Version 117 seem to have no problems with calibration. + +**Advice**: do **NOT** calibrate a version 118. + +Note: the version 0.2.0 determines the version in the calibration function so +it won't calibrate any non 117 version. + + +### Please report your experiences. + +If you have a AGS20MA device, version 117 or 118 or other, +please let me know your experiences +with the sensor and this (or other) library. + + +## Interface + +```cpp +#include "AGS02MA.h" +``` + +### Constructor + +- **AGS02MA(uint8_t deviceAddress = 26, TwoWire \*wire = &Wire)** constructor, +with default address and default I2C interface. +- **bool begin()** initialize the library. +Returns false if deviceAddress cannot be seen on the I2C bus. +- **bool isConnected()** returns true if deviceAddress can be seen on I2C, false otherwise. +- **void reset()** resets the internal variables. + + +### Heating + +- **bool isHeated()** returns true if 2 minutes have passed after call of **begin()**. +Otherwise the device is not optimal ready. +According to the datasheet, preheating will improve the quality of the measurements. +Note: if begin() is not called, isHeated() might be incorrect. +- **uint32_t lastRead()** returns the last time the device is read, +timestamp is in milliseconds since start. +Returns 0 if **readPPB()** or **readUGM3()** is not called yet. +This function allows to implement sort of asynchronous wait. +One must keep reads / measurements at least 1.5 seconds but preferred 3 seconds +apart according to the datasheet. + + +### Administration + +- **bool setAddress(const uint8_t deviceAddress)** sets a new address for the sensor. +If function succeeds the address changes immediately and will be persistent over a reboot. +- **uint8_t getAddress()** returns the set address. Default the function will return 26 or 0x1A. +- **uint8_t getSensorVersion()** reads sensor version from the device. +If the version cannot be read the function will return 255. +My test sensors all return version 117, version 118 is reported to exist too. +- **uint32_t getSensorDate()** (experimental) reads bytes from the sensor that seem +to indicate the production date(?). +This date is encoded in an uint32_t to minimize footprint as it is a debug function. + +```cpp +uint32_t dd = sensor.getSensorDate(); +Serial.println(dd, HEX); // prints YYYYMMDD e.g. 20210203 +``` + + +### I2C clock speed + +The library sets the clock speed to 25 KHz during operation +and resets it to 100 KHz after operation. +This is done to minimize interference with the communication of other devices. +The following function can change the I2C reset speed to e.g. 200 or 400 KHz. + +- **void setI2CResetSpeed(uint32_t speed)** sets the I2C speed the library need to reset the I2C speed to. +- **uint32_t getI2CResetSpeed()** returns the value set. Default is 100 KHz. + + +### setMode + +The default mode at startup of the sensor is PPB = parts per billion. + +- **bool setPPBMode()** sets device in PartPerBillion mode. Returns true on success. +- **bool setUGM3Mode()** sets device in micro gram per cubic meter mode. Returns true on success. +- **uint8_t getMode()** returns mode set. 0 = PPB, 1 = UGm3, 255 = not set. + + +### Air quality classification + +Indicative description and colour representation. + +| TVOC(ppb) | Scale | Description | Colour | Notes | +|:---------:|:-------:|:---------------------:|:-------------|:--------| +| <= 220 | 1 | Good | Green | +| <= 660 | 3 | Moderate | Yellow | +| <= 1430 | 7 | Bad | Orange | +| <= 2200 | 10 | Unhealthy | Red | +| <= 3300 | 15 | Very unhealthy | Purple | add pulsating effect +| <= 5500 | 25 | Hazardous | Deep Purple | add pulsating effect +| > 5500 | 50 | Extremely Hazardous | Deep Purple | add pulsating effect + +[Source](https://learn.kaiterra.com/en/resources/understanding-tvoc-volatile-organic-compounds) + +- Scale is a relative (linear) scale where 220 ~~ 1 +- Colour is an indicative colour mapping. + - https://github.com/RobTillaart/map2colour for continuous colour scale mapping. + + +### PPB versus UGM3 + +There is no 1 to 1 relation between the PPB and the uG/m3 readings as this relation depends +on the weight of the individual molecules. +PPB is therefore an more an absolute indicator where uG/m3 is sort of relative indicator. +If the gas is unknown, PPB is in my opinion the preferred measurement. + + +From an unverified source the following formula: +M = molecular weight of the gas. + +**μg/m3 = ppb \* M \* 12.187 / (273.15 + °C)** + +Simplified formula for 1 atm @ 25°C: + +**μg/m3 = ppb \* M \* 0.04087539829** + +Some known gasses + +| gas | Common name | ratio ppb-μg/m3 | molecular weight M | +|:-------|:--------------------|:----------------------|:--------------------:| +| SO2 | Sulphur dioxide | 1 ppb = 2.62 μg/m3 | 64 gr/mol | +| NO2 | Nitrogen dioxide | 1 ppb = 1.88 μg/m3 | 46 gr/mol | +| NO | Nitrogen monoxide | 1 ppb = 1.25 μg/m3 | 30 gr/mol | +| O3 | Ozone | 1 ppb = 2.00 μg/m3 | 48 gr/mol | +| CO | Carbon Monoxide | 1 ppb = 1.145 μg/m3 | 28 gr/mol | +| C6H6 | Benzene | 1 ppb = 3.19 μg/m3 | 78 gr/mol | + + +- https://github.com/RobTillaart/AtomicWeight (determine Mass from chemical formula) + + +### Read the sensor + +WARNING: The datasheet advises to take 3 seconds between reads. +Tests gave stable results at 1.5 second intervals. +Use this faster rate at your own risk. + +- **uint32_t readPPB()** reads the PPB (parts per billion) from the device. +Typical value should be between 1 .. 999999. +Returns **lastPPB()** value if failed so one does not get sudden jumps in graphs. +Check **lastStatus()** and **lastError()** to get more info about the success of the read(). +Time needed is ~35 milliseconds (which might cause problems). +- **uint32_t readUGM3()** reads UGM3 (microgram per cubic meter) current value from device. +Typical values depend on the molecular weight of the TVOC. +Returns **lastUGM3()** if failed so one does not get sudden jumps in graphs. + +Wrappers + +- **float readPPM()** returns parts per million (PPM). +This function is a wrapper around readPPB(). +Typical value should be between 0.01 .. 999.99 +- **float readMGM3()** returns milligram per cubic meter. +- **float readUGF3()** returns microgram per cubic feet. + + +### Error Codes + +| ERROR_CODES | value | +|:----------------------------|:-------:| +| AGS02MA_OK | 0 | +| AGS02MA_ERROR | -10 | +| AGS02MA_ERROR_CRC | -11 | +| AGS02MA_ERROR_READ | -12 | +| AGS02MA_ERROR_NOT_READY | -13 | + + +### Cached values + +- **float lastPPM()** returns last readPPM (parts per million) value (cached). +- **uint32_t lastPPB()** returns last read PPB (parts per billion) value (cached). Should be between 1..999999. +- **uint32_t lastUGM3()** returns last read UGM3 (microgram per cubic meter) value (cached). + + +### Calibration + +- **bool zeroCalibration()** to be called after at least 5 minutes in fresh air. +See example sketch. +- **bool manualZeroCalibration(uint16_t value = 0)** Set the zero calibration value manually. +To be called after at least 5 minutes in fresh air. + - For v117: 0-65535 = automatic calibration. + - For v118: 0 = automatic calibration, 1-65535 manual calibration. +- **bool getZeroCalibrationData(ZeroCalibrationData &data)** fills a data struct with the +current zero calibration status and value. +Returns true on success. + + +### Other + +- **bool readRegister(uint8_t address, RegisterData ®)** fills a data struct with the chip's register data at that address. +Primarily intended for troubleshooting and analysis of the sensor. Not recommended to build applications on top of this method's raw data. +Returns true when the **RegisterData** is filled, false when the data could not be read. +Note: unlike other public methods, CRC errors don't return false or show up in `lastError()`, +instead the CRC result is stored in `RegisterData.crcValid`. +- **int lastError()** returns last error. +- **uint8_t lastStatus()** returns status byte from last read. +Read datasheet or table below for details. A new read is needed to update this. +- **uint8_t dataReady()** returns RDY bit from last read. + + +### Status bits. + +| bit | description | notes | +|:-----:|:------------------------------------|:--------| +| 7-4 | internal use | +| 3-1 | 000 = PPB 001 = uG/M3 | +| 0 | RDY bit 0 = ready 1 = not ready | 1 == busy + + +## Future + +#### Must + +- improve documentation + - references? + +#### Should + +- check the mode bits of the status byte with internal \_mode. + - maximize robustness of state +- test with hardware + - different gasses ? indoor / outdoor? +- test with different processors +- isHeated() bugs if begin() is not called before... + +#### Could + +- elaborate error handling. +- create an async interface for **readPPB()** if possible + - delay(30) blocks performance ==> async version of **readRegister()** + - could introduce complex I2C speed handling... + - separate state - request pending or so? +- move code to .cpp? + +#### Wont + + +## Support + +If you appreciate my libraries, you can support the development and maintenance. +Improve the quality of the libraries by providing issues and Pull Requests, or +donate through PayPal or GitHub sponsors. + +Thank you, + diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB/AGS02MA_PPB.ino b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB/AGS02MA_PPB.ino new file mode 100644 index 000000000..c38a99b02 --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB/AGS02MA_PPB.ino @@ -0,0 +1,74 @@ +// +// FILE: AGS02MA_PPB.ino +// AUTHOR: Rob Tillaart +// PURPOSE: test application +// URL: https://github.com/RobTillaart/AGS02MA + + +#include "AGS02MA.h" + + +AGS02MA AGS(26); + + +void setup() +{ + // ESP devices typically miss the first serial log lines after flashing. + // Delay somewhat to include all output. + delay(1000); + + Serial.begin(115200); + Serial.println(); + Serial.println(__FILE__); + Serial.print("AGS02MA_LIB_VERSION: "); + Serial.println(AGS02MA_LIB_VERSION); + Serial.println(); + + Wire.begin(); + + bool b = AGS.begin(); + Serial.print("BEGIN:\t"); + Serial.println(b); + + Serial.print("VERSION:\t"); + Serial.println(AGS.getSensorVersion()); + + // pre-heating improves measurement quality + // can be skipped + Serial.println("\nWarming up (120 seconds = 24 dots)"); + while (AGS.isHeated() == false) + { + delay(5000); + Serial.print("."); + } + Serial.println(); + + b = AGS.setPPBMode(); + uint8_t m = AGS.getMode(); + Serial.print("MODE:\t"); + Serial.print(b); + Serial.print("\t"); + Serial.println(m); + + uint8_t version = AGS.getSensorVersion(); + Serial.print("VERS:\t"); + Serial.println(version); +} + + +void loop() +{ + delay(3000); + uint32_t value = AGS.readPPB(); + Serial.print("PPB:\t"); + Serial.print(value); + Serial.print("\t"); + Serial.print(AGS.lastStatus(), HEX); + Serial.print("\t"); + Serial.print(AGS.lastError(), HEX); + Serial.println(); + +} + + +// -- END OF FILE -- diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB_TIMING/AGS02MA_PPB_TIMING.ino b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB_TIMING/AGS02MA_PPB_TIMING.ino new file mode 100644 index 000000000..648a78563 --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB_TIMING/AGS02MA_PPB_TIMING.ino @@ -0,0 +1,75 @@ +// +// FILE: AGS02MA_PPB_TIMING.ino +// AUTHOR: Rob Tillaart +// PURPOSE: test application +// URL: https://github.com/RobTillaart/AGS02MA +// + +#include "AGS02MA.h" + + +AGS02MA AGS(26); + + +void setup() +{ + // ESP devices typically miss the first serial log lines after flashing. + // Delay somewhat to include all output. + delay(1000); + + Serial.begin(115200); + Serial.println(); + Serial.println(__FILE__); + Serial.print("AGS02MA_LIB_VERSION: "); + Serial.println(AGS02MA_LIB_VERSION); + Serial.println(); + + Wire.begin(); + + bool b = AGS.begin(); + Serial.print("BEGIN:\t"); + Serial.println(b); + + Serial.print("VERS:\t"); + Serial.println(AGS.getSensorVersion()); + + // pre-heating improves measurement quality + // can be skipped + Serial.println("\nWarming up (120 seconds = 24 dots)"); + while (AGS.isHeated() == false) + { + delay(5000); + Serial.print("."); + } + Serial.println(); + + b = AGS.setPPBMode(); + uint8_t m = AGS.getMode(); + Serial.print("MODE:\t"); + Serial.print(b); + Serial.print("\t"); + Serial.println(m); +} + + +void loop() +{ + delay(1500); + uint32_t start = millis(); + uint32_t value = AGS.readPPB(); + uint32_t stop = millis(); + + uint32_t duration = stop - start; + Serial.print(duration); + Serial.print("\t"); + Serial.print(value); + Serial.print("\t"); + Serial.print(AGS.lastStatus(), HEX); + Serial.print("\t"); + Serial.print(AGS.lastError(), HEX); + Serial.println(); +} + + +// -- END OF FILE -- + diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB_TIMING/performance_0.3.0.txt b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB_TIMING/performance_0.3.0.txt new file mode 100644 index 000000000..719715033 --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB_TIMING/performance_0.3.0.txt @@ -0,0 +1,29 @@ + +30 KHz + +16:14:33.046 -> ...\AGS02MA_PPB_TIMING.ino +16:14:33.046 -> AGS02MA_LIB_VERSION: 0.3.0 +16:14:33.046 -> +16:14:33.046 -> BEGIN: 1 +16:14:33.046 -> VERS: 117 +16:14:33.093 -> +16:14:33.093 -> Warming up (120 seconds = 24 dots) +16:14:38.107 -> ........................ +16:16:33.243 -> MODE: 1 0 +16:16:34.790 -> 32 549 10 0 +16:16:36.325 -> 32 547 10 0 +16:16:37.872 -> 31 545 10 0 +16:16:39.418 -> 32 545 10 0 +16:16:40.922 -> 33 544 10 0 +16:16:42.469 -> 33 543 10 0 +16:16:44.015 -> 33 542 10 0 +16:16:45.562 -> 33 543 10 0 +16:16:47.062 -> 33 536 10 0 +16:16:48.608 -> 33 541 10 0 +16:16:50.155 -> 33 537 10 0 +16:16:51.701 -> 32 537 10 0 +16:16:53.201 -> 33 536 10 0 +16:16:54.747 -> 33 535 10 0 +16:16:56.294 -> 33 534 10 0 +16:16:57.810 -> 32 533 10 0 +16:16:59.357 -> 32 531 10 0 diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB_TIMING/performance_0.3.1_10khz.txt b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB_TIMING/performance_0.3.1_10khz.txt new file mode 100644 index 000000000..79b0d8393 --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB_TIMING/performance_0.3.1_10khz.txt @@ -0,0 +1,27 @@ + +10 KHz (TWBR 198, TWSR 1) just a test + +16:08:08.836 -> ...\AGS02MA_PPB_TIMING.ino +16:20:01.824 -> AGS02MA_LIB_VERSION: 0.3.1 +16:20:01.824 -> +16:20:01.824 -> BEGIN: 1 +16:20:01.824 -> VERS: 117 +16:20:01.871 -> +16:20:01.871 -> Warming up (120 seconds = 24 dots) +16:20:06.885 -> ........................ +16:22:02.077 -> MODE: 1 0 +16:22:03.624 -> 38 476 10 0 +16:22:05.165 -> 38 474 10 0 +16:22:06.712 -> 38 471 10 0 +16:22:08.247 -> 38 474 10 0 +16:22:09.794 -> 38 471 10 0 +16:22:11.325 -> 38 472 10 0 +16:22:12.871 -> 38 468 10 0 +16:22:14.418 -> 37 465 10 0 +16:22:15.917 -> 38 463 10 0 +16:22:17.464 -> 38 463 10 0 +16:22:19.016 -> 38 459 10 0 +16:22:20.547 -> 37 460 10 0 +16:22:22.094 -> 36 459 10 0 +16:22:23.640 -> 38 461 10 0 + diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB_TIMING/performance_0.3.1_25khz.txt b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB_TIMING/performance_0.3.1_25khz.txt new file mode 100644 index 000000000..4c4e18e1c --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPB_TIMING/performance_0.3.1_25khz.txt @@ -0,0 +1,32 @@ + +25 KHz + +16:08:08.836 -> ...\AGS02MA_PPB_TIMING.ino +16:08:08.836 -> AGS02MA_LIB_VERSION: 0.3.1 +16:08:08.836 -> +16:08:08.836 -> BEGIN: 1 +16:08:08.836 -> VERS: 117 +16:08:08.929 -> +16:08:08.929 -> Warming up (120 seconds = 24 dots) +16:08:13.944 -> ........................ +16:10:09.086 -> MODE: 1 0 +16:10:10.633 -> 33 845 10 0 +16:10:12.179 -> 32 846 10 0 +16:10:13.726 -> 32 847 10 0 +16:10:15.225 -> 33 847 10 0 +16:10:16.772 -> 32 847 10 0 +16:10:18.318 -> 33 841 10 0 +16:10:19.849 -> 33 835 10 0 +16:10:21.396 -> 34 829 10 0 +16:10:22.895 -> 33 825 10 0 +16:10:24.442 -> 33 828 10 0 +16:10:25.988 -> 33 823 10 0 +16:10:27.535 -> 34 824 10 0 +16:10:29.081 -> 33 821 10 0 +16:10:30.581 -> 33 821 10 0 +16:10:32.127 -> 33 816 10 0 +16:10:33.674 -> 34 826 10 0 +16:10:35.221 -> 33 839 10 0 +16:10:36.720 -> 33 855 10 0 +16:10:38.267 -> 33 857 10 0 +16:10:39.813 -> 33 857 10 0 diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPM/AGS02MA_PPM.ino b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPM/AGS02MA_PPM.ino new file mode 100644 index 000000000..8287a1345 --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_PPM/AGS02MA_PPM.ino @@ -0,0 +1,71 @@ +// +// FILE: AGS02MA_PPM.ino +// AUTHOR: Rob Tillaart +// PURPOSE: test application +// URL: https://github.com/RobTillaart/AGS02MA +// + +#include "AGS02MA.h" + + +AGS02MA AGS(26); + + +void setup() +{ + // ESP devices typically miss the first serial log lines after flashing. + // Delay somewhat to include all output. + delay(1000); + + Serial.begin(115200); + Serial.println(); + Serial.println(__FILE__); + Serial.print("AGS02MA_LIB_VERSION: "); + Serial.println(AGS02MA_LIB_VERSION); + Serial.println(); + + Wire.begin(); + + bool b = AGS.begin(); + Serial.print("BEGIN:\t"); + Serial.println(b); + + Serial.print("VERSION:\t"); + Serial.println(AGS.getSensorVersion()); + + // pre-heating improves measurement quality + // can be skipped + Serial.println("\nWarming up (120 seconds = 24 dots)"); + while (AGS.isHeated() == false) + { + delay(5000); + Serial.print("."); + } + Serial.println(); + + b = AGS.setPPBMode(); + uint8_t m = AGS.getMode(); + Serial.print("MODE:\t"); + Serial.print(b); + Serial.print("\t"); + Serial.println(m); +} + + +void loop() +{ + delay(3000); + Serial.print("PPM:\t"); + Serial.print(AGS.readPPM(), 3); + Serial.print("\t"); + Serial.print(AGS.dataReady(), HEX); + Serial.print("\t"); + Serial.print(AGS.lastStatus(), HEX); + Serial.print("\t"); + Serial.print(AGS.lastError(), HEX); + Serial.println(); +} + + +// -- END OF FILE -- + diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_UGM3/AGS02MA_UGM3.ino b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_UGM3/AGS02MA_UGM3.ino new file mode 100644 index 000000000..5cce44f59 --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_UGM3/AGS02MA_UGM3.ino @@ -0,0 +1,71 @@ +// +// FILE: AGS02MA_UGM3.ino +// AUTHOR: Rob Tillaart +// PURPOSE: test application +// URL: https://github.com/RobTillaart/AGS02MA + + +#include "AGS02MA.h" + + +AGS02MA AGS(26); + + +void setup() +{ + // ESP devices typically miss the first serial log lines after flashing. + // Delay somewhat to include all output. + delay(1000); + + Serial.begin(115200); + Serial.println(); + Serial.println(__FILE__); + Serial.print("AGS02MA_LIB_VERSION: "); + Serial.println(AGS02MA_LIB_VERSION); + Serial.println(); + + Wire.begin(); + + bool b = AGS.begin(); + Serial.print("BEGIN:\t"); + Serial.println(b); + + // pre-heating improves measurement quality + // can be skipped + Serial.println("\nWarming up (120 seconds = 24 dots)"); + while (AGS.isHeated() == false) + { + delay(5000); + Serial.print("."); + } + Serial.println(); + + b = AGS.setUGM3Mode(); + uint8_t m = AGS.getMode(); + Serial.print("MODE:\t"); + Serial.print(b); + Serial.print("\t"); + Serial.println(m); + + uint8_t version = AGS.getSensorVersion(); + Serial.print("VERS:\t"); + Serial.println(version); +} + + +void loop() +{ + delay(3000); + uint32_t value = AGS.readUGM3(); + Serial.print("UGM3:\t"); + Serial.print(value); + Serial.print("\t"); + Serial.print(AGS.lastStatus(), HEX); + Serial.print("\t"); + Serial.print(AGS.lastError(), HEX); + Serial.println(); + +} + + +// -- END OF FILE -- diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_calibrate/AGS02MA_calibrate.ino b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_calibrate/AGS02MA_calibrate.ino new file mode 100644 index 000000000..ab8b50277 --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_calibrate/AGS02MA_calibrate.ino @@ -0,0 +1,164 @@ +// +// FILE: AGS02MA_calibrate.ino +// AUTHOR: Rob Tillaart, Beanow +// PURPOSE: test application +// URL: https://github.com/RobTillaart/AGS02MA + + +#include "AGS02MA.h" + +// You can decrease/disable warmup when you're certain the chip already warmed up. +#define WARMUP_MINUTES 6 +#define READ_INTERVAL 3000 + + +uint32_t start, stop; +uint8_t version; + +AGS02MA AGS(26); + + +void setup() +{ + // ESP devices typically miss the first serial log lines after flashing. + // Delay somewhat to include all output. + delay(1000); + + Serial.begin(115200); + Serial.println(); + Serial.println(__FILE__); + Serial.print("AGS02MA_LIB_VERSION: "); + Serial.println(AGS02MA_LIB_VERSION); + Serial.println(); + + Serial.print("WARMUP:\t\t"); + Serial.println(WARMUP_MINUTES); + Serial.print("INTERVAL:\t"); + Serial.println(READ_INTERVAL); + + Wire.begin(); + + bool b = AGS.begin(); + Serial.print("BEGIN:\t\t"); + Serial.println(b); + + Serial.print("VERSION:\t"); + version = AGS.getSensorVersion(); + Serial.println(version); + int err = AGS.lastError(); + + // Reading version correctly matters, as we display additional comments based on it. + if(err != AGS02MA_OK) + { + Serial.print("Error reading version:\t"); + Serial.println(err); + Serial.println("Won't attempt to calibrate. Reset when connection with the sensor is stable."); + Serial.println(); + return; + } + + b = AGS.setPPBMode(); + uint8_t m = AGS.getMode(); + Serial.print("MODE:\t\t"); + Serial.print(b); + Serial.print("\t"); + Serial.println(m); + + Serial.println(); + Serial.print("Place the device outside in open air for "); + Serial.print(WARMUP_MINUTES); + Serial.println(" minute(s)."); + Serial.println("Make sure your device has warmed up sufficiently for the best results."); + Serial.println("The PPB values should be stable (may include noise) not constantly decreasing."); + Serial.println(); + + start = millis(); + stop = WARMUP_MINUTES * 60000UL; + while(millis() - start < stop) + { + Serial.print("[PRE ]\t"); + printPPB(); + delay(READ_INTERVAL); + } + + Serial.println(); + Serial.println("About to perform calibration now."); + + AGS02MA::ZeroCalibrationData initialValue; + if (!AGS.getZeroCalibrationData(initialValue)) + { + Serial.print("Error reading zero calibration data:\t"); + Serial.println(AGS.lastError()); + Serial.println("Won't attempt to calibrate. Reset when connection with the sensor is stable."); + Serial.println(); + return; + } + + Serial.println("Your previous calibration data was:"); + printZeroCalibrationData(initialValue); + + delay(1000); + + // returns 1 if successful written + b = AGS.zeroCalibration(); + Serial.println(); + Serial.print("CALIB:\t"); + Serial.println(b); + Serial.println(); + Serial.println("Calibration done."); + + AGS02MA::ZeroCalibrationData zc; + while (!AGS.getZeroCalibrationData(zc)) + { + Serial.print("Error:\t"); + Serial.print(AGS.lastError()); + Serial.println("\tretrying..."); + delay(READ_INTERVAL); + } + + Serial.println("Your new calibration data is:"); + printZeroCalibrationData(zc); + + Serial.println(); + Serial.println("Showing what PPB values look like post calibration."); + // A 125 status is typically shown on v118's after they've been powered off. + // Either having this version at all, or seeing this status, we'll display a notice. + if (version == 118 || initialValue.status == 125) + { + Serial.println("NOTICE: v118 sensors are known to give different results after powering off!"); + Serial.println("You may need to manually set your calibration value every time power was lost."); + } + Serial.println(); +} + + +void loop() +{ + Serial.print("[POST]\t"); + printPPB(); + delay(READ_INTERVAL); +} + + +void printZeroCalibrationData(AGS02MA::ZeroCalibrationData &zc) { + Serial.print("Status:\t"); + Serial.println(zc.status); + Serial.print("Value:\t"); + Serial.println(zc.value); +} + + +void printPPB() +{ + uint32_t value = AGS.readPPB(); + Serial.print("PPB:\t"); + Serial.print(value); + Serial.print("\t"); + Serial.print(AGS.lastStatus(), HEX); + Serial.print("\t"); + Serial.print(AGS.lastError(), HEX); + Serial.println(); +} + + +// -- END OF FILE -- diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_calibrate_manual/AGS02MA_calibrate_manual.ino b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_calibrate_manual/AGS02MA_calibrate_manual.ino new file mode 100644 index 000000000..07326a45b --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_calibrate_manual/AGS02MA_calibrate_manual.ino @@ -0,0 +1,172 @@ +// +// FILE: AGS02MA_calibrate_manual.ino +// AUTHOR: Rob Tillaart, Beanow +// PURPOSE: test application +// URL: https://github.com/RobTillaart/AGS02MA + + +#include "AGS02MA.h" + +// The zero calibration value we'll (temporarily) set in the example. +#define ZC_VALUE 700 + +#define READS 10 +#define INTERVAL 3000 + + +AGS02MA AGS(26); + +AGS02MA::ZeroCalibrationData initialValue; +uint8_t version; + + +void setup() +{ + // ESP devices typically miss the first serial log lines after flashing. + // Delay somewhat to include all output. + delay(1000); + + Serial.begin(115200); + Serial.println(); + Serial.println(__FILE__); + Serial.print("AGS02MA_LIB_VERSION: "); + Serial.println(AGS02MA_LIB_VERSION); + Serial.println(); + + Serial.print("READS:\t\t"); + Serial.println(READS); + Serial.print("INTERVAL:\t"); + Serial.println(INTERVAL); + + Wire.begin(); + + bool b = AGS.begin(); + Serial.print("BEGIN:\t\t"); + Serial.println(b); + + Serial.print("VERSION:\t"); + version = AGS.getSensorVersion(); + Serial.println(version); + int err = AGS.lastError(); + + // Reading version correctly matters, as we display additional comments based on it. + if(err != AGS02MA_OK) + { + Serial.print("Error reading version:\t"); + Serial.println(err); + Serial.println("Won't attempt to calibrate. Reset when connection with the sensor is stable."); + Serial.println(); + return; + } + + if (version != 118) + { + Serial.println(); + Serial.println("Only v118 sensors support manual zero calibration. For other versions, you can use the 'AGS02MA_calibrate' example instead."); + } + else + { + b = AGS.setPPBMode(); + uint8_t m = AGS.getMode(); + Serial.print("MODE:\t\t"); + Serial.print(b); + Serial.print("\t"); + Serial.println(m); + + while (!AGS.getZeroCalibrationData(initialValue)) + { + onError(AGS.lastError()); + } + + Serial.println(); + Serial.println("Your initial zero calibration is:"); + printZeroCalibrationData(initialValue); + Serial.println(); + + Serial.println("Showing sample data before changing."); + for (size_t i = 0; i < READS; i++) + { + delay(INTERVAL); + printPPB(); + } + + Serial.println(); + Serial.println("Manually setting zero calibration:"); + b = AGS.manualZeroCalibration(ZC_VALUE); + Serial.print("CALIB:\t"); + Serial.println(b); + + AGS02MA::ZeroCalibrationData newValue; + while (!AGS.getZeroCalibrationData(newValue)) + { + onError(AGS.lastError()); + } + + printZeroCalibrationData(newValue); + Serial.println(); + + Serial.println("Showing sample data."); + Serial.println("NOTICE: v118 sensors are known to give different results after powering off!"); + Serial.println("You may need to manually set your calibration value every time power was lost."); + for (size_t i = 0; i < READS; i++) + { + delay(INTERVAL); + printPPB(); + } + + Serial.println(); + Serial.println("Restoring initial zero calibration:"); + b = AGS.manualZeroCalibration(initialValue.value); + Serial.print("CALIB:\t"); + Serial.println(b); + + AGS02MA::ZeroCalibrationData restoredValue; + while (!AGS.getZeroCalibrationData(restoredValue)) + { + onError(AGS.lastError()); + } + + printZeroCalibrationData(restoredValue); + Serial.println(); + } + +} + + +void loop() +{ + delay(INTERVAL); + printPPB(); +} + + +void onError(int err) { + Serial.print("Error:\t"); + Serial.print(err); + Serial.println("\tretrying..."); + delay(INTERVAL); +} + + +void printZeroCalibrationData(AGS02MA::ZeroCalibrationData &zc) { + Serial.print("Status:\t"); + Serial.println(zc.status); + Serial.print("Value:\t"); + Serial.println(zc.value); +} + + +void printPPB() +{ + uint32_t value = AGS.readPPB(); + Serial.print("PPB:\t"); + Serial.print(value); + Serial.print("\t"); + Serial.print(AGS.lastStatus(), HEX); + Serial.print("\t"); + Serial.print(AGS.lastError(), HEX); + Serial.println(); +} + + +// -- END OF FILE -- diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_get_registers/.arduino-ci.yml b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_get_registers/.arduino-ci.yml new file mode 100644 index 000000000..783ab315f --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_get_registers/.arduino-ci.yml @@ -0,0 +1,28 @@ +platforms: + rpipico: + board: rp2040:rp2040:rpipico + package: rp2040:rp2040 + gcc: + features: + defines: + - ARDUINO_ARCH_RP2040 + warnings: + flags: + +packages: + rp2040:rp2040: + url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json + +compile: + # Choosing to run compilation tests on 2 different Arduino platforms + platforms: + # - uno + # - due + # - zero + # - leonardo + # - m4 + # - esp32 + # - esp8266 + # - mega2560 + # - rpipico + diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_get_registers/AGS02MA_get_registers.ino b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_get_registers/AGS02MA_get_registers.ino new file mode 100644 index 000000000..9fe085a2d --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_get_registers/AGS02MA_get_registers.ino @@ -0,0 +1,108 @@ +// +// FILE: AGS02MA_get_registers.ino +// AUTHOR: Rob Tillaart +// PURPOSE: low level develop application +// URL: https://github.com/RobTillaart/AGS02MA +// +// PURPOSE: this is a debugging tool for developing / investigating. +// Do not use it unless you are willing to crash your sensor. +// +// usage: make _readRegister(), _writeRegister() and _buffer public +// +// USE AT OWN RISK + + +#include "AGS02MA.h" + + +AGS02MA AGS(26); + + +void setup() +{ + Serial.begin(115200); + Serial.println(); + Serial.println(__FILE__); + Serial.print("AGS02MA_LIB_VERSION: "); + Serial.println(AGS02MA_LIB_VERSION); + Serial.println(); + + Wire.begin(); + + bool b = AGS.begin(); + Serial.print("BEGIN:\t"); + Serial.println(b); + + uint8_t version = AGS.getSensorVersion(); + Serial.print("VERS:\t"); + Serial.println(version); + + // AGS._buffer[0] = 0; + // AGS._buffer[1] = 13; + // AGS._buffer[2] = 14; + // AGS._buffer[3] = 90; + // AGS._buffer[5] = 248; + // int x = AGS._writeRegister(0x01); // does not work. + // Serial.println(x); + + for (uint8_t reg = 0; reg < 9; reg++) + { + dumpRegister(reg); + } + dumpRegister(0x11); + dumpRegister(0x20); + dumpRegister(0x21); + + AGS.setPPBMode(); +} + + +void loop() +{ +// dumpRegister(0); +// uint32_t zero = dumpRegister(1); +// uint32_t x = dumpRegister(0x20); // seems to be the raw value. +// delay(100); +// uint32_t y = AGS.readPPB(); +// Serial.print(zero); +// Serial.print("\t"); +// Serial.print(x); +// Serial.print("\t"); +// Serial.print(y); +// Serial.print("\t"); +// Serial.print((1.0 * x) / y, 2); +// Serial.print("\t"); +// Serial.print((zero - x) / 350); +// Serial.println(); +// Serial.println(); +// delay(2000); +} + + +uint32_t dumpRegister(uint8_t reg) +{ + Serial.print("REG["); + Serial.print(reg); + Serial.print("]"); + + bool b = AGS._readRegister(reg); + uint32_t value = 0; + for (int i = 0; i < 4; i++) + { + Serial.print("\t"); + Serial.print(AGS._buffer[i]); + + value *= 256; + value += AGS._buffer[i]; + } + Serial.print("\t"); + Serial.print(value); + + Serial.println(); + delay(100); + + return value; +} + + +// -- END OF FILE -- diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_minimal/AGS02MA_minimal.ino b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_minimal/AGS02MA_minimal.ino new file mode 100644 index 000000000..2089b7d44 --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_minimal/AGS02MA_minimal.ino @@ -0,0 +1,59 @@ +// +// FILE: AGS02MA_minimal.ino +// AUTHOR: Rob Tillaart +// PURPOSE: test application +// URL: https://github.com/RobTillaart/AGS02MA +// +// default register is 0x00 at start of the sensor +// datasheet states one can get the value with minimal interaction. +// note this sketch does not use the library! + + +#include "Wire.h" + + +uint8_t buffer[5]; + + +void setup() +{ + // ESP devices typically miss the first serial log lines after flashing. + // Delay somewhat to include all output. + delay(1000); + + Serial.begin(115200); + Serial.println(); + Serial.println(__FILE__); + // Serial.print("AGS02MA_LIB_VERSION: "); + // Serial.println(AGS02MA_LIB_VERSION); + Serial.println(); + + Wire.begin(); + Wire.setClock(30400); // lowest speed an UNO supports that works with sensor. +} + + +void loop() +{ + delay(3000); + Wire.requestFrom(26, 5); + for ( int i = 0; i < 5; i++) + { + buffer[i] = Wire.read(); + // Serial.print(buffer[i], HEX); // for debugging. + // Serial.print('\t'); + } + Serial.println(); + + // CONVERT RAW DATA + Serial.print("STAT:\t"); + Serial.println(buffer[0]); + Serial.print("PPB:\t"); + Serial.println(buffer[1] * 65536UL + buffer[2] * 256 + buffer[3]); + Serial.print("CRC:\t"); + Serial.println(buffer[4]); + Serial.println(); +} + + +// -- END OF FILE -- diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_minimal_plotter/AGS02MA_minimal_plotter.ino b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_minimal_plotter/AGS02MA_minimal_plotter.ino new file mode 100644 index 000000000..0abc3c0cf --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_minimal_plotter/AGS02MA_minimal_plotter.ino @@ -0,0 +1,69 @@ +// +// FILE: AGS02MA_minimal_plotter.ino +// AUTHOR: Rob Tillaart +// PURPOSE: test application not using the library +// URL: https://github.com/RobTillaart/AGS02MA +// +// default register is 0x00 at start of the sensor +// datasheet states one can get the value with minimal interaction. +// note this sketch does not use the library! + + +#include "Wire.h" + + +uint8_t buffer[5]; + +uint8_t cnt = 0; + + +void setup() +{ + // ESP devices typically miss the first serial log lines after flashing. + // Delay somewhat to include all output. + delay(1000); + + Serial.begin(115200); + // Serial.println(); + // Serial.println(__FILE__); + // Serial.print("AGS02MA_LIB_VERSION: "); + // Serial.println(AGS02MA_LIB_VERSION); + // Serial.println(); + + Wire.begin(); + Wire.setClock(30400); // lowest speed an UNO supports that works with sensor. +} + + +void loop() +{ + Wire.requestFrom(26, 5); + for ( int i = 0; i < 5; i++) + { + buffer[i] = Wire.read(); + // Serial.print(buffer[i], HEX); // for debugging. + // Serial.print('\t'); + } + // Serial.println(); + + if (cnt == 0) + { + // CONVERT RAW DATA + Serial.println("\nSTAT\tPPB\tCRC"); + cnt = 20; + } + cnt--; + if (buffer[0] == 0x10) + { + Serial.print(buffer[0]); + Serial.print("\t"); + Serial.print(buffer[1] * 65536UL + buffer[2] * 256 + buffer[3]); + Serial.print("\t"); + Serial.print(buffer[4]); + Serial.println(); + delay(2000); + } +} + + +// -- END OF FILE -- diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_readRegister/AGS02MA_readRegister.ino b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_readRegister/AGS02MA_readRegister.ino new file mode 100644 index 000000000..b2ed4437e --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_readRegister/AGS02MA_readRegister.ino @@ -0,0 +1,123 @@ +// +// FILE: AGS02MA_readRegister.ino +// AUTHOR: Rob Tillaart, Beanow +// PURPOSE: test application +// URL: https://github.com/RobTillaart/AGS02MA + + +#include "AGS02MA.h" + + +const uint8_t addresses[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 0x11, 0x20, 0x21}; + + +AGS02MA::RegisterData reg; +AGS02MA AGS(26); + + +void setup() +{ + // ESP devices typically miss the first serial log lines after flashing. + // Delay somewhat to include all output. + delay(1000); + + Serial.begin(115200); + Serial.println(); + Serial.println(__FILE__); + Serial.print("AGS02MA_LIB_VERSION: "); + Serial.println(AGS02MA_LIB_VERSION); + Serial.println(); + + Wire.begin(); + + bool b = AGS.begin(); + Serial.print("BEGIN:\t"); + Serial.println(b); +} + + +void loop() +{ + delay(3000); + for (auto address : addresses) + { + bool b = AGS.readRegister(address, reg); + Serial.print("REG[0x"); + Serial.print(address, HEX); + Serial.print("]"); + + if(b) + { + printRegister(address, reg); + } + else + { + Serial.print("\tError:\t"); + Serial.println(AGS.lastError()); + } + delay(50); + } + Serial.println(); +} + + +void printRegister(uint8_t address, AGS02MA::RegisterData ®) { + // Raw bytes first for any register. + for (auto b : reg.data) + { + Serial.print("\t"); + Serial.print(b); + } + + Serial.print("\tCRC: "); + Serial.print(reg.crcValid ? "OK " : "ERR "); + Serial.print(reg.crc); + + // Specific interpretations + switch (address) + { + case 0x00: + Serial.print("\tSensor data:\t"); + Serial.print(reg.data[0]); + Serial.print("\t"); + Serial.print( + (reg.data[1] << 16) + + (reg.data[2] << 8) + + reg.data[3] + ); + break; + + case 0x01: + case 0x02: + case 0x03: + case 0x04: + Serial.print("\tCalibration:\t"); + Serial.print( + (reg.data[0] << 8) + + reg.data[1] + ); + Serial.print("\t"); + Serial.print( + (reg.data[2] << 8) + + reg.data[3] + ); + break; + + case 0x11: + Serial.print("\tVersion:\t"); + Serial.print(reg.data[3]); + break; + + case 0x21: + Serial.print("\tI2C address:\t0x"); + Serial.print(reg.data[0], HEX); + break; + + default: + break; + } + Serial.println(); +} + + +// -- END OF FILE -- diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_setAddress/AGS02MA_setAddress.ino b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_setAddress/AGS02MA_setAddress.ino new file mode 100644 index 000000000..dbff0abb6 --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_setAddress/AGS02MA_setAddress.ino @@ -0,0 +1,60 @@ +// +// FILE: AGS02MA_setAddress.ino +// AUTHOR: Rob Tillaart +// PURPOSE: test application +// URL: https://github.com/RobTillaart/AGS02MA + + +#include "AGS02MA.h" + + +AGS02MA AGS(26); + + +void setup() +{ + // ESP devices typically miss the first serial log lines after flashing. + // Delay somewhat to include all output. + delay(1000); + + Serial.begin(115200); + Serial.println(); + Serial.println(__FILE__); + Serial.print("AGS02MA_LIB_VERSION: "); + Serial.println(AGS02MA_LIB_VERSION); + Serial.println(); + + Wire.begin(); + + bool b = AGS.begin(); + Serial.print("BEGIN:\t"); + Serial.println(b); + + b = AGS.setAddress(42); + Serial.print("SET_ADDR:\t"); + Serial.print(b, HEX); + Serial.print("\t"); + Serial.print(AGS.lastStatus(), HEX); + Serial.print("\t"); + Serial.print(AGS.lastError(), HEX); + Serial.println(); + + uint8_t addr = AGS.getAddress(); + Serial.print("GET_ADDR:\t"); + Serial.print(addr, HEX); + Serial.print("\t"); + Serial.print(AGS.lastStatus(), HEX); + Serial.print("\t"); + Serial.print(AGS.lastError(), HEX); + Serial.println(); + + Serial.println("\ndone..."); +} + + +void loop() +{ +} + + +// -- END OF FILE -- diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_test/AGS02MA_test.ino b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_test/AGS02MA_test.ino new file mode 100644 index 000000000..2ad4b1924 --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_test/AGS02MA_test.ino @@ -0,0 +1,55 @@ +// +// FILE: AGS02MA_test.ino +// AUTHOR: Rob Tillaart +// PURPOSE: test application +// URL: https://github.com/RobTillaart/AGS02MA + + +#include "AGS02MA.h" + + +AGS02MA AGS(26); + + +void setup() +{ + // ESP devices typically miss the first serial log lines after flashing. + // Delay somewhat to include all output. + delay(1000); + + Serial.begin(115200); + Serial.println(); + Serial.println(__FILE__); + Serial.print("AGS02MA_LIB_VERSION: "); + Serial.println(AGS02MA_LIB_VERSION); + Serial.println(); + + Wire.begin(); + + bool b = AGS.begin(); + Serial.print("BEGIN:\t"); + Serial.println(b); + + b = AGS.setPPBMode(); + uint8_t m = AGS.getMode(); + Serial.print("MODE:\t"); + Serial.print(b); + Serial.print("\t"); + Serial.println(m); + + uint8_t version = AGS.getSensorVersion(); + Serial.print("VERS:\t"); + Serial.println(version); +} + + +void loop() +{ + delay(2000); + uint32_t value = AGS.readPPB(); + Serial.print("PPB:\t"); + Serial.println(value); +} + + +// -- END OF FILE -- diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_test_CRC8/.arduino-ci.yml b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_test_CRC8/.arduino-ci.yml new file mode 100644 index 000000000..1877a86ed --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_test_CRC8/.arduino-ci.yml @@ -0,0 +1,7 @@ +compile: + # Choosing to run compilation tests on 2 different Arduino platforms + platforms: + # - uno + # - leonardo + # - due + # - zero diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_test_CRC8/AGS02MA_test_CRC8.ino b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_test_CRC8/AGS02MA_test_CRC8.ino new file mode 100644 index 000000000..07c3de29d --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/AGS02MA_test_CRC8/AGS02MA_test_CRC8.ino @@ -0,0 +1,76 @@ +// +// FILE: AGS02MA_test_CRC8.ino +// AUTHOR: Rob Tillaart +// PURPOSE: test application +// URL: https://github.com/RobTillaart/AGS02MA +// +// NOTE: this is a low level test for the communication / CRC +// to have this example to work, +// one need to make the _CRC8() and _buffer[] +// public in the AGS02MA.h file. + + +#include "AGS02MA.h" + + +AGS02MA AGS(26); + + +void setup() +{ + // ESP devices typically miss the first serial log lines after flashing. + // Delay somewhat to include all output. + delay(1000); + + Serial.begin(115200); + Serial.println(); + Serial.println(__FILE__); + Serial.print("AGS02MA_LIB_VERSION: "); + Serial.println(AGS02MA_LIB_VERSION); + Serial.println(); + + Wire.begin(); + + bool b = AGS.begin(); + Serial.print("BEGIN:\t"); + Serial.println(b); + + uint8_t version = AGS.getSensorVersion(); + Serial.print("VERS:\t"); + Serial.println(version); + dump("getSensorVersion"); +} + + +void loop() +{ + delay(3000); + AGS.setPPBMode(); + dump("MODE0"); + AGS.readPPB(); + dump("PPB"); + + delay(3000); + AGS.setUGM3Mode(); + dump("MODE1"); + AGS.readUGM3(); + dump("UGM3"); +} + + +void dump(char * str) +{ + Serial.print(str); + for (int i = 0; i < 5; i++) + { + Serial.print("\t"); + Serial.print(AGS._buffer[i], HEX); + } + Serial.print('\t'); + Serial.print(AGS._CRC8(AGS._buffer, 4), HEX); + Serial.println(); +} + + +// -- END OF FILE -- + diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/issue/issue.ino b/lib/lib_i2c/AGS02MA-0.4.3/examples/issue/issue.ino new file mode 100644 index 000000000..038cbf863 --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/issue/issue.ino @@ -0,0 +1,105 @@ +// +// FILE: AGS02MA_PPB.ino +// AUTHOR: Rob Tillaart +// PURPOSE: test application +// URL: https://github.com/RobTillaart/AGS02MA + + +#include "AGS02MA.h" + +AGS02MA AGS(26); + +uint32_t rounds = 0; + + +void setup() +{ + // ESP devices typically miss the first serial log lines after flashing. + // Delay somewhat to include all output. + delay(1000); + + Serial.begin(115200); + Serial.println(); + Serial.println(__FILE__); + Serial.print("AGS02MA_LIB_VERSION: "); + Serial.println(AGS02MA_LIB_VERSION); + Serial.println(); + + Wire.begin(); + + bool b = AGS.begin(); + Serial.print("BEGIN:\t"); + Serial.println(b); + + Serial.print("VERSION:\t"); + Serial.println(AGS.getSensorVersion()); + + Serial.print("DATE:\t"); + Serial.println(AGS.getSensorDate(), HEX); + + // pre-heating improves measurement quality + // can be skipped + // Serial.println("\nWarming up (120 seconds = 24 dots)"); + // while (AGS.isHeated() == false) + // { + // delay(5000); + // Serial.print("."); + // } + // Serial.println(); + + uint8_t version = AGS.getSensorVersion(); + Serial.print("VERS:\t"); + Serial.println(version); +} + + +void loop() +{ + delay(3000); + + uint8_t kind = rounds % 20; + + // Switch mode every 10 and 20 rounds. + bool b; + if (kind == 0) { + b = AGS.setPPBMode(); + uint8_t m = AGS.getMode(); + Serial.print("MODE:\t"); + Serial.print(b); + Serial.print("\t"); + Serial.println(m); + } else if (kind == 10) { + b = AGS.setUGM3Mode(); + uint8_t m = AGS.getMode(); + Serial.print("MODE:\t"); + Serial.print(b); + Serial.print("\t"); + Serial.println(m); + } + + // Read PPB in first half of a 20-round cycle. + if (kind < 10) { + uint32_t value = AGS.readPPB(); + Serial.print("PPB:\t"); + Serial.print(value); + Serial.print("\t"); + Serial.print(AGS.lastStatus(), HEX); + Serial.print("\t"); + Serial.print(AGS.lastError(), HEX); + Serial.println(); + } else { + uint32_t value = AGS.readUGM3(); + Serial.print("UGM3:\t"); + Serial.print(value); + Serial.print("\t"); + Serial.print(AGS.lastStatus(), HEX); + Serial.print("\t"); + Serial.print(AGS.lastError(), HEX); + Serial.println(); + } + + rounds++; +} + + +// -- END OF FILE -- diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/test_CRC8/.arduino-ci.yml b/lib/lib_i2c/AGS02MA-0.4.3/examples/test_CRC8/.arduino-ci.yml new file mode 100644 index 000000000..1877a86ed --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/test_CRC8/.arduino-ci.yml @@ -0,0 +1,7 @@ +compile: + # Choosing to run compilation tests on 2 different Arduino platforms + platforms: + # - uno + # - leonardo + # - due + # - zero diff --git a/lib/lib_i2c/AGS02MA-0.4.3/examples/test_CRC8/test_CRC8.ino b/lib/lib_i2c/AGS02MA-0.4.3/examples/test_CRC8/test_CRC8.ino new file mode 100644 index 000000000..36ada9918 --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/examples/test_CRC8/test_CRC8.ino @@ -0,0 +1,83 @@ +// +// FILE: test_CRC8.ino +// AUTHOR: Rob Tillaart +// PURPOSE: test application +// URL: https://github.com/RobTillaart/AGS02MA +// +// just for develop scratch pad +// need to make the CRC function public in the library + + +#include "AGS02MA.h" + + +AGS02MA AGS(26); + + +void setup() +{ + // ESP devices typically miss the first serial log lines after flashing. + // Delay somewhat to include all output. + delay(1000); + + Serial.begin(115200); + Serial.println(); + Serial.println(__FILE__); + Serial.print("AGS02MA_LIB_VERSION: "); + Serial.println(AGS02MA_LIB_VERSION); + Serial.println(); + + Wire.begin(); + + bool b = AGS.begin(); + Serial.print("BEGIN:\t"); + Serial.println(b); + + uint8_t _buffer[8]; + + _buffer[0] = 0x00; + _buffer[1] = 0x0C; + _buffer[2] = 0xFF; + _buffer[3] = 0xF3; + _buffer[4] = 0xFC; + Serial.println(AGS._CRC8(_buffer, 5), HEX); + + + _buffer[0] = 0x02; + _buffer[1] = 0xFD; + _buffer[2] = 0x02; + _buffer[3] = 0xFD; + _buffer[4] = 0x00; + Serial.println(AGS._CRC8(_buffer, 5), HEX); + + _buffer[0] = 0x00; + _buffer[1] = 0xFF; + _buffer[2] = 0x00; + _buffer[3] = 0xFF; + _buffer[4] = 0x30; + Serial.println(AGS._CRC8(_buffer, 5), HEX); + + _buffer[0] = 0x14; + _buffer[1] = 0x14; + _buffer[2] = 0x14; + _buffer[3] = 0x1C; + _buffer[4] = 0x75; + Serial.println(AGS._CRC8(_buffer, 5), HEX); + + _buffer[0] = 0x0; + _buffer[1] = 0x0; + _buffer[2] = 0xBB; + Serial.println( _buffer[0] * 65536UL + _buffer[1] * 256 + _buffer[2]); + _buffer[0] = 0x0; + _buffer[1] = 0x01; + _buffer[2] = 0xAE; + Serial.println( _buffer[0] * 65536UL + _buffer[1] * 256 + _buffer[2]); +} + + +void loop() +{ +} + + +// -- END OF FILE -- diff --git a/lib/lib_i2c/AGS02MA-0.4.3/keywords.txt b/lib/lib_i2c/AGS02MA-0.4.3/keywords.txt new file mode 100644 index 000000000..75bfdadb0 --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/keywords.txt @@ -0,0 +1,60 @@ +# Syntax Colouring Map for AGS02MA + + +# Data types (KEYWORD1) +AGS02MA KEYWORD1 + + +# Methods and Functions (KEYWORD2) +begin KEYWORD2 +isConnected KEYWORD2 +reset KEYWORD2 + +isHeated KEYWORD2 + +setAddress KEYWORD2 +getAddress KEYWORD2 + +getSensorVersion KEYWORD2 +getSensorDate KEYWORD2 + +setI2CResetSpeed KEYWORD2 +getI2CResetSpeed KEYWORD2 + +zeroCalibration KEYWORD2 +manualZeroCalibration KEYWORD2 +getZeroCalibrationData KEYWORD2 + +setPPBMode KEYWORD2 +setUGM3Mode KEYWORD2 +getMode KEYWORD2 + +readPPB KEYWORD2 +readUGM3 KEYWORD2 +readPPM KEYWORD2 +readMGM3 KEYWORD2 +readUGF3 KEYWORD2 + +lastPPM KEYWORD2 +lastPPB KEYWORD2 +lastUGM3 KEYWORD2 + +lastRead KEYWORD2 +lastError KEYWORD2 +lastStatus KEYWORD2 +dataReady KEYWORD2 + +readRegister KEYWORD2 + + +# Constants ( LITERAL1) +AGS02MA_LIB_VERSION LITERAL1 + +AGS02MA_OK LITERAL1 +AGS02MA_ERROR LITERAL1 +AGS02MA_ERROR_CRC LITERAL1 +AGS02MA_ERROR_READ LITERAL1 +AGS02MA_I2C_CLOCK LITERAL1 +AGS02MA_ERROR_REQUEST LITERAL1 + + diff --git a/lib/lib_i2c/AGS02MA-0.4.3/library.json b/lib/lib_i2c/AGS02MA-0.4.3/library.json new file mode 100644 index 000000000..2e64480da --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/library.json @@ -0,0 +1,29 @@ +{ + "name": "AGS02MA", + "keywords": "I2C, AGS02MA, tvoc", + "description": "Arduino library for AGS02MA - TVOC sensor.", + "authors": + [ + { + "name": "Viktor Balint" + }, + { + "name": "Rob Tillaart", + "email": "Rob.Tillaart@gmail.com", + "maintainer": true + }, + { + "name": "Beanow" + } + ], + "repository": + { + "type": "git", + "url": "https://github.com/RobTillaart/AGS02MA.git" + }, + "version": "0.4.3", + "license": "MIT", + "frameworks": "*", + "platforms": "*", + "headers": "AGS02MA.h" +} diff --git a/lib/lib_i2c/AGS02MA-0.4.3/library.properties b/lib/lib_i2c/AGS02MA-0.4.3/library.properties new file mode 100644 index 000000000..3c4efbf9a --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/library.properties @@ -0,0 +1,11 @@ +name=AGS02MA +version=0.4.3 +author=Rob Tillaart +maintainer=Rob Tillaart +sentence=Arduino library for AGS02MA - TVOC sensor +paragraph=Note it uses slow I2C < 30KHz. See readme.md +category=Sensors +url=https://github.com/RobTillaart/AGS02MA.git +architectures=* +includes=AGS02MA.h +depends= diff --git a/lib/lib_i2c/AGS02MA-0.4.3/test/unit_test_001.cpp b/lib/lib_i2c/AGS02MA-0.4.3/test/unit_test_001.cpp new file mode 100644 index 000000000..a7866440b --- /dev/null +++ b/lib/lib_i2c/AGS02MA-0.4.3/test/unit_test_001.cpp @@ -0,0 +1,102 @@ +// +// FILE: unit_test_001.cpp +// AUTHOR: Rob Tillaart +// DATE: 2021-08-12 +// PURPOSE: unit tests for the AGS02MA TVOC sensor +// https://github.com/RobTillaart/AGS02MA +// https://github.com/Arduino-CI/arduino_ci/blob/master/REFERENCE.md +// + +// supported assertions +// ---------------------------- +// assertEqual(expected, actual) +// assertNotEqual(expected, actual) +// assertLess(expected, actual) +// assertMore(expected, actual) +// assertLessOrEqual(expected, actual) +// assertMoreOrEqual(expected, actual) +// assertTrue(actual) +// assertFalse(actual) +// assertNull(actual) + +#include + + +#include "Arduino.h" +#include "AGS02MA.h" + +// writing to a virtual device does not work +// as millis() function is not implemented in +// the Arduino-CI environment + + +unittest_setup() +{ + fprintf(stderr, "AGS02MA_LIB_VERSION: %s\n", (char *) AGS02MA_LIB_VERSION); +} + + +unittest_teardown() +{ +} + + +unittest(test_constants) +{ + assertEqual( 0, AGS02MA_OK); + assertEqual(-10, AGS02MA_ERROR); + assertEqual(-11, AGS02MA_ERROR_CRC); + assertEqual(-12, AGS02MA_ERROR_READ); + assertEqual(-13, AGS02MA_ERROR_NOT_READY); + assertEqual(-14, AGS02MA_ERROR_REQUEST); + + assertEqual(25000, AGS02MA_I2C_CLOCK); +} + + +unittest(test_base) +{ + AGS02MA AGS(26); + Wire.begin(); + + assertTrue(AGS.begin()); + assertTrue(AGS.isConnected()); // TODO - GODMODE + + assertFalse(AGS.isHeated()); + assertEqual(0, AGS.lastRead()); + + assertEqual(26, AGS.getAddress()); + //assertTrue(AGS.setAddress(42)); + //assertEqual(42, AGS.getAddress()); + + assertEqual(100000, AGS.getI2CResetSpeed()); + AGS.setI2CResetSpeed(400000); + assertEqual(400000, AGS.getI2CResetSpeed()); + + assertEqual(0, AGS.lastError()); + assertEqual(0, AGS.lastStatus()); +} + + +unittest(test_mode) +{ + AGS02MA AGS(26); + Wire.begin(); + + assertTrue(AGS.begin()); + assertTrue(AGS.isConnected()); // TODO - GODMODE + + assertEqual(255, AGS.getMode()); + + // assertTrue(AGS.setPPBMode()); + // assertEqual(0, AGS.getMode()); + + // assertTrue(AGS.setUGM3Mode()); + // assertEqual(1, AGS.getMode()); +} + + + +unittest_main() + +// -- END OF FILE -- diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 94e50d1a1..16f5f7b26 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -807,6 +807,8 @@ // #define USE_RX8030 // [I2cDriver90] Enable RX8030 RTC - used by #23855 - support both I2C buses on ESP32 (I2C address 0x32) (+0k7 code) // #define USE_PCF85063 // [I2cDriver92] Enable PCF85063 RTC support (I2C address 0x51) +// #define USE_AGS02MA // [I2cDriver95] Enable AGS02MA Air Quality Sensor (I2C address 0x1A) + // #define USE_DISPLAY // Add I2C/TM1637/MAX7219 Display Support (+2k code) #define USE_DISPLAY_MODES1TO5 // Enable display mode 1 to 5 in addition to mode 0 #define USE_DISPLAY_LCD // [DisplayModel 1] [I2cDriver3] Enable Lcd display (I2C addresses 0x27 and 0x3F) (+6k code) diff --git a/tasmota/tasmota_xsns_sensor/xsns_118_ags02ma.ino b/tasmota/tasmota_xsns_sensor/xsns_118_ags02ma.ino new file mode 100644 index 000000000..aab7479d0 --- /dev/null +++ b/tasmota/tasmota_xsns_sensor/xsns_118_ags02ma.ino @@ -0,0 +1,199 @@ +/* + xsns_118_ags02ma.ino - AGS02MA TVOC sensor support for Tasmota + + Copyright (C) 2025 Akshaylal S + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_I2C +#ifdef USE_AGS02MA +/*********************************************************************************************\ + * AGS02MA - TVOC (Total Volatile Organic Compounds) Sensor + * + * Source: RobTillaart/AGS02MA library + * Adaption for TASMOTA: Akshaylal S + * + * I2C Address: 0x1A +\*********************************************************************************************/ + +#define XSNS_118 118 +#define XI2C_95 95 // See I2CDEVICES.md + +#define AGS02MA_ADDRESS 0x1A + +#include "AGS02MA.h" + +enum AGS02MA_State { + STATE_AGS02MA_START, + STATE_AGS02MA_INIT, + STATE_AGS02MA_HEATING, + STATE_AGS02MA_NORMAL, + STATE_AGS02MA_FAIL, +}; + +AGS02MA *ags02ma = nullptr; +AGS02MA_State ags02ma_state = STATE_AGS02MA_START; + +bool ags02ma_init = false; +bool ags02ma_read_pend = false; + +uint32_t ags02ma_ppb_value = 0; +uint16_t heating_counter = 0; // Counter for heating period (120 seconds / 24 checks = 5 seconds per check) + +/********************************************************************************************/ + +void Ags02maInit(void) +{ + if (!I2cSetDevice(AGS02MA_ADDRESS)) { return; } + + ags02ma = new AGS02MA(AGS02MA_ADDRESS, &I2cGetWire()); + + bool b = ags02ma->begin(); + if (!b) { + AddLog(LOG_LEVEL_INFO, PSTR("AGS02MA: Sensor not found or initialization failed")); + ags02ma_state = STATE_AGS02MA_FAIL; + return; + } + + uint8_t version = ags02ma->getSensorVersion(); + AddLog(LOG_LEVEL_INFO, PSTR("AGS02MA: Sensor version 0x%02X"), version); + + // Set PPB mode + b = ags02ma->setPPBMode(); + if (!b) { + AddLog(LOG_LEVEL_INFO, PSTR("AGS02MA: Failed to set PPB mode")); + ags02ma_state = STATE_AGS02MA_FAIL; + return; + } + + uint8_t mode = ags02ma->getMode(); + AddLog(LOG_LEVEL_INFO, PSTR("AGS02MA: Mode set to %d"), mode); + + I2cSetActiveFound(AGS02MA_ADDRESS, "AGS02MA", XI2C_95); + + ags02ma_init = true; + ags02ma_state = STATE_AGS02MA_HEATING; + heating_counter = 0; + + AddLog(LOG_LEVEL_INFO, PSTR("AGS02MA: Starting warm-up period (120 seconds)")); +} + +void Ags02maUpdate(void) +{ + if (ags02ma_state == STATE_AGS02MA_FAIL) { + AddLog(LOG_LEVEL_DEBUG, PSTR("AGS02MA: In FAIL state")); + return; + } + + // Handle heating period + if (ags02ma_state == STATE_AGS02MA_HEATING) { + // Check every 5 seconds (called from FUNC_EVERY_SECOND, so count to 5) + if (heating_counter % 5 == 0) { + if (ags02ma->isHeated()) { + AddLog(LOG_LEVEL_INFO, PSTR("AGS02MA: Warm-up complete, sensor ready")); + ags02ma_state = STATE_AGS02MA_NORMAL; + heating_counter = 0; + return; + } + } + heating_counter++; + + // Log progress every 24 seconds (approximately) + if (heating_counter % 24 == 0) { + AddLog(LOG_LEVEL_DEBUG, PSTR("AGS02MA: Warming up... %d seconds elapsed"), heating_counter); + } + return; + } + + // Normal operation - read sensor value + if (ags02ma_state == STATE_AGS02MA_NORMAL) { + ags02ma_ppb_value = ags02ma->readPPB(); + + uint8_t status = ags02ma->lastStatus(); + uint8_t error = ags02ma->lastError(); + + if (error != 0) { + AddLog(LOG_LEVEL_DEBUG, PSTR("AGS02MA: Read error - Status: 0x%02X, Error: 0x%02X"), status, error); + } else { + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("AGS02MA: PPB value: %d"), ags02ma_ppb_value); + } + } +} + +#ifdef USE_WEBSERVER +const char HTTP_SNS_AGS02MA[] PROGMEM = + "{s}AGS02MA " D_TVOC "{m}%d " D_UNIT_PARTS_PER_BILLION "{e}"; // {s} = , {m} = , {e} = +#endif + +void Ags02maShow(bool json) +{ + if (ags02ma_state == STATE_AGS02MA_NORMAL) { + if (json) { + ResponseAppend_P(PSTR(",\"AGS02MA\":{\"" D_JSON_TVOC "\":%d}"), + ags02ma_ppb_value); +#ifdef USE_DOMOTICZ + if (0 == TasmotaGlobal.tele_period) { + DomoticzSensor(DZ_AIRQUALITY, ags02ma_ppb_value); + } +#endif // USE_DOMOTICZ +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_AGS02MA, ags02ma_ppb_value); +#endif + } + } else if (ags02ma_state == STATE_AGS02MA_HEATING) { + if (json) { + ResponseAppend_P(PSTR(",\"AGS02MA\":{\"Status\":\"Heating\"}")); +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(PSTR("{s}AGS02MA Status{m}Warming up...{e}")); +#endif + } + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xsns118(uint32_t function) +{ + if (!I2cEnabled(XI2C_95)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + Ags02maInit(); + } + else if (ags02ma_init) { + switch (function) { + case FUNC_EVERY_SECOND: + Ags02maUpdate(); + break; + case FUNC_JSON_APPEND: + Ags02maShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Ags02maShow(0); + break; +#endif // USE_WEBSERVER + } + } + return result; +} + +#endif // USE_AGS02MA +#endif // USE_I2C \ No newline at end of file