ESPHome  2022.8.0
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  uint8_t led_drive = 0; // led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA
50  val |= (led_drive & 0b11) << 6;
51 
52  val &= 0b11110011;
53  uint8_t proximity_gain = 2; // proximity gain, 0 -> 1x, 1 -> 2X, 2 -> 4X, 4 -> 8X
54  val |= (proximity_gain & 0b11) << 2;
55 
56  val &= 0b11111100;
57  uint8_t ambient_gain = 1; // ambient light gain, 0 -> 1x, 1 -> 4x, 2 -> 16x, 3 -> 64x
58  val |= (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  uint8_t gesture_gain = 2; // gesture gain, 0 -> 1x, 1 -> 2x, 2 -> 4x, 3 -> 8x
79  val |= (gesture_gain & 0b11) << 5;
80 
81  val &= 0b11100111;
82  uint8_t gesture_led_drive = 0; // gesture led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA
83  val |= (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  uint8_t gesture_wait_time = 1; // gesture wait time
90  val |= (gesture_wait_time & 0b111) << 0;
91  APDS9960_WRITE_BYTE(0xA3, val);
92 
93  // GOffsetU (0xA4) -> 0x00 (no offset)
94  APDS9960_WRITE_BYTE(0xA4, 0x00);
95  // GOffsetD (0xA5) -> 0x00 (no offset)
96  APDS9960_WRITE_BYTE(0xA5, 0x00);
97  // GOffsetL (0xA7) -> 0x00 (no offset)
98  APDS9960_WRITE_BYTE(0xA7, 0x00);
99  // GOffsetR (0xA9) -> 0x00 (no offset)
100  APDS9960_WRITE_BYTE(0xA9, 0x00);
101  // GPulse (0xA6) -> 0xC9 (32 ┬Ás, 10 pulses)
102  APDS9960_WRITE_BYTE(0xA6, 0xC9);
103 
104  // GConf 3 (0xAA, gesture config 3) -> 0x00 (all photodiodes active during gesture, all gesture dimensions enabled)
105  // 0x00 -> all dimensions, 0x01 -> up down, 0x02 -> left right
106  APDS9960_WRITE_BYTE(0xAA, 0x00);
107 
108  // Enable (0x80) ->
109  val = 0;
110  val |= (0b1) << 0; // power on
111  val |= (this->is_color_enabled_() & 0b1) << 1;
112  val |= (this->is_proximity_enabled_() & 0b1) << 2;
113  val |= 0b0 << 3; // wait timer disabled
114  val |= 0b0 << 4; // color interrupt disabled
115  val |= 0b0 << 5; // proximity interrupt disabled
116  val |= (this->is_gesture_enabled_() & 0b1) << 6; // proximity is required for gestures
117  APDS9960_WRITE_BYTE(0x80, val);
118 }
120  return this->red_channel_ != nullptr || this->green_channel_ != nullptr || this->blue_channel_ != nullptr ||
121  this->clear_channel_ != nullptr;
122 }
123 
125  ESP_LOGCONFIG(TAG, "APDS9960:");
126  LOG_I2C_DEVICE(this);
127 
128  LOG_UPDATE_INTERVAL(this);
129  if (this->is_failed()) {
130  switch (this->error_code_) {
132  ESP_LOGE(TAG, "Communication with APDS9960 failed!");
133  break;
134  case WRONG_ID:
135  ESP_LOGE(TAG, "APDS9960 has invalid id!");
136  break;
137  default:
138  ESP_LOGE(TAG, "Setting up APDS9960 registers failed!");
139  break;
140  }
141  }
142 }
143 
144 #define APDS9960_WARNING_CHECK(func, warning) \
145  if (!(func)) { \
146  ESP_LOGW(TAG, warning); \
147  this->status_set_warning(); \
148  return; \
149  }
150 
152  uint8_t status;
153  APDS9960_WARNING_CHECK(this->read_byte(0x93, &status), "Reading status bit failed.");
154  this->status_clear_warning();
155 
156  this->read_color_data_(status);
157  this->read_proximity_data_(status);
158 }
159 
161 
162 void APDS9960::read_color_data_(uint8_t status) {
163  if (!this->is_color_enabled_())
164  return;
165 
166  if ((status & 0x01) == 0x00) {
167  // color data not ready yet.
168  return;
169  }
170 
171  uint8_t raw[8];
172  APDS9960_WARNING_CHECK(this->read_bytes(0x94, raw, 8), "Reading color values failed.");
173 
174  uint16_t uint_clear = (uint16_t(raw[1]) << 8) | raw[0];
175  uint16_t uint_red = (uint16_t(raw[3]) << 8) | raw[2];
176  uint16_t uint_green = (uint16_t(raw[5]) << 8) | raw[4];
177  uint16_t uint_blue = (uint16_t(raw[7]) << 8) | raw[6];
178 
179  float clear_perc = (uint_clear / float(UINT16_MAX)) * 100.0f;
180  float red_perc = (uint_red / float(UINT16_MAX)) * 100.0f;
181  float green_perc = (uint_green / float(UINT16_MAX)) * 100.0f;
182  float blue_perc = (uint_blue / float(UINT16_MAX)) * 100.0f;
183 
184  ESP_LOGD(TAG, "Got clear=%.1f%% red=%.1f%% green=%.1f%% blue=%.1f%%", clear_perc, red_perc, green_perc, blue_perc);
185  if (this->clear_channel_ != nullptr)
186  this->clear_channel_->publish_state(clear_perc);
187  if (this->red_channel_ != nullptr)
188  this->red_channel_->publish_state(red_perc);
189  if (this->green_channel_ != nullptr)
190  this->green_channel_->publish_state(green_perc);
191  if (this->blue_channel_ != nullptr)
192  this->blue_channel_->publish_state(blue_perc);
193 }
194 void APDS9960::read_proximity_data_(uint8_t status) {
195  if (this->proximity_ == nullptr)
196  return;
197 
198  if ((status & 0b10) == 0x00) {
199  // proximity data not ready yet.
200  return;
201  }
202 
203  uint8_t prox;
204  APDS9960_WARNING_CHECK(this->read_byte(0x9C, &prox), "Reading proximity values failed.");
205 
206  float prox_perc = (prox / float(UINT8_MAX)) * 100.0f;
207  ESP_LOGD(TAG, "Got proximity=%.1f%%", prox_perc);
208  this->proximity_->publish_state(prox_perc);
209 }
211  if (!this->is_gesture_enabled_())
212  return;
213 
214  uint8_t status;
215  APDS9960_WARNING_CHECK(this->read_byte(0xAF, &status), "Reading gesture status failed.");
216 
217  if ((status & 0b01) == 0) {
218  // GVALID is false
219  return;
220  }
221 
222  if ((status & 0b10) == 0b10) {
223  ESP_LOGV(TAG, "FIFO buffer has filled to capacity!");
224  }
225 
226  uint8_t fifo_level;
227  APDS9960_WARNING_CHECK(this->read_byte(0xAE, &fifo_level), "Reading FIFO level failed.");
228  if (fifo_level == 0) {
229  // no data to process
230  return;
231  }
232 
233  APDS9960_WARNING_CHECK(fifo_level <= 32, "FIFO level has invalid value.")
234 
235  uint8_t buf[128];
236  for (uint8_t pos = 0; pos < fifo_level * 4; pos += 32) {
237  // The ESP's i2c driver has a limited buffer size.
238  // This way of retrieving the data should be wrong according to the datasheet
239  // but it seems to work.
240  uint8_t read = std::min(32, fifo_level * 4 - pos);
241  APDS9960_WARNING_CHECK(this->read_bytes(0xFC + pos, buf + pos, read), "Reading FIFO buffer failed.");
242  }
243 
244  if (millis() - this->gesture_start_ > 500) {
245  this->gesture_up_started_ = false;
246  this->gesture_down_started_ = false;
247  this->gesture_left_started_ = false;
248  this->gesture_right_started_ = false;
249  }
250 
251  for (uint32_t i = 0; i < fifo_level * 4; i += 4) {
252  const int up = buf[i + 0]; // NOLINT
253  const int down = buf[i + 1];
254  const int left = buf[i + 2];
255  const int right = buf[i + 3];
256  this->process_dataset_(up, down, left, right);
257  }
258 }
259 void APDS9960::report_gesture_(int gesture) {
261  switch (gesture) {
262  case 1:
263  bin = this->up_direction_;
264  this->gesture_up_started_ = false;
265  this->gesture_down_started_ = false;
266  ESP_LOGD(TAG, "Got gesture UP");
267  break;
268  case 2:
269  bin = this->down_direction_;
270  this->gesture_up_started_ = false;
271  this->gesture_down_started_ = false;
272  ESP_LOGD(TAG, "Got gesture DOWN");
273  break;
274  case 3:
275  bin = this->left_direction_;
276  this->gesture_left_started_ = false;
277  this->gesture_right_started_ = false;
278  ESP_LOGD(TAG, "Got gesture LEFT");
279  break;
280  case 4:
281  bin = this->right_direction_;
282  this->gesture_left_started_ = false;
283  this->gesture_right_started_ = false;
284  ESP_LOGD(TAG, "Got gesture RIGHT");
285  break;
286  default:
287  return;
288  }
289 
290  if (bin != nullptr) {
291  bin->publish_state(true);
292  bin->publish_state(false);
293  }
294 }
295 void APDS9960::process_dataset_(int up, int down, int left, int right) {
296  /* Algorithm: (see Figure 11 in datasheet)
297  *
298  * Observation: When a gesture is started, we will see a short amount of time where
299  * the photodiode in the direction of the motion has a much higher count value
300  * than where the gesture originates.
301  *
302  * In this algorithm we continually check the difference between the count values of opposing
303  * directions. For example in the down/up direction we continually look at the difference of the
304  * up count and down count. When DOWN gesture begins, this difference will be positive with a
305  * high magnitude for a short amount of time (magic value here is the difference is at least 13).
306  *
307  * If we see such a pattern, we store that we saw the first part of a gesture (the leading edge).
308  * After that some time can pass during which the difference is zero again (though the count values
309  * are not zero). At the end of a gesture, we will see this difference go into the opposite direction
310  * for a short period of time.
311  *
312  * If a gesture is not ended within 500 milliseconds, we consider the initial trailing edge invalid
313  * and reset the state.
314  *
315  * This algorithm does work, but not too well. Some good signal processing algorithms could
316  * probably improve this a lot, especially since the incoming signal has such a characteristic
317  * and quite noise-free pattern.
318  */
319  const int up_down_delta = up - down;
320  const int left_right_delta = left - right;
321  const bool up_down_significant = abs(up_down_delta) > 13;
322  const bool left_right_significant = abs(left_right_delta) > 13;
323 
324  if (up_down_significant) {
325  if (up_down_delta < 0) {
326  if (this->gesture_up_started_) {
327  // trailing edge of gesture up
328  this->report_gesture_(1); // UP
329  } else {
330  // leading edge of gesture down
331  this->gesture_down_started_ = true;
332  this->gesture_start_ = millis();
333  }
334  } else {
335  if (this->gesture_down_started_) {
336  // trailing edge of gesture down
337  this->report_gesture_(2); // DOWN
338  } else {
339  // leading edge of gesture up
340  this->gesture_up_started_ = true;
341  this->gesture_start_ = millis();
342  }
343  }
344  }
345 
346  if (left_right_significant) {
347  if (left_right_delta < 0) {
348  if (this->gesture_left_started_) {
349  // trailing edge of gesture left
350  this->report_gesture_(3); // LEFT
351  } else {
352  // leading edge of gesture right
353  this->gesture_right_started_ = true;
354  this->gesture_start_ = millis();
355  }
356  } else {
357  if (this->gesture_right_started_) {
358  // trailing edge of gesture right
359  this->report_gesture_(4); // RIGHT
360  } else {
361  // leading edge of gesture left
362  this->gesture_left_started_ = true;
363  this->gesture_start_ = millis();
364  }
365  }
366  }
367 }
369 bool APDS9960::is_proximity_enabled_() const { return this->proximity_ != nullptr || this->is_gesture_enabled_(); }
371  return this->up_direction_ != nullptr || this->left_direction_ != nullptr || this->down_direction_ != nullptr ||
372  this->right_direction_ != nullptr;
373 }
374 
375 } // namespace apds9960
376 } // 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:45
bool is_color_enabled_() const
Definition: apds9960.cpp:119
ErrorCode read(uint8_t *data, size_t len)
Definition: i2c.h:48
sensor::Sensor * blue_channel_
Definition: apds9960.h:41
sensor::Sensor * red_channel_
Definition: apds9960.h:39
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:638
void read_color_data_(uint8_t status)
Definition: apds9960.cpp:162
void process_dataset_(int up, int down, int left, int right)
Definition: apds9960.cpp:295
void dump_config() override
Definition: apds9960.cpp:124
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:26
void read_proximity_data_(uint8_t status)
Definition: apds9960.cpp:194
void status_clear_warning()
Definition: component.cpp:148
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:42
void report_gesture_(int gesture)
Definition: apds9960.cpp:259
binary_sensor::BinarySensor * right_direction_
Definition: apds9960.h:44
float get_setup_priority() const override
Definition: apds9960.cpp:368
void update() override
Definition: apds9960.cpp:151
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:111
Definition: a4988.cpp:4
Base class for all binary_sensor-type classes.
Definition: binary_sensor.h:26
uint32_t val
Definition: datatypes.h:85
bool is_gesture_enabled_() const
Definition: apds9960.cpp:370
binary_sensor::BinarySensor * up_direction_
Definition: apds9960.h:43
bool is_proximity_enabled_() const
Definition: apds9960.cpp:369
binary_sensor::BinarySensor * left_direction_
Definition: apds9960.h:46
sensor::Sensor * proximity_
Definition: apds9960.h:47
sensor::Sensor * green_channel_
Definition: apds9960.h:40