mirror of
https://github.com/esphome/esphome.git
synced 2026-10-17 19:52:50 +00:00
[as734x] [2/3] Add basic counts, calibration and saturation level (#18011)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
co-authored by
J. Nick Koston
parent
27d8d49b17
commit
a58ce2d641
@@ -1,7 +1,14 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_GAIN, CONF_ID, CONF_TYPE, CONF_UPDATE_INTERVAL
|
||||
from esphome.const import (
|
||||
CONF_CALIBRATION,
|
||||
CONF_GAIN,
|
||||
CONF_GLASS_ATTENUATION_FACTOR,
|
||||
CONF_ID,
|
||||
CONF_TYPE,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@latonita", "@mrgnr"]
|
||||
@@ -13,6 +20,9 @@ DOMAIN = "as734x"
|
||||
CONF_AS734X_ID = "as734x_id"
|
||||
CONF_ATIME = "atime"
|
||||
CONF_ASTEP = "astep"
|
||||
CONF_CHANNEL_CORRECTION = "channel_correction"
|
||||
CONF_DARK_CURRENT = "dark_current"
|
||||
CONF_NORMALIZE_BASIC_COUNTS = "normalize_basic_counts"
|
||||
|
||||
# Band order is a contract with the readout maps in as734x.cpp: the index selects the counts slot.
|
||||
BANDS_43 = (
|
||||
@@ -44,11 +54,57 @@ GAINS_41 = {
|
||||
}
|
||||
GAINS_43 = {**GAINS_41, "X1024": Gain.GAIN_1024X, "X2048": Gain.GAIN_2048X}
|
||||
|
||||
Normalization = as734x_ns.enum("Normalization", True)
|
||||
NORMALIZATIONS = {
|
||||
"NONE": Normalization.NORMALIZATION_NONE,
|
||||
"ALL": Normalization.NORMALIZATION_ALL,
|
||||
"BANDS": Normalization.NORMALIZATION_BANDS,
|
||||
"CLEAR": Normalization.NORMALIZATION_CLEAR,
|
||||
}
|
||||
|
||||
# Per-channel correction for the golden device, measured by the manufacturer.
|
||||
DEFAULT_CHANNEL_CORRECTION_43 = [
|
||||
1.055464349,
|
||||
1.043509797,
|
||||
1.029576268,
|
||||
1.0175052,
|
||||
1.00441899,
|
||||
0.987356499,
|
||||
0.957597044,
|
||||
0.995863485,
|
||||
1.014628964,
|
||||
0.996500814,
|
||||
0.933072749,
|
||||
1.052236338,
|
||||
0.999570232,
|
||||
]
|
||||
|
||||
# One integration cycle lasts (ATIME + 1) * (ASTEP + 1) * 2.78 us
|
||||
CYCLE_STEP_SECONDS = 2.78e-6
|
||||
|
||||
|
||||
def _float_list(length: int) -> cv.All:
|
||||
"""One float per band, so the list length has to match the chip."""
|
||||
return cv.All(cv.ensure_list(cv.float_), cv.Length(min=length, max=length))
|
||||
|
||||
|
||||
def _calibration_schema(bands: tuple[str, ...]) -> cv.Schema:
|
||||
correction = (
|
||||
DEFAULT_CHANNEL_CORRECTION_43 if bands == BANDS_43 else [1.0] * len(bands)
|
||||
)
|
||||
return cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_CHANNEL_CORRECTION, default=correction): _float_list(
|
||||
len(bands)
|
||||
),
|
||||
cv.Optional(CONF_DARK_CURRENT, default=[0.0] * len(bands)): _float_list(
|
||||
len(bands)
|
||||
),
|
||||
cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=1.0): cv.positive_float,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _validate_exposure(cycles: int):
|
||||
"""Reject an exposure that is empty or longer than the update interval."""
|
||||
|
||||
@@ -67,7 +123,9 @@ def _validate_exposure(cycles: int):
|
||||
return validator
|
||||
|
||||
|
||||
def _schema(gains: dict, address: int, cycles: int) -> cv.Schema:
|
||||
def _schema(
|
||||
gains: dict, address: int, cycles: int, bands: tuple[str, ...]
|
||||
) -> cv.Schema:
|
||||
return cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -75,6 +133,10 @@ def _schema(gains: dict, address: int, cycles: int) -> cv.Schema:
|
||||
cv.Optional(CONF_GAIN, default="X8"): cv.enum(gains),
|
||||
cv.Optional(CONF_ATIME, default=29): cv.int_range(min=0, max=255),
|
||||
cv.Optional(CONF_ASTEP, default=599): cv.int_range(min=0, max=65534),
|
||||
cv.Optional(CONF_CALIBRATION, default={}): _calibration_schema(bands),
|
||||
cv.Optional(CONF_NORMALIZE_BASIC_COUNTS, default="NONE"): cv.enum(
|
||||
NORMALIZATIONS, upper=True
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
@@ -85,14 +147,25 @@ def _schema(gains: dict, address: int, cycles: int) -> cv.Schema:
|
||||
|
||||
CONFIG_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
"AS7341": _schema(GAINS_41, 0x39, cycles=2), # two SMUX steps per update
|
||||
"AS7343": _schema(GAINS_43, 0x39, cycles=3),
|
||||
"TCS3448": _schema(GAINS_43, 0x59, cycles=3),
|
||||
# two SMUX steps per update
|
||||
"AS7341": _schema(GAINS_41, 0x39, cycles=2, bands=BANDS_41),
|
||||
"AS7343": _schema(GAINS_43, 0x39, cycles=3, bands=BANDS_43),
|
||||
"TCS3448": _schema(GAINS_43, 0x59, cycles=3, bands=BANDS_43),
|
||||
},
|
||||
upper=True,
|
||||
)
|
||||
|
||||
|
||||
def _per_band(
|
||||
values: list[float], bands: tuple[str, ...], absent: float
|
||||
) -> list[float]:
|
||||
"""Spread per-band calibration over the 13 slots the C++ side indexes by band."""
|
||||
spread = [absent] * len(BANDS_43)
|
||||
for value, band in zip(values, bands, strict=True):
|
||||
spread[BANDS_43.index(band)] = value
|
||||
return spread
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
chip = config[CONF_TYPE]
|
||||
cg.add_define("USE_AS7341" if chip == "AS7341" else "USE_AS7343")
|
||||
@@ -102,3 +175,14 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_gain(config[CONF_GAIN]))
|
||||
cg.add(var.set_atime(config[CONF_ATIME]))
|
||||
cg.add(var.set_astep(config[CONF_ASTEP]))
|
||||
cg.add(var.set_basic_counts_normalization(config[CONF_NORMALIZE_BASIC_COUNTS]))
|
||||
|
||||
bands = BANDS_41 if chip == "AS7341" else BANDS_43
|
||||
calibration = config[CONF_CALIBRATION]
|
||||
cg.add(
|
||||
var.set_channel_correction(
|
||||
_per_band(calibration[CONF_CHANNEL_CORRECTION], bands, 1.0)
|
||||
)
|
||||
)
|
||||
cg.add(var.set_dark_current(_per_band(calibration[CONF_DARK_CURRENT], bands, 0.0)))
|
||||
cg.add(var.set_glass_attenuation_factor(calibration[CONF_GLASS_ATTENUATION_FACTOR]))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -20,6 +21,8 @@ static constexpr uint8_t CFG0_REG_BANK = 0x10;
|
||||
static constexpr uint8_t BANK_SPLIT = 0x80; // registers below this need REG_BANK set
|
||||
static constexpr uint8_t STATUS2_AVALID = 0x40;
|
||||
static constexpr uint8_t ASTATUS_ASAT = 0x80;
|
||||
static constexpr uint8_t LED_ACT = 0x80; // LED driver on, in the chip's LED register
|
||||
static constexpr uint8_t CONFIG_LED_SEL = 0x08;
|
||||
|
||||
static constexpr uint8_t AS7341_CFG6 = 0xAF;
|
||||
static constexpr uint8_t AS7341_SMUX_CMD_WRITE = 0x10; // copy the SMUX image from RAM to the chain
|
||||
@@ -40,6 +43,9 @@ static constexpr uint8_t CLEAR_BAND = NUM_BANDS - 1;
|
||||
static constexpr uint8_t MAX_SMUX_LEN = 20;
|
||||
static constexpr uint8_t MAX_ADC_SLOTS = 18;
|
||||
static constexpr uint32_t MIN_COLLECTION_TIMEOUT_MS = 30 * 1000;
|
||||
static constexpr uint32_t MAX_ADC_COUNT = 65535;
|
||||
|
||||
PROGMEM_STRING_TABLE(BandNames, "F1", "F2", "FZ", "F3", "F4", "FY", "F5", "FXL", "F6", "F7", "F8", "NIR", "Clear");
|
||||
|
||||
#ifdef USE_AS7341
|
||||
static const char AS7341_NAME[] PROGMEM = "AS7341";
|
||||
@@ -60,11 +66,25 @@ static const uint8_t AS7341_READOUT[] PROGMEM = {
|
||||
N, N, N, N, N, N, 0, N, 1, 2, 3, 5, 4, // step 1: SMUX routes clear to ADC4 and NIR to ADC5
|
||||
};
|
||||
static_assert(sizeof(AS7341_READOUT) == AS7341_SMUX_STEPS * NUM_BANDS && AS7341_ADC_SLOTS <= MAX_ADC_SLOTS);
|
||||
// This chip has no FZ, FY or FXL band, so those slots are marked absent.
|
||||
// clang-format off
|
||||
static const uint16_t AS7341_WAVELENGTHS[] PROGMEM = {
|
||||
415, 445, ABSENT_NM, 480, 515, ABSENT_NM, 555, ABSENT_NM, 590, 630, 680, 910, WIDEBAND_NM,
|
||||
};
|
||||
// clang-format on
|
||||
static_assert(sizeof(AS7341_WAVELENGTHS) == NUM_BANDS * sizeof(uint16_t));
|
||||
// Datasheet values for the golden device: gain correction per gain step, the same for every band.
|
||||
// The last two entries stay at zero because this chip has no 1024x or 2048x gain.
|
||||
static const float AS7341_GAIN_CORRECTION[] PROGMEM = {1.0577f, 1.0491f, 1.0479f, 1.0491f, 1.0207f, 1.0158f, 1.0109f,
|
||||
1.0000f, 1.0003f, 0.9873f, 0.9593f, 0.0f, 0.0f};
|
||||
static_assert(sizeof(AS7341_GAIN_CORRECTION) == GAIN_COUNT * sizeof(float));
|
||||
const ChipInfo AS7341_CHIP PROGMEM = {
|
||||
.name = AS7341_NAME,
|
||||
.init = AS7341_INIT,
|
||||
.smux = AS7341_SMUX,
|
||||
.readout = AS7341_READOUT,
|
||||
.wavelengths = AS7341_WAVELENGTHS,
|
||||
.gain_correction = AS7341_GAIN_CORRECTION,
|
||||
.init_count = 1,
|
||||
.astep_reg = 0xCA,
|
||||
.cfg0_reg = 0xA9,
|
||||
@@ -73,11 +93,14 @@ const ChipInfo AS7341_CHIP PROGMEM = {
|
||||
.id_reg = 0x92,
|
||||
.id_mask = 0xFC,
|
||||
.id_value = 0x09 << 2,
|
||||
.led_reg = 0x74,
|
||||
.led_sel_reg = 0x70, // CONFIG, whose LED_SEL bit connects the pin to the driver
|
||||
.adc_slots = AS7341_ADC_SLOTS,
|
||||
.smux_steps = AS7341_SMUX_STEPS,
|
||||
.integration_cycles = 1,
|
||||
.smux_len = AS7341_SMUX_LEN,
|
||||
.auto_smux = false,
|
||||
.gain_correction_per_band = false,
|
||||
.extra_clear_slot = NO_SLOT,
|
||||
};
|
||||
#endif
|
||||
@@ -97,12 +120,41 @@ static const uint8_t AS7343_SMUX[] PROGMEM = {0x00, 0x04, 0x65, 0x02, 0x00, 0x05
|
||||
static const uint8_t AS7343_READOUT[] PROGMEM = {12, 6, 0, 7, 8, 1, 15, 2, 9, 13, 14, 3, 10};
|
||||
static_assert(sizeof(AS7343_SMUX) == AS7343_CHAINS * AS7343_SMUX_LEN && AS7343_SMUX_LEN <= MAX_SMUX_LEN);
|
||||
static_assert(sizeof(AS7343_READOUT) == NUM_BANDS && AS7343_ADC_SLOTS <= MAX_ADC_SLOTS);
|
||||
// clang-format off
|
||||
static const uint16_t AS7343_WAVELENGTHS[] PROGMEM = {
|
||||
405, 425, 450, 475, 515, 555, 550, 600, 640, 690, 745, 855, WIDEBAND_NM,
|
||||
};
|
||||
// clang-format on
|
||||
static_assert(sizeof(AS7343_WAVELENGTHS) == NUM_BANDS * sizeof(uint16_t));
|
||||
// Datasheet values for the golden device: gain correction per gain step, per band.
|
||||
// clang-format off
|
||||
static const float AS7343_GAIN_CORRECTION[] PROGMEM = {
|
||||
1.149f, 1.100f, 1.060f, 1.070f, 1.063f, 1.051f, 1.062f, 1.056f, 1.049f, 1.040f, 1.080f, 1.038f, 1.065f, // 0.5x
|
||||
1.090f, 1.128f, 1.064f, 1.071f, 1.063f, 1.050f, 1.068f, 1.055f, 1.047f, 1.039f, 1.075f, 1.038f, 1.085f, // 1x
|
||||
1.083f, 1.086f, 1.062f, 1.070f, 1.062f, 1.049f, 1.057f, 1.053f, 1.045f, 1.038f, 1.063f, 1.037f, 1.069f, // 2x
|
||||
1.059f, 1.068f, 1.056f, 1.066f, 1.058f, 1.046f, 1.051f, 1.051f, 1.044f, 1.036f, 1.059f, 1.035f, 1.053f, // 4x
|
||||
1.100f, 1.109f, 1.096f, 1.108f, 1.099f, 1.089f, 1.091f, 1.092f, 1.082f, 1.078f, 1.100f, 1.076f, 1.088f, // 8x
|
||||
1.099f, 1.109f, 1.096f, 1.108f, 1.099f, 1.089f, 1.091f, 1.092f, 1.082f, 1.078f, 1.100f, 1.075f, 1.087f, // 16x
|
||||
1.088f, 1.096f, 1.085f, 1.097f, 1.087f, 1.078f, 1.079f, 1.080f, 1.071f, 1.067f, 1.087f, 1.064f, 1.076f, // 32x
|
||||
1.083f, 1.091f, 1.078f, 1.090f, 1.079f, 1.072f, 1.072f, 1.073f, 1.064f, 1.062f, 1.080f, 1.057f, 1.069f, // 64x
|
||||
1.076f, 1.084f, 1.072f, 1.085f, 1.074f, 1.066f, 1.062f, 1.067f, 1.055f, 1.056f, 1.074f, 1.051f, 1.061f, // 128x
|
||||
1.067f, 1.074f, 1.063f, 1.075f, 1.064f, 1.059f, 1.055f, 1.058f, 1.049f, 1.051f, 1.064f, 1.044f, 1.053f, // 256x
|
||||
1.000f, 1.000f, 1.000f, 1.000f, 1.000f, 1.000f, 1.000f, 1.000f, 1.000f, 1.000f, 1.000f, 1.000f, 1.000f, // 512x
|
||||
1.033303857f, 1.034763813f, 1.029855013f, 1.036911249f, 0.988150537f, 1.029003501f, 1.021363258f,
|
||||
0.98762089f, 1.021713376f, 1.040108204f, 0.987417698f, 1.019481421f, 0.987270057f, // 1024x
|
||||
0.986859143f, 0.980877221f, 0.9469769f, 0.985535324f, 0.926243961f, 0.959814787f, 1.007963896f,
|
||||
0.942685187f, 1.007630587f, 1.025460005f, 0.928696275f, 1.001296639f, 1.041275263f, // 2048x
|
||||
};
|
||||
// clang-format on
|
||||
static_assert(sizeof(AS7343_GAIN_CORRECTION) == GAIN_COUNT * NUM_BANDS * sizeof(float));
|
||||
static constexpr ChipInfo as7343_chip(const char *name) {
|
||||
return {
|
||||
.name = name,
|
||||
.init = AS7343_INIT,
|
||||
.smux = AS7343_SMUX,
|
||||
.readout = AS7343_READOUT,
|
||||
.wavelengths = AS7343_WAVELENGTHS,
|
||||
.gain_correction = AS7343_GAIN_CORRECTION,
|
||||
.init_count = sizeof(AS7343_INIT) / sizeof(RegValue),
|
||||
.astep_reg = 0xD4,
|
||||
.cfg0_reg = 0xBF,
|
||||
@@ -111,11 +163,14 @@ static constexpr ChipInfo as7343_chip(const char *name) {
|
||||
.id_reg = 0x5A,
|
||||
.id_mask = 0xFF,
|
||||
.id_value = 0x81,
|
||||
.led_reg = 0xCD,
|
||||
.led_sel_reg = 0, // the LED register drives the pin on its own
|
||||
.adc_slots = AS7343_ADC_SLOTS,
|
||||
.smux_steps = 1,
|
||||
.integration_cycles = 3,
|
||||
.smux_len = AS7343_SMUX_LEN,
|
||||
.auto_smux = true,
|
||||
.gain_correction_per_band = true,
|
||||
.extra_clear_slot = 4,
|
||||
};
|
||||
}
|
||||
@@ -133,6 +188,34 @@ bool AS734XComponent::select_bank_(uint8_t reg) {
|
||||
return this->write_byte(pgm(this->chip_->cfg0_reg), low ? CFG0_REG_BANK : 0);
|
||||
}
|
||||
|
||||
bool AS734XComponent::update_bits_(uint8_t reg, uint8_t mask, bool value) {
|
||||
uint8_t data = 0;
|
||||
if (!this->select_bank_(reg) || !this->read_byte(reg, &data)) {
|
||||
return false;
|
||||
}
|
||||
data = value ? (data | mask) : (data & ~mask);
|
||||
return this->write_byte(reg, data);
|
||||
}
|
||||
|
||||
// The exposure is applied before every measurement, so a gain, ATIME or ASTEP changed at runtime
|
||||
// takes effect on the next reading rather than only after a restart.
|
||||
bool AS734XComponent::write_exposure_() {
|
||||
const ChipInfo *chip = this->chip_;
|
||||
if (!this->select_bank_(REG_ATIME) || !this->write_byte(REG_ATIME, this->atime_) ||
|
||||
!this->write_byte_16(pgm(chip->astep_reg), byteswap(this->astep_)) ||
|
||||
!this->write_byte(pgm(chip->cfg1_reg), this->gain_)) {
|
||||
return false;
|
||||
}
|
||||
this->reading_gain_ = this->gain_;
|
||||
this->reading_atime_ = this->atime_;
|
||||
this->reading_astep_ = this->astep_;
|
||||
// One integration cycle lasts (ATIME + 1) * (ASTEP + 1) * 2.78 us.
|
||||
const uint32_t cycle_ms = (this->atime_ + 1UL) * (this->astep_ + 1UL) * 139 / 50000;
|
||||
this->integration_ms_ = cycle_ms * pgm(chip->integration_cycles);
|
||||
this->timeout_ms_ = std::max(MIN_COLLECTION_TIMEOUT_MS, 2 * this->integration_ms_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AS734XComponent::setup() {
|
||||
const ChipInfo *chip = this->chip_;
|
||||
const uint8_t id_reg = pgm(chip->id_reg);
|
||||
@@ -152,9 +235,7 @@ void AS734XComponent::setup() {
|
||||
ok = this->select_bank_(rv.reg) && this->write_byte(rv.reg, rv.value);
|
||||
}
|
||||
// Everything from here on, including the measurement registers, is in the high bank.
|
||||
ok = ok && this->select_bank_(REG_ATIME) && this->write_byte(REG_ATIME, this->atime_) &&
|
||||
this->write_byte_16(pgm(chip->astep_reg), byteswap(this->astep_)) &&
|
||||
this->write_byte(pgm(chip->cfg1_reg), this->gain_);
|
||||
ok = ok && this->write_exposure_();
|
||||
if (pgm(chip->auto_smux)) {
|
||||
const uint8_t *smux = pgm(chip->smux);
|
||||
const uint8_t len = pgm(chip->smux_len);
|
||||
@@ -167,10 +248,6 @@ void AS734XComponent::setup() {
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return;
|
||||
}
|
||||
// One integration cycle lasts (ATIME + 1) * (ASTEP + 1) * 2.78 us.
|
||||
const uint32_t cycle_ms = (this->atime_ + 1UL) * (this->astep_ + 1UL) * 139 / 50000;
|
||||
this->integration_ms_ = cycle_ms * pgm(chip->integration_cycles);
|
||||
this->timeout_ms_ = std::max(MIN_COLLECTION_TIMEOUT_MS, 2 * this->integration_ms_);
|
||||
this->disable_loop();
|
||||
}
|
||||
|
||||
@@ -181,24 +258,61 @@ void AS734XComponent::dump_config() {
|
||||
" Chip: %s\n"
|
||||
" Gain: %gx\n"
|
||||
" ATIME: %u\n"
|
||||
" ASTEP: %u",
|
||||
LOG_STR_ARG(pgm(this->chip_->name)), gain, this->atime_, this->astep_);
|
||||
" ASTEP: %u\n"
|
||||
" Glass attenuation factor: %.3f",
|
||||
LOG_STR_ARG(pgm(this->chip_->name)), gain, this->atime_, this->astep_, this->glass_attenuation_factor_);
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
for (auto *sens : this->sensors_) {
|
||||
LOG_SENSOR(" ", "Counts", sens);
|
||||
for (uint8_t band = 0; band < NUM_BANDS; band++) {
|
||||
char label[24];
|
||||
const char *name = LOG_STR_ARG(BandNames::get_log_str(band, BandNames::LAST_INDEX));
|
||||
snprintf(label, sizeof(label), "Counts %s", name);
|
||||
sensor::log_sensor(TAG, " ", label, this->sensors_[band]);
|
||||
snprintf(label, sizeof(label), "Basic counts %s", name);
|
||||
sensor::log_sensor(TAG, " ", label, this->basic_counts_sensors_[band]);
|
||||
}
|
||||
LOG_SENSOR(" ", "Saturation level", this->saturation_level_sensor_);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
void AS734XComponent::enable_led(bool enable) {
|
||||
const uint8_t led_reg = pgm(this->chip_->led_reg);
|
||||
if (led_reg == 0) {
|
||||
return;
|
||||
}
|
||||
// The LED register only drives the LED while LED_SEL is set, so the pin is handed over before
|
||||
// switching on and released only after switching off. The other order would leave the LED lit
|
||||
// with nothing able to turn it off.
|
||||
const uint8_t led_sel_reg = pgm(this->chip_->led_sel_reg);
|
||||
bool ok = true;
|
||||
if (enable && led_sel_reg != 0) {
|
||||
ok = this->update_bits_(led_sel_reg, CONFIG_LED_SEL, true);
|
||||
}
|
||||
ok = this->update_bits_(led_reg, LED_ACT, enable) && ok;
|
||||
if (!enable && led_sel_reg != 0) {
|
||||
ok = this->update_bits_(led_sel_reg, CONFIG_LED_SEL, false) && ok;
|
||||
}
|
||||
// The measurement path assumes the high bank, so leave the chip there for a reading in flight
|
||||
ok = this->select_bank_(REG_ENABLE) && ok;
|
||||
if (!ok) {
|
||||
ESP_LOGW(TAG, "Failed to switch the LED");
|
||||
}
|
||||
}
|
||||
|
||||
void AS734XComponent::update() {
|
||||
if (this->state_ != State::STATE_IDLE) {
|
||||
ESP_LOGW(TAG, "Skipping update, previous measurement still running");
|
||||
return;
|
||||
}
|
||||
if (!this->write_exposure_()) {
|
||||
this->abort_measurement_(LOG_STR("Failed to apply measurement settings"));
|
||||
return;
|
||||
}
|
||||
this->step_ = 0;
|
||||
this->peak_raw_count_ = 0;
|
||||
this->raw_counts_.fill(0);
|
||||
this->enable_loop();
|
||||
if (!this->start_step_()) {
|
||||
this->abort_measurement_(LOG_STR("Failed to start measurement"));
|
||||
@@ -270,14 +384,18 @@ void AS734XComponent::read_step_() {
|
||||
const uint8_t extra_clear = pgm(this->chip_->extra_clear_slot);
|
||||
for (uint8_t band = 0; band < NUM_BANDS; band++) {
|
||||
const uint8_t slot = progmem_read_byte(&readout[band]);
|
||||
if (slot == NO_SLOT || this->sensors_[band] == nullptr) {
|
||||
if (slot == NO_SLOT) {
|
||||
continue;
|
||||
}
|
||||
uint32_t counts = slot_value(slot);
|
||||
const uint16_t raw = slot_value(slot);
|
||||
this->peak_raw_count_ = std::max(this->peak_raw_count_, raw);
|
||||
uint16_t counts = raw;
|
||||
if (band == CLEAR_BAND && extra_clear != NO_SLOT) {
|
||||
counts = (counts + slot_value(extra_clear)) / 2;
|
||||
const uint16_t extra = slot_value(extra_clear);
|
||||
this->peak_raw_count_ = std::max(this->peak_raw_count_, extra);
|
||||
counts = (static_cast<uint32_t>(raw) + extra) / 2;
|
||||
}
|
||||
this->sensors_[band]->publish_state(counts);
|
||||
this->raw_counts_[band] = counts;
|
||||
}
|
||||
if (++this->step_ < pgm(this->chip_->smux_steps)) {
|
||||
if (!this->start_step_()) {
|
||||
@@ -285,11 +403,104 @@ void AS734XComponent::read_step_() {
|
||||
}
|
||||
return;
|
||||
}
|
||||
this->publish_measurement_();
|
||||
this->status_clear_warning();
|
||||
this->state_ = State::STATE_IDLE;
|
||||
this->disable_loop();
|
||||
}
|
||||
|
||||
// A basic count is the raw count scaled to one unit of gain and one millisecond of integration
|
||||
// time, which makes readings taken with different settings comparable. The manufacturer's
|
||||
// conversion factors are calibrated against that definition.
|
||||
void AS734XComponent::calculate_basic_counts_() {
|
||||
this->basic_counts_.fill(0.0f);
|
||||
this->max_basic_count_ = 0.0f;
|
||||
this->max_band_basic_count_ = 0.0f;
|
||||
this->clear_basic_count_ = 0.0f;
|
||||
|
||||
// The gain enum counts in powers of two starting at 0.5x, so the multiplier is 2^gain / 2.
|
||||
const float gain_x = static_cast<float>(1 << this->reading_gain_) / 2.0f;
|
||||
const float integration_ms = (1.0f + this->reading_atime_) * (1.0f + this->reading_astep_) * 2.78e-3f;
|
||||
if (gain_x <= 0.0f || integration_ms <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
const float inv_exposure = 1.0f / (gain_x * integration_ms);
|
||||
const ChipInfo *chip = this->chip_;
|
||||
const uint16_t *wavelengths = pgm(chip->wavelengths);
|
||||
const float *gain_correction = pgm(chip->gain_correction);
|
||||
const bool per_band = pgm(chip->gain_correction_per_band);
|
||||
const uint8_t correction_base = per_band ? this->reading_gain_ * NUM_BANDS : this->reading_gain_;
|
||||
|
||||
for (uint8_t band = 0; band < NUM_BANDS; band++) {
|
||||
const uint16_t wavelength = pgm(wavelengths[band]);
|
||||
if (wavelength == ABSENT_NM) {
|
||||
continue;
|
||||
}
|
||||
float basic_count = this->raw_counts_[band] * inv_exposure;
|
||||
basic_count -= this->dark_current_[band];
|
||||
basic_count = std::max(basic_count, 0.0f);
|
||||
basic_count *= pgm(gain_correction[per_band ? correction_base + band : correction_base]);
|
||||
basic_count *= this->channel_correction_[band];
|
||||
basic_count *= this->glass_attenuation_factor_;
|
||||
|
||||
this->basic_counts_[band] = basic_count;
|
||||
this->max_basic_count_ = std::max(this->max_basic_count_, basic_count);
|
||||
if (wavelength == WIDEBAND_NM) {
|
||||
this->clear_basic_count_ = basic_count;
|
||||
} else if (wavelength >= VISIBLE_MIN_NM && wavelength <= VISIBLE_MAX_NM) {
|
||||
this->max_band_basic_count_ = std::max(this->max_band_basic_count_, basic_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float AS734XComponent::normalization_divisor_() const {
|
||||
float divisor = 1.0f;
|
||||
switch (this->normalization_) {
|
||||
case Normalization::NORMALIZATION_ALL:
|
||||
divisor = this->max_basic_count_;
|
||||
break;
|
||||
case Normalization::NORMALIZATION_BANDS:
|
||||
divisor = this->max_band_basic_count_;
|
||||
break;
|
||||
case Normalization::NORMALIZATION_CLEAR:
|
||||
divisor = this->clear_basic_count_;
|
||||
break;
|
||||
case Normalization::NORMALIZATION_NONE:
|
||||
return 1.0f;
|
||||
}
|
||||
// In the dark the reference can be zero, and dividing by that would publish infinities.
|
||||
return divisor > 0.0f ? divisor : 1.0f;
|
||||
}
|
||||
|
||||
void AS734XComponent::publish_basic_counts_() {
|
||||
// Normalization describes the shape of the spectrum rather than its absolute level. Channels
|
||||
// outside the chosen reference can exceed 1.0 and are left that way rather than clipped: with
|
||||
// BANDS the near infrared channel legitimately sits well above the visible bands.
|
||||
const float scale = 1.0f / this->normalization_divisor_();
|
||||
for (uint8_t band = 0; band < NUM_BANDS; band++) {
|
||||
if (this->basic_counts_sensors_[band] != nullptr) {
|
||||
this->basic_counts_sensors_[band]->publish_state(this->basic_counts_[band] * scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AS734XComponent::publish_measurement_() {
|
||||
this->calculate_basic_counts_();
|
||||
for (uint8_t band = 0; band < NUM_BANDS; band++) {
|
||||
if (this->sensors_[band] != nullptr) {
|
||||
this->sensors_[band]->publish_state(this->raw_counts_[band]);
|
||||
}
|
||||
}
|
||||
this->publish_basic_counts_();
|
||||
if (this->saturation_level_sensor_ != nullptr) {
|
||||
const uint32_t max_adc =
|
||||
std::min<uint32_t>((this->reading_atime_ + 1UL) * (this->reading_astep_ + 1UL), MAX_ADC_COUNT);
|
||||
const float level = max_adc == 0 ? 0.0f : 100.0f * this->peak_raw_count_ / max_adc;
|
||||
ESP_LOGV(TAG, "Highest ADC count %u of %u (%.1f%%)", this->peak_raw_count_, static_cast<unsigned>(max_adc), level);
|
||||
this->saturation_level_sensor_->publish_state(level);
|
||||
}
|
||||
}
|
||||
|
||||
void AS734XComponent::abort_measurement_(const LogString *reason) {
|
||||
ESP_LOGW(TAG, "%s", LOG_STR_ARG(reason));
|
||||
this->write_byte(REG_ENABLE, ENABLE_PON);
|
||||
|
||||
@@ -26,7 +26,26 @@ enum Gain : uint8_t {
|
||||
GAIN_2048X, // AS7343 only
|
||||
};
|
||||
|
||||
static constexpr uint8_t NUM_BANDS = 13; // band order of BANDS_43 in __init__.py
|
||||
static constexpr uint8_t GAIN_COUNT = 13; // entries in Gain, for sizing the correction tables
|
||||
static constexpr uint8_t NUM_BANDS = 13; // band order of BANDS_43 in __init__.py
|
||||
|
||||
// What the published basic counts are divided by. The clear channel is wideband, so it is roughly
|
||||
// the size of all the bands together, and the near infrared channel outgrows them under daylight.
|
||||
// Dividing by the largest of every channel therefore leaves the visible bands squeezed into the
|
||||
// bottom of the range.
|
||||
enum class Normalization : uint8_t {
|
||||
NORMALIZATION_NONE, // publish absolute basic counts
|
||||
NORMALIZATION_ALL, // largest of every channel
|
||||
NORMALIZATION_BANDS, // largest of the visible bands only
|
||||
NORMALIZATION_CLEAR, // the wideband clear channel
|
||||
};
|
||||
|
||||
// Nominal centre wavelengths in nanometres bound the visible bands. The clear channel is wideband
|
||||
// and has no single centre wavelength, so it is marked with zero instead.
|
||||
static constexpr uint16_t VISIBLE_MIN_NM = 380;
|
||||
static constexpr uint16_t VISIBLE_MAX_NM = 700;
|
||||
static constexpr uint16_t WIDEBAND_NM = 0;
|
||||
static constexpr uint16_t ABSENT_NM = 0xFFFF; // band this chip does not have
|
||||
|
||||
struct RegValue {
|
||||
uint8_t reg;
|
||||
@@ -36,9 +55,11 @@ struct RegValue {
|
||||
/// Per-chip constants, stored in flash. The pointed-to tables are in flash too.
|
||||
struct ChipInfo {
|
||||
const char *name;
|
||||
const RegValue *init; // written in order; the bank is selected per register address
|
||||
const uint8_t *smux; // AS7341: SMUX RAM image per step; AS7343: the three chain images
|
||||
const uint8_t *readout; // per step, the ADC slot of each band (0xFF: not read in this step)
|
||||
const RegValue *init; // written in order; the bank is selected per register address
|
||||
const uint8_t *smux; // AS7341: SMUX RAM image per step; AS7343: the three chain images
|
||||
const uint8_t *readout; // per step, the ADC slot of each band (0xFF: not read in this step)
|
||||
const uint16_t *wavelengths; // per band, WIDEBAND_NM for clear and ABSENT_NM for a missing band
|
||||
const float *gain_correction; // GAIN_COUNT entries, or GAIN_COUNT * NUM_BANDS when per band
|
||||
uint8_t init_count;
|
||||
uint8_t astep_reg;
|
||||
uint8_t cfg0_reg;
|
||||
@@ -47,12 +68,15 @@ struct ChipInfo {
|
||||
uint8_t id_reg;
|
||||
uint8_t id_mask;
|
||||
uint8_t id_value;
|
||||
uint8_t adc_slots; // 16-bit data registers read after ASTATUS
|
||||
uint8_t smux_steps; // measurements per update
|
||||
uint8_t led_reg; // LED driver register (0: this chip has no LED control)
|
||||
uint8_t led_sel_reg; // register whose LED_SEL bit hands the pin to the driver (0: not needed)
|
||||
uint8_t adc_slots; // 16-bit data registers read after ASTATUS
|
||||
uint8_t smux_steps; // measurements per update
|
||||
uint8_t integration_cycles;
|
||||
uint8_t smux_len; // bytes per SMUX image
|
||||
bool auto_smux; // the chip cycles its SMUX itself after a one-time chain setup
|
||||
uint8_t extra_clear_slot; // averaged into the clear band (0xFF: none)
|
||||
uint8_t smux_len; // bytes per SMUX image
|
||||
bool auto_smux; // the chip cycles its SMUX itself after a one-time chain setup
|
||||
bool gain_correction_per_band; // the correction table is indexed by gain and band, not gain alone
|
||||
uint8_t extra_clear_slot; // averaged into the clear band (0xFF: none)
|
||||
};
|
||||
|
||||
#ifdef USE_AS7341
|
||||
@@ -65,7 +89,9 @@ extern const ChipInfo TCS3448_CHIP;
|
||||
|
||||
class AS734XComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
explicit AS734XComponent(const ChipInfo *chip) : chip_(chip) {}
|
||||
// Channel correction is multiplicative, so it starts at unity rather than zero in case no
|
||||
// calibration is configured.
|
||||
explicit AS734XComponent(const ChipInfo *chip) : chip_(chip) { this->channel_correction_.fill(1.0f); }
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
@@ -75,21 +101,58 @@ class AS734XComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
void set_gain(Gain gain) { this->gain_ = gain; }
|
||||
void set_atime(uint8_t atime) { this->atime_ = atime; }
|
||||
void set_astep(uint16_t astep) { this->astep_ = astep; }
|
||||
|
||||
/// Turns the sensor's LED driver on or off. Meant for lambdas.
|
||||
void enable_led(bool enable);
|
||||
|
||||
// Calibration applied while turning raw counts into basic counts.
|
||||
void set_dark_current(const std::array<float, NUM_BANDS> &values) { this->dark_current_ = values; }
|
||||
void set_channel_correction(const std::array<float, NUM_BANDS> &values) { this->channel_correction_ = values; }
|
||||
void set_glass_attenuation_factor(float factor) { this->glass_attenuation_factor_ = factor; }
|
||||
|
||||
/// Chooses what the published basic counts are divided by.
|
||||
void set_basic_counts_normalization(Normalization normalization) { this->normalization_ = normalization; }
|
||||
|
||||
void set_counts_sensor(sensor::Sensor *sensor, uint8_t band) { this->sensors_[band] = sensor; }
|
||||
void set_basic_counts_sensor(sensor::Sensor *sensor, uint8_t band) { this->basic_counts_sensors_[band] = sensor; }
|
||||
|
||||
SUB_SENSOR(saturation_level)
|
||||
|
||||
protected:
|
||||
bool select_bank_(uint8_t reg);
|
||||
bool update_bits_(uint8_t reg, uint8_t mask, bool value);
|
||||
bool write_exposure_();
|
||||
bool start_step_();
|
||||
void read_step_();
|
||||
void abort_measurement_(const LogString *reason);
|
||||
void calculate_basic_counts_();
|
||||
void publish_measurement_();
|
||||
void publish_basic_counts_();
|
||||
float normalization_divisor_() const;
|
||||
|
||||
std::array<sensor::Sensor *, NUM_BANDS> sensors_{};
|
||||
std::array<sensor::Sensor *, NUM_BANDS> basic_counts_sensors_{};
|
||||
std::array<float, NUM_BANDS> dark_current_{};
|
||||
std::array<float, NUM_BANDS> channel_correction_;
|
||||
std::array<float, NUM_BANDS> basic_counts_{};
|
||||
std::array<uint16_t, NUM_BANDS> raw_counts_{};
|
||||
const ChipInfo *chip_; // in flash, read field by field with progmem_memcpy
|
||||
uint32_t start_ms_{0};
|
||||
uint32_t integration_ms_{0};
|
||||
uint32_t timeout_ms_{0};
|
||||
float glass_attenuation_factor_{1.0f};
|
||||
float max_basic_count_{0.0f}; // largest of every channel
|
||||
float max_band_basic_count_{0.0f}; // largest of the visible bands only
|
||||
float clear_basic_count_{0.0f}; // the wideband clear channel
|
||||
uint16_t astep_{0};
|
||||
uint16_t peak_raw_count_{0}; // highest ADC value of this measurement, before the clear channels are averaged
|
||||
// What the running measurement was started with, so the maths matches the data even if the
|
||||
// configuration is changed while a measurement is in flight.
|
||||
uint16_t reading_astep_{0};
|
||||
Gain reading_gain_{GAIN_1X};
|
||||
uint8_t reading_atime_{0};
|
||||
enum class State : uint8_t { STATE_IDLE, STATE_WAIT_SMUX, STATE_READ_DATA } state_{State::STATE_IDLE};
|
||||
Normalization normalization_{Normalization::NORMALIZATION_NONE};
|
||||
Gain gain_{GAIN_1X};
|
||||
uint8_t atime_{0};
|
||||
uint8_t step_{0};
|
||||
|
||||
@@ -2,7 +2,7 @@ import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
from esphome.components.const import UNIT_COUNTS
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_COUNTS, CONF_TYPE, STATE_CLASS_MEASUREMENT
|
||||
from esphome.const import CONF_COUNTS, CONF_TYPE, STATE_CLASS_MEASUREMENT, UNIT_PERCENT
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
@@ -10,7 +10,11 @@ from . import BANDS_41, BANDS_43, CONF_AS734X_ID, DOMAIN, AS734XComponent
|
||||
|
||||
DEPENDENCIES = [DOMAIN]
|
||||
|
||||
CONF_BASIC_COUNTS = "basic_counts"
|
||||
CONF_SATURATION_LEVEL = "saturation_level"
|
||||
|
||||
ICON_COUNTS = "mdi:counter"
|
||||
ICON_SATURATION = "mdi:weather-sunny-alert"
|
||||
|
||||
_COUNTS_SENSOR = sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_COUNTS,
|
||||
@@ -19,12 +23,31 @@ _COUNTS_SENSOR = sensor.sensor_schema(
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
|
||||
# Basic counts are fractional, so they need decimals where raw counts do not.
|
||||
_BASIC_COUNTS_SENSOR = sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_COUNTS,
|
||||
icon=ICON_COUNTS,
|
||||
accuracy_decimals=6,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
|
||||
_BAND_KEYS = (CONF_COUNTS, CONF_BASIC_COUNTS)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_AS734X_ID): cv.use_id(AS734XComponent),
|
||||
cv.Optional(CONF_COUNTS, default={}): cv.Schema(
|
||||
{cv.Optional(band): _COUNTS_SENSOR for band in BANDS_43}
|
||||
),
|
||||
cv.Optional(CONF_BASIC_COUNTS, default={}): cv.Schema(
|
||||
{cv.Optional(band): _BASIC_COUNTS_SENSOR for band in BANDS_43}
|
||||
),
|
||||
cv.Optional(CONF_SATURATION_LEVEL): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_SATURATION,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -32,12 +55,13 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
full = fv.full_config.get()
|
||||
hub = full.get_config_for_path(full.get_path_for_id(config[CONF_AS734X_ID])[:-1])
|
||||
if hub[CONF_TYPE] == "AS7341" and (
|
||||
extra := [band for band in config[CONF_COUNTS] if band not in BANDS_41]
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"The AS7341 has no {', '.join(extra)} band", path=[CONF_COUNTS, extra[0]]
|
||||
)
|
||||
if hub[CONF_TYPE] != "AS7341":
|
||||
return config
|
||||
for key in _BAND_KEYS:
|
||||
if extra := [band for band in config.get(key, {}) if band not in BANDS_41]:
|
||||
raise cv.Invalid(
|
||||
f"The AS7341 has no {', '.join(extra)} band", path=[key, extra[0]]
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
@@ -47,7 +71,14 @@ FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_AS734X_ID])
|
||||
counts = config[CONF_COUNTS]
|
||||
basic_counts = config[CONF_BASIC_COUNTS]
|
||||
for i, band in enumerate(BANDS_43):
|
||||
if (conf := counts.get(band)) is not None:
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(hub.set_counts_sensor(sens, i))
|
||||
if (conf := basic_counts.get(band)) is not None:
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(hub.set_basic_counts_sensor(sens, i))
|
||||
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_SATURATION_LEVEL, hub.set_saturation_level_sensor)
|
||||
|
||||
@@ -92,3 +92,31 @@ def test_exposure_bound(
|
||||
return
|
||||
with pytest.raises(cv.Invalid, match=error):
|
||||
HUB_SCHEMA(config)
|
||||
|
||||
|
||||
def test_as7343_basic_counts_keep_their_index(generate_main) -> None:
|
||||
main_cpp = generate_main(CONFIG)
|
||||
|
||||
for band in ("f1", "fz", "clear"):
|
||||
assert (
|
||||
f"hub43->set_basic_counts_sensor(b43_{band}, {BAND_INDEX[band]});"
|
||||
in main_cpp
|
||||
)
|
||||
|
||||
|
||||
def test_as7341_calibration_spreads_over_the_band_slots(generate_main) -> None:
|
||||
"""The AS7341 has ten bands, but the C++ side indexes all thirteen slots.
|
||||
|
||||
The three bands it does not have keep the neutral value, so a correction or dark
|
||||
current given for F3 reaches slot 3 rather than slot 2.
|
||||
"""
|
||||
main_cpp = generate_main(CONFIG)
|
||||
|
||||
assert (
|
||||
"hub41->set_channel_correction({1.1f, 1.2f, 1.0f, 1.3f, 1.4f, 1.0f, 1.5f, "
|
||||
"1.0f, 1.6f, 1.7f, 1.8f, 1.9f, 2.0f});" in main_cpp
|
||||
)
|
||||
assert (
|
||||
"hub41->set_dark_current({0.1f, 0.2f, 0.0f, 0.3f, 0.4f, 0.0f, 0.5f, "
|
||||
"0.0f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f});" in main_cpp
|
||||
)
|
||||
|
||||
@@ -11,6 +11,10 @@ i2c:
|
||||
as734x:
|
||||
- id: hub41
|
||||
type: AS7341
|
||||
normalize_basic_counts: ALL
|
||||
calibration:
|
||||
channel_correction: [1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
|
||||
dark_current: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
|
||||
- id: hub43
|
||||
type: AS7343
|
||||
address: 0x3A
|
||||
@@ -45,3 +49,8 @@ sensor:
|
||||
f8: {id: s43_f8, name: s43 f8}
|
||||
nir: {id: s43_nir, name: s43 nir}
|
||||
clear: {id: s43_clear, name: s43 clear}
|
||||
basic_counts:
|
||||
f1: {id: b43_f1, name: b43 f1}
|
||||
fz: {id: b43_fz, name: b43 fz}
|
||||
clear: {id: b43_clear, name: b43 clear}
|
||||
saturation_level: {id: sat43, name: sat 43}
|
||||
|
||||
@@ -6,15 +6,24 @@ as734x:
|
||||
gain: X8
|
||||
atime: 120
|
||||
astep: 99
|
||||
normalize_basic_counts: BANDS
|
||||
calibration:
|
||||
channel_correction: [1.0, 1.01, 1.02, 1.03, 1.04, 1.05, 1.06, 1.07, 1.08, 1.09]
|
||||
dark_current: [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
|
||||
glass_attenuation_factor: 1.2
|
||||
- id: as7343_hub
|
||||
type: AS7343
|
||||
i2c_id: i2c_bus
|
||||
address: 0x3A
|
||||
gain: X512
|
||||
normalize_basic_counts: CLEAR
|
||||
- id: tcs3448_hub
|
||||
type: TCS3448
|
||||
i2c_id: i2c_bus
|
||||
gain: X2048
|
||||
calibration:
|
||||
dark_current:
|
||||
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
|
||||
|
||||
sensor:
|
||||
- platform: as734x
|
||||
@@ -26,6 +35,13 @@ sensor:
|
||||
name: AS7341 NIR
|
||||
clear:
|
||||
name: AS7341 Clear
|
||||
basic_counts:
|
||||
f1:
|
||||
name: AS7341 F1 basic
|
||||
clear:
|
||||
name: AS7341 Clear basic
|
||||
saturation_level:
|
||||
name: AS7341 Saturation
|
||||
- platform: as734x
|
||||
as734x_id: as7343_hub
|
||||
counts:
|
||||
@@ -35,6 +51,13 @@ sensor:
|
||||
name: AS7343 FXL
|
||||
clear:
|
||||
name: AS7343 Clear
|
||||
basic_counts:
|
||||
fz:
|
||||
name: AS7343 FZ basic
|
||||
fy:
|
||||
name: AS7343 FY basic
|
||||
saturation_level:
|
||||
name: AS7343 Saturation
|
||||
- platform: as734x
|
||||
as734x_id: tcs3448_hub
|
||||
counts:
|
||||
|
||||
Reference in New Issue
Block a user