Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add opt3001 component #6625

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Empty file.
124 changes: 124 additions & 0 deletions esphome/components/opt3001/opt3001.cpp
Original file line number Diff line number Diff line change
@@ -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
33 changes: 33 additions & 0 deletions esphome/components/opt3001/opt3001.h
Original file line number Diff line number Diff line change
@@ -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
36 changes: 36 additions & 0 deletions esphome/components/opt3001/sensor.py
Original file line number Diff line number Diff line change
@@ -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)
10 changes: 10 additions & 0 deletions tests/components/opt3001/test.esp32-c3-idf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
i2c:
- id: i2c_opt3001
scl: 5
sda: 4

sensor:
- platform: opt3001
name: Living Room Brightness
address: 0x44
update_interval: 30s
10 changes: 10 additions & 0 deletions tests/components/opt3001/test.esp32-c3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
i2c:
- id: i2c_opt3001
scl: 5
sda: 4

sensor:
- platform: opt3001
name: Living Room Brightness
address: 0x44
update_interval: 30s
10 changes: 10 additions & 0 deletions tests/components/opt3001/test.esp32-idf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
i2c:
- id: i2c_opt3001
scl: 16
sda: 17

sensor:
- platform: opt3001
name: Living Room Brightness
address: 0x44
update_interval: 30s
10 changes: 10 additions & 0 deletions tests/components/opt3001/test.esp32.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
i2c:
- id: i2c_opt3001
scl: 16
sda: 17

sensor:
- platform: opt3001
name: Living Room Brightness
address: 0x44
update_interval: 30s
10 changes: 10 additions & 0 deletions tests/components/opt3001/test.esp8266.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
i2c:
- id: i2c_opt3001
scl: 5
sda: 4

sensor:
- platform: opt3001
name: Living Room Brightness
address: 0x44
update_interval: 30s
10 changes: 10 additions & 0 deletions tests/components/opt3001/test.rp2040.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
i2c:
- id: i2c_opt3001
scl: 5
sda: 4

sensor:
- platform: opt3001
name: Living Room Brightness
address: 0x44
update_interval: 30s