[xdb401] XDB401 Pressure Sensor (#15108)

Co-authored-by: Ricky Tsai <ricky@rtnztech.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Ricky Tsai
2026-06-10 11:58:02 +12:00
committed by GitHub
parent 7533835e04
commit eb6d6eac7d
8 changed files with 298 additions and 0 deletions

View File

@@ -596,6 +596,7 @@ esphome/components/wk2212_spi/* @DrCoolZic
esphome/components/wl_134/* @hobbypunk90
esphome/components/wts01/* @alepee
esphome/components/x9c/* @EtienneMD
esphome/components/xdb401/* @RT530
esphome/components/xgzp68xx/* @gcormier
esphome/components/xiaomi_hhccjcy10/* @fariouche
esphome/components/xiaomi_lywsd02mmc/* @juanluss31

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@RT530"]

View File

@@ -0,0 +1,64 @@
import esphome.codegen as cg
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_PRESSURE,
CONF_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PASCAL,
)
DEPENDENCIES = ["i2c"]
CONF_PRESSURE_RANGE_BAR = "pressure_range_bar"
xdb401_ns = cg.esphome_ns.namespace("xdb401")
XDB401Component = xdb401_ns.class_(
"XDB401Component", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(XDB401Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_PASCAL,
accuracy_decimals=0,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PRESSURE_RANGE_BAR, default=10): cv.one_of(
1, 2, 5, 10, 20, 50, 100, int=True
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x7F))
)
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)
cg.add(var.set_pressure_range_bar(config[CONF_PRESSURE_RANGE_BAR]))
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens))
if pressure_config := config.get(CONF_PRESSURE):
sens = await sensor.new_sensor(pressure_config)
cg.add(var.set_pressure_sensor(sens))

View File

@@ -0,0 +1,177 @@
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "xdb401.h"
namespace esphome::xdb401 {
static const char *const TAG = "xdb401";
static const uint8_t REG_PRESSURE = 0x06;
static const uint8_t REG_TEMPERATURE = 0x09;
static const uint8_t REG_MAKE_MEASURE = 0x30;
static const uint8_t CMD_MAKE_MEASURE = 0x0A;
static const uint8_t MASK_MEASURE_READY = 0x08;
static const float CONVERT_PRESSURE = 8388608.0f; // 0x800000
static const uint32_t CHECK_DELAY = 5;
static const uint8_t CHECK_ATTEMPTS = 6;
static const uint8_t MARK_FAIL_AFTER = 5;
void XDB401Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
uint8_t meas_resp[1] = {};
i2c::ErrorCode err_code = this->read_register(REG_MAKE_MEASURE, meas_resp, sizeof(meas_resp));
if (err_code != i2c::ERROR_OK) {
this->mark_failed(LOG_STR("I2C communication failed"));
return;
}
this->comm_err_counter_ = 0;
}
void XDB401Component::dump_config() {
ESP_LOGCONFIG(TAG, "XDB401:");
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
ESP_LOGCONFIG(TAG, " Pressure Range: %u bar", this->pressure_range_bar_);
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
}
void XDB401Component::handle_comm_failure_(const char *message) {
this->status_set_warning(message);
if (this->comm_err_counter_ >= MARK_FAIL_AFTER) {
this->mark_failed(LOG_STR("Too many consecutive I2C communication errors"));
} else {
this->comm_err_counter_++;
}
this->measurement_in_progress_ = false;
}
i2c::ErrorCode XDB401Component::start_measurement_() {
i2c::ErrorCode err_code = this->write_register(REG_MAKE_MEASURE, &CMD_MAKE_MEASURE, sizeof(CMD_MAKE_MEASURE));
if (err_code != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Error starting measurement, code: %u", err_code);
return err_code;
}
return i2c::ERROR_OK;
}
void XDB401Component::check_measurement_ready_(uint8_t attempt) {
uint8_t meas_resp[1] = {};
i2c::ErrorCode err_code = this->read_register(REG_MAKE_MEASURE, meas_resp, sizeof(meas_resp));
if (err_code != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Error reading measurement status, code: %u", err_code);
this->handle_comm_failure_("I2C communication failed");
return;
}
ESP_LOGV(TAG, "Config response %02X", meas_resp[0]);
// Bit 3 shall be 0 when measurement is ready
if ((meas_resp[0] & MASK_MEASURE_READY) == 0) {
ESP_LOGV(TAG, "Meas mode entered after %u ms", attempt * CHECK_DELAY);
this->read_measurement_();
return;
}
if (attempt >= CHECK_ATTEMPTS) {
ESP_LOGE(TAG, "Device not in measurement mode after timeout of %u ms", CHECK_DELAY * CHECK_ATTEMPTS);
this->handle_comm_failure_("Measurement timeout");
return;
}
this->set_timeout(CHECK_DELAY, [this, attempt]() { this->check_measurement_ready_(attempt + 1); });
}
void XDB401Component::read_measurement_() {
float temperature{};
float pressure{};
i2c::ErrorCode err_code = this->read_pressure_(pressure);
if (err_code != i2c::ERROR_OK) {
this->handle_comm_failure_("Could not read pressure data");
return;
}
err_code = this->read_temperature_(temperature);
if (err_code != i2c::ERROR_OK) {
this->handle_comm_failure_("Could not read temperature data");
return;
}
ESP_LOGD(TAG, "Got pressure=%.1f Pa, temperature=%.2f°C", pressure, temperature);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->pressure_sensor_ != nullptr)
this->pressure_sensor_->publish_state(pressure);
this->comm_err_counter_ = 0;
this->status_clear_warning();
this->measurement_in_progress_ = false;
}
i2c::ErrorCode XDB401Component::read_pressure_(float &pressure) {
uint8_t p_data[3]{};
i2c::ErrorCode err_code = this->read_register(REG_PRESSURE, p_data, 3);
if (err_code != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Error reading pressure register");
return err_code;
}
char pressure_buf[format_hex_pretty_size(3)];
format_hex_pretty_to(pressure_buf, sizeof(pressure_buf), p_data, 3);
ESP_LOGV(TAG, "Got pressure data: %s", pressure_buf);
// Sign-extend 24-bit big-endian pressure value to int32_t.
int32_t raw_pressure = static_cast<int32_t>(encode_uint24(p_data[0], p_data[1], p_data[2]) << 8) >> 8;
ESP_LOGD(TAG, "Pressure data raw %i", raw_pressure);
pressure = (static_cast<float>(raw_pressure) / CONVERT_PRESSURE) *
XDB401Component::full_scale_pressure_pa(this->pressure_range_bar_);
return err_code;
}
i2c::ErrorCode XDB401Component::read_temperature_(float &temperature) {
uint8_t t_data[2]{};
i2c::ErrorCode err_code = this->read_register(REG_TEMPERATURE, t_data, 2);
if (err_code != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Error reading temperature register");
return err_code;
}
char temperature_buf[format_hex_pretty_size(2)];
format_hex_pretty_to(temperature_buf, sizeof(temperature_buf), t_data, 2);
ESP_LOGV(TAG, "Got temperature data: %s", temperature_buf);
// Temperature is a signed 16-bit big-endian value in 1/256 °C (Q8.8 fixed point).
int16_t raw_temperature = static_cast<int16_t>(encode_uint16(t_data[0], t_data[1]));
ESP_LOGD(TAG, "Temperature data raw %i", raw_temperature);
temperature = static_cast<float>(raw_temperature) / 256.0f;
return err_code;
}
void XDB401Component::update() {
if (this->measurement_in_progress_) {
ESP_LOGV(TAG, "Skipping update, measurement already in progress");
return;
}
i2c::ErrorCode err_code = this->start_measurement_();
if (err_code != i2c::ERROR_OK) {
this->handle_comm_failure_("I2C communication failed");
return;
}
this->measurement_in_progress_ = true;
this->set_timeout(CHECK_DELAY, [this]() { this->check_measurement_ready_(1); });
}
} // namespace esphome::xdb401

View File

@@ -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::xdb401 {
class XDB401Component : public PollingComponent, public i2c::I2CDevice {
public:
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_pressure_range_bar(uint8_t pressure_range_bar) { this->pressure_range_bar_ = pressure_range_bar; }
void setup() override;
void dump_config() override;
void update() override;
protected:
void handle_comm_failure_(const char *message);
i2c::ErrorCode start_measurement_();
void check_measurement_ready_(uint8_t attempt);
void read_measurement_();
i2c::ErrorCode read_pressure_(float &pressure);
i2c::ErrorCode read_temperature_(float &temperature);
static constexpr float full_scale_pressure_pa(uint8_t pressure_range_bar) { return pressure_range_bar * 100000.0f; }
uint8_t comm_err_counter_{0};
bool measurement_in_progress_{false};
uint8_t pressure_range_bar_{10};
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
};
} // namespace esphome::xdb401

View File

@@ -0,0 +1,10 @@
sensor:
- platform: xdb401
update_interval: 1s
i2c_id: i2c_bus
temperature:
name: water temperature
pressure:
name: water pressure

View File

@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
<<: !include common.yaml

View File

@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
<<: !include common.yaml