Skip to content

Commit a94544f

Browse files
authored
Merge pull request #811 from mikeysklar/sgpxx-faster
SGP30 / SGP40 background sampling
2 parents 3dc6bc4 + a0fdee7 commit a94544f

File tree

4 files changed

+222
-30
lines changed

4 files changed

+222
-30
lines changed

src/components/i2c/WipperSnapper_I2C.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1054,7 +1054,7 @@ bool WipperSnapper_Component_I2C::initI2CDevice(
10541054
_drivers_out.push_back(_ssd1306);
10551055
WS_DEBUG_PRINTLN("SSD1306 display initialized Successfully!");
10561056
} else {
1057-
WS_DEBUG_PRINTLN("ERROR: I2C device type not found!")
1057+
WS_DEBUG_PRINTLN("ERROR: I2C device type not found!");
10581058
_busStatusResponse =
10591059
wippersnapper_i2c_v1_BusResponse_BUS_RESPONSE_UNSUPPORTED_SENSOR;
10601060
return false;
@@ -1376,9 +1376,13 @@ void WipperSnapper_Component_I2C::update() {
13761376
while (sensorsReturningFalse && retries > 0) {
13771377
sensorsReturningFalse = false;
13781378
retries--;
1379+
curTime = millis();
13791380

13801381
std::vector<WipperSnapper_I2C_Driver *>::iterator iter, end;
13811382
for (iter = drivers.begin(), end = drivers.end(); iter != end; ++iter) {
1383+
// Per-driver fast tick (non-blocking)
1384+
(*iter)->fastTick();
1385+
13821386
// Number of events which occurred for this driver
13831387
msgi2cResponse.payload.resp_i2c_device_event.sensor_event_count = 0;
13841388

src/components/i2c/drivers/WipperSnapper_I2C_Driver.h

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class WipperSnapper_I2C_Driver {
3434
public:
3535
/*******************************************************************************/
3636
/*!
37-
@brief Instanciates an I2C sensor.
37+
@brief Instantiates an I2C sensor.
3838
@param i2c
3939
The I2C hardware interface, default is Wire.
4040
@param sensorAddress
@@ -53,6 +53,21 @@ class WipperSnapper_I2C_Driver {
5353
/*******************************************************************************/
5454
virtual ~WipperSnapper_I2C_Driver() {}
5555

56+
/*******************************************************************************/
57+
/*!
58+
@brief Lightweight, per-update background hook for drivers that need
59+
more frequent internal polling than the publish interval.
60+
Default is a no-op; concrete drivers (e.g., SGP30/40) may
61+
override this to perform a single non-blocking read and cache
62+
results for later retrieval by getEvent*().
63+
@note Call site: WipperSnapper_Component_I2C::update() invokes this
64+
once per loop for each driver. Implementations must be
65+
non-blocking (do not delay); use millis()-based timing if
66+
cadence is required.
67+
*/
68+
/*******************************************************************************/
69+
virtual void fastTick() {}
70+
5671
/*******************************************************************************/
5772
/*!
5873
@brief Initializes the I2C sensor and begins I2C.
@@ -1312,7 +1327,7 @@ class WipperSnapper_I2C_Driver {
13121327
@brief Updates the properties of a proximity sensor.
13131328
@param period
13141329
The time interval at which to return new data from the
1315-
proimity sensor.
1330+
proximity sensor.
13161331
*/
13171332
/*******************************************************************************/
13181333
virtual void updateSensorProximity(float period) {

src/components/i2c/drivers/WipperSnapper_I2C_Driver_SGP30.h

Lines changed: 113 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
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

66145
protected:
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

src/components/i2c/drivers/WipperSnapper_I2C_Driver_SGP40.h

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#include <Adafruit_SGP40.h>
2121
#include <Wire.h>
2222

23+
#define SGP40_FASTTICK_INTERVAL_MS 1000 ///< Enforce ~1 Hz cadence
24+
2325
/**************************************************************************/
2426
/*!
2527
@brief Class that provides a driver interface for the SGP40 sensor.
@@ -40,6 +42,21 @@ class WipperSnapper_I2C_Driver_SGP40 : public WipperSnapper_I2C_Driver {
4042
: WipperSnapper_I2C_Driver(i2c, sensorAddress) {
4143
_i2c = i2c;
4244
_sensorAddress = sensorAddress;
45+
_sgp40 = nullptr;
46+
}
47+
48+
/*******************************************************************************/
49+
/*!
50+
@brief Destructor for an SGP40 sensor driver.
51+
Cleans up and deallocates the underlying Adafruit_SGP40 object
52+
when the driver is destroyed.
53+
*/
54+
/*******************************************************************************/
55+
~WipperSnapper_I2C_Driver_SGP40() {
56+
if (_sgp40) {
57+
delete _sgp40;
58+
_sgp40 = nullptr;
59+
}
4360
}
4461

4562
/*******************************************************************************/
@@ -50,12 +67,15 @@ class WipperSnapper_I2C_Driver_SGP40 : public WipperSnapper_I2C_Driver {
5067
/*******************************************************************************/
5168
bool begin() {
5269
_sgp40 = new Adafruit_SGP40();
53-
if (!_sgp40->begin(_i2c)) {
70+
if (!_sgp40 || !_sgp40->begin(_i2c)) {
71+
delete _sgp40;
72+
_sgp40 = nullptr;
5473
return false;
5574
}
56-
57-
// TODO: update to use setCalibration() and pass in temp/humidity
58-
75+
// Initialize cached values
76+
_rawValue = 0;
77+
_vocIdx = 0;
78+
_lastFastMs = millis() - SGP40_FASTTICK_INTERVAL_MS;
5979
return true;
6080
}
6181

@@ -64,12 +84,14 @@ class WipperSnapper_I2C_Driver_SGP40 : public WipperSnapper_I2C_Driver {
6484
@brief Gets the sensor's current raw unprocessed value.
6585
@param rawEvent
6686
Pointer to an Adafruit_Sensor event.
67-
@returns True if the temperature was obtained successfully, False
87+
@returns True if the raw value was obtained successfully, False
6888
otherwise.
6989
*/
7090
/*******************************************************************************/
71-
bool getEventRaw(sensors_event_t *rawEvent) {
72-
rawEvent->data[0] = (float)_sgp40->measureRaw();
91+
bool getEventRaw(sensors_event_t *rawEvent) override {
92+
if (!_sgp40)
93+
return false;
94+
rawEvent->data[0] = (float)_rawValue;
7395
return true;
7496
}
7597

@@ -82,13 +104,67 @@ class WipperSnapper_I2C_Driver_SGP40 : public WipperSnapper_I2C_Driver {
82104
otherwise.
83105
*/
84106
/*******************************************************************************/
85-
bool getEventVOCIndex(sensors_event_t *vocIndexEvent) {
86-
vocIndexEvent->voc_index = (float)_sgp40->measureVocIndex();
107+
bool getEventVOCIndex(sensors_event_t *vocIndexEvent) override {
108+
if (!_sgp40)
109+
return false;
110+
vocIndexEvent->voc_index = (uint16_t)_vocIdx;
87111
return true;
88112
}
89113

114+
/*******************************************************************************/
115+
/*!
116+
@brief Performs background sampling for the SGP40.
117+
118+
This method enforces a ~1 Hz cadence recommended by the sensor
119+
datasheet. On each call, it checks the elapsed time since the last
120+
poll using `millis()`. If at least SGP40_FASTTICK_INTERVAL_MS ms
121+
have passed, it reads a new raw value and VOC index from the
122+
sensor and caches them in `_rawValue` and `_vocIdx`.
123+
124+
Cached results are later returned by `getEventRaw()` and
125+
`getEventVOCIndex()` without re-triggering I2C traffic.
126+
127+
@note Called automatically from
128+
`WipperSnapper_Component_I2C::update()` once per loop iteration.
129+
Must be non-blocking (no delays). The millis-based guard ensures
130+
the sensor is not over-polled.
131+
*/
132+
/*******************************************************************************/
133+
void fastTick() override {
134+
if (!_sgp40)
135+
return;
136+
if (!vocEnabled())
137+
return;
138+
139+
uint32_t now = millis();
140+
if (now - _lastFastMs >= SGP40_FASTTICK_INTERVAL_MS) {
141+
_rawValue = _sgp40->measureRaw();
142+
_vocIdx = (int32_t)_sgp40->measureVocIndex();
143+
_lastFastMs = now;
144+
}
145+
}
146+
90147
protected:
91-
Adafruit_SGP40 *_sgp40; ///< SEN5X driver object
148+
Adafruit_SGP40 *_sgp40; ///< Pointer to SGP40 sensor object
149+
150+
/**
151+
* Cached latest measurements from the sensor.
152+
* - _rawValue: raw sensor output (ticks)
153+
* - _vocIdx: VOC Index (signed, per datasheet)
154+
*/
155+
uint16_t _rawValue = 0; ///< Raw sensor output (ticks)
156+
int32_t _vocIdx = 0; ///< VOC Index (signed, per datasheet)
157+
uint32_t _lastFastMs = 0; ///< Last poll timestamp to enforce 1 Hz cadence
158+
159+
/*******************************************************************************/
160+
/*!
161+
@brief Returns whether VOC background sampling should be active.
162+
@return True if either VOC Index or raw value is configured to publish.
163+
*/
164+
/*******************************************************************************/
165+
inline bool vocEnabled() {
166+
return (getSensorVOCIndexPeriod() > 0) || (getSensorRawPeriod() > 0);
167+
}
92168
};
93169

94-
#endif // WipperSnapper_I2C_Driver_SEN5X
170+
#endif // WipperSnapper_I2C_Driver_SGP40_H

0 commit comments

Comments
 (0)