From d5bfcd3bcf107978e863c3a230caa177acb96fc7 Mon Sep 17 00:00:00 2001 From: Dan Jackson Date: Mon, 4 Mar 2024 13:49:57 -0800 Subject: [PATCH] Support for MS8607 PHT (Pressure Humidity Temperature) sensor (#3307) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/ms8607/__init__.py | 1 + esphome/components/ms8607/ms8607.cpp | 444 ++++++++++++++++++++++++++ esphome/components/ms8607/ms8607.h | 109 +++++++ esphome/components/ms8607/sensor.py | 83 +++++ esphome/core/component.h | 2 +- tests/test5.yaml | 23 ++ 7 files changed, 662 insertions(+), 1 deletion(-) create mode 100644 esphome/components/ms8607/__init__.py create mode 100644 esphome/components/ms8607/ms8607.cpp create mode 100644 esphome/components/ms8607/ms8607.h create mode 100644 esphome/components/ms8607/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 8d0ac7983f..de1f3253d3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -225,6 +225,7 @@ esphome/components/mopeka_pro_check/* @spbrogan esphome/components/mopeka_std_check/* @Fabian-Schmidt esphome/components/mpl3115a2/* @kbickar esphome/components/mpu6886/* @fabaff +esphome/components/ms8607/* @e28eta esphome/components/network/* @esphome/core esphome/components/nextion/* @senexcrenshaw esphome/components/nextion/binary_sensor/* @senexcrenshaw diff --git a/esphome/components/ms8607/__init__.py b/esphome/components/ms8607/__init__.py new file mode 100644 index 0000000000..e1cd49ec7b --- /dev/null +++ b/esphome/components/ms8607/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@e28eta"] diff --git a/esphome/components/ms8607/ms8607.cpp b/esphome/components/ms8607/ms8607.cpp new file mode 100644 index 0000000000..4ad6ac336d --- /dev/null +++ b/esphome/components/ms8607/ms8607.cpp @@ -0,0 +1,444 @@ +#include "ms8607.h" + +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ms8607 { + +/// TAG used for logging calls +static const char *const TAG = "ms8607"; + +/// Reset the Pressure/Temperature sensor +static const uint8_t MS8607_PT_CMD_RESET = 0x1E; + +/// Beginning of PROM register addresses. Same for both i2c addresses. Each address has 16 bits of data, and +/// PROM addresses step by two, so the LSB is always 0 +static const uint8_t MS8607_PROM_START = 0xA0; +/// Last PROM register address. +static const uint8_t MS8607_PROM_END = 0xAE; +/// Number of PROM registers. +static const uint8_t MS8607_PROM_COUNT = (MS8607_PROM_END - MS8607_PROM_START) >> 1; + +/// Reset the Humidity sensor +static const uint8_t MS8607_CMD_H_RESET = 0xFE; +/// Read relative humidity, without holding i2c master +static const uint8_t MS8607_CMD_H_MEASURE_NO_HOLD = 0xF5; +/// Temperature correction coefficient for Relative Humidity from datasheet +static const float MS8607_H_TEMP_COEFFICIENT = -0.18; + +/// Read the converted analog value, either D1 (pressure) or D2 (temperature) +static const uint8_t MS8607_CMD_ADC_READ = 0x00; + +// TODO: allow OSR to be turned down for speed and/or lower power consumption via configuration. +// ms8607 supports 6 different settings + +/// Request conversion of analog D1 (pressure) with OSR=8192 (highest oversampling ratio). Takes maximum of 17.2ms +static const uint8_t MS8607_CMD_CONV_D1_OSR_8K = 0x4A; +/// Request conversion of analog D2 (temperature) with OSR=8192 (highest oversampling ratio). Takes maximum of 17.2ms +static const uint8_t MS8607_CMD_CONV_D2_OSR_8K = 0x5A; + +enum class MS8607Component::ErrorCode { + /// Component hasn't failed (yet?) + NONE = 0, + /// Both the Pressure/Temperature address and the Humidity address failed to reset + PTH_RESET_FAILED = 1, + /// Asking the Pressure/Temperature sensor to reset failed + PT_RESET_FAILED = 2, + /// Asking the Humidity sensor to reset failed + H_RESET_FAILED = 3, + /// Reading the PROM calibration values failed + PROM_READ_FAILED = 4, + /// The PROM calibration values failed the CRC check + PROM_CRC_FAILED = 5, +}; + +enum class MS8607Component::SetupStatus { + /// This component has not successfully reset the PT & H devices + NEEDS_RESET, + /// Reset commands succeeded, need to wait >= 15ms to read PROM + NEEDS_PROM_READ, + /// Successfully read PROM and ready to update sensors + SUCCESSFUL, +}; + +static uint8_t crc4(uint16_t *buffer, size_t length); +static uint8_t hsensor_crc_check(uint16_t value); + +void MS8607Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MS8607..."); + this->error_code_ = ErrorCode::NONE; + this->setup_status_ = SetupStatus::NEEDS_RESET; + + // I do not know why the device sometimes NACKs the reset command, but + // try 3 times in case it's a transitory issue on this boot + this->set_retry( + "reset", 5, 3, + [this](const uint8_t remaining_setup_attempts) { + ESP_LOGD(TAG, "Resetting both I2C addresses: 0x%02X, 0x%02X", this->address_, + this->humidity_device_->get_address()); + // I believe sending the reset command to both addresses is preferable to + // skipping humidity if PT fails for some reason. + // However, only consider the reset successful if they both ACK + bool const pt_successful = this->write_bytes(MS8607_PT_CMD_RESET, nullptr, 0); + bool const h_successful = this->humidity_device_->write_bytes(MS8607_CMD_H_RESET, nullptr, 0); + + if (!(pt_successful && h_successful)) { + ESP_LOGE(TAG, "Resetting I2C devices failed"); + if (!pt_successful && !h_successful) { + this->error_code_ = ErrorCode::PTH_RESET_FAILED; + } else if (!pt_successful) { + this->error_code_ = ErrorCode::PT_RESET_FAILED; + } else { + this->error_code_ = ErrorCode::H_RESET_FAILED; + } + + if (remaining_setup_attempts > 0) { + this->status_set_error(); + } else { + this->mark_failed(); + } + return RetryResult::RETRY; + } + + this->setup_status_ = SetupStatus::NEEDS_PROM_READ; + this->error_code_ = ErrorCode::NONE; + this->status_clear_error(); + + // 15ms delay matches datasheet, Adafruit_MS8607 & SparkFun_PHT_MS8607_Arduino_Library + this->set_timeout("prom-read", 15, [this]() { + if (this->read_calibration_values_from_prom_()) { + this->setup_status_ = SetupStatus::SUCCESSFUL; + this->status_clear_error(); + } else { + this->mark_failed(); + return; + } + }); + + return RetryResult::DONE; + }, + 5.0f); // executes at now, +5ms, +25ms +} + +void MS8607Component::update() { + if (this->setup_status_ != SetupStatus::SUCCESSFUL) { + // setup is still occurring, either because reset had to retry or due to the 15ms + // delay needed between reset & reading the PROM values + return; + } + + // Updating happens async and sequentially. + // Temperature, then pressure, then humidity + this->request_read_temperature_(); +} + +void MS8607Component::dump_config() { + ESP_LOGCONFIG(TAG, "MS8607:"); + LOG_I2C_DEVICE(this); + // LOG_I2C_DEVICE doesn't work for humidity, the `address_` is protected. Log using get_address() + ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->humidity_device_->get_address()); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with MS8607 failed."); + switch (this->error_code_) { + case ErrorCode::PT_RESET_FAILED: + ESP_LOGE(TAG, "Temperature/Pressure RESET failed"); + break; + case ErrorCode::H_RESET_FAILED: + ESP_LOGE(TAG, "Humidity RESET failed"); + break; + case ErrorCode::PTH_RESET_FAILED: + ESP_LOGE(TAG, "Temperature/Pressure && Humidity RESET failed"); + break; + case ErrorCode::PROM_READ_FAILED: + ESP_LOGE(TAG, "Reading PROM failed"); + break; + case ErrorCode::PROM_CRC_FAILED: + ESP_LOGE(TAG, "PROM values failed CRC"); + break; + case ErrorCode::NONE: + default: + ESP_LOGE(TAG, "Error reason unknown %u", static_cast(this->error_code_)); + break; + } + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); +} + +bool MS8607Component::read_calibration_values_from_prom_() { + ESP_LOGD(TAG, "Reading calibration values from PROM"); + + uint16_t buffer[MS8607_PROM_COUNT]; + bool successful = true; + + for (uint8_t idx = 0; idx < MS8607_PROM_COUNT; ++idx) { + uint8_t const address_to_read = MS8607_PROM_START + (idx * 2); + successful &= this->read_byte_16(address_to_read, &buffer[idx]); + } + + if (!successful) { + ESP_LOGE(TAG, "Reading calibration values from PROM failed"); + this->error_code_ = ErrorCode::PROM_READ_FAILED; + return false; + } + + ESP_LOGD(TAG, "Checking CRC of calibration values from PROM"); + uint8_t const expected_crc = (buffer[0] & 0xF000) >> 12; // first 4 bits + buffer[0] &= 0x0FFF; // strip CRC from buffer, in order to run CRC + uint8_t const actual_crc = crc4(buffer, MS8607_PROM_COUNT); + + if (expected_crc != actual_crc) { + ESP_LOGE(TAG, "Incorrect CRC value. Provided value 0x%01X != calculated value 0x%01X", expected_crc, actual_crc); + this->error_code_ = ErrorCode::PROM_CRC_FAILED; + return false; + } + + this->calibration_values_.pressure_sensitivity = buffer[1]; + this->calibration_values_.pressure_offset = buffer[2]; + this->calibration_values_.pressure_sensitivity_temperature_coefficient = buffer[3]; + this->calibration_values_.pressure_offset_temperature_coefficient = buffer[4]; + this->calibration_values_.reference_temperature = buffer[5]; + this->calibration_values_.temperature_coefficient_of_temperature = buffer[6]; + ESP_LOGD(TAG, "Finished reading calibration values"); + + // Skipping reading Humidity PROM, since it doesn't have anything interesting for us + + return true; +} + +/** + CRC-4 algorithm from datasheet. It operates on a buffer of 16-bit values, one byte at a time, using a 16-bit + value to collect the CRC result into. + + The provided/expected CRC value must already be zeroed out from the buffer. + */ +static uint8_t crc4(uint16_t *buffer, size_t length) { + uint16_t crc_remainder = 0; + + // algorithm to add a byte into the crc + auto apply_crc = [&crc_remainder](uint8_t next) { + crc_remainder ^= next; + for (uint8_t bit = 8; bit > 0; --bit) { + if (crc_remainder & 0x8000) { + crc_remainder = (crc_remainder << 1) ^ 0x3000; + } else { + crc_remainder = (crc_remainder << 1); + } + } + }; + + // add all the bytes + for (size_t idx = 0; idx < length; ++idx) { + for (auto byte : decode_value(buffer[idx])) { + apply_crc(byte); + } + } + // For the MS8607 CRC, add a pair of zeros to shift the last byte from `buffer` through + apply_crc(0); + apply_crc(0); + + return (crc_remainder >> 12) & 0xF; // only the most significant 4 bits +} + +/** + * @brief Calculates CRC value for the provided humidity (+ status bits) value + * + * CRC-8 check comes from other MS8607 libraries on github. I did not find it in the datasheet, + * and it differs from the crc8 implementation that's already part of esphome. + * + * @param value two byte humidity sensor value read from i2c + * @return uint8_t computed crc value + */ +static uint8_t hsensor_crc_check(uint16_t value) { + uint32_t polynom = 0x988000; // x^8 + x^5 + x^4 + 1 + uint32_t msb = 0x800000; + uint32_t mask = 0xFF8000; + uint32_t result = (uint32_t) value << 8; // Pad with zeros as specified in spec + + while (msb != 0x80) { + // Check if msb of current value is 1 and apply XOR mask + if (result & msb) { + result = ((result ^ polynom) & mask) | (result & ~mask); + } + + // Shift by one + msb >>= 1; + mask >>= 1; + polynom >>= 1; + } + return result & 0xFF; +} + +void MS8607Component::request_read_temperature_() { + // Tell MS8607 to start ADC conversion of temperature sensor + if (!this->write_bytes(MS8607_CMD_CONV_D2_OSR_8K, nullptr, 0)) { + this->status_set_warning(); + return; + } + + auto f = std::bind(&MS8607Component::read_temperature_, this); + // datasheet says 17.2ms max conversion time at OSR 8192 + this->set_timeout("temperature", 20, f); +} + +void MS8607Component::read_temperature_() { + uint8_t bytes[3]; // 24 bits + if (!this->read_bytes(MS8607_CMD_ADC_READ, bytes, 3)) { + this->status_set_warning(); + return; + } + + const uint32_t d2_raw_temperature = encode_uint32(0, bytes[0], bytes[1], bytes[2]); + this->request_read_pressure_(d2_raw_temperature); +} + +void MS8607Component::request_read_pressure_(uint32_t d2_raw_temperature) { + if (!this->write_bytes(MS8607_CMD_CONV_D1_OSR_8K, nullptr, 0)) { + this->status_set_warning(); + return; + } + + auto f = std::bind(&MS8607Component::read_pressure_, this, d2_raw_temperature); + // datasheet says 17.2ms max conversion time at OSR 8192 + this->set_timeout("pressure", 20, f); +} + +void MS8607Component::read_pressure_(uint32_t d2_raw_temperature) { + uint8_t bytes[3]; // 24 bits + if (!this->read_bytes(MS8607_CMD_ADC_READ, bytes, 3)) { + this->status_set_warning(); + return; + } + const uint32_t d1_raw_pressure = encode_uint32(0, bytes[0], bytes[1], bytes[2]); + this->calculate_values_(d2_raw_temperature, d1_raw_pressure); +} + +void MS8607Component::request_read_humidity_(float temperature_float) { + if (!this->humidity_device_->write_bytes(MS8607_CMD_H_MEASURE_NO_HOLD, nullptr, 0)) { + ESP_LOGW(TAG, "Request to measure humidity failed"); + this->status_set_warning(); + return; + } + + auto f = std::bind(&MS8607Component::read_humidity_, this, temperature_float); + // datasheet says 15.89ms max conversion time at OSR 8192 + this->set_timeout("humidity", 20, f); +} + +void MS8607Component::read_humidity_(float temperature_float) { + uint8_t bytes[3]; + if (!this->humidity_device_->read_bytes_raw(bytes, 3)) { + ESP_LOGW(TAG, "Failed to read the measured humidity value"); + this->status_set_warning(); + return; + } + + // "the measurement is stored into 14 bits. The two remaining LSBs are used for transmitting status information. + // Bit1 of the two LSBS must be set to '1'. Bit0 is currently not assigned" + uint16_t humidity = encode_uint16(bytes[0], bytes[1]); + uint8_t const expected_crc = bytes[2]; + uint8_t const actual_crc = hsensor_crc_check(humidity); + if (expected_crc != actual_crc) { + ESP_LOGE(TAG, "Incorrect Humidity CRC value. Provided value 0x%01X != calculated value 0x%01X", expected_crc, + actual_crc); + this->status_set_warning(); + return; + } + if (!(humidity & 0x2)) { + // data sheet says Bit1 should always set, but nothing about what happens if it isn't + ESP_LOGE(TAG, "Humidity status bit was not set to 1?"); + } + humidity &= ~(0b11); // strip status & unassigned bits from data + + // map 16 bit humidity value into range [-6%, 118%] + float const humidity_partial = double(humidity) / (1 << 16); + float const humidity_percentage = lerp(humidity_partial, -6.0, 118.0); + float const compensated_humidity_percentage = + humidity_percentage + (20 - temperature_float) * MS8607_H_TEMP_COEFFICIENT; + ESP_LOGD(TAG, "Compensated for temperature, humidity=%.2f%%", compensated_humidity_percentage); + + if (this->humidity_sensor_ != nullptr) { + this->humidity_sensor_->publish_state(compensated_humidity_percentage); + } + this->status_clear_warning(); +} + +void MS8607Component::calculate_values_(uint32_t d2_raw_temperature, uint32_t d1_raw_pressure) { + // Perform the first order pressure/temperature calculation + + // d_t: "difference between actual and reference temperature" = D2 - [C5] * 2**8 + const int32_t d_t = int32_t(d2_raw_temperature) - (int32_t(this->calibration_values_.reference_temperature) << 8); + // actual temperature as hundredths of degree celsius in range [-4000, 8500] + // 2000 + d_t * [C6] / (2**23) + int32_t temperature = + 2000 + ((int64_t(d_t) * this->calibration_values_.temperature_coefficient_of_temperature) >> 23); + + // offset at actual temperature. [C2] * (2**17) + (d_t * [C4] / (2**6)) + int64_t pressure_offset = (int64_t(this->calibration_values_.pressure_offset) << 17) + + ((int64_t(d_t) * this->calibration_values_.pressure_offset_temperature_coefficient) >> 6); + // sensitivity at actual temperature. [C1] * (2**16) + ([C3] * d_t) / (2**7) + int64_t pressure_sensitivity = + (int64_t(this->calibration_values_.pressure_sensitivity) << 16) + + ((int64_t(d_t) * this->calibration_values_.pressure_sensitivity_temperature_coefficient) >> 7); + + // Perform the second order compensation, for non-linearity over temperature range + const int64_t d_t_squared = int64_t(d_t) * d_t; + int64_t temperature_2 = 0; + int32_t pressure_offset_2 = 0; + int32_t pressure_sensitivity_2 = 0; + if (temperature < 2000) { + // (TEMP - 2000)**2 / 2**4 + const int32_t low_temperature_adjustment = (temperature - 2000) * (temperature - 2000) >> 4; + + // T2 = 3 * (d_t**2) / 2**33 + temperature_2 = (3 * d_t_squared) >> 33; + // OFF2 = 61 * (TEMP-2000)**2 / 2**4 + pressure_offset_2 = 61 * low_temperature_adjustment; + // SENS2 = 29 * (TEMP-2000)**2 / 2**4 + pressure_sensitivity_2 = 29 * low_temperature_adjustment; + + if (temperature < -1500) { + // (TEMP+1500)**2 + const int32_t very_low_temperature_adjustment = (temperature + 1500) * (temperature + 1500); + + // OFF2 = OFF2 + 17 * (TEMP+1500)**2 + pressure_offset_2 += 17 * very_low_temperature_adjustment; + // SENS2 = SENS2 + 9 * (TEMP+1500)**2 + pressure_sensitivity_2 += 9 * very_low_temperature_adjustment; + } + } else { + // T2 = 5 * (d_t**2) / 2**38 + temperature_2 = (5 * d_t_squared) >> 38; + } + + temperature -= temperature_2; + pressure_offset -= pressure_offset_2; + pressure_sensitivity -= pressure_sensitivity_2; + + // Temperature compensated pressure. [1000, 120000] => [10.00 mbar, 1200.00 mbar] + const int32_t pressure = (((d1_raw_pressure * pressure_sensitivity) >> 21) - pressure_offset) >> 15; + + const float temperature_float = temperature / 100.0f; + const float pressure_float = pressure / 100.0f; + ESP_LOGD(TAG, "Temperature=%0.2f°C, Pressure=%0.2fhPa", temperature_float, pressure_float); + + if (this->temperature_sensor_ != nullptr) { + this->temperature_sensor_->publish_state(temperature_float); + } + if (this->pressure_sensor_ != nullptr) { + this->pressure_sensor_->publish_state(pressure_float); // hPa aka mbar + } + this->status_clear_warning(); + + if (this->humidity_sensor_ != nullptr) { + // now that we have temperature (to compensate the humidity with), kick off that read + this->request_read_humidity_(temperature_float); + } +} + +} // namespace ms8607 +} // namespace esphome diff --git a/esphome/components/ms8607/ms8607.h b/esphome/components/ms8607/ms8607.h new file mode 100644 index 0000000000..0bee7e97b7 --- /dev/null +++ b/esphome/components/ms8607/ms8607.h @@ -0,0 +1,109 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace ms8607 { + +/** + Class for I2CDevice used to communicate with the Humidity sensor + on the chip. See MS8607Component instead + */ +class MS8607HumidityDevice : public i2c::I2CDevice { + public: + uint8_t get_address() { return address_; } +}; + +/** + Temperature, pressure, and humidity sensor. + + By default, the MS8607 measures sensors at the highest resolution. + A potential enhancement would be to expose the resolution as a configurable + setting. A lower resolution speeds up ADC conversion time & uses less power. + + Datasheet: + https://www.te.com/commerce/DocumentDelivery/DDEController?Action=showdoc&DocId=Data+Sheet%7FMS8607-02BA01%7FB3%7Fpdf%7FEnglish%7FENG_DS_MS8607-02BA01_B3.pdf%7FCAT-BLPS0018 + + Other implementations: + - https://github.com/TEConnectivity/MS8607_Generic_C_Driver + - https://github.com/adafruit/Adafruit_MS8607 + - https://github.com/sparkfun/SparkFun_PHT_MS8607_Arduino_Library + */ +class MS8607Component : public PollingComponent, public i2c::I2CDevice { + public: + virtual ~MS8607Component() = default; + void setup() override; + void update() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; }; + + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } + void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } + void set_humidity_device(MS8607HumidityDevice *humidity_device) { humidity_device_ = humidity_device; } + + protected: + /** + Read and store the Pressure & Temperature calibration settings from the PROM. + Intended to be called during setup(), this will set the `failure_reason_` + */ + bool read_calibration_values_from_prom_(); + + /// Start async temperature read + void request_read_temperature_(); + /// Process async temperature read + void read_temperature_(); + /// start async pressure read + void request_read_pressure_(uint32_t raw_temperature); + /// process async pressure read + void read_pressure_(uint32_t raw_temperature); + /// start async humidity read + void request_read_humidity_(float temperature_float); + /// process async humidity read + void read_humidity_(float temperature_float); + /// use raw temperature & pressure to calculate & publish values + void calculate_values_(uint32_t raw_temperature, uint32_t raw_pressure); + + sensor::Sensor *temperature_sensor_; + sensor::Sensor *pressure_sensor_; + sensor::Sensor *humidity_sensor_; + + /** I2CDevice object to communicate with secondary I2C address for the humidity sensor + * + * The MS8607 only has one set of I2C pins, despite using two different addresses. + * + * Default address for humidity is 0x40 + */ + MS8607HumidityDevice *humidity_device_; + + /// This device's pressure & temperature calibration values, read from PROM + struct CalibrationValues { + /// Pressure sensitivity | SENS-T1. [C1] + uint16_t pressure_sensitivity; + /// Temperature coefficient of pressure sensitivity | TCS. [C3] + uint16_t pressure_sensitivity_temperature_coefficient; + /// Pressure offset | OFF-T1. [C2] + uint16_t pressure_offset; + /// Temperature coefficient of pressure offset | TCO. [C4] + uint16_t pressure_offset_temperature_coefficient; + /// Reference temperature | T-REF. [C5] + uint16_t reference_temperature; + /// Temperature coefficient of the temperature | TEMPSENS. [C6] + uint16_t temperature_coefficient_of_temperature; + } calibration_values_; + + /// Possible failure reasons of this component + enum class ErrorCode; + /// Keep track of the reason why this component failed, to augment the dumped config + ErrorCode error_code_; + + /// Current progress through required component setup + enum class SetupStatus; + /// Current step in the multi-step & possibly delayed setup() process + SetupStatus setup_status_; +}; + +} // namespace ms8607 +} // namespace esphome diff --git a/esphome/components/ms8607/sensor.py b/esphome/components/ms8607/sensor.py new file mode 100644 index 0000000000..1113e14af2 --- /dev/null +++ b/esphome/components/ms8607/sensor.py @@ -0,0 +1,83 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, + UNIT_PERCENT, +) + +DEPENDENCIES = ["i2c"] + +ms8607_ns = cg.esphome_ns.namespace("ms8607") +MS8607Component = ms8607_ns.class_( + "MS8607Component", cg.PollingComponent, i2c.I2CDevice +) + +CONF_HUMIDITY_I2C_ID = "humidity_i2c_id" +MS8607HumidityDevice = ms8607_ns.class_("MS8607HumidityDevice", i2c.I2CDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MS8607Component), + cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, # Resolution: 0.01 + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Required(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=2, # Resolution: 0.016 + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Required(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=2, # Resolution: 0.04 + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.GenerateID(CONF_HUMIDITY_I2C_ID): cv.declare_id( + MS8607HumidityDevice + ), + } + ) + .extend(i2c.i2c_device_schema(0x40)), # default address for humidity + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x76)) # default address for temp/pressure +) + + +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)) + + if humidity_config := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(humidity_config) + cg.add(var.set_humidity_sensor(sens)) + humidity_device = cg.new_Pvariable(humidity_config[CONF_HUMIDITY_I2C_ID]) + await i2c.register_i2c_device(humidity_device, humidity_config) + cg.add(var.set_humidity_device(humidity_device)) diff --git a/esphome/core/component.h b/esphome/core/component.h index 594f8b65af..deefedf3d8 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -193,7 +193,7 @@ class Component { * again in the future. * * The first retry of f happens after `initial_wait_time` milliseconds. The delay between retries is - * increased by multipling by `backoff_increase_factor` each time. If no backoff_increase_factor is + * increased by multiplying by `backoff_increase_factor` each time. If no backoff_increase_factor is * supplied (default = 1.0), the wait time will stay constant. * * The retry function f needs to accept a single argument: the number of attempts remaining. On the diff --git a/tests/test5.yaml b/tests/test5.yaml index 55efba57d1..81615b24b0 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -483,6 +483,29 @@ sensor: address: 0x77 iir_filter: 2X + - platform: ms8607 + temperature: + name: Temperature + humidity: + name: Humidity + pressure: + name: Pressure + - platform: ms8607 + id: ms8607_more_config + temperature: + name: Indoor Temperature + accuracy_decimals: 1 + pressure: + name: Indoor Pressure + internal: true + humidity: + name: Indoor Humidity + address: 0x41 + i2c_id: + i2c_id: + address: 0x77 + update_interval: 10min + - platform: sen5x id: sen54 temperature: