mirror of
https://github.com/esphome/esphome.git
synced 2026-03-23 22:37:31 +08:00
[spa06_base] Add SPA06-003 Temperature and Pressure Sensor (Part 1 of 3) (#14521)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
This commit is contained in:
@@ -457,6 +457,7 @@ esphome/components/sn74hc165/* @jesserockz
|
||||
esphome/components/socket/* @esphome/core
|
||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
|
||||
esphome/components/sound_level/* @kahrendt
|
||||
esphome/components/spa06_base/* @danielkent-net
|
||||
esphome/components/speaker/* @jesserockz @kahrendt
|
||||
esphome/components/speaker/media_player/* @kahrendt @synesthesiam
|
||||
esphome/components/speaker_source/* @kahrendt
|
||||
|
||||
201
esphome/components/spa06_base/__init__.py
Normal file
201
esphome/components/spa06_base/__init__.py
Normal file
@@ -0,0 +1,201 @@
|
||||
import math
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_OVERSAMPLING,
|
||||
CONF_PRESSURE,
|
||||
CONF_SAMPLE_RATE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PASCAL,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@danielkent-net"]
|
||||
|
||||
spa06_ns = cg.esphome_ns.namespace("spa06_base")
|
||||
|
||||
SampleRate = spa06_ns.enum("SampleRate")
|
||||
SAMPLE_RATE_OPTIONS = {
|
||||
"1": SampleRate.SAMPLE_RATE_1,
|
||||
"2": SampleRate.SAMPLE_RATE_2,
|
||||
"4": SampleRate.SAMPLE_RATE_4,
|
||||
"8": SampleRate.SAMPLE_RATE_8,
|
||||
"16": SampleRate.SAMPLE_RATE_16,
|
||||
"32": SampleRate.SAMPLE_RATE_32,
|
||||
"64": SampleRate.SAMPLE_RATE_64,
|
||||
"128": SampleRate.SAMPLE_RATE_128,
|
||||
"25p16": SampleRate.SAMPLE_RATE_25P16,
|
||||
"25p8": SampleRate.SAMPLE_RATE_25P8,
|
||||
"25p4": SampleRate.SAMPLE_RATE_25P4,
|
||||
"25p2": SampleRate.SAMPLE_RATE_25P2,
|
||||
"25": SampleRate.SAMPLE_RATE_25,
|
||||
"50": SampleRate.SAMPLE_RATE_50,
|
||||
"100": SampleRate.SAMPLE_RATE_100,
|
||||
"200": SampleRate.SAMPLE_RATE_200,
|
||||
}
|
||||
|
||||
Oversampling = spa06_ns.enum("Oversampling")
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
"NONE": Oversampling.OVERSAMPLING_NONE,
|
||||
"2X": Oversampling.OVERSAMPLING_X2,
|
||||
"4X": Oversampling.OVERSAMPLING_X4,
|
||||
"8X": Oversampling.OVERSAMPLING_X8,
|
||||
"16X": Oversampling.OVERSAMPLING_X16,
|
||||
"32X": Oversampling.OVERSAMPLING_X32,
|
||||
"64X": Oversampling.OVERSAMPLING_X64,
|
||||
"128X": Oversampling.OVERSAMPLING_X128,
|
||||
}
|
||||
|
||||
SPA06Component = spa06_ns.class_("SPA06Component", cg.PollingComponent)
|
||||
|
||||
|
||||
def spa_oversample_time(oversample):
|
||||
# Pressure oversampling conversion times are listed on datasheet Pg. 26
|
||||
# Datasheet does not have a table for temperature oversampling;
|
||||
# assumption is that it is the same as pressure
|
||||
OVERSAMPLING_CONVERSION_TIMES = {
|
||||
"NONE": 3.6,
|
||||
"2X": 5.2,
|
||||
"4X": 8.4,
|
||||
"8X": 14.8,
|
||||
"16X": 27.6,
|
||||
"32X": 53.2,
|
||||
"64X": 104.4,
|
||||
"128X": 206.8,
|
||||
}
|
||||
return OVERSAMPLING_CONVERSION_TIMES[oversample]
|
||||
|
||||
|
||||
def spa_sample_rate(rate):
|
||||
SAMPLE_RATE_OPTIONS_HZ = {
|
||||
"1": 1.0,
|
||||
"2": 2.0,
|
||||
"4": 4.0,
|
||||
"8": 8.0,
|
||||
"16": 16.0,
|
||||
"32": 32.0,
|
||||
"64": 64.0,
|
||||
"128": 128.0,
|
||||
"25p16": 25.0 / 16.0,
|
||||
"25p8": 25.0 / 8.0,
|
||||
"25p4": 25.0 / 4.0,
|
||||
"25p2": 25.0 / 2.0,
|
||||
"25": 25.0,
|
||||
"50": 50.0,
|
||||
"100": 100.0,
|
||||
"200": 200.0,
|
||||
}
|
||||
return SAMPLE_RATE_OPTIONS_HZ[rate]
|
||||
|
||||
|
||||
def compute_measurement_conversion_time(config):
|
||||
# - adds up sensor conversion time based on temperature and pressure oversampling rates given in datasheet
|
||||
# - returns a rounded up time in ms
|
||||
|
||||
# No conversion time necessary without a pressure sensor
|
||||
pressure_conversion_time = 0.0
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
pressure_conversion_time = spa_oversample_time(
|
||||
pressure_config.get(CONF_OVERSAMPLING)
|
||||
)
|
||||
# Temperature required in all cases, default to minimum sample time
|
||||
temperature_conversion_time = 3.6
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
temperature_conversion_time = spa_oversample_time(
|
||||
temperature_config.get(CONF_OVERSAMPLING)
|
||||
)
|
||||
|
||||
# TODO: Read datasheet to find conversion time error
|
||||
return math.ceil(1.05 * (pressure_conversion_time + temperature_conversion_time))
|
||||
|
||||
|
||||
def measurement_timing_check(config):
|
||||
|
||||
temp_time = 0.0
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
temp_oss = (
|
||||
spa_oversample_time(temperature_config.get(CONF_OVERSAMPLING)) / 1000.0
|
||||
)
|
||||
temp_hz = spa_sample_rate(temperature_config.get(CONF_SAMPLE_RATE))
|
||||
temp_time = temp_oss * temp_hz
|
||||
|
||||
pres_time = 0.0
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
pres_oss = spa_oversample_time(pressure_config.get(CONF_OVERSAMPLING)) / 1000.0
|
||||
pres_hz = spa_sample_rate(pressure_config.get(CONF_SAMPLE_RATE))
|
||||
pres_time = pres_oss * pres_hz
|
||||
|
||||
if temp_time + pres_time >= 1:
|
||||
raise cv.Invalid(
|
||||
"Combined sample_rate and oversampling for temperature and pressure is too high"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA_BASE = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SPA06Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="NONE"): cv.enum(
|
||||
OVERSAMPLING_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_SAMPLE_RATE, default="1"): cv.enum(
|
||||
SAMPLE_RATE_OPTIONS, lower=True
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PASCAL,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
OVERSAMPLING_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_SAMPLE_RATE, default="1"): cv.enum(
|
||||
SAMPLE_RATE_OPTIONS, lower=True
|
||||
),
|
||||
}
|
||||
),
|
||||
},
|
||||
).extend(cv.polling_component_schema("60s"))
|
||||
CONFIG_SCHEMA_BASE.add_extra(measurement_timing_check)
|
||||
|
||||
|
||||
async def to_code_base(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
cg.add(
|
||||
var.set_temperature_oversampling_config(
|
||||
temperature_config[CONF_OVERSAMPLING]
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
var.set_temperature_sample_rate_config(temperature_config[CONF_SAMPLE_RATE])
|
||||
)
|
||||
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure_config)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
cg.add(var.set_pressure_oversampling_config(pressure_config[CONF_OVERSAMPLING]))
|
||||
cg.add(var.set_pressure_sample_rate_config(pressure_config[CONF_SAMPLE_RATE]))
|
||||
|
||||
cg.add(var.set_conversion_time(compute_measurement_conversion_time(config)))
|
||||
return var
|
||||
320
esphome/components/spa06_base/spa06_base.cpp
Normal file
320
esphome/components/spa06_base/spa06_base.cpp
Normal file
@@ -0,0 +1,320 @@
|
||||
#include "spa06_base.h"
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::spa06_base {
|
||||
|
||||
static const char *const TAG = "spa06";
|
||||
|
||||
// Sign extension function for <=16 bit types
|
||||
inline int16_t decode16(uint8_t msb, uint8_t lsb, size_t bits, size_t head = 0) {
|
||||
return static_cast<int16_t>(encode_uint16(msb, lsb) << head) >> (16 - bits);
|
||||
}
|
||||
|
||||
// Sign extension function for <=32 bit types
|
||||
inline int32_t decode32(uint8_t xmsb, uint8_t msb, uint8_t lsb, uint8_t xlsb, size_t bits, size_t head = 0) {
|
||||
return static_cast<int32_t>(encode_uint32(xmsb, msb, lsb, xlsb) << head) >> (32 - bits);
|
||||
}
|
||||
|
||||
void SPA06Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "SPA06:");
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
ESP_LOGCONFIG(TAG, " Measurement conversion time: %ums", this->conversion_time_);
|
||||
if (this->temperature_sensor_) {
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Oversampling: %s\n"
|
||||
" Rate: %s",
|
||||
LOG_STR_ARG(oversampling_to_str(this->temperature_oversampling_)),
|
||||
LOG_STR_ARG(meas_rate_to_str(this->temperature_rate_)));
|
||||
}
|
||||
if (this->pressure_sensor_) {
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Oversampling: %s\n"
|
||||
" Rate: %s",
|
||||
LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_)),
|
||||
LOG_STR_ARG(meas_rate_to_str(this->pressure_rate_)));
|
||||
}
|
||||
}
|
||||
|
||||
void SPA06Component::setup() {
|
||||
// Startup sequence for SPA06 (Pg. 16, Figure 4.6.4):
|
||||
// 1. Perform a soft reset
|
||||
// 2. Verify sensor chip ID matches
|
||||
// 3. Verify coefficients are ready
|
||||
// 4. Read coefficients
|
||||
// 5. Configure temperature and pressure sensors
|
||||
// 6. Write communication settings
|
||||
// 7. Write measurement settings (background measurement mode)
|
||||
|
||||
// 1. Soft reset
|
||||
if (!this->soft_reset_()) {
|
||||
this->mark_failed(LOG_STR("Reset failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
// soft_reset_() internally delays by 3ms to make sure that
|
||||
// the sensor is in a ready state and coefficients are ready.
|
||||
|
||||
// 2. Read chip ID
|
||||
// TODO: check ID for consistency?
|
||||
if (!spa_read_byte(SPA06_ID, &this->prod_id_.reg)) {
|
||||
this->mark_failed(LOG_STR("Chip ID read failure"));
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG,
|
||||
"Product Info:\n"
|
||||
" Prod ID: %u\n"
|
||||
" Rev ID: %u",
|
||||
this->prod_id_.bit.prod_id, this->prod_id_.bit.rev_id);
|
||||
|
||||
// 3. Read chip readiness from MEAS_CFG
|
||||
// First check if the sensor reports ready
|
||||
if (!spa_read_byte(SPA06_MEAS_CFG, &this->meas_.reg)) {
|
||||
this->mark_failed(LOG_STR("Sensor status read failure"));
|
||||
return;
|
||||
}
|
||||
// Check if the sensor reports coefficients are ready
|
||||
if (!meas_.bit.coef_ready) {
|
||||
this->mark_failed(LOG_STR("Coefficients not ready"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Read coefficients
|
||||
if (!this->read_coefficients_()) {
|
||||
this->mark_failed(LOG_STR("Coefficients read error"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Configure temperature and pressure sensors
|
||||
// Default to measuring both temperature and pressure
|
||||
|
||||
// Temperature must be read regardless of configuration to compute pressure
|
||||
// If temperature is not configured in config:
|
||||
// - No oversampling is used
|
||||
// - Lowest possible rate is configured
|
||||
if (!this->temperature_sensor_) {
|
||||
this->temperature_rate_ = SAMPLE_RATE_1;
|
||||
this->temperature_oversampling_ = OVERSAMPLING_NONE;
|
||||
this->kt_ = oversampling_to_scale_factor(OVERSAMPLING_NONE);
|
||||
}
|
||||
|
||||
// If pressure is not configured in config
|
||||
// - No oversampling is used
|
||||
// - Lowest possible rate is configured
|
||||
if (!this->pressure_sensor_) {
|
||||
this->pressure_rate_ = SAMPLE_RATE_1;
|
||||
this->pressure_oversampling_ = OVERSAMPLING_NONE;
|
||||
this->kp_ = oversampling_to_scale_factor(OVERSAMPLING_NONE);
|
||||
}
|
||||
|
||||
// Write temperature settings
|
||||
if (!write_temperature_settings_(this->temperature_oversampling_, this->temperature_rate_)) {
|
||||
this->mark_failed(LOG_STR("Temperature settings write fail"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Write pressure settings
|
||||
if (!write_pressure_settings_(this->pressure_oversampling_, this->pressure_rate_)) {
|
||||
this->mark_failed(LOG_STR("Pressure settings write fail"));
|
||||
return;
|
||||
}
|
||||
// 6. Write communication settings
|
||||
// This call sets the bit shifts for pressure and temperature if
|
||||
// their respective oversampling config is > X8
|
||||
// This call also disables interrupts, FIFO, and specifies SPI 4-wire
|
||||
if (!write_communication_settings_(this->pressure_oversampling_ > OVERSAMPLING_X8,
|
||||
this->temperature_oversampling_ > OVERSAMPLING_X8)) {
|
||||
this->mark_failed(LOG_STR("Comm settings write fail"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 7. Write measurement settings
|
||||
// This function sets background measurement mode without FIFO
|
||||
if (!write_measurement_settings_(this->pressure_sensor_ ? MeasCrtl::MEASCRTL_BG_BOTH : MeasCrtl::MEASCRTL_BG_TEMP)) {
|
||||
this->mark_failed(LOG_STR("Measurement settings write fail"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool SPA06Component::write_temperature_settings_(Oversampling oversampling, SampleRate rate) {
|
||||
return this->write_sensor_settings_(oversampling, rate, SPA06_TMP_CFG);
|
||||
}
|
||||
|
||||
bool SPA06Component::write_pressure_settings_(Oversampling oversampling, SampleRate rate) {
|
||||
return this->write_sensor_settings_(oversampling, rate, SPA06_PSR_CFG);
|
||||
}
|
||||
|
||||
bool SPA06Component::write_sensor_settings_(Oversampling oversampling, SampleRate rate, uint8_t reg) {
|
||||
if (reg != SPA06_PSR_CFG && reg != SPA06_TMP_CFG) {
|
||||
return false;
|
||||
}
|
||||
this->pt_meas_cfg_.bit.rate = rate;
|
||||
this->pt_meas_cfg_.bit.prc = oversampling;
|
||||
ESP_LOGD(TAG, "Config write: %02x", this->pt_meas_cfg_.reg);
|
||||
return spa_write_byte(reg, this->pt_meas_cfg_.reg);
|
||||
}
|
||||
|
||||
bool SPA06Component::write_measurement_settings_(MeasCrtl crtl) {
|
||||
this->meas_.bit.meas_crtl = crtl;
|
||||
return spa_write_byte(SPA06_MEAS_CFG, this->meas_.reg);
|
||||
}
|
||||
|
||||
bool SPA06Component::write_communication_settings_(bool pressure_shift, bool temperature_shift, bool interrupt_hl,
|
||||
bool interrupt_fifo, bool interrupt_tmp, bool interrupt_prs,
|
||||
bool enable_fifo, bool spi_3wire) {
|
||||
this->cfg_.bit.p_shift = pressure_shift;
|
||||
this->cfg_.bit.t_shift = temperature_shift;
|
||||
this->cfg_.bit.int_hl = interrupt_hl;
|
||||
this->cfg_.bit.int_fifo = interrupt_fifo;
|
||||
this->cfg_.bit.int_tmp = interrupt_tmp;
|
||||
this->cfg_.bit.int_prs = interrupt_prs;
|
||||
this->cfg_.bit.fifo_en = enable_fifo;
|
||||
this->cfg_.bit.spi_3wire = spi_3wire;
|
||||
return spa_write_byte(SPA06_CFG_REG, this->cfg_.reg);
|
||||
}
|
||||
|
||||
bool SPA06Component::read_coefficients_() {
|
||||
uint8_t coef[SPA06_COEF_LEN];
|
||||
if (!spa_read_bytes(SPA06_COEF, coef, SPA06_COEF_LEN)) {
|
||||
return false;
|
||||
}
|
||||
this->c0_ = decode16(coef[0], coef[1], 12);
|
||||
this->c1_ = decode16(coef[1], coef[2], 12, 4);
|
||||
this->c00_ = decode32(coef[3], coef[4], coef[5], 0, 20);
|
||||
this->c10_ = decode32(coef[5], coef[6], coef[7], 0, 20, 4);
|
||||
this->c01_ = decode16(coef[8], coef[9], 16);
|
||||
this->c11_ = decode16(coef[10], coef[11], 16);
|
||||
this->c20_ = decode16(coef[12], coef[13], 16);
|
||||
this->c21_ = decode16(coef[14], coef[15], 16);
|
||||
this->c30_ = decode16(coef[16], coef[17], 16);
|
||||
this->c31_ = decode16(coef[18], coef[19], 12);
|
||||
this->c40_ = decode16(coef[19], coef[20], 12, 4);
|
||||
|
||||
ESP_LOGV(TAG,
|
||||
"Coefficients:\n"
|
||||
" c0: %i, c1: %i,\n"
|
||||
" c00: %i, c10: %i, c20: %i, c30: %i, c40: %i,\n"
|
||||
" c01: %i, c11: %i, c21: %i, c31: %i",
|
||||
this->c0_, this->c1_, this->c00_, this->c10_, this->c20_, this->c30_, this->c40_, this->c01_, this->c11_,
|
||||
this->c21_, this->c31_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SPA06Component::soft_reset_() {
|
||||
// Setup steps for SPA06:
|
||||
// 1. Perform a protocol reset (required to write command for SPI code, noop for I2C)
|
||||
this->protocol_reset();
|
||||
|
||||
// 2. Perform the actual reset
|
||||
this->reset_.bit.fifo_flush = true;
|
||||
this->reset_.bit.soft_rst = SPA06_SOFT_RESET;
|
||||
if (!this->spa_write_byte(SPA06_RESET, this->reset_.reg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. Wait for chip to become ready. Datasheet specifies 2 ms; wait 3
|
||||
delay(3);
|
||||
// 4. Perform another protocol reset (required for SPI code, noop for I2C)
|
||||
this->protocol_reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Temperature conversion formula. See datasheet pg. 14
|
||||
float SPA06Component::convert_temperature_(const float &t_raw_sc) { return this->c0_ * 0.5 + this->c1_ * t_raw_sc; }
|
||||
// Pressure conversion formula. See datasheet pg. 14
|
||||
float SPA06Component::convert_pressure_(const float &p_raw_sc, const float &t_raw_sc) {
|
||||
float p2_raw_sc = p_raw_sc * p_raw_sc;
|
||||
float p3_raw_sc = p2_raw_sc * p_raw_sc;
|
||||
float p4_raw_sc = p3_raw_sc * p_raw_sc;
|
||||
return this->c00_ + (float) this->c10_ * p_raw_sc + (float) this->c20_ * p2_raw_sc + (float) this->c30_ * p3_raw_sc +
|
||||
(float) this->c40_ * p4_raw_sc +
|
||||
t_raw_sc * ((float) this->c01_ + (float) this->c11_ * p_raw_sc + (float) this->c21_ * p2_raw_sc +
|
||||
(float) this->c31_ * p3_raw_sc);
|
||||
}
|
||||
|
||||
void SPA06Component::update() {
|
||||
// Verify either a temperature or pressure sensor is defined before proceeding
|
||||
if ((!this->temperature_sensor_) && (!this->pressure_sensor_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue a background task for retrieving the measurement
|
||||
this->set_timeout("measurement", this->conversion_time_, [this]() {
|
||||
float raw_temperature;
|
||||
float temperature = 0.0;
|
||||
float pressure = 0.0;
|
||||
|
||||
// Check measurement register for readiness
|
||||
if (!this->spa_read_byte(SPA06_MEAS_CFG, &this->meas_.reg)) {
|
||||
ESP_LOGW(TAG, "Cannot read meas config");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
if (this->pressure_sensor_) {
|
||||
if (!this->meas_.bit.prs_ready || !this->meas_.bit.tmp_ready) {
|
||||
ESP_LOGW(TAG, "Temperature and pressure not ready");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
if (!this->read_temperature_and_pressure_(temperature, pressure, raw_temperature)) {
|
||||
ESP_LOGW(TAG, "Temperature and pressure read failure");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!this->meas_.bit.tmp_ready) {
|
||||
ESP_LOGW(TAG, "Temperature not ready");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
if (!this->read_temperature_(temperature, raw_temperature)) {
|
||||
ESP_LOGW(TAG, "Temperature read fail");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this->temperature_sensor_) {
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "No temperature sensor configured");
|
||||
}
|
||||
if (this->pressure_sensor_) {
|
||||
this->pressure_sensor_->publish_state(pressure);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "No pressure sensor configured");
|
||||
}
|
||||
this->status_clear_warning();
|
||||
});
|
||||
}
|
||||
|
||||
bool SPA06Component::read_temperature_and_pressure_(float &temperature, float &pressure, float &t_raw_sc) {
|
||||
// Temperature read and decode
|
||||
if (!this->read_temperature_(temperature, t_raw_sc)) {
|
||||
return false;
|
||||
}
|
||||
// Read raw pressure from device
|
||||
uint8_t buf[3];
|
||||
if (!this->spa_read_bytes(SPA06_PSR, buf, 3)) {
|
||||
return false;
|
||||
}
|
||||
// Calculate raw scaled pressure value
|
||||
float p_raw_sc = (float) decode32(buf[0], buf[1], buf[2], 0, 24) / (float) this->kp_;
|
||||
|
||||
// Calculate full pressure values
|
||||
pressure = this->convert_pressure_(p_raw_sc, t_raw_sc);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SPA06Component::read_temperature_(float &temperature, float &t_raw_sc) {
|
||||
uint8_t buf[3];
|
||||
if (!this->spa_read_bytes(SPA06_TMP, buf, 3)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
t_raw_sc = (float) decode32(buf[0], buf[1], buf[2], 0, 24) / (float) this->kt_;
|
||||
temperature = this->convert_temperature_(t_raw_sc);
|
||||
return true;
|
||||
}
|
||||
} // namespace esphome::spa06_base
|
||||
257
esphome/components/spa06_base/spa06_base.h
Normal file
257
esphome/components/spa06_base/spa06_base.h
Normal file
@@ -0,0 +1,257 @@
|
||||
// SPA06 interface code for ESPHome
|
||||
// All datasheet page references refer to Goermicro SPA06-003 datasheet version 2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
namespace esphome::spa06_base {
|
||||
|
||||
// Read sizes. All other registers are size 1
|
||||
constexpr size_t SPA06_MEAS_LEN = 3;
|
||||
constexpr size_t SPA06_COEF_LEN = 21;
|
||||
|
||||
// Soft reset command (0b1001, 0x9)
|
||||
constexpr uint8_t SPA06_SOFT_RESET = 0x9;
|
||||
|
||||
// SPA06 Register Addresses
|
||||
enum Register : uint8_t {
|
||||
SPA06_PSR = 0x00, // Pressure Reading MSB (or all 3)
|
||||
SPA06_PSR_B1 = 0x01, // Pressure Reading LSB
|
||||
SPA06_PSR_B0 = 0x02, // Pressure Reading XLSB (LSB: Pressure flag in FIFO)
|
||||
SPA06_TMP = 0x03, // Temperature Reading MSB (or all 3)
|
||||
SPA06_TMP_B1 = 0x04, // Temperature Reading LSB
|
||||
SPA06_TMP_B0 = 0x05, // Temperature Reading XLSB
|
||||
SPA06_PSR_CFG = 0x06, // Pressure Configuration
|
||||
SPA06_TMP_CFG = 0x07, // Temperature Configuration
|
||||
SPA06_MEAS_CFG = 0x08, // Measurement Configuration (includes readiness)
|
||||
SPA06_CFG_REG = 0x09, // Configuration Register
|
||||
SPA06_INT_STS = 0x0A, // Interrupt Status
|
||||
SPA06_FIFO_STS = 0x0B, // FIFO Status
|
||||
SPA06_RESET = 0x0C, // Reset + FIFO Flush
|
||||
SPA06_ID = 0x0D, // Product ID and revision
|
||||
SPA06_COEF = 0x10, // Coefficients (0x10-0x24)
|
||||
SPA06_INVALID_CMD = 0x25, // End of enum command
|
||||
};
|
||||
|
||||
// Oversampling config.
|
||||
enum Oversampling : uint8_t {
|
||||
OVERSAMPLING_NONE = 0x0,
|
||||
OVERSAMPLING_X2 = 0x1,
|
||||
OVERSAMPLING_X4 = 0x2,
|
||||
OVERSAMPLING_X8 = 0x3,
|
||||
OVERSAMPLING_X16 = 0x4,
|
||||
OVERSAMPLING_X32 = 0x5,
|
||||
OVERSAMPLING_X64 = 0x6,
|
||||
OVERSAMPLING_X128 = 0x7,
|
||||
OVERSAMPLING_COUNT = 0x8,
|
||||
};
|
||||
|
||||
// Measuring rate config
|
||||
enum SampleRate : uint8_t {
|
||||
SAMPLE_RATE_1 = 0x0,
|
||||
SAMPLE_RATE_2 = 0x1,
|
||||
SAMPLE_RATE_4 = 0x2,
|
||||
SAMPLE_RATE_8 = 0x3,
|
||||
SAMPLE_RATE_16 = 0x4,
|
||||
SAMPLE_RATE_32 = 0x5,
|
||||
SAMPLE_RATE_64 = 0x6,
|
||||
SAMPLE_RATE_128 = 0x7,
|
||||
SAMPLE_RATE_25P16 = 0x8,
|
||||
SAMPLE_RATE_25P8 = 0x9,
|
||||
SAMPLE_RATE_25P4 = 0xA,
|
||||
SAMPLE_RATE_25P2 = 0xB,
|
||||
SAMPLE_RATE_25 = 0xC,
|
||||
SAMPLE_RATE_50 = 0xD,
|
||||
SAMPLE_RATE_100 = 0xE,
|
||||
SAMPLE_RATE_200 = 0xF,
|
||||
};
|
||||
|
||||
// Measuring control config, set in MEAS_CFG register.
|
||||
// See datasheet pages 28-29
|
||||
enum MeasCrtl : uint8_t {
|
||||
MEASCRTL_IDLE = 0x0,
|
||||
MEASCRTL_PRES = 0x1,
|
||||
MEASCRTL_TEMP = 0x2,
|
||||
MEASCRTL_BG_PRES = 0x5,
|
||||
MEASCRTL_BG_TEMP = 0x6,
|
||||
MEASCRTL_BG_BOTH = 0x7,
|
||||
};
|
||||
|
||||
// Oversampling scale factors. See datasheet page 15.
|
||||
constexpr uint32_t OVERSAMPLING_K_LUT[8] = {524288, 1572864, 3670016, 7864320, 253952, 516096, 1040384, 2088960};
|
||||
PROGMEM_STRING_TABLE(MeasRateStrings, "1Hz", "2Hz", "4Hz", "8Hz", "16Hz", "32Hz", "64Hz", "128Hz", "1.5625Hz",
|
||||
"3.125Hz", "6.25Hz", "12.5Hz", "25Hz", "50Hz", "100Hz", "200Hz");
|
||||
PROGMEM_STRING_TABLE(OversamplingStrings, "X1", "X2", "X4", "X8", "X16", "X32", "X64", "X128");
|
||||
|
||||
inline static const LogString *oversampling_to_str(const Oversampling oversampling) {
|
||||
return OversamplingStrings::get_log_str(static_cast<uint8_t>(oversampling), OversamplingStrings::LAST_INDEX);
|
||||
}
|
||||
inline static const LogString *meas_rate_to_str(SampleRate rate) {
|
||||
return MeasRateStrings::get_log_str(static_cast<uint8_t>(rate), MeasRateStrings::LAST_INDEX);
|
||||
}
|
||||
inline uint32_t oversampling_to_scale_factor(const Oversampling oversampling) {
|
||||
return OVERSAMPLING_K_LUT[static_cast<uint8_t>(oversampling)];
|
||||
};
|
||||
|
||||
class SPA06Component : public PollingComponent {
|
||||
public:
|
||||
//// Standard ESPHome component class functions
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
|
||||
//// ESPHome-side settings
|
||||
void set_conversion_time(uint16_t conversion_time) { this->conversion_time_ = conversion_time; }
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { this->pressure_sensor_ = pressure_sensor; }
|
||||
void set_temperature_oversampling_config(Oversampling temperature_oversampling) {
|
||||
this->temperature_oversampling_ = temperature_oversampling;
|
||||
this->kt_ = oversampling_to_scale_factor(temperature_oversampling);
|
||||
}
|
||||
void set_pressure_oversampling_config(Oversampling pressure_oversampling) {
|
||||
this->pressure_oversampling_ = pressure_oversampling;
|
||||
this->kp_ = oversampling_to_scale_factor(pressure_oversampling);
|
||||
}
|
||||
void set_pressure_sample_rate_config(SampleRate rate) { this->pressure_rate_ = rate; }
|
||||
void set_temperature_sample_rate_config(SampleRate rate) { this->temperature_rate_ = rate; }
|
||||
|
||||
protected:
|
||||
// Virtual read functions. Implemented in SPI/I2C components
|
||||
virtual bool spa_read_byte(uint8_t reg, uint8_t *data) = 0;
|
||||
virtual bool spa_write_byte(uint8_t reg, uint8_t data) = 0;
|
||||
virtual bool spa_read_bytes(uint8_t reg, uint8_t *data, size_t len) = 0;
|
||||
virtual bool spa_write_bytes(uint8_t reg, uint8_t *data, size_t len) = 0;
|
||||
|
||||
//// Protocol-specific read functions
|
||||
// Soft reset
|
||||
bool soft_reset_();
|
||||
// Protocol-specific reset (used for SPI only, implemented as noop for I2C)
|
||||
virtual void protocol_reset() {}
|
||||
// Read temperature and calculate Celsius and scaled raw temperatures
|
||||
bool read_temperature_(float &temperature, float &t_raw_sc);
|
||||
// No pressure only read! Pressure calculation depends on scaled temperature value
|
||||
// Read temperature and calculate Celsius temperature, Pascal pressure, and scaled raw temperature
|
||||
bool read_temperature_and_pressure_(float &temperature, float &pressure, float &t_raw_sc);
|
||||
// Read coefficients. Stores in class variables.
|
||||
bool read_coefficients_();
|
||||
|
||||
//// Protocol-specific write functions
|
||||
// Write temperature settings to TMP_CFG register
|
||||
bool write_temperature_settings_(Oversampling oversampling, SampleRate rate);
|
||||
// Write pressure settings to PRS_CFG register
|
||||
bool write_pressure_settings_(Oversampling oversampling, SampleRate rate);
|
||||
// Write measurement settings to MEAS_CRTL register
|
||||
bool write_measurement_settings_(MeasCrtl crtl);
|
||||
|
||||
// Write communication settings to CFG_REG register
|
||||
// Set pressure_shift to true if pressure oversampling >X8
|
||||
// Set temperature_shift to true if temperature oversampling >X8
|
||||
bool write_communication_settings_(bool pressure_shift, bool temperature_shift, bool interrupt_hl = false,
|
||||
bool interrupt_fifo = false, bool interrupt_tmp = false,
|
||||
bool interrupt_prs = false, bool enable_fifo = false, bool spi_3wire = false);
|
||||
|
||||
//// Protocol helper functions
|
||||
// Write function for both temperature and pressure (deduplicates code)
|
||||
bool write_sensor_settings_(Oversampling oversampling, SampleRate rate, uint8_t reg);
|
||||
// Convert raw temperature reading into Celsius
|
||||
float convert_temperature_(const float &t_raw_sc);
|
||||
// Convert raw pressure and scaled raw temperature into Pascals
|
||||
float convert_pressure_(const float &p_raw_sc, const float &t_raw_sc);
|
||||
|
||||
//// Protocol-related variables
|
||||
// Oversampling scale factors. Defaults are for X16 (pressure) and X1 (temp)
|
||||
uint32_t kp_{253952}, kt_{524288};
|
||||
// Coefficients for calculating pressure and temperature from raw values
|
||||
// Obtained from IC during setup
|
||||
int32_t c00_{0}, c10_{0};
|
||||
int16_t c0_{0}, c1_{0}, c01_{0}, c11_{0}, c20_{0}, c21_{0}, c30_{0}, c31_{0}, c40_{0};
|
||||
|
||||
//// ESPHome class objects and configuration
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
Oversampling temperature_oversampling_{Oversampling::OVERSAMPLING_NONE};
|
||||
Oversampling pressure_oversampling_{Oversampling::OVERSAMPLING_X16};
|
||||
SampleRate temperature_rate_{SampleRate::SAMPLE_RATE_1};
|
||||
SampleRate pressure_rate_{SampleRate::SAMPLE_RATE_1};
|
||||
// Default conversion time: 27.6ms (16x pres) + 3.6ms (1x temp) ~ 32ms
|
||||
uint16_t conversion_time_{32};
|
||||
|
||||
union {
|
||||
struct {
|
||||
Oversampling prc : 4;
|
||||
SampleRate rate : 4;
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} pt_meas_cfg_ = {.reg = 0}; // PRS_CFG and TMP_CFG
|
||||
|
||||
union {
|
||||
struct {
|
||||
uint8_t meas_crtl : 3;
|
||||
bool tmp_ext : 1;
|
||||
bool prs_ready : 1;
|
||||
bool tmp_ready : 1;
|
||||
bool sensor_ready : 1;
|
||||
bool coef_ready : 1;
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} meas_ = {.reg = 0}; // MEAS_REG
|
||||
|
||||
union {
|
||||
struct {
|
||||
uint8_t _reserved : 5;
|
||||
bool int_prs : 1;
|
||||
bool int_tmp : 1;
|
||||
bool int_fifo_full : 1;
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} int_status_ = {.reg = 0}; // INT_STS
|
||||
|
||||
union {
|
||||
struct {
|
||||
bool spi_3wire : 1;
|
||||
bool fifo_en : 1;
|
||||
bool p_shift : 1;
|
||||
bool t_shift : 1;
|
||||
bool int_prs : 1;
|
||||
bool int_tmp : 1;
|
||||
bool int_fifo : 1;
|
||||
bool int_hl : 1;
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} cfg_ = {.reg = 0}; // CFG_REG
|
||||
|
||||
union {
|
||||
struct {
|
||||
bool fifo_empty : 1;
|
||||
bool fifo_full : 1;
|
||||
uint8_t _reserved : 6;
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} fifo_sts_ = {.reg = 0}; // FIFO_STS
|
||||
|
||||
union {
|
||||
struct {
|
||||
// Set to true to flush FIFO
|
||||
bool fifo_flush : 1;
|
||||
// Reserved bits
|
||||
uint8_t _reserved : 3;
|
||||
// Soft reset. Set to 1001 (0x9) to perform reset.
|
||||
uint8_t soft_rst : 4;
|
||||
} bit;
|
||||
uint8_t reg = 0;
|
||||
} reset_ = {.reg = 0}; // RESET
|
||||
|
||||
union {
|
||||
struct {
|
||||
uint8_t prod_id : 4;
|
||||
uint8_t rev_id : 4;
|
||||
} bit;
|
||||
uint8_t reg = 0;
|
||||
} prod_id_ = {.reg = 0}; // ID
|
||||
|
||||
}; // class SPA06Component
|
||||
} // namespace esphome::spa06_base
|
||||
Reference in New Issue
Block a user