mirror of
https://github.com/esphome/esphome.git
synced 2024-11-28 12:46:22 +01:00
Support for MS8607 PHT (Pressure Humidity Temperature) sensor (#3307)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
f3ed091395
commit
d5bfcd3bcf
@ -225,6 +225,7 @@ esphome/components/mopeka_pro_check/* @spbrogan
|
|||||||
esphome/components/mopeka_std_check/* @Fabian-Schmidt
|
esphome/components/mopeka_std_check/* @Fabian-Schmidt
|
||||||
esphome/components/mpl3115a2/* @kbickar
|
esphome/components/mpl3115a2/* @kbickar
|
||||||
esphome/components/mpu6886/* @fabaff
|
esphome/components/mpu6886/* @fabaff
|
||||||
|
esphome/components/ms8607/* @e28eta
|
||||||
esphome/components/network/* @esphome/core
|
esphome/components/network/* @esphome/core
|
||||||
esphome/components/nextion/* @senexcrenshaw
|
esphome/components/nextion/* @senexcrenshaw
|
||||||
esphome/components/nextion/binary_sensor/* @senexcrenshaw
|
esphome/components/nextion/binary_sensor/* @senexcrenshaw
|
||||||
|
1
esphome/components/ms8607/__init__.py
Normal file
1
esphome/components/ms8607/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
CODEOWNERS = ["@e28eta"]
|
444
esphome/components/ms8607/ms8607.cpp
Normal file
444
esphome/components/ms8607/ms8607.cpp
Normal file
@ -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<uint8_t>(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
|
109
esphome/components/ms8607/ms8607.h
Normal file
109
esphome/components/ms8607/ms8607.h
Normal file
@ -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
|
83
esphome/components/ms8607/sensor.py
Normal file
83
esphome/components/ms8607/sensor.py
Normal file
@ -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))
|
@ -193,7 +193,7 @@ class Component {
|
|||||||
* again in the future.
|
* again in the future.
|
||||||
*
|
*
|
||||||
* The first retry of f happens after `initial_wait_time` milliseconds. The delay between retries is
|
* 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.
|
* 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
|
* The retry function f needs to accept a single argument: the number of attempts remaining. On the
|
||||||
|
@ -483,6 +483,29 @@ sensor:
|
|||||||
address: 0x77
|
address: 0x77
|
||||||
iir_filter: 2X
|
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
|
- platform: sen5x
|
||||||
id: sen54
|
id: sen54
|
||||||
temperature:
|
temperature:
|
||||||
|
Loading…
Reference in New Issue
Block a user