ESPHome  2024.12.2
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 
5 namespace esphome {
6 namespace apds9960 {
7 
8 static 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 
17 void 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 
127 void 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 
163 void 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 
172 void APDS9960::loop() { this->read_gesture_data_(); }
173 
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 }
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 }
277 void 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 }
315 void 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 }
388 float 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
bool read_byte(uint8_t a_register, uint8_t *data, bool stop=true)
Definition: i2c.h:235
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:19
uint8_t raw[35]
Definition: bl0939.h:19
bool is_color_enabled_() const
Definition: apds9960.cpp:118
bool is_failed() const
Definition: component.cpp:143
ErrorCode read(uint8_t *data, size_t len)
reads an array of bytes from the device using an I2CBus
Definition: i2c.h:160
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:212
T id(T value)
Helper function to make id(var) known from lambdas work in custom components.
Definition: helpers.h:728
void read_color_data_(uint8_t status)
Definition: apds9960.cpp:174
mopeka_std_values val[4]
void process_dataset_(int up, int down, int left, int right)
Definition: apds9960.cpp:315
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
void read_proximity_data_(uint8_t status)
Definition: apds9960.cpp:208
virtual void setup()
Where the component&#39;s initialization should happen.
Definition: component.cpp:48
void status_clear_warning()
Definition: component.cpp:166
void publish_state(bool state)
Publish a new state to the front-end.
void report_gesture_(int gesture)
Definition: apds9960.cpp:277
uint8_t status
Definition: bl0942.h:74
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:118
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
Base class for all binary_sensor-type classes.
Definition: binary_sensor.h:37
bool is_gesture_enabled_() const
Definition: apds9960.cpp:398
bool is_proximity_enabled_() const
Definition: apds9960.cpp:389