mirror of
https://github.com/esphome/esphome.git
synced 2025-02-21 20:38:16 +00:00
* Socket refactor and SSL * esp-idf temp * Fixes * Echo component and noise * Add noise API transport support * Updates * ESP-IDF * Complete * Fixes * Fixes * Versions update * New i2c APIs * Complete i2c refactor * SPI migration * Revert ESP Preferences migration, too complex for now * OTA support * Remove echo again * Remove ssl again * GPIOFlags updates * Rename esphal and ICACHE_RAM_ATTR * Make ESP32 arduino compilable again * Fix GPIO flags * Complete pin registry refactor and fixes * Fixes to make test1 compile * Remove sdkconfig file * Ignore sdkconfig file * Fixes in reviewing * Make test2 compile * Make test4 compile * Make test5 compile * Run clang-format * Fix lint errors * Use esp-idf APIs instead of btStart * Another round of fixes * Start implementing ESP8266 * Make test3 compile * Guard esp8266 code * Lint * Reformat * Fixes * Fixes v2 * more fixes * ESP-IDF tidy target * Convert ARDUINO_ARCH_ESPxx * Update WiFiSignalSensor * Update time ifdefs * OTA needs millis from hal * RestartSwitch needs delay from hal * ESP-IDF Uart * Fix OTA blank password * Allow setting sdkconfig * Fix idf partitions and allow setting sdkconfig from yaml * Re-add read/write compat APIs and fix esp8266 uart * Fix esp8266 store log strings in flash * Fix ESP32 arduino preferences not initialized * Update ifdefs * Change how sdkconfig change is detected * Add checks to ci-custom and fix them * Run clang-format * Add esp-idf clang-tidy target and fix errors * Fixes from clang-tidy idf round 2 * Fixes from compiling tests with esp-idf * Run clang-format * Switch test5.yaml to esp-idf * Implement ESP8266 Preferences * Lint * Re-do PIO package version selection a bit * Fix arduinoespressif32 package version * Fix unit tests * Lint * Lint fixes * Fix readv/writev not defined * Fix graphing component * Re-add all old options from core/config.py Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
376 lines
13 KiB
C++
376 lines
13 KiB
C++
#include "apds9960.h"
|
|
#include "esphome/core/log.h"
|
|
#include "esphome/core/hal.h"
|
|
|
|
namespace esphome {
|
|
namespace apds9960 {
|
|
|
|
static const char *const TAG = "apds9960";
|
|
|
|
#define APDS9960_ERROR_CHECK(func) \
|
|
if (!(func)) { \
|
|
this->mark_failed(); \
|
|
return; \
|
|
}
|
|
#define APDS9960_WRITE_BYTE(reg, value) APDS9960_ERROR_CHECK(this->write_byte(reg, value));
|
|
|
|
void APDS9960::setup() {
|
|
ESP_LOGCONFIG(TAG, "Setting up APDS9960...");
|
|
uint8_t id;
|
|
if (!this->read_byte(0x92, &id)) { // ID register
|
|
this->error_code_ = COMMUNICATION_FAILED;
|
|
this->mark_failed();
|
|
return;
|
|
}
|
|
|
|
if (id != 0xAB && id != 0x9C) { // APDS9960 all should have one of these IDs
|
|
this->error_code_ = WRONG_ID;
|
|
this->mark_failed();
|
|
return;
|
|
}
|
|
|
|
// ATime (ADC integration time, 2.78ms increments, 0x81) -> 0xDB (103ms)
|
|
APDS9960_WRITE_BYTE(0x81, 0xDB);
|
|
// WTime (Wait time, 0x83) -> 0xF6 (27ms)
|
|
APDS9960_WRITE_BYTE(0x83, 0xF6);
|
|
// PPulse (0x8E) -> 0x87 (16us, 8 pulses)
|
|
APDS9960_WRITE_BYTE(0x8E, 0x87);
|
|
// POffset UR (0x9D) -> 0 (no offset)
|
|
APDS9960_WRITE_BYTE(0x9D, 0x00);
|
|
// POffset DL (0x9E) -> 0 (no offset)
|
|
APDS9960_WRITE_BYTE(0x9E, 0x00);
|
|
// Config 1 (0x8D) -> 0x60 (no wtime factor)
|
|
APDS9960_WRITE_BYTE(0x8D, 0x60);
|
|
|
|
// Control (0x8F) ->
|
|
uint8_t val = 0;
|
|
APDS9960_ERROR_CHECK(this->read_byte(0x8F, &val));
|
|
val &= 0b00111111;
|
|
uint8_t led_drive = 0; // led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA
|
|
val |= (led_drive & 0b11) << 6;
|
|
|
|
val &= 0b11110011;
|
|
uint8_t proximity_gain = 2; // proximity gain, 0 -> 1x, 1 -> 2X, 2 -> 4X, 4 -> 8X
|
|
val |= (proximity_gain & 0b11) << 2;
|
|
|
|
val &= 0b11111100;
|
|
uint8_t ambient_gain = 1; // ambient light gain, 0 -> 1x, 1 -> 4x, 2 -> 16x, 3 -> 64x
|
|
val |= (ambient_gain & 0b11) << 0;
|
|
APDS9960_WRITE_BYTE(0x8F, val);
|
|
|
|
// Pers (0x8C) -> 0x11 (2 consecutive proximity or ALS for interrupt)
|
|
APDS9960_WRITE_BYTE(0x8C, 0x11);
|
|
// Config 2 (0x90) -> 0x01 (no saturation interrupts or LED boost)
|
|
APDS9960_WRITE_BYTE(0x90, 0x01);
|
|
// Config 3 (0x9F) -> 0x00 (enable all photodiodes, no SAI)
|
|
APDS9960_WRITE_BYTE(0x9F, 0x00);
|
|
// GPenTh (0xA0, gesture enter threshold) -> 0x28 (also 0x32)
|
|
APDS9960_WRITE_BYTE(0xA0, 0x28);
|
|
// GPexTh (0xA1, gesture exit threshold) -> 0x1E
|
|
APDS9960_WRITE_BYTE(0xA1, 0x1E);
|
|
|
|
// GConf 1 (0xA2, gesture config 1) -> 0x40 (4 gesture events for interrupt (GFIFO 3), 1 for exit)
|
|
APDS9960_WRITE_BYTE(0xA2, 0x40);
|
|
|
|
// GConf 2 (0xA3, gesture config 2) ->
|
|
APDS9960_ERROR_CHECK(this->read_byte(0xA3, &val));
|
|
val &= 0b10011111;
|
|
uint8_t gesture_gain = 2; // gesture gain, 0 -> 1x, 1 -> 2x, 2 -> 4x, 3 -> 8x
|
|
val |= (gesture_gain & 0b11) << 5;
|
|
|
|
val &= 0b11100111;
|
|
uint8_t gesture_led_drive = 0; // gesture led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA
|
|
val |= (gesture_led_drive & 0b11) << 3;
|
|
|
|
val &= 0b11111000;
|
|
// gesture wait time
|
|
// 0 -> 0ms, 1 -> 2.8ms, 2 -> 5.6ms, 3 -> 8.4ms
|
|
// 4 -> 14.0ms, 5 -> 22.4 ms, 6 -> 30.8ms, 7 -> 39.2 ms
|
|
uint8_t gesture_wait_time = 1; // gesture wait time
|
|
val |= (gesture_wait_time & 0b111) << 0;
|
|
APDS9960_WRITE_BYTE(0xA3, val);
|
|
|
|
// GOffsetU (0xA4) -> 0x00 (no offset)
|
|
APDS9960_WRITE_BYTE(0xA4, 0x00);
|
|
// GOffsetD (0xA5) -> 0x00 (no offset)
|
|
APDS9960_WRITE_BYTE(0xA5, 0x00);
|
|
// GOffsetL (0xA7) -> 0x00 (no offset)
|
|
APDS9960_WRITE_BYTE(0xA7, 0x00);
|
|
// GOffsetR (0xA9) -> 0x00 (no offset)
|
|
APDS9960_WRITE_BYTE(0xA9, 0x00);
|
|
// GPulse (0xA6) -> 0xC9 (32 µs, 10 pulses)
|
|
APDS9960_WRITE_BYTE(0xA6, 0xC9);
|
|
|
|
// GConf 3 (0xAA, gesture config 3) -> 0x00 (all photodiodes active during gesture, all gesture dimensions enabled)
|
|
// 0x00 -> all dimensions, 0x01 -> up down, 0x02 -> left right
|
|
APDS9960_WRITE_BYTE(0xAA, 0x00);
|
|
|
|
// Enable (0x80) ->
|
|
val = 0;
|
|
val |= (0b1) << 0; // power on
|
|
val |= (this->is_color_enabled_() & 0b1) << 1;
|
|
val |= (this->is_proximity_enabled_() & 0b1) << 2;
|
|
val |= 0b0 << 3; // wait timer disabled
|
|
val |= 0b0 << 4; // color interrupt disabled
|
|
val |= 0b0 << 5; // proximity interrupt disabled
|
|
val |= (this->is_gesture_enabled_() & 0b1) << 6; // proximity is required for gestures
|
|
APDS9960_WRITE_BYTE(0x80, val);
|
|
}
|
|
bool APDS9960::is_color_enabled_() const {
|
|
return this->red_channel_ != nullptr || this->green_channel_ != nullptr || this->blue_channel_ != nullptr ||
|
|
this->clear_channel_ != nullptr;
|
|
}
|
|
|
|
void APDS9960::dump_config() {
|
|
ESP_LOGCONFIG(TAG, "APDS9960:");
|
|
LOG_I2C_DEVICE(this);
|
|
|
|
LOG_UPDATE_INTERVAL(this);
|
|
if (this->is_failed()) {
|
|
switch (this->error_code_) {
|
|
case COMMUNICATION_FAILED:
|
|
ESP_LOGE(TAG, "Communication with APDS9960 failed!");
|
|
break;
|
|
case WRONG_ID:
|
|
ESP_LOGE(TAG, "APDS9960 has invalid id!");
|
|
break;
|
|
default:
|
|
ESP_LOGE(TAG, "Setting up APDS9960 registers failed!");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define APDS9960_WARNING_CHECK(func, warning) \
|
|
if (!(func)) { \
|
|
ESP_LOGW(TAG, warning); \
|
|
this->status_set_warning(); \
|
|
return; \
|
|
}
|
|
|
|
void APDS9960::update() {
|
|
uint8_t status;
|
|
APDS9960_WARNING_CHECK(this->read_byte(0x93, &status), "Reading status bit failed.");
|
|
this->status_clear_warning();
|
|
|
|
this->read_color_data_(status);
|
|
this->read_proximity_data_(status);
|
|
}
|
|
|
|
void APDS9960::loop() { this->read_gesture_data_(); }
|
|
|
|
void APDS9960::read_color_data_(uint8_t status) {
|
|
if (!this->is_color_enabled_())
|
|
return;
|
|
|
|
if ((status & 0x01) == 0x00) {
|
|
// color data not ready yet.
|
|
return;
|
|
}
|
|
|
|
uint8_t raw[8];
|
|
APDS9960_WARNING_CHECK(this->read_bytes(0x94, raw, 8), "Reading color values failed.");
|
|
|
|
uint16_t uint_clear = (uint16_t(raw[1]) << 8) | raw[0];
|
|
uint16_t uint_red = (uint16_t(raw[3]) << 8) | raw[2];
|
|
uint16_t uint_green = (uint16_t(raw[5]) << 8) | raw[4];
|
|
uint16_t uint_blue = (uint16_t(raw[7]) << 8) | raw[6];
|
|
|
|
float clear_perc = (uint_clear / float(UINT16_MAX)) * 100.0f;
|
|
float red_perc = (uint_red / float(UINT16_MAX)) * 100.0f;
|
|
float green_perc = (uint_green / float(UINT16_MAX)) * 100.0f;
|
|
float blue_perc = (uint_blue / float(UINT16_MAX)) * 100.0f;
|
|
|
|
ESP_LOGD(TAG, "Got clear=%.1f%% red=%.1f%% green=%.1f%% blue=%.1f%%", clear_perc, red_perc, green_perc, blue_perc);
|
|
if (this->clear_channel_ != nullptr)
|
|
this->clear_channel_->publish_state(clear_perc);
|
|
if (this->red_channel_ != nullptr)
|
|
this->red_channel_->publish_state(red_perc);
|
|
if (this->green_channel_ != nullptr)
|
|
this->green_channel_->publish_state(green_perc);
|
|
if (this->blue_channel_ != nullptr)
|
|
this->blue_channel_->publish_state(blue_perc);
|
|
}
|
|
void APDS9960::read_proximity_data_(uint8_t status) {
|
|
if (this->proximity_ == nullptr)
|
|
return;
|
|
|
|
if ((status & 0b10) == 0x00) {
|
|
// proximity data not ready yet.
|
|
return;
|
|
}
|
|
|
|
uint8_t prox;
|
|
APDS9960_WARNING_CHECK(this->read_byte(0x9C, &prox), "Reading proximity values failed.");
|
|
|
|
float prox_perc = (prox / float(UINT8_MAX)) * 100.0f;
|
|
ESP_LOGD(TAG, "Got proximity=%.1f%%", prox_perc);
|
|
this->proximity_->publish_state(prox_perc);
|
|
}
|
|
void APDS9960::read_gesture_data_() {
|
|
if (!this->is_gesture_enabled_())
|
|
return;
|
|
|
|
uint8_t status;
|
|
APDS9960_WARNING_CHECK(this->read_byte(0xAF, &status), "Reading gesture status failed.");
|
|
|
|
if ((status & 0b01) == 0) {
|
|
// GVALID is false
|
|
return;
|
|
}
|
|
|
|
if ((status & 0b10) == 0b10) {
|
|
ESP_LOGV(TAG, "FIFO buffer has filled to capacity!");
|
|
}
|
|
|
|
uint8_t fifo_level;
|
|
APDS9960_WARNING_CHECK(this->read_byte(0xAE, &fifo_level), "Reading FIFO level failed.");
|
|
if (fifo_level == 0)
|
|
// no data to process
|
|
return;
|
|
|
|
APDS9960_WARNING_CHECK(fifo_level <= 32, "FIFO level has invalid value.")
|
|
|
|
uint8_t buf[128];
|
|
for (uint8_t pos = 0; pos < fifo_level * 4; pos += 32) {
|
|
// The ESP's i2c driver has a limited buffer size.
|
|
// This way of retrieving the data should be wrong according to the datasheet
|
|
// but it seems to work.
|
|
uint8_t read = std::min(32, fifo_level * 4 - pos);
|
|
APDS9960_WARNING_CHECK(this->read_bytes(0xFC + pos, buf + pos, read), "Reading FIFO buffer failed.");
|
|
}
|
|
|
|
if (millis() - this->gesture_start_ > 500) {
|
|
this->gesture_up_started_ = false;
|
|
this->gesture_down_started_ = false;
|
|
this->gesture_left_started_ = false;
|
|
this->gesture_right_started_ = false;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < fifo_level * 4; i += 4) {
|
|
const int up = buf[i + 0]; // NOLINT
|
|
const int down = buf[i + 1];
|
|
const int left = buf[i + 2];
|
|
const int right = buf[i + 3];
|
|
this->process_dataset_(up, down, left, right);
|
|
}
|
|
}
|
|
void APDS9960::report_gesture_(int gesture) {
|
|
binary_sensor::BinarySensor *bin;
|
|
switch (gesture) {
|
|
case 1:
|
|
bin = this->up_direction_;
|
|
this->gesture_up_started_ = false;
|
|
this->gesture_down_started_ = false;
|
|
ESP_LOGD(TAG, "Got gesture UP");
|
|
break;
|
|
case 2:
|
|
bin = this->down_direction_;
|
|
this->gesture_up_started_ = false;
|
|
this->gesture_down_started_ = false;
|
|
ESP_LOGD(TAG, "Got gesture DOWN");
|
|
break;
|
|
case 3:
|
|
bin = this->left_direction_;
|
|
this->gesture_left_started_ = false;
|
|
this->gesture_right_started_ = false;
|
|
ESP_LOGD(TAG, "Got gesture LEFT");
|
|
break;
|
|
case 4:
|
|
bin = this->right_direction_;
|
|
this->gesture_left_started_ = false;
|
|
this->gesture_right_started_ = false;
|
|
ESP_LOGD(TAG, "Got gesture RIGHT");
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if (bin != nullptr) {
|
|
bin->publish_state(true);
|
|
bin->publish_state(false);
|
|
}
|
|
}
|
|
void APDS9960::process_dataset_(int up, int down, int left, int right) {
|
|
/* Algorithm: (see Figure 11 in datasheet)
|
|
*
|
|
* Observation: When a gesture is started, we will see a short amount of time where
|
|
* the photodiode in the direction of the motion has a much higher count value
|
|
* than where the gesture originates.
|
|
*
|
|
* In this algorithm we continually check the difference between the count values of opposing
|
|
* directions. For example in the down/up direction we continually look at the difference of the
|
|
* up count and down count. When DOWN gesture begins, this difference will be positive with a
|
|
* high magnitude for a short amount of time (magic value here is the difference is at least 13).
|
|
*
|
|
* If we see such a pattern, we store that we saw the first part of a gesture (the leading edge).
|
|
* After that some time can pass during which the difference is zero again (though the count values
|
|
* are not zero). At the end of a gesture, we will see this difference go into the opposite direction
|
|
* for a short period of time.
|
|
*
|
|
* If a gesture is not ended within 500 milliseconds, we consider the initial trailing edge invalid
|
|
* and reset the state.
|
|
*
|
|
* This algorithm does work, but not too well. Some good signal processing algorithms could
|
|
* probably improve this a lot, especially since the incoming signal has such a characteristic
|
|
* and quite noise-free pattern.
|
|
*/
|
|
const int up_down_delta = up - down;
|
|
const int left_right_delta = left - right;
|
|
const bool up_down_significant = abs(up_down_delta) > 13;
|
|
const bool left_right_significant = abs(left_right_delta) > 13;
|
|
|
|
if (up_down_significant) {
|
|
if (up_down_delta < 0) {
|
|
if (this->gesture_up_started_) {
|
|
// trailing edge of gesture up
|
|
this->report_gesture_(1); // UP
|
|
} else {
|
|
// leading edge of gesture down
|
|
this->gesture_down_started_ = true;
|
|
this->gesture_start_ = millis();
|
|
}
|
|
} else {
|
|
if (this->gesture_down_started_) {
|
|
// trailing edge of gesture down
|
|
this->report_gesture_(2); // DOWN
|
|
} else {
|
|
// leading edge of gesture up
|
|
this->gesture_up_started_ = true;
|
|
this->gesture_start_ = millis();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (left_right_significant) {
|
|
if (left_right_delta < 0) {
|
|
if (this->gesture_left_started_) {
|
|
// trailing edge of gesture left
|
|
this->report_gesture_(3); // LEFT
|
|
} else {
|
|
// leading edge of gesture right
|
|
this->gesture_right_started_ = true;
|
|
this->gesture_start_ = millis();
|
|
}
|
|
} else {
|
|
if (this->gesture_right_started_) {
|
|
// trailing edge of gesture right
|
|
this->report_gesture_(4); // RIGHT
|
|
} else {
|
|
// leading edge of gesture left
|
|
this->gesture_left_started_ = true;
|
|
this->gesture_start_ = millis();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
float APDS9960::get_setup_priority() const { return setup_priority::DATA; }
|
|
bool APDS9960::is_proximity_enabled_() const { return this->proximity_ != nullptr || this->is_gesture_enabled_(); }
|
|
bool APDS9960::is_gesture_enabled_() const {
|
|
return this->up_direction_ != nullptr || this->left_direction_ != nullptr || this->down_direction_ != nullptr ||
|
|
this->right_direction_ != nullptr;
|
|
}
|
|
|
|
} // namespace apds9960
|
|
} // namespace esphome
|