44#include " WipperSnapper_I2C_Driver.h"
55#include < Adafruit_SGP30.h>
66
7+ #define SGP30_FASTTICK_INTERVAL_MS 1000 // /< Enforce ~1 Hz cadence
8+
79/* *************************************************************************/
810/* !
911 @brief Class that provides a driver interface for a SGP30 sensor.
@@ -24,6 +26,7 @@ class WipperSnapper_I2C_Driver_SGP30 : public WipperSnapper_I2C_Driver {
2426 : WipperSnapper_I2C_Driver(i2c, sensorAddress) {
2527 _i2c = i2c;
2628 _sensorAddress = sensorAddress;
29+ _sgp30 = nullptr ;
2730 }
2831
2932 /* ******************************************************************************/
@@ -32,8 +35,10 @@ class WipperSnapper_I2C_Driver_SGP30 : public WipperSnapper_I2C_Driver {
3235 */
3336 /* ******************************************************************************/
3437 ~WipperSnapper_I2C_Driver_SGP30 () {
35- // Called when a SGP30 component is deleted.
36- delete _sgp30;
38+ if (_sgp30) {
39+ delete _sgp30;
40+ _sgp30 = nullptr ;
41+ }
3742 }
3843
3944 /* ******************************************************************************/
@@ -44,27 +49,119 @@ class WipperSnapper_I2C_Driver_SGP30 : public WipperSnapper_I2C_Driver {
4449 /* ******************************************************************************/
4550 bool begin () {
4651 _sgp30 = new Adafruit_SGP30 ();
47- return _sgp30->begin (_i2c);
52+ if (!_sgp30->begin (_i2c)) {
53+ delete _sgp30; // avoid leak on init failure
54+ _sgp30 = nullptr ;
55+ return false ;
56+ }
57+ _sgp30->IAQinit (); // start IAQ algorithm
58+
59+ // Initialize cached values and cadence
60+ _eco2 = 0 ;
61+ _tvoc = 0 ;
62+ _lastFastMs = millis () - SGP30_FASTTICK_INTERVAL_MS;
63+ return true ;
4864 }
4965
50- bool getEventECO2 (sensors_event_t *senseEvent) {
51- bool result = _sgp30->IAQmeasure ();
52- if (result) {
53- senseEvent->eCO2 = _sgp30->eCO2 ;
54- }
55- return result;
66+ /* ******************************************************************************/
67+ /* !
68+ @brief Gets the most recently cached eCO2 reading.
69+
70+ This value is updated in `fastTick()` at a ~1 Hz cadence
71+ and returned directly here without re-triggering an I2C
72+ transaction.
73+
74+ @param senseEvent
75+ Pointer to an Adafruit_Sensor event that will be populated
76+ with the cached eCO2 value (in ppm).
77+
78+ @returns True if a cached value is available, False otherwise.
79+ */
80+ /* ******************************************************************************/
81+ bool getEventECO2 (sensors_event_t *senseEvent) override {
82+ if (!_sgp30)
83+ return false ;
84+ senseEvent->eCO2 = _eco2;
85+ return true ;
5686 }
5787
58- bool getEventTVOC (sensors_event_t *senseEvent) {
59- bool result = _sgp30->IAQmeasure ();
60- if (result) {
61- senseEvent->tvoc = _sgp30->TVOC ;
88+ /* ******************************************************************************/
89+ /* !
90+ @brief Gets the most recently cached TVOC reading.
91+
92+ This value is updated in `fastTick()` at a ~1 Hz cadence
93+ and returned directly here without re-triggering an I2C
94+ transaction.
95+
96+ @param senseEvent
97+ Pointer to an Adafruit_Sensor event that will be populated
98+ with the cached TVOC value (in ppb).
99+
100+ @returns True if a cached value is available, False otherwise.
101+ */
102+ /* ******************************************************************************/
103+ bool getEventTVOC (sensors_event_t *senseEvent) override {
104+ if (!_sgp30)
105+ return false ;
106+ senseEvent->tvoc = _tvoc;
107+ return true ;
108+ }
109+
110+ /* ******************************************************************************/
111+ /* !
112+ @brief Performs background sampling for the SGP30.
113+
114+ This method enforces a ~1 Hz cadence recommended by the sensor
115+ datasheet. On each call, it checks the elapsed time since the
116+ last poll using `millis()`. If at least
117+ SGP30_FASTTICK_INTERVAL_MS have passed, it performs a single
118+ IAQ measurement and caches the results in `_eco2` and `_tvoc`.
119+
120+ Cached values are then returned by `getEventECO2()` and
121+ `getEventTVOC()` without re-triggering I2C traffic.
122+
123+ @note Called automatically from
124+ `WipperSnapper_Component_I2C::update()` once per loop iteration.
125+ Must be non-blocking (no delays). The millis-based guard ensures
126+ the sensor is not over-polled.
127+ */
128+ /* ******************************************************************************/
129+ void fastTick () override {
130+ if (!_sgp30)
131+ return ;
132+ if (!iaqEnabled ())
133+ return ;
134+
135+ uint32_t now = millis ();
136+ if (now - _lastFastMs >= SGP30_FASTTICK_INTERVAL_MS) {
137+ if (_sgp30->IAQmeasure ()) {
138+ _eco2 = (uint16_t )_sgp30->eCO2 ;
139+ _tvoc = (uint16_t )_sgp30->TVOC ;
140+ }
141+ _lastFastMs = now;
62142 }
63- return result;
64143 }
65144
66145protected:
67- Adafruit_SGP30 *_sgp30; // /< Pointer to SGP30 temperature sensor object
146+ Adafruit_SGP30 *_sgp30; // /< Pointer to SGP30 sensor object
147+
148+ /* * Cached latest measurements (no averaging). */
149+ uint16_t _eco2 = 0 ; // /< eCO2, in ppm
150+ uint16_t _tvoc = 0 ; // /< TVOC, in ppb
151+
152+ /* * Timestamp of last poll to enforce 1 Hz cadence. */
153+ uint32_t _lastFastMs = 0 ;
154+
155+ /* ******************************************************************************/
156+ /* !
157+ @brief Returns whether IAQ background sampling should be active.
158+ @return True if either eCO2 or TVOC metrics are configured to publish.
159+ */
160+ /* ******************************************************************************/
161+ inline bool iaqEnabled () {
162+ // Enable IAQ background reads if either metric is requested
163+ return (getSensorECO2Period () > 0 ) || (getSensorTVOCPeriod () > 0 );
164+ }
68165};
69166
70- #endif // WipperSnapper_I2C_Driver_SGP30
167+ #endif // WipperSnapper_I2C_Driver_SGP30_H
0 commit comments