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