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/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 | ||||
|   | ||||
							
								
								
									
										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. | ||||
|    * | ||||
|    * 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 | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user