diff --git a/CODEOWNERS b/CODEOWNERS index f533ff5c47..d2971e7c73 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -94,6 +94,7 @@ esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lock/* @esphome/core esphome/components/logger/* @esphome/core esphome/components/ltr390/* @sjtrny +esphome/components/max44009/* @berfenger esphome/components/max7219digit/* @rspaargaren esphome/components/max9611/* @mckaymatthew esphome/components/mcp23008/* @jesserockz diff --git a/esphome/components/max44009/__init__.py b/esphome/components/max44009/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/max44009/max44009.cpp b/esphome/components/max44009/max44009.cpp new file mode 100644 index 0000000000..6f12fb6583 --- /dev/null +++ b/esphome/components/max44009/max44009.cpp @@ -0,0 +1,144 @@ +#include "max44009.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace max44009 { + +static const char *const TAG = "max44009.sensor"; + +// REGISTERS +static const uint8_t MAX44009_REGISTER_CONFIGURATION = 0x02; +static const uint8_t MAX44009_LUX_READING_HIGH = 0x03; +static const uint8_t MAX44009_LUX_READING_LOW = 0x04; +// CONFIGURATION MASKS +static const uint8_t MAX44009_CFG_CONTINUOUS = 0x80; +// ERROR CODES +static const uint8_t MAX44009_OK = 0; +static const uint8_t MAX44009_ERROR_WIRE_REQUEST = -10; +static const uint8_t MAX44009_ERROR_OVERFLOW = -20; +static const uint8_t MAX44009_ERROR_HIGH_BYTE = -30; +static const uint8_t MAX44009_ERROR_LOW_BYTE = -31; + +void MAX44009Sensor::setup() { + ESP_LOGCONFIG(TAG, "Setting up MAX44009..."); + bool state_ok = false; + if (this->mode_ == MAX44009Mode::MAX44009_MODE_LOW_POWER) { + state_ok = this->set_low_power_mode(); + } else if (this->mode_ == MAX44009Mode::MAX44009_MODE_CONTINUOUS) { + state_ok = this->set_continuous_mode(); + } else { + /* + * Mode AUTO: Set mode depending on update interval + * - On low power mode, the IC measures lux intensity only once every 800ms + * regardless of integration time + * - On continuous mode, the IC continuously measures lux intensity + */ + if (this->get_update_interval() < 800) { + state_ok = this->set_continuous_mode(); + } else { + state_ok = this->set_low_power_mode(); + } + } + if (!state_ok) + this->mark_failed(); +} + +void MAX44009Sensor::dump_config() { + ESP_LOGCONFIG(TAG, "MAX44009:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with MAX44009 failed!"); + } +} + +float MAX44009Sensor::get_setup_priority() const { return setup_priority::DATA; } + +void MAX44009Sensor::update() { + // update sensor illuminance value + float lux = this->read_illuminance_(); + if (this->error_ != MAX44009_OK) { + this->status_set_warning(); + this->publish_state(NAN); + } else { + this->status_clear_warning(); + this->publish_state(lux); + } +} + +float MAX44009Sensor::read_illuminance_() { + uint8_t datahigh = this->read_(MAX44009_LUX_READING_HIGH); + if (error_ != MAX44009_OK) { + this->error_ = MAX44009_ERROR_HIGH_BYTE; + return this->error_; + } + uint8_t datalow = this->read_(MAX44009_LUX_READING_LOW); + if (error_ != MAX44009_OK) { + this->error_ = MAX44009_ERROR_LOW_BYTE; + return this->error_; + } + uint8_t exponent = datahigh >> 4; + if (exponent == 0x0F) { + this->error_ = MAX44009_ERROR_OVERFLOW; + return this->error_; + } + + return this->convert_to_lux_(datahigh, datalow); +} + +float MAX44009Sensor::convert_to_lux_(uint8_t data_high, uint8_t data_low) { + uint8_t exponent = data_high >> 4; + uint32_t mantissa = ((data_high & 0x0F) << 4) + (data_low & 0x0F); + return ((0x0001 << exponent) * 0.045) * mantissa; +} + +bool MAX44009Sensor::set_continuous_mode() { + uint8_t config = this->read_(MAX44009_REGISTER_CONFIGURATION); + if (this->error_ == MAX44009_OK) { + config |= MAX44009_CFG_CONTINUOUS; + this->write_(MAX44009_REGISTER_CONFIGURATION, config); + this->status_clear_warning(); + ESP_LOGV(TAG, "set to continuous mode"); + return true; + } else { + this->status_set_warning(); + return false; + } +} + +bool MAX44009Sensor::set_low_power_mode() { + uint8_t config = this->read_(MAX44009_REGISTER_CONFIGURATION); + if (this->error_ == MAX44009_OK) { + config &= ~MAX44009_CFG_CONTINUOUS; + this->write_(MAX44009_REGISTER_CONFIGURATION, config); + this->status_clear_warning(); + ESP_LOGV(TAG, "set to low power mode"); + return true; + } else { + this->status_set_warning(); + return false; + } +} + +uint8_t MAX44009Sensor::read_(uint8_t reg) { + uint8_t data = 0; + if (!this->read_byte(reg, &data)) { + this->error_ = MAX44009_ERROR_WIRE_REQUEST; + } else { + this->error_ = MAX44009_OK; + } + return data; +} + +void MAX44009Sensor::write_(uint8_t reg, uint8_t value) { + if (!this->write_byte(reg, value)) { + this->error_ = MAX44009_ERROR_WIRE_REQUEST; + } else { + this->error_ = MAX44009_OK; + } +} + +void MAX44009Sensor::set_mode(MAX44009Mode mode) { this->mode_ = mode; } + +} // namespace max44009 +} // namespace esphome diff --git a/esphome/components/max44009/max44009.h b/esphome/components/max44009/max44009.h new file mode 100644 index 0000000000..c85d1c1028 --- /dev/null +++ b/esphome/components/max44009/max44009.h @@ -0,0 +1,37 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace max44009 { + +enum MAX44009Mode { MAX44009_MODE_AUTO, MAX44009_MODE_LOW_POWER, MAX44009_MODE_CONTINUOUS }; + +/// This class implements support for the MAX44009 Illuminance i2c sensor. +class MAX44009Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { + public: + MAX44009Sensor() {} + + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + void set_mode(MAX44009Mode mode); + bool set_continuous_mode(); + bool set_low_power_mode(); + + protected: + /// Read the illuminance value + float read_illuminance_(); + float convert_to_lux_(uint8_t data_high, uint8_t data_low); + uint8_t read_(uint8_t reg); + void write_(uint8_t reg, uint8_t value); + + int error_; + MAX44009Mode mode_; +}; + +} // namespace max44009 +} // namespace esphome diff --git a/esphome/components/max44009/sensor.py b/esphome/components/max44009/sensor.py new file mode 100644 index 0000000000..498cccb77b --- /dev/null +++ b/esphome/components/max44009/sensor.py @@ -0,0 +1,53 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, i2c +from esphome.const import ( + CONF_ID, + CONF_MODE, + DEVICE_CLASS_ILLUMINANCE, + STATE_CLASS_MEASUREMENT, + UNIT_LUX, +) + +CODEOWNERS = ["@berfenger"] +DEPENDENCIES = ["i2c"] + +max44009_ns = cg.esphome_ns.namespace("max44009") +MAX44009Sensor = max44009_ns.class_( + "MAX44009Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice +) + +MAX44009Mode = max44009_ns.enum("MAX44009Mode") +MODE_OPTIONS = { + "auto": MAX44009Mode.MAX44009_MODE_AUTO, + "low_power": MAX44009Mode.MAX44009_MODE_LOW_POWER, + "continuous": MAX44009Mode.MAX44009_MODE_CONTINUOUS, +} + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + unit_of_measurement=UNIT_LUX, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.GenerateID(): cv.declare_id(MAX44009Sensor), + cv.Optional(CONF_MODE, default="low_power"): cv.enum( + MODE_OPTIONS, lower=True + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x4A)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + await sensor.register_sensor(var, config) + + cg.add(var.set_mode(config[CONF_MODE])) diff --git a/tests/test1.yaml b/tests/test1.yaml index 3d533f33fc..8b99f86773 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -463,6 +463,13 @@ sensor: state_topic: livingroom/custom_state_topic measurement_duration: 31 i2c_id: i2c_bus + - platform: max44009 + name: "Outside Brightness 1" + internal: true + address: 0x4A + update_interval: 30s + mode: low_power + i2c_id: i2c_bus - platform: bme280 temperature: name: "Outside Temperature"