ESPHome  2022.12.8
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 
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) { // 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  return this->red_channel_ != nullptr || this->green_channel_ != nullptr || this->blue_channel_ != nullptr ||
120  this->clear_channel_ != nullptr;
121 }
122 
124  ESP_LOGCONFIG(TAG, "APDS9960:");
125  LOG_I2C_DEVICE(this);
126 
127  LOG_UPDATE_INTERVAL(this);
128  if (this->is_failed()) {
129  switch (this->error_code_) {
131  ESP_LOGE(TAG, "Communication with APDS9960 failed!");
132  break;
133  case WRONG_ID:
134  ESP_LOGE(TAG, "APDS9960 has invalid id!");
135  break;
136  default:
137  ESP_LOGE(TAG, "Setting up APDS9960 registers failed!");
138  break;
139  }
140  }
141 }
142 
143 #define APDS9960_WARNING_CHECK(func, warning) \
144  if (!(func)) { \
145  ESP_LOGW(TAG, warning); \
146  this->status_set_warning(); \
147  return; \
148  }
149 
151  uint8_t status;
152  APDS9960_WARNING_CHECK(this->read_byte(0x93, &status), "Reading status bit failed.");
153  this->status_clear_warning();
154 
155  this->read_color_data_(status);
156  this->read_proximity_data_(status);
157 }
158 
160 
162  if (!this->is_color_enabled_())
163  return;
164 
165  if ((status & 0x01) == 0x00) {
166  // color data not ready yet.
167  return;
168  }
169 
170  uint8_t raw[8];
171  APDS9960_WARNING_CHECK(this->read_bytes(0x94, raw, 8), "Reading color values failed.");
172 
173  uint16_t uint_clear = (uint16_t(raw[1]) << 8) | raw[0];
174  uint16_t uint_red = (uint16_t(raw[3]) << 8) | raw[2];
175  uint16_t uint_green = (uint16_t(raw[5]) << 8) | raw[4];
176  uint16_t uint_blue = (uint16_t(raw[7]) << 8) | raw[6];
177 
178  float clear_perc = (uint_clear / float(UINT16_MAX)) * 100.0f;
179  float red_perc = (uint_red / float(UINT16_MAX)) * 100.0f;
180  float green_perc = (uint_green / float(UINT16_MAX)) * 100.0f;
181  float blue_perc = (uint_blue / float(UINT16_MAX)) * 100.0f;
182 
183  ESP_LOGD(TAG, "Got clear=%.1f%% red=%.1f%% green=%.1f%% blue=%.1f%%", clear_perc, red_perc, green_perc, blue_perc);
184  if (this->clear_channel_ != nullptr)
185  this->clear_channel_->publish_state(clear_perc);
186  if (this->red_channel_ != nullptr)
187  this->red_channel_->publish_state(red_perc);
188  if (this->green_channel_ != nullptr)
189  this->green_channel_->publish_state(green_perc);
190  if (this->blue_channel_ != nullptr)
191  this->blue_channel_->publish_state(blue_perc);
192 }
194  if (this->proximity_ == nullptr)
195  return;
196 
197  if ((status & 0b10) == 0x00) {
198  // proximity data not ready yet.
199  return;
200  }
201 
202  uint8_t prox;
203  APDS9960_WARNING_CHECK(this->read_byte(0x9C, &prox), "Reading proximity values failed.");
204 
205  float prox_perc = (prox / float(UINT8_MAX)) * 100.0f;
206  ESP_LOGD(TAG, "Got proximity=%.1f%%", prox_perc);
207  this->proximity_->publish_state(prox_perc);
208 }
210  if (!this->is_gesture_enabled_())
211  return;
212 
213  uint8_t status;
214  APDS9960_WARNING_CHECK(this->read_byte(0xAF, &status), "Reading gesture status failed.");
215 
216  if ((status & 0b01) == 0) {
217  // GVALID is false
218  return;
219  }
220 
221  if ((status & 0b10) == 0b10) {
222  ESP_LOGV(TAG, "FIFO buffer has filled to capacity!");
223  }
224 
225  uint8_t fifo_level;
226  APDS9960_WARNING_CHECK(this->read_byte(0xAE, &fifo_level), "Reading FIFO level failed.");
227  if (fifo_level == 0) {
228  // no data to process
229  return;
230  }
231 
232  APDS9960_WARNING_CHECK(fifo_level <= 32, "FIFO level has invalid value.")
233 
234  uint8_t buf[128];
235  for (uint8_t pos = 0; pos < fifo_level * 4; pos += 32) {
236  // The ESP's i2c driver has a limited buffer size.
237  // This way of retrieving the data should be wrong according to the datasheet
238  // but it seems to work.
239  uint8_t read = std::min(32, fifo_level * 4 - pos);
240  APDS9960_WARNING_CHECK(this->read_bytes(0xFC + pos, buf + pos, read), "Reading FIFO buffer failed.");
241  }
242 
243  if (millis() - this->gesture_start_ > 500) {
244  this->gesture_up_started_ = false;
245  this->gesture_down_started_ = false;
246  this->gesture_left_started_ = false;
247  this->gesture_right_started_ = false;
248  }
249 
250  for (uint32_t i = 0; i < fifo_level * 4; i += 4) {
251  const int up = buf[i + 0]; // NOLINT
252  const int down = buf[i + 1];
253  const int left = buf[i + 2];
254  const int right = buf[i + 3];
255  this->process_dataset_(up, down, left, right);
256  }
257 }
258 void APDS9960::report_gesture_(int gesture) {
260  switch (gesture) {
261  case 1:
262  bin = this->up_direction_;
263  this->gesture_up_started_ = false;
264  this->gesture_down_started_ = false;
265  ESP_LOGD(TAG, "Got gesture UP");
266  break;
267  case 2:
268  bin = this->down_direction_;
269  this->gesture_up_started_ = false;
270  this->gesture_down_started_ = false;
271  ESP_LOGD(TAG, "Got gesture DOWN");
272  break;
273  case 3:
274  bin = this->left_direction_;
275  this->gesture_left_started_ = false;
276  this->gesture_right_started_ = false;
277  ESP_LOGD(TAG, "Got gesture LEFT");
278  break;
279  case 4:
280  bin = this->right_direction_;
281  this->gesture_left_started_ = false;
282  this->gesture_right_started_ = false;
283  ESP_LOGD(TAG, "Got gesture RIGHT");
284  break;
285  default:
286  return;
287  }
288 
289  if (bin != nullptr) {
290  bin->publish_state(true);
291  bin->publish_state(false);
292  }
293 }
294 void APDS9960::process_dataset_(int up, int down, int left, int right) {
295  /* Algorithm: (see Figure 11 in datasheet)
296  *
297  * Observation: When a gesture is started, we will see a short amount of time where
298  * the photodiode in the direction of the motion has a much higher count value
299  * than where the gesture originates.
300  *
301  * In this algorithm we continually check the difference between the count values of opposing
302  * directions. For example in the down/up direction we continually look at the difference of the
303  * up count and down count. When DOWN gesture begins, this difference will be positive with a
304  * high magnitude for a short amount of time (magic value here is the difference is at least 13).
305  *
306  * If we see such a pattern, we store that we saw the first part of a gesture (the leading edge).
307  * After that some time can pass during which the difference is zero again (though the count values
308  * are not zero). At the end of a gesture, we will see this difference go into the opposite direction
309  * for a short period of time.
310  *
311  * If a gesture is not ended within 500 milliseconds, we consider the initial trailing edge invalid
312  * and reset the state.
313  *
314  * This algorithm does work, but not too well. Some good signal processing algorithms could
315  * probably improve this a lot, especially since the incoming signal has such a characteristic
316  * and quite noise-free pattern.
317  */
318  const int up_down_delta = up - down;
319  const int left_right_delta = left - right;
320  const bool up_down_significant = abs(up_down_delta) > 13;
321  const bool left_right_significant = abs(left_right_delta) > 13;
322 
323  if (up_down_significant) {
324  if (up_down_delta < 0) {
325  if (this->gesture_up_started_) {
326  // trailing edge of gesture up
327  this->report_gesture_(1); // UP
328  } else {
329  // leading edge of gesture down
330  this->gesture_down_started_ = true;
331  this->gesture_start_ = millis();
332  }
333  } else {
334  if (this->gesture_down_started_) {
335  // trailing edge of gesture down
336  this->report_gesture_(2); // DOWN
337  } else {
338  // leading edge of gesture up
339  this->gesture_up_started_ = true;
340  this->gesture_start_ = millis();
341  }
342  }
343  }
344 
345  if (left_right_significant) {
346  if (left_right_delta < 0) {
347  if (this->gesture_left_started_) {
348  // trailing edge of gesture left
349  this->report_gesture_(3); // LEFT
350  } else {
351  // leading edge of gesture right
352  this->gesture_right_started_ = true;
353  this->gesture_start_ = millis();
354  }
355  } else {
356  if (this->gesture_right_started_) {
357  // trailing edge of gesture right
358  this->report_gesture_(4); // RIGHT
359  } else {
360  // leading edge of gesture left
361  this->gesture_left_started_ = true;
362  this->gesture_start_ = millis();
363  }
364  }
365  }
366 }
368 bool APDS9960::is_proximity_enabled_() const { return this->proximity_ != nullptr || this->is_gesture_enabled_(); }
370  return this->up_direction_ != nullptr || this->left_direction_ != nullptr || this->down_direction_ != nullptr ||
371  this->right_direction_ != nullptr;
372 }
373 
374 } // namespace apds9960
375 } // namespace esphome
bool read_byte(uint8_t a_register, uint8_t *data, bool stop=true)
Definition: i2c.h:96
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:18
uint8_t raw[35]
Definition: bl0939.h:19
binary_sensor::BinarySensor * down_direction_
Definition: apds9960.h:59
bool is_color_enabled_() const
Definition: apds9960.cpp:118
ErrorCode read(uint8_t *data, size_t len)
Definition: i2c.h:48
sensor::Sensor * blue_channel_
Definition: apds9960.h:55
sensor::Sensor * red_channel_
Definition: apds9960.h:53
bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len)
Definition: i2c.h:68
T id(T value)
Helper function to make id(var) known from lambdas work in custom components.
Definition: helpers.h:641
void read_color_data_(uint8_t status)
Definition: apds9960.cpp:161
void process_dataset_(int up, int down, int left, int right)
Definition: apds9960.cpp:294
void dump_config() override
Definition: apds9960.cpp:123
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:26
void read_proximity_data_(uint8_t status)
Definition: apds9960.cpp:193
void status_clear_warning()
Definition: component.cpp:149
void setup() override
Definition: apds9960.cpp:17
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:72
void publish_state(bool state)
Publish a new state to the front-end.
sensor::Sensor * clear_channel_
Definition: apds9960.h:56
void report_gesture_(int gesture)
Definition: apds9960.cpp:258
binary_sensor::BinarySensor * right_direction_
Definition: apds9960.h:58
float get_setup_priority() const override
Definition: apds9960.cpp:367
uint8_t status
Definition: bl0942.h:23
void update() override
Definition: apds9960.cpp:150
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:112
Definition: a4988.cpp:4
Base class for all binary_sensor-type classes.
Definition: binary_sensor.h:28
uint32_t val
Definition: datatypes.h:85
bool is_gesture_enabled_() const
Definition: apds9960.cpp:369
binary_sensor::BinarySensor * up_direction_
Definition: apds9960.h:57
bool is_proximity_enabled_() const
Definition: apds9960.cpp:368
binary_sensor::BinarySensor * left_direction_
Definition: apds9960.h:60
sensor::Sensor * proximity_
Definition: apds9960.h:61
sensor::Sensor * green_channel_
Definition: apds9960.h:54