ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
apds9960.cpp
Go to the documentation of this file.
1#include "apds9960.h"
2#include "esphome/core/log.h"
3#include "esphome/core/hal.h"
4
6
7static const char *const TAG = "apds9960";
8
9#define APDS9960_ERROR_CHECK(func) \
10 if (!(func)) { \
11 this->mark_failed(); \
12 return; \
13 }
14#define APDS9960_WRITE_BYTE(reg, value) APDS9960_ERROR_CHECK(this->write_byte(reg, value));
15
16void APDS9960::setup() {
17 uint8_t id;
18 if (!this->read_byte(0x92, &id)) { // ID register
19 this->error_code_ = COMMUNICATION_FAILED;
20 this->mark_failed();
21 return;
22 }
23
24 if (id != 0xAB && id != 0x9C && id != 0xA8 && id != 0x9E) { // APDS9960 all should have one of these IDs
25 this->error_code_ = WRONG_ID;
26 this->mark_failed();
27 return;
28 }
29
30 // ATime (ADC integration time, 2.78ms increments, 0x81) -> 0xDB (103ms)
31 APDS9960_WRITE_BYTE(0x81, 0xDB);
32 // WTime (Wait time, 0x83) -> 0xF6 (27ms)
33 APDS9960_WRITE_BYTE(0x83, 0xF6);
34 // PPulse (0x8E) -> 0x87 (16us, 8 pulses)
35 APDS9960_WRITE_BYTE(0x8E, 0x87);
36 // POffset UR (0x9D) -> 0 (no offset)
37 APDS9960_WRITE_BYTE(0x9D, 0x00);
38 // POffset DL (0x9E) -> 0 (no offset)
39 APDS9960_WRITE_BYTE(0x9E, 0x00);
40 // Config 1 (0x8D) -> 0x60 (no wtime factor)
41 APDS9960_WRITE_BYTE(0x8D, 0x60);
42
43 // Control (0x8F) ->
44 uint8_t val = 0;
45 APDS9960_ERROR_CHECK(this->read_byte(0x8F, &val));
46 val &= 0b00111111;
47 // led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA
48 val |= (this->led_drive_ & 0b11) << 6;
49
50 val &= 0b11110011;
51 // proximity gain, 0 -> 1x, 1 -> 2X, 2 -> 4X, 3 -> 8X
52 val |= (this->proximity_gain_ & 0b11) << 2;
53
54 val &= 0b11111100;
55 // ambient light gain, 0 -> 1x, 1 -> 4x, 2 -> 16x, 3 -> 64x
56 val |= (this->ambient_gain_ & 0b11) << 0;
57 APDS9960_WRITE_BYTE(0x8F, val);
58
59 // Pers (0x8C) -> 0x11 (2 consecutive proximity or ALS for interrupt)
60 APDS9960_WRITE_BYTE(0x8C, 0x11);
61 // Config 2 (0x90) -> 0x01 (no saturation interrupts or LED boost)
62 APDS9960_WRITE_BYTE(0x90, 0x01);
63 // Config 3 (0x9F) -> 0x00 (enable all photodiodes, no SAI)
64 APDS9960_WRITE_BYTE(0x9F, 0x00);
65 // GPenTh (0xA0, gesture enter threshold) -> 0x28 (also 0x32)
66 APDS9960_WRITE_BYTE(0xA0, 0x28);
67 // GPexTh (0xA1, gesture exit threshold) -> 0x1E
68 APDS9960_WRITE_BYTE(0xA1, 0x1E);
69
70 // GConf 1 (0xA2, gesture config 1) -> 0x40 (4 gesture events for interrupt (GFIFO 3), 1 for exit)
71 APDS9960_WRITE_BYTE(0xA2, 0x40);
72
73 // GConf 2 (0xA3, gesture config 2) ->
74 APDS9960_ERROR_CHECK(this->read_byte(0xA3, &val));
75 val &= 0b10011111;
76 // gesture gain, 0 -> 1x, 1 -> 2x, 2 -> 4x, 3 -> 8x
77 val |= (this->gesture_gain_ & 0b11) << 5;
78
79 val &= 0b11100111;
80 // gesture led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA
81 val |= (this->gesture_led_drive_ & 0b11) << 3;
82
83 val &= 0b11111000;
84 // gesture wait time
85 // 0 -> 0ms, 1 -> 2.8ms, 2 -> 5.6ms, 3 -> 8.4ms
86 // 4 -> 14.0ms, 5 -> 22.4 ms, 6 -> 30.8ms, 7 -> 39.2 ms
87 val |= (this->gesture_wait_time_ & 0b111) << 0;
88 APDS9960_WRITE_BYTE(0xA3, val);
89
90 // GOffsetU (0xA4) -> 0x00 (no offset)
91 APDS9960_WRITE_BYTE(0xA4, 0x00);
92 // GOffsetD (0xA5) -> 0x00 (no offset)
93 APDS9960_WRITE_BYTE(0xA5, 0x00);
94 // GOffsetL (0xA7) -> 0x00 (no offset)
95 APDS9960_WRITE_BYTE(0xA7, 0x00);
96 // GOffsetR (0xA9) -> 0x00 (no offset)
97 APDS9960_WRITE_BYTE(0xA9, 0x00);
98 // GPulse (0xA6) -> 0xC9 (32 µs, 10 pulses)
99 APDS9960_WRITE_BYTE(0xA6, 0xC9);
100
101 // GConf 3 (0xAA, gesture config 3) -> 0x00 (all photodiodes active during gesture, all gesture dimensions enabled)
102 // 0x00 -> all dimensions, 0x01 -> up down, 0x02 -> left right
103 APDS9960_WRITE_BYTE(0xAA, 0x00);
104
105 // Enable (0x80) ->
106 val = 0;
107 val |= (0b1) << 0; // power on
108 val |= (this->is_color_enabled_() & 0b1) << 1;
109 val |= (this->is_proximity_enabled_() & 0b1) << 2;
110 val |= 0b0 << 3; // wait timer disabled
111 val |= 0b0 << 4; // color interrupt disabled
112 val |= 0b0 << 5; // proximity interrupt disabled
113 val |= (this->is_gesture_enabled_() & 0b1) << 6; // proximity is required for gestures
114 APDS9960_WRITE_BYTE(0x80, val);
115}
117#ifdef USE_SENSOR
118 return this->red_sensor_ != nullptr || this->green_sensor_ != nullptr || this->blue_sensor_ != nullptr ||
119 this->clear_sensor_ != nullptr;
120#else
121 return false;
122#endif
123}
124
125void APDS9960::dump_config() {
126 ESP_LOGCONFIG(TAG, "APDS9960:");
127 LOG_I2C_DEVICE(this);
128
129 LOG_UPDATE_INTERVAL(this);
130
131#ifdef USE_SENSOR
132 LOG_SENSOR(" ", "Red channel", this->red_sensor_);
133 LOG_SENSOR(" ", "Green channel", this->green_sensor_);
134 LOG_SENSOR(" ", "Blue channel", this->blue_sensor_);
135 LOG_SENSOR(" ", "Clear channel", this->clear_sensor_);
136 LOG_SENSOR(" ", "Proximity", this->proximity_sensor_);
137#endif
138
139 if (this->is_failed()) {
140 switch (this->error_code_) {
142 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
143 break;
144 case WRONG_ID:
145 ESP_LOGE(TAG, "APDS9960 has invalid id!");
146 break;
147 default:
148 ESP_LOGE(TAG, "Setting up APDS9960 registers failed!");
149 break;
150 }
151 }
152}
153
154#define APDS9960_WARNING_CHECK(func, warning) \
155 if (!(func)) { \
156 ESP_LOGW(TAG, warning); \
157 this->status_set_warning(); \
158 return; \
159 }
160
161void APDS9960::update() {
162 uint8_t status;
163 APDS9960_WARNING_CHECK(this->read_byte(0x93, &status), "Reading status bit failed.");
164 this->status_clear_warning();
165
166 this->read_color_data_(status);
167 this->read_proximity_data_(status);
168}
169
170void APDS9960::loop() { this->read_gesture_data_(); }
171
172void APDS9960::read_color_data_(uint8_t status) {
173 if (!this->is_color_enabled_())
174 return;
175
176 if ((status & 0x01) == 0x00) {
177 // color data not ready yet.
178 return;
179 }
180
181 uint8_t raw[8];
182 APDS9960_WARNING_CHECK(this->read_bytes(0x94, raw, 8), "Reading color values failed.");
183
184 uint16_t uint_clear = (uint16_t(raw[1]) << 8) | raw[0];
185 uint16_t uint_red = (uint16_t(raw[3]) << 8) | raw[2];
186 uint16_t uint_green = (uint16_t(raw[5]) << 8) | raw[4];
187 uint16_t uint_blue = (uint16_t(raw[7]) << 8) | raw[6];
188
189 float clear_perc = (uint_clear / float(UINT16_MAX)) * 100.0f;
190 float red_perc = (uint_red / float(UINT16_MAX)) * 100.0f;
191 float green_perc = (uint_green / float(UINT16_MAX)) * 100.0f;
192 float blue_perc = (uint_blue / float(UINT16_MAX)) * 100.0f;
193
194 ESP_LOGD(TAG, "Got clear=%.1f%% red=%.1f%% green=%.1f%% blue=%.1f%%", clear_perc, red_perc, green_perc, blue_perc);
195#ifdef USE_SENSOR
196 if (this->clear_sensor_ != nullptr)
197 this->clear_sensor_->publish_state(clear_perc);
198 if (this->red_sensor_ != nullptr)
199 this->red_sensor_->publish_state(red_perc);
200 if (this->green_sensor_ != nullptr)
201 this->green_sensor_->publish_state(green_perc);
202 if (this->blue_sensor_ != nullptr)
203 this->blue_sensor_->publish_state(blue_perc);
204#endif
205}
206void APDS9960::read_proximity_data_(uint8_t status) {
207#ifndef USE_SENSOR
208 return;
209#else
210 if (this->proximity_sensor_ == nullptr)
211 return;
212
213 if ((status & 0b10) == 0x00) {
214 // proximity data not ready yet.
215 return;
216 }
217
218 uint8_t prox;
219 APDS9960_WARNING_CHECK(this->read_byte(0x9C, &prox), "Reading proximity values failed.");
220
221 float prox_perc = (prox / float(UINT8_MAX)) * 100.0f;
222 ESP_LOGD(TAG, "Got proximity=%.1f%%", prox_perc);
223 this->proximity_sensor_->publish_state(prox_perc);
224#endif
225}
227 if (!this->is_gesture_enabled_())
228 return;
229
230 uint8_t status;
231 APDS9960_WARNING_CHECK(this->read_byte(0xAF, &status), "Reading gesture status failed.");
232
233 if ((status & 0b01) == 0) {
234 // GVALID is false
235 return;
236 }
237
238 if ((status & 0b10) == 0b10) {
239 ESP_LOGV(TAG, "FIFO buffer has filled to capacity!");
240 }
241
242 uint8_t fifo_level;
243 APDS9960_WARNING_CHECK(this->read_byte(0xAE, &fifo_level), "Reading FIFO level failed.");
244 if (fifo_level == 0) {
245 // no data to process
246 return;
247 }
248
249 APDS9960_WARNING_CHECK(fifo_level <= 32, "FIFO level has invalid value.")
250
251 uint8_t buf[128];
252 for (uint8_t pos = 0; pos < fifo_level * 4; pos += 32) {
253 // Read in 32-byte chunks due to ESP8266 I2C buffer limit.
254 // Always read from 0xFC — the FIFO auto-increments through 0xFC-0xFF
255 // and advances its internal pointer after every 4th byte.
256 uint8_t read = std::min(32, fifo_level * 4 - pos);
257 APDS9960_WARNING_CHECK(this->read_bytes(0xFC, buf + pos, read), "Reading FIFO buffer failed.");
258 }
259
260 if (millis() - this->gesture_start_ > 500) {
261 this->gesture_up_started_ = false;
262 this->gesture_down_started_ = false;
263 this->gesture_left_started_ = false;
264 this->gesture_right_started_ = false;
265 }
266
267 for (uint32_t i = 0; i < fifo_level * 4; i += 4) {
268 const int up = buf[i + 0]; // NOLINT
269 const int down = buf[i + 1];
270 const int left = buf[i + 2];
271 const int right = buf[i + 3];
272 this->process_dataset_(up, down, left, right);
273 }
274}
275void APDS9960::report_gesture_(int gesture) {
276#ifdef USE_BINARY_SENSOR
278 switch (gesture) {
279 case 1:
280 bin = this->up_direction_binary_sensor_;
281 this->gesture_up_started_ = false;
282 this->gesture_down_started_ = false;
283 ESP_LOGD(TAG, "Got gesture UP");
284 break;
285 case 2:
286 bin = this->down_direction_binary_sensor_;
287 this->gesture_up_started_ = false;
288 this->gesture_down_started_ = false;
289 ESP_LOGD(TAG, "Got gesture DOWN");
290 break;
291 case 3:
292 bin = this->left_direction_binary_sensor_;
293 this->gesture_left_started_ = false;
294 this->gesture_right_started_ = false;
295 ESP_LOGD(TAG, "Got gesture LEFT");
296 break;
297 case 4:
298 bin = this->right_direction_binary_sensor_;
299 this->gesture_left_started_ = false;
300 this->gesture_right_started_ = false;
301 ESP_LOGD(TAG, "Got gesture RIGHT");
302 break;
303 default:
304 return;
305 }
306
307 if (bin != nullptr) {
308 bin->publish_state(true);
309 bin->publish_state(false);
310 }
311#endif
312}
313void APDS9960::process_dataset_(int up, int down, int left, int right) {
314 /* Algorithm: (see Figure 11 in datasheet)
315 *
316 * Observation: When a gesture is started, we will see a short amount of time where
317 * the photodiode in the direction of the motion has a much higher count value
318 * than where the gesture originates.
319 *
320 * In this algorithm we continually check the difference between the count values of opposing
321 * directions. For example in the down/up direction we continually look at the difference of the
322 * up count and down count. When DOWN gesture begins, this difference will be positive with a
323 * high magnitude for a short amount of time (magic value here is the difference is at least 13).
324 *
325 * If we see such a pattern, we store that we saw the first part of a gesture (the leading edge).
326 * After that some time can pass during which the difference is zero again (though the count values
327 * are not zero). At the end of a gesture, we will see this difference go into the opposite direction
328 * for a short period of time.
329 *
330 * If a gesture is not ended within 500 milliseconds, we consider the initial trailing edge invalid
331 * and reset the state.
332 *
333 * This algorithm does work, but not too well. Some good signal processing algorithms could
334 * probably improve this a lot, especially since the incoming signal has such a characteristic
335 * and quite noise-free pattern.
336 */
337 const int up_down_delta = up - down;
338 const int left_right_delta = left - right;
339 const bool up_down_significant = abs(up_down_delta) > 13;
340 const bool left_right_significant = abs(left_right_delta) > 13;
341
342 if (up_down_significant) {
343 if (up_down_delta < 0) {
344 if (this->gesture_up_started_) {
345 // trailing edge of gesture up
346 this->report_gesture_(1); // UP
347 } else {
348 // leading edge of gesture down
349 this->gesture_down_started_ = true;
350 this->gesture_start_ = millis();
351 }
352 } else {
353 if (this->gesture_down_started_) {
354 // trailing edge of gesture down
355 this->report_gesture_(2); // DOWN
356 } else {
357 // leading edge of gesture up
358 this->gesture_up_started_ = true;
359 this->gesture_start_ = millis();
360 }
361 }
362 }
363
364 if (left_right_significant) {
365 if (left_right_delta < 0) {
366 if (this->gesture_left_started_) {
367 // trailing edge of gesture left
368 this->report_gesture_(3); // LEFT
369 } else {
370 // leading edge of gesture right
371 this->gesture_right_started_ = true;
372 this->gesture_start_ = millis();
373 }
374 } else {
375 if (this->gesture_right_started_) {
376 // trailing edge of gesture right
377 this->report_gesture_(4); // RIGHT
378 } else {
379 // leading edge of gesture left
380 this->gesture_left_started_ = true;
381 this->gesture_start_ = millis();
382 }
383 }
384 }
385}
387 return
388#ifdef USE_SENSOR
389 this->proximity_sensor_ != nullptr
390#else
391 false
392#endif
393 || this->is_gesture_enabled_();
394}
396#ifdef USE_BINARY_SENSOR
397 return this->up_direction_binary_sensor_ != nullptr || this->left_direction_binary_sensor_ != nullptr ||
398 this->down_direction_binary_sensor_ != nullptr || this->right_direction_binary_sensor_ != nullptr;
399#else
400 return false;
401#endif
402}
403
404} // namespace esphome::apds9960
uint8_t raw[35]
Definition bl0939.h:0
uint8_t status
Definition bl0942.h:8
void mark_failed()
Mark this component as failed.
virtual void setup()
Where the component's initialization should happen.
Definition component.cpp:84
bool is_failed() const
Definition component.h:272
void status_clear_warning()
Definition component.h:289
void read_proximity_data_(uint8_t status)
Definition apds9960.cpp:206
bool is_gesture_enabled_() const
Definition apds9960.cpp:395
void report_gesture_(int gesture)
Definition apds9960.cpp:275
void read_color_data_(uint8_t status)
Definition apds9960.cpp:172
void process_dataset_(int up, int down, int left, int right)
Definition apds9960.cpp:313
bool is_proximity_enabled_() const
Definition apds9960.cpp:386
Base class for all binary_sensor-type classes.
void publish_state(bool new_state)
Publish a new state to the front-end.
ErrorCode read(uint8_t *data, size_t len) const
reads an array of bytes from the device using an I2CBus
Definition i2c.h:163
bool read_byte(uint8_t a_register, uint8_t *data)
Definition i2c.h:240
bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len)
Compat APIs All methods below have been added for compatibility reasons.
Definition i2c.h:217
uint16_t id
mopeka_std_values val[3]
size_t size_t pos
Definition helpers.h:1038
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
static void uint32_t