From 17d784bbc5cffa5df47a8219850842356840a2a1 Mon Sep 17 00:00:00 2001 From: Stephen Tierney Date: Mon, 1 Feb 2021 20:43:35 +1100 Subject: [PATCH] Add support for ltr390 --- esphome/components/ltr390/__init__.py | 0 esphome/components/ltr390/ltr390.cpp | 220 ++++++++++++++++++++++++++ esphome/components/ltr390/ltr390.h | 115 ++++++++++++++ esphome/components/ltr390/sensor.py | 75 +++++++++ 4 files changed, 410 insertions(+) create mode 100644 esphome/components/ltr390/__init__.py create mode 100644 esphome/components/ltr390/ltr390.cpp create mode 100644 esphome/components/ltr390/ltr390.h create mode 100644 esphome/components/ltr390/sensor.py diff --git a/esphome/components/ltr390/__init__.py b/esphome/components/ltr390/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/ltr390/ltr390.cpp b/esphome/components/ltr390/ltr390.cpp new file mode 100644 index 0000000000..fef0d30cc0 --- /dev/null +++ b/esphome/components/ltr390/ltr390.cpp @@ -0,0 +1,220 @@ +#include "ltr390.h" +#include "esphome/core/log.h" +#include + + +namespace esphome { +namespace ltr390 { + +static const char *TAG = "ltr390"; + +static const float gain_values_[5] = {1.0, 3.0, 6.0, 9.0, 18.0}; +static const float resolution_values_[6] = {4.0, 2.0, 1.0, 0.5, 0.25, 0.125}; +static const uint32_t mode_addresses_[2] = {0x0D, 0x10}; + +bool LTR390Component::enabled(void) { + std::bitset<8> crtl_value (this->ctrl_reg_->get()); + return (bool)crtl_value[LTR390_CTRL_EN]; +} + +void LTR390Component::enable(bool en) { + std::bitset<8> crtl_value (this->ctrl_reg_->get()); + crtl_value[LTR390_CTRL_EN] = en; + *this->ctrl_reg_ = crtl_value.to_ulong(); +} + +bool LTR390Component::reset(void) { + std::bitset<8> crtl_value (this->ctrl_reg_->get()); + + crtl_value[LTR390_CTRL_RST] = 1; + *this->ctrl_reg_ = crtl_value.to_ulong(); + + delay(10); + // Read after reset + crtl_value = std::bitset<8>(this->ctrl_reg_->get()); + if (crtl_value.to_ulong()) { + return false; + } + + return true; +} + +void LTR390Component::set_mode(ltr390_mode_t mode) { + std::bitset<8> crtl_value (this->ctrl_reg_->get()); + crtl_value[LTR390_CTRL_MODE] = mode; + *this->ctrl_reg_ = crtl_value.to_ulong(); +} + +ltr390_mode_t LTR390Component::get_mode(void) { + std::bitset<8> crtl_value (this->ctrl_reg_->get()); + return (ltr390_mode_t)(int)crtl_value[LTR390_CTRL_MODE]; +} + +void LTR390Component::set_gain(ltr390_gain_t gain) { + *this->gain_reg_ = gain; +} + +ltr390_gain_t LTR390Component::get_gain(void) { + std::bitset<8> gain_value (this->gain_reg_->get()); + return (ltr390_gain_t)gain_value.to_ulong(); +} + +void LTR390Component::set_resolution(ltr390_resolution_t res) { + std::bitset<8> res_value (this->res_reg_->get()); + + std::bitset<3> new_res_value (res); + + for (int i = 0; i < 3; i++) { + res_value[4+i] = new_res_value[i]; + } + + *this->res_reg_ = res_value.to_ulong(); +} + +ltr390_resolution_t LTR390Component::get_resolution(void) { + std::bitset<8> res_value (this->res_reg_->get()); + + std::bitset<3> output_value (0); + for (int i = 0; i < 3; i++) { + output_value[i] = res_value[4+i]; + } + + return (ltr390_resolution_t)output_value.to_ulong(); +} + +bool LTR390Component::new_data_available(void) { + std::bitset<8> status_value (this->status_reg_->get()); + return (bool)status_value[3]; +} + +uint32_t little_endian_bytes_to_int(uint8_t *buffer, uint8_t num_bytes) { + uint32_t value = 0; + + for (int i = 0; i < num_bytes; i++) { + value <<= 8; + value |= buffer[num_bytes - i - 1]; + } + + return value; +} + +uint32_t LTR390Component::read_sensor_data(ltr390_mode_t mode) { + const uint8_t num_bytes = 3; + uint8_t buffer[num_bytes]; + + while (!this->new_data_available()) { + ESP_LOGD(TAG, "WAITING FOR DATA"); + delay(2); + } + + this->read_bytes(mode_addresses_[mode], buffer, num_bytes); + + return little_endian_bytes_to_int(buffer, num_bytes); +} + +void LTR390Component::setup() { + + + ESP_LOGCONFIG(TAG, "Setting up ltr390..."); + + this->ctrl_reg_ = new i2c::I2CRegister(this, LTR390_MAIN_CTRL); + this->status_reg_ = new i2c::I2CRegister(this, LTR390_MAIN_STATUS); + this->gain_reg_ = new i2c::I2CRegister(this, LTR390_GAIN); + this->res_reg_ = new i2c::I2CRegister(this, LTR390_MEAS_RATE); + + this->reset(); + + this->enable(true); + ESP_LOGD(TAG, "%s", this->enabled() ? "ENABLED" : "DISABLED"); + if (!this->enabled()) { + this->mark_failed(); + return; + } + + // Set gain + this->set_gain(this->gain_); + + // Set resolution + this->set_resolution(this->res_); + + // Set sensor read state + this->reading = false; + + // Create a list of modes and corresponding read functions + this->mode_funcs_ = new std::vector > >(); + + // If we need the light sensor then add to the list + if (this->light_sensor_ != nullptr || this->als_sensor_ != nullptr) { + this->mode_funcs_->push_back(std::make_tuple(LTR390_MODE_ALS, std::bind(<R390Component::read_als, this))); + } + + // If we need the UV sensor then add to the list + if (this->uvi_sensor_ != nullptr || this->uv_sensor_ != nullptr) { + this->mode_funcs_->push_back(std::make_tuple(LTR390_MODE_UVS, std::bind(<R390Component::read_uvs, this))); + } + +} + +void LTR390Component::dump_config() { + LOG_I2C_DEVICE(this); +} + +void LTR390Component::read_als() { + uint32_t als = this->read_sensor_data(LTR390_MODE_ALS); + + if (this->light_sensor_ != nullptr) { + float lux = (0.6 * als) / (gain_values_[this->gain_] * resolution_values_[this->res_]) * this->wfac_; + this->light_sensor_->publish_state(lux); + } + + if (this->als_sensor_ != nullptr) { + this->als_sensor_->publish_state(als); + } + +} + +void LTR390Component::read_uvs() { + uint32_t uv = this->read_sensor_data(LTR390_MODE_UVS); + + if (this->uvi_sensor_ != nullptr) { + this->uvi_sensor_->publish_state(uv/LTR390_SENSITIVITY * this->wfac_); + } + + if (this->uv_sensor_ != nullptr) { + this->uv_sensor_->publish_state(uv); + } +} + + +void LTR390Component::read_mode(int mode_index) { + + // Set mode + this->set_mode(std::get<0>(this->mode_funcs_->at(mode_index))); + + // After the sensor integration time do the following + this->set_timeout(resolution_values_[this->res_] * 100, [this, mode_index]() { + // Read from the sensor + std::get<1>(this->mode_funcs_->at(mode_index))(); + + // If there are more modes to read then begin the next + // otherwise stop + if (mode_index + 1 < this->mode_funcs_->size()) { + this->read_mode(mode_index + 1); + } else { + this->reading = false; + } + }); + +} + +void LTR390Component::update() { + + if (!this->reading) { + this->reading = true; + this->read_mode(0); + } + +} + +} // namespace ltr390 +} // namespace esphome diff --git a/esphome/components/ltr390/ltr390.h b/esphome/components/ltr390/ltr390.h new file mode 100644 index 0000000000..ac2a3b21c1 --- /dev/null +++ b/esphome/components/ltr390/ltr390.h @@ -0,0 +1,115 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" +#include +#include +#include + +namespace esphome { +namespace ltr390 { + +typedef enum { + LTR390_CTRL_EN = 1, + LTR390_CTRL_MODE = 3, + LTR390_CTRL_RST = 4, +} ltr390_ctrl_t; + +// enums from https://github.com/adafruit/Adafruit_LTR390/ + +#define LTR390_MAIN_CTRL 0x00 ///< Main control register +#define LTR390_MEAS_RATE 0x04 ///< Resolution and data rate +#define LTR390_GAIN 0x05 ///< ALS and UVS gain range +#define LTR390_PART_ID 0x06 ///< Part id/revision register +#define LTR390_MAIN_STATUS 0x07 ///< Main status register + +#define LTR390_SENSITIVITY 2300.0 + +// Sensing modes +typedef enum { + LTR390_MODE_ALS, + LTR390_MODE_UVS, +} ltr390_mode_t; + +// Sensor gain levels +typedef enum { + LTR390_GAIN_1 = 0, + LTR390_GAIN_3, // Default + LTR390_GAIN_6, + LTR390_GAIN_9, + LTR390_GAIN_18, +} ltr390_gain_t; + +// Sensor resolution +typedef enum { + LTR390_RESOLUTION_20BIT, + LTR390_RESOLUTION_19BIT, + LTR390_RESOLUTION_18BIT, // Default + LTR390_RESOLUTION_17BIT, + LTR390_RESOLUTION_16BIT, + LTR390_RESOLUTION_13BIT, +} ltr390_resolution_t; + +class LTR390Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + void update() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + void set_gain_value(ltr390_gain_t gain) { this->gain_ = gain; } + void set_res_value(ltr390_resolution_t res) { this->res_ = res; } + void set_wfac_value(float wfac) { this->wfac_ = wfac; } + + void set_light_sensor(sensor::Sensor *light_sensor) { this->light_sensor_ = light_sensor; } + void set_als_sensor(sensor::Sensor *als_sensor) { this->als_sensor_ = als_sensor; } + void set_uvi_sensor(sensor::Sensor *uvi_sensor) { this->uvi_sensor_ = uvi_sensor; } + void set_uv_sensor(sensor::Sensor *uv_sensor) { this->uv_sensor_ = uv_sensor; } + + protected: + bool enabled(); + void enable(bool en); + + bool reset(void); + + void set_mode(ltr390_mode_t mode); + ltr390_mode_t get_mode(void); + + void set_gain(ltr390_gain_t gain); + ltr390_gain_t get_gain(void); + + void set_resolution(ltr390_resolution_t res); + ltr390_resolution_t get_resolution(void); + + bool new_data_available(void); + uint32_t read_sensor_data(ltr390_mode_t mode); + + void read_als(void); + void read_uvs(void); + + void read_mode(int mode_index); + + std::atomic reading; + + std::vector< std::tuple< ltr390_mode_t, std::function > > *mode_funcs_; + + i2c::I2CRegister *ctrl_reg_; + i2c::I2CRegister *status_reg_; + i2c::I2CRegister *gain_reg_; + i2c::I2CRegister *res_reg_; + + ltr390_gain_t gain_; + ltr390_resolution_t res_; + float wfac_; + + sensor::Sensor *light_sensor_{nullptr}; + sensor::Sensor *als_sensor_{nullptr}; + + sensor::Sensor *uvi_sensor_{nullptr}; + sensor::Sensor *uv_sensor_{nullptr}; + +}; + +} // namespace ltr390 +} // namespace esphome diff --git a/esphome/components/ltr390/sensor.py b/esphome/components/ltr390/sensor.py new file mode 100644 index 0000000000..97ddc3bf5b --- /dev/null +++ b/esphome/components/ltr390/sensor.py @@ -0,0 +1,75 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import CONF_ID, CONF_GAIN, CONF_RESOLUTION, UNIT_LUX, ICON_BRIGHTNESS_5 + +DEPENDENCIES = ['i2c'] + +ltr390_ns = cg.esphome_ns.namespace('ltr390') + +LTR390Component = ltr390_ns.class_('LTR390Component', cg.PollingComponent, i2c.I2CDevice) + +CONF_LIGHT = 'light' +CONF_ALS = 'als' +CONF_UVI = 'uvi' +CONF_UV = 'uv' +CONF_WFAC = 'wfac' + +UNIT_COUNTS = '#' +UNIT_UVI = 'UVI' + +ltr390_gain_t = ltr390_ns.enum('ltr390_gain_t') +GAIN_OPTIONS = { + "X1": ltr390_gain_t.LTR390_GAIN_1, + "X3": ltr390_gain_t.LTR390_GAIN_3, + "X6": ltr390_gain_t.LTR390_GAIN_6, + "X9": ltr390_gain_t.LTR390_GAIN_9, + "X18": ltr390_gain_t.LTR390_GAIN_18, +} + +ltr390_resolution_t = ltr390_ns.enum('ltr390_resolution_t') +RES_OPTIONS = { + 20: ltr390_resolution_t.LTR390_RESOLUTION_20BIT, + 19: ltr390_resolution_t.LTR390_RESOLUTION_19BIT, + 18: ltr390_resolution_t.LTR390_RESOLUTION_18BIT, + 17: ltr390_resolution_t.LTR390_RESOLUTION_17BIT, + 16: ltr390_resolution_t.LTR390_RESOLUTION_16BIT, + 13: ltr390_resolution_t.LTR390_RESOLUTION_13BIT, +} + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(LTR390Component), + + cv.Optional(CONF_LIGHT): sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1), + cv.Optional(CONF_ALS): sensor.sensor_schema(UNIT_COUNTS, ICON_BRIGHTNESS_5, 1), + + cv.Optional(CONF_UVI): sensor.sensor_schema(UNIT_UVI, ICON_BRIGHTNESS_5, 5), + cv.Optional(CONF_UV): sensor.sensor_schema(UNIT_COUNTS, ICON_BRIGHTNESS_5, 1), + + cv.Optional(CONF_GAIN, default="X3"): cv.enum(GAIN_OPTIONS), + cv.Optional(CONF_RESOLUTION, default="18"): cv.enum(RES_OPTIONS), + cv.Optional(CONF_WFAC, default=1.0): cv.float_range(min=1.0), + +}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x53)) + +TYPES = { + CONF_LIGHT: 'set_light_sensor', + CONF_ALS: 'set_als_sensor', + CONF_UVI: 'set_uvi_sensor', + CONF_UV: 'set_uv_sensor', +} + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield i2c.register_i2c_device(var, config) + + cg.add(var.set_gain_value(config[CONF_GAIN])) + cg.add(var.set_res_value(config[CONF_RESOLUTION])) + cg.add(var.set_wfac_value(config[CONF_WFAC])) + + for key, funcName in TYPES.items(): + + if key in config: + sens = yield sensor.new_sensor(config[key]) + cg.add(getattr(var, funcName)(sens))