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