Add opt3001 component

This commit is contained in:
Cody Cutrer 2024-04-23 15:40:06 -06:00
parent 8027921ba3
commit 4fc6a8b675
11 changed files with 254 additions and 0 deletions

View file

@ -249,6 +249,7 @@ esphome/components/nextion/text_sensor/* @senexcrenshaw
esphome/components/nfc/* @jesserockz @kbx81
esphome/components/noblex/* @AGalfra
esphome/components/number/* @esphome/core
esphome/components/opt3001/* @ccutrer
esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/pca6416a/* @Mat931

View file

View file

@ -0,0 +1,124 @@
#include "opt3001.h"
#include "esphome/core/log.h"
namespace esphome {
namespace opt3001 {
static const char *const TAG = "opt3001.sensor";
static const uint8_t OPT3001_REG_RESULT = 0x00;
static const uint8_t OPT3001_REG_CONFIGURATION = 0x01;
// See datasheet for full description of each bit.
static const uint16_t OPT3001_CONFIGURATION_RANGE_FULL = 0b1100000000000000;
static const uint16_t OPT3001_CONFIGURATION_CONVERSION_TIME_800 = 0b100000000000;
static const uint16_t OPT3001_CONFIGURATION_CONVERSION_MODE_MASK = 0b11000000000;
static const uint16_t OPT3001_CONFIGURATION_CONVERSION_MODE_SINGLE_SHOT = 0b01000000000;
static const uint16_t OPT3001_CONFIGURATION_CONVERSION_MODE_SHUTDOWN = 0b00000000000;
// tl;dr: Configure an automatic-ranged, 800ms single shot reading,
// with INT processing disabled
static const uint16_t OPT3001_CONFIGURATION_FULL_RANGE_ONE_SHOT = OPT3001_CONFIGURATION_RANGE_FULL |
OPT3001_CONFIGURATION_CONVERSION_TIME_800 |
OPT3001_CONFIGURATION_CONVERSION_MODE_SINGLE_SHOT;
static const uint16_t OPT3001_CONVERSION_TIME_800 = 800;
/*
opt3001 properties:
- e (exponent) = high 4 bits of result register
- m (mantissa) = low 12 bits of result register
- formula: (0.01 * 2^e) * m lx
*/
OPT3001Sensor::OPT3001Sensor() { updating_ = false; }
void OPT3001Sensor::read_result_(const std::function<void(float)> &f) {
// ensure the single shot flag is clear, indicating it's done
uint16_t raw_value;
if (this->read_(&raw_value) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Reading configuration register failed");
f(NAN);
return;
}
raw_value = i2c::i2ctohs(raw_value);
if ((raw_value & OPT3001_CONFIGURATION_CONVERSION_MODE_MASK) != OPT3001_CONFIGURATION_CONVERSION_MODE_SHUTDOWN) {
// not ready; wait 10ms and try again
ESP_LOGW(TAG, "Data not ready; waiting 10ms");
this->set_timeout("wait", 10, [this, f]() { read_result_(f); });
return;
}
if (this->read_register(OPT3001_REG_RESULT, reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Reading result register failed");
f(NAN);
return;
}
raw_value = i2c::i2ctohs(raw_value);
uint8_t exponent = raw_value >> 12;
uint16_t mantissa = raw_value & 0b111111111111;
double lx = 0.01 * pow(2.0, double(exponent)) * double(mantissa);
f(float(lx));
}
void OPT3001Sensor::read_lx_(const std::function<void(float)> &f) {
// turn on (after one-shot sensor automatically powers down)
uint16_t start_measurement = i2c::htoi2cs(OPT3001_CONFIGURATION_FULL_RANGE_ONE_SHOT);
if (this->write_register(OPT3001_REG_CONFIGURATION, reinterpret_cast<uint8_t *>(&start_measurement), 2) !=
i2c::ERROR_OK) {
ESP_LOGW(TAG, "Triggering one shot measurement failed");
f(NAN);
return;
}
this->set_timeout("read", OPT3001_CONVERSION_TIME_800, [this, f]() {
if (this->setup_read_(OPT3001_REG_CONFIGURATION) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Starting configuration register read failed");
f(NAN);
return;
}
read_result_(f);
});
}
void OPT3001Sensor::dump_config() {
LOG_SENSOR("", "OPT3001", this);
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with OPT3001 failed!");
}
LOG_UPDATE_INTERVAL(this);
}
void OPT3001Sensor::update() {
// Set a flag and skip just in case the sensor isn't responding,
// and we just keep waiting for it in read_result_.
// This way we don't end up with potentially boundless "threads"
// using up memory and eventually crashing the device
if (updating_) {
return;
}
updating_ = true;
this->read_lx_([this](float val) {
updating_ = false;
if (std::isnan(val)) {
this->status_set_warning();
this->publish_state(NAN);
return;
}
ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), val);
this->status_clear_warning();
this->publish_state(val);
});
}
float OPT3001Sensor::get_setup_priority() const { return setup_priority::DATA; }
} // namespace opt3001
} // namespace esphome

View file

@ -0,0 +1,33 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace opt3001 {
/// This class implements support for the i2c-based OPT3001 ambient light sensor.
class OPT3001Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
public:
OPT3001Sensor();
void dump_config() override;
void update() override;
float get_setup_priority() const override;
protected:
// checks if one-shot is complete before reading the result and returnig it
void read_result_(const std::function<void(float)> &f);
// begins a one-shot measurement
void read_lx_(const std::function<void(float)> &f);
// begins a read, but doesn't actually do it
i2c::ErrorCode setup_read_(uint8_t a_register) { return this->write(&a_register, 1, true); }
// reads without setting the register first
i2c::ErrorCode read_(uint16_t *data) { return this->read(reinterpret_cast<uint8_t *>(data), 2); }
bool updating_;
};
} // namespace opt3001
} // namespace esphome

View file

@ -0,0 +1,36 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
DEVICE_CLASS_ILLUMINANCE,
STATE_CLASS_MEASUREMENT,
UNIT_LUX,
)
DEPENDENCIES = ["i2c"]
CODEOWNERS = ["@ccutrer"]
opt3001_ns = cg.esphome_ns.namespace("opt3001")
OPT3001Sensor = opt3001_ns.class_(
"OPT3001Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
sensor.sensor_schema(
OPT3001Sensor,
unit_of_measurement=UNIT_LUX,
accuracy_decimals=1,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend({})
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x44))
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)

View file

@ -0,0 +1,10 @@
i2c:
- id: i2c_opt3001
scl: 5
sda: 4
sensor:
- platform: opt3001
name: Living Room Brightness
address: 0x44
update_interval: 30s

View file

@ -0,0 +1,10 @@
i2c:
- id: i2c_opt3001
scl: 5
sda: 4
sensor:
- platform: opt3001
name: Living Room Brightness
address: 0x44
update_interval: 30s

View file

@ -0,0 +1,10 @@
i2c:
- id: i2c_opt3001
scl: 16
sda: 17
sensor:
- platform: opt3001
name: Living Room Brightness
address: 0x44
update_interval: 30s

View file

@ -0,0 +1,10 @@
i2c:
- id: i2c_opt3001
scl: 16
sda: 17
sensor:
- platform: opt3001
name: Living Room Brightness
address: 0x44
update_interval: 30s

View file

@ -0,0 +1,10 @@
i2c:
- id: i2c_opt3001
scl: 5
sda: 4
sensor:
- platform: opt3001
name: Living Room Brightness
address: 0x44
update_interval: 30s

View file

@ -0,0 +1,10 @@
i2c:
- id: i2c_opt3001
scl: 5
sda: 4
sensor:
- platform: opt3001
name: Living Room Brightness
address: 0x44
update_interval: 30s