From 972c18a7ca2dd36fc4118152fff6007f760df873 Mon Sep 17 00:00:00 2001 From: Greg Cormier Date: Mon, 6 Nov 2023 18:46:30 -0500 Subject: [PATCH] Add differential pressure sensor support for CFSensor XGZP68xxD devices (#5562) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/xgzp68xx/__init__.py | 1 + esphome/components/xgzp68xx/sensor.py | 62 ++++++++++++++++ esphome/components/xgzp68xx/xgzp68xx.cpp | 91 ++++++++++++++++++++++++ esphome/components/xgzp68xx/xgzp68xx.h | 27 +++++++ tests/test1.yaml | 8 +++ 6 files changed, 190 insertions(+) create mode 100644 esphome/components/xgzp68xx/__init__.py create mode 100644 esphome/components/xgzp68xx/sensor.py create mode 100644 esphome/components/xgzp68xx/xgzp68xx.cpp create mode 100644 esphome/components/xgzp68xx/xgzp68xx.h diff --git a/CODEOWNERS b/CODEOWNERS index a48d0ab2e0..067b886320 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -349,6 +349,7 @@ esphome/components/wiegand/* @ssieb esphome/components/wireguard/* @droscy @lhoracek @thomas0bernard esphome/components/wl_134/* @hobbypunk90 esphome/components/x9c/* @EtienneMD +esphome/components/xgzp68xx/* @gcormier esphome/components/xiaomi_lywsd03mmc/* @ahpohl esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc401/* @vevsvevs diff --git a/esphome/components/xgzp68xx/__init__.py b/esphome/components/xgzp68xx/__init__.py new file mode 100644 index 0000000000..122ffaf6ed --- /dev/null +++ b/esphome/components/xgzp68xx/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@gcormier"] diff --git a/esphome/components/xgzp68xx/sensor.py b/esphome/components/xgzp68xx/sensor.py new file mode 100644 index 0000000000..3e381aaa61 --- /dev/null +++ b/esphome/components/xgzp68xx/sensor.py @@ -0,0 +1,62 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + DEVICE_CLASS_PRESSURE, + CONF_ID, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_PASCAL, + UNIT_CELSIUS, + CONF_TEMPERATURE, + CONF_PRESSURE, +) + +DEPENDENCIES = ["i2c"] +CODEOWNERS = ["@gcormier"] + +CONF_K_VALUE = "k_value" + +xgzp68xx_ns = cg.esphome_ns.namespace("xgzp68xx") +XGZP68XXComponent = xgzp68xx_ns.class_( + "XGZP68XXComponent", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XGZP68XXComponent), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement=UNIT_PASCAL, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + 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, + ), + cv.Optional(CONF_K_VALUE, default=4096): cv.uint16_t, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x6D)) +) + + +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) + + 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)) + + cg.add(var.set_k_value(config[CONF_K_VALUE])) diff --git a/esphome/components/xgzp68xx/xgzp68xx.cpp b/esphome/components/xgzp68xx/xgzp68xx.cpp new file mode 100644 index 0000000000..ea3583c3c5 --- /dev/null +++ b/esphome/components/xgzp68xx/xgzp68xx.cpp @@ -0,0 +1,91 @@ +#include "xgzp68xx.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace xgzp68xx { + +static const char *const TAG = "xgzp68xx.sensor"; + +static const uint8_t CMD_ADDRESS = 0x30; +static const uint8_t SYSCONFIG_ADDRESS = 0xA5; +static const uint8_t PCONFIG_ADDRESS = 0xA6; +static const uint8_t READ_COMMAND = 0x0A; + +void XGZP68XXComponent::update() { + // Request temp + pressure acquisition + this->write_register(0x30, &READ_COMMAND, 1); + + // Wait 20mS per datasheet + this->set_timeout("measurement", 20, [this]() { + uint8_t data[5]; + uint32_t pressure_raw; + uint16_t temperature_raw; + float pressure_in_pa, temperature; + int success; + + // Read the sensor data + success = this->read_register(0x06, data, 5); + if (success != 0) { + ESP_LOGE(TAG, "Failed to read sensor data! Error code: %i", success); + return; + } + + pressure_raw = encode_uint24(data[0], data[1], data[2]); + temperature_raw = encode_uint16(data[3], data[4]); + + // Convert the pressure data to hPa + ESP_LOGV(TAG, "Got raw pressure=%d, raw temperature=%d ", pressure_raw, temperature_raw); + ESP_LOGV(TAG, "K value is %d ", this->k_value_); + + // The most significant bit of both pressure and temperature will be 1 to indicate a negative value. + // This is directly from the datasheet, and the calculations below will handle this. + if (pressure_raw > pow(2, 23)) { + // Negative pressure + pressure_in_pa = (pressure_raw - pow(2, 24)) / (float) (this->k_value_); + } else { + // Positive pressure + pressure_in_pa = pressure_raw / (float) (this->k_value_); + } + + if (temperature_raw > pow(2, 15)) { + // Negative temperature + temperature = (float) (temperature_raw - pow(2, 16)) / 256.0f; + } else { + // Positive temperature + temperature = (float) temperature_raw / 256.0f; + } + + if (this->pressure_sensor_ != nullptr) + this->pressure_sensor_->publish_state(pressure_in_pa); + + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temperature); + }); // end of set_timeout +} + +void XGZP68XXComponent::setup() { + ESP_LOGD(TAG, "Setting up XGZP68xx..."); + uint8_t config; + + // Display some sample bits to confirm we are talking to the sensor + this->read_register(SYSCONFIG_ADDRESS, &config, 1); + ESP_LOGCONFIG(TAG, "Gain value is %d", (config >> 3) & 0b111); + ESP_LOGCONFIG(TAG, "XGZP68xx started!"); +} + +void XGZP68XXComponent::dump_config() { + ESP_LOGCONFIG(TAG, "XGZP68xx"); + LOG_SENSOR(" ", "Temperature: ", this->temperature_sensor_); + LOG_SENSOR(" ", "Pressure: ", this->pressure_sensor_); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, " Connection with XGZP68xx failed!"); + } + LOG_UPDATE_INTERVAL(this); +} + +} // namespace xgzp68xx +} // namespace esphome diff --git a/esphome/components/xgzp68xx/xgzp68xx.h b/esphome/components/xgzp68xx/xgzp68xx.h new file mode 100644 index 0000000000..1bb7304b15 --- /dev/null +++ b/esphome/components/xgzp68xx/xgzp68xx.h @@ -0,0 +1,27 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace xgzp68xx { + +class XGZP68XXComponent : public PollingComponent, public sensor::Sensor, public i2c::I2CDevice { + public: + SUB_SENSOR(temperature) + SUB_SENSOR(pressure) + void set_k_value(uint16_t k_value) { this->k_value_ = k_value; } + + void update() override; + void setup() override; + void dump_config() override; + + protected: + /// Internal method to read the pressure from the component after it has been scheduled. + void read_pressure_(); + uint16_t k_value_; +}; + +} // namespace xgzp68xx +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 64d1d36f52..f653960c40 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -351,6 +351,14 @@ dfrobot_sen0395: uart_id: dfrobot_mmwave_uart sensor: + - platform: xgzp68xx + i2c_id: i2c_bus + temperature: + name: Pressure Temperature + pressure: + name: Differential pressure + k_value: 4096 + - platform: pmwcs3 i2c_id: i2c_bus e25: