mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00: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:
		| @@ -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: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user