mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	feat: Add HTU31D Support (#5805)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		| @@ -152,6 +152,7 @@ esphome/components/honeywellabp2_i2c/* @jpfaff | |||||||
| esphome/components/host/* @esphome/core | esphome/components/host/* @esphome/core | ||||||
| esphome/components/hrxl_maxsonar_wr/* @netmikey | esphome/components/hrxl_maxsonar_wr/* @netmikey | ||||||
| esphome/components/hte501/* @Stock-M | esphome/components/hte501/* @Stock-M | ||||||
|  | esphome/components/htu31d/* @betterengineering | ||||||
| esphome/components/hydreon_rgxx/* @functionpointer | esphome/components/hydreon_rgxx/* @functionpointer | ||||||
| esphome/components/hyt271/* @Philippe12 | esphome/components/hyt271/* @Philippe12 | ||||||
| esphome/components/i2c/* @esphome/core | esphome/components/i2c/* @esphome/core | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								esphome/components/htu31d/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/htu31d/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | CODEOWNERS = ["@betterengineering"] | ||||||
							
								
								
									
										269
									
								
								esphome/components/htu31d/htu31d.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										269
									
								
								esphome/components/htu31d/htu31d.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,269 @@ | |||||||
|  | /* | ||||||
|  |  * This file contains source code derived from Adafruit_HTU31D which is under | ||||||
|  |  * the BSD license: | ||||||
|  |  *   Written by Limor Fried/Ladyada for Adafruit Industries. | ||||||
|  |  *   BSD license, all text above must be included in any redistribution. | ||||||
|  |  * | ||||||
|  |  * Modifications made by Mark Spicer. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #include "htu31d.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace htu31d { | ||||||
|  |  | ||||||
|  | /** Logging prefix */ | ||||||
|  | static const char *const TAG = "htu31d"; | ||||||
|  |  | ||||||
|  | /** Default I2C address for the HTU31D. */ | ||||||
|  | static const uint8_t HTU31D_DEFAULT_I2CADDR = 0x40; | ||||||
|  |  | ||||||
|  | /** Read temperature and humidity. */ | ||||||
|  | static const uint8_t HTU31D_READTEMPHUM = 0x00; | ||||||
|  |  | ||||||
|  | /** Start a conversion! */ | ||||||
|  | static const uint8_t HTU31D_CONVERSION = 0x40; | ||||||
|  |  | ||||||
|  | /** Read serial number command. */ | ||||||
|  | static const uint8_t HTU31D_READSERIAL = 0x0A; | ||||||
|  |  | ||||||
|  | /** Enable heater */ | ||||||
|  | static const uint8_t HTU31D_HEATERON = 0x04; | ||||||
|  |  | ||||||
|  | /** Disable heater */ | ||||||
|  | static const uint8_t HTU31D_HEATEROFF = 0x02; | ||||||
|  |  | ||||||
|  | /** Reset command. */ | ||||||
|  | static const uint8_t HTU31D_RESET = 0x1E; | ||||||
|  |  | ||||||
|  | /** Diagnostics command. */ | ||||||
|  | static const uint8_t HTU31D_DIAGNOSTICS = 0x08; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Computes a CRC result for the provided input. | ||||||
|  |  * | ||||||
|  |  * @returns the computed CRC result for the provided input | ||||||
|  |  */ | ||||||
|  | uint8_t compute_crc(uint32_t value) { | ||||||
|  |   uint32_t polynom = 0x98800000;  // x^8 + x^5 + x^4 + 1 | ||||||
|  |   uint32_t msb = 0x80000000; | ||||||
|  |   uint32_t mask = 0xFF800000; | ||||||
|  |   uint32_t threshold = 0x00000080; | ||||||
|  |   uint32_t result = value; | ||||||
|  |  | ||||||
|  |   while (msb != threshold) { | ||||||
|  |     // 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; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Resets the sensor and ensures that the devices serial number can be read over | ||||||
|  |  * I2C. | ||||||
|  |  */ | ||||||
|  | void HTU31DComponent::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up esphome/components/htu31d HTU31D..."); | ||||||
|  |  | ||||||
|  |   if (!this->reset_()) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->read_serial_num_() == 0) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Called once every update interval (user configured, defaults to 60s) and sets | ||||||
|  |  * the current temperature and humidity. | ||||||
|  |  */ | ||||||
|  | void HTU31DComponent::update() { | ||||||
|  |   ESP_LOGD(TAG, "Checking temperature and humidty values"); | ||||||
|  |  | ||||||
|  |   // Trigger a conversion. From the spec sheet: The conversion command triggers | ||||||
|  |   // a single temperature and humidity conversion. | ||||||
|  |   if (this->write_register(HTU31D_CONVERSION, nullptr, 0) != i2c::ERROR_OK) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     ESP_LOGE(TAG, "Received errror writing conversion register"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Wait conversion time. | ||||||
|  |   this->set_timeout(20, [this]() { | ||||||
|  |     uint8_t thdata[6]; | ||||||
|  |     if (this->read_register(HTU31D_READTEMPHUM, thdata, 6) != i2c::ERROR_OK) { | ||||||
|  |       this->status_set_warning(); | ||||||
|  |       ESP_LOGE(TAG, "Error reading temperature/humidty register"); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Calculate temperature value. | ||||||
|  |     uint16_t raw_temp = encode_uint16(thdata[0], thdata[1]); | ||||||
|  |  | ||||||
|  |     uint8_t crc = compute_crc((uint32_t) raw_temp << 8); | ||||||
|  |     if (crc != thdata[2]) { | ||||||
|  |       this->status_set_warning(); | ||||||
|  |       ESP_LOGE(TAG, "Error validating temperature CRC"); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     float temperature = raw_temp; | ||||||
|  |     temperature /= 65535.0f; | ||||||
|  |     temperature *= 165; | ||||||
|  |     temperature -= 40; | ||||||
|  |  | ||||||
|  |     if (this->temperature_ != nullptr) { | ||||||
|  |       this->temperature_->publish_state(temperature); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Calculate humidty value. | ||||||
|  |     uint16_t raw_hum = encode_uint16(thdata[3], thdata[4]); | ||||||
|  |  | ||||||
|  |     crc = compute_crc((uint32_t) raw_hum << 8); | ||||||
|  |     if (crc != thdata[5]) { | ||||||
|  |       this->status_set_warning(); | ||||||
|  |       ESP_LOGE(TAG, "Error validating humidty CRC"); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     float humidity = raw_hum; | ||||||
|  |     humidity /= 65535.0f; | ||||||
|  |     humidity *= 100; | ||||||
|  |  | ||||||
|  |     if (this->humidity_ != nullptr) { | ||||||
|  |       this->humidity_->publish_state(humidity); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%%", temperature, humidity); | ||||||
|  |     this->status_clear_warning(); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Logs the current compoenent config. | ||||||
|  |  */ | ||||||
|  | void HTU31DComponent::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "HTU31D:"); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, "Communication with HTU31D failed!"); | ||||||
|  |   } | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  |   LOG_SENSOR("  ", "Temperature", this->temperature_); | ||||||
|  |   LOG_SENSOR("  ", "Humidity", this->humidity_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Sends a 'reset' request to the HTU31D, followed by a 15ms delay. | ||||||
|  |  * | ||||||
|  |  * @returns True if was able to write the command successfully | ||||||
|  |  */ | ||||||
|  | bool HTU31DComponent::reset_() { | ||||||
|  |   if (this->write_register(HTU31D_RESET, nullptr, 0) != i2c::ERROR_OK) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   delay(15); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Reads the serial number from the device and checks the CRC. | ||||||
|  |  * | ||||||
|  |  * @returns the 24bit serial number from the device | ||||||
|  |  */ | ||||||
|  | uint32_t HTU31DComponent::read_serial_num_() { | ||||||
|  |   uint8_t reply[4]; | ||||||
|  |   uint32_t serial = 0; | ||||||
|  |   uint8_t padding = 0; | ||||||
|  |  | ||||||
|  |   // Verify we can read the device serial. | ||||||
|  |   if (this->read_register(HTU31D_READSERIAL, reply, 4) != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGE(TAG, "Error reading device serial"); | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   serial = encode_uint32(reply[0], reply[1], reply[2], padding); | ||||||
|  |  | ||||||
|  |   uint8_t crc = compute_crc(serial); | ||||||
|  |   if (crc != reply[3]) { | ||||||
|  |     ESP_LOGE(TAG, "Error validating serial CRC"); | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGD(TAG, "Found serial: 0x%X", serial); | ||||||
|  |  | ||||||
|  |   return serial; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Checks the diagnostics register to determine if the heater is currently | ||||||
|  |  * enabled. | ||||||
|  |  * | ||||||
|  |  * @returns True if the heater is currently enabled, False otherwise | ||||||
|  |  */ | ||||||
|  | bool HTU31DComponent::is_heater_enabled() { | ||||||
|  |   uint8_t reply[1]; | ||||||
|  |   uint8_t heater_enabled_position = 0; | ||||||
|  |   uint8_t mask = 1 << heater_enabled_position; | ||||||
|  |   uint8_t diagnostics = 0; | ||||||
|  |  | ||||||
|  |   if (this->read_register(HTU31D_DIAGNOSTICS, reply, 1) != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGE(TAG, "Error reading device serial"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   diagnostics = reply[0]; | ||||||
|  |   return (diagnostics & mask) != 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Sets the heater state on or off. | ||||||
|  |  * | ||||||
|  |  * @param desired True for on, and False for off. | ||||||
|  |  */ | ||||||
|  | void HTU31DComponent::set_heater_state(bool desired) { | ||||||
|  |   bool current = this->is_heater_enabled(); | ||||||
|  |  | ||||||
|  |   // If the current state matches the desired state, there is nothing to do. | ||||||
|  |   if (current == desired) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Update heater state. | ||||||
|  |   esphome::i2c::ErrorCode err; | ||||||
|  |   if (desired) { | ||||||
|  |     err = this->write_register(HTU31D_HEATERON, nullptr, 0); | ||||||
|  |   } else { | ||||||
|  |     err = this->write_register(HTU31D_HEATEROFF, nullptr, 0); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Record any error. | ||||||
|  |   if (err != i2c::ERROR_OK) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     ESP_LOGE(TAG, "Received error updating heater state"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Sets the startup priority for this component. | ||||||
|  |  * | ||||||
|  |  * @returns The startup priority | ||||||
|  |  */ | ||||||
|  | float HTU31DComponent::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  | }  // namespace htu31d | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										33
									
								
								esphome/components/htu31d/htu31d.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								esphome/components/htu31d/htu31d.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace htu31d { | ||||||
|  |  | ||||||
|  | class HTU31DComponent : public PollingComponent, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   void setup() override;        /// Setup (reset) the sensor and check connection. | ||||||
|  |   void update() override;       /// Update the sensor values (temperature+humidity). | ||||||
|  |   void dump_config() override;  /// Dumps the configuration values. | ||||||
|  |  | ||||||
|  |   void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; } | ||||||
|  |   void set_humidity(sensor::Sensor *humidity) { this->humidity_ = humidity; } | ||||||
|  |  | ||||||
|  |   void set_heater_state(bool desired); | ||||||
|  |   bool is_heater_enabled(); | ||||||
|  |  | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool reset_(); | ||||||
|  |   uint32_t read_serial_num_(); | ||||||
|  |  | ||||||
|  |   sensor::Sensor *temperature_{nullptr}; | ||||||
|  |   sensor::Sensor *humidity_{nullptr}; | ||||||
|  | }; | ||||||
|  | }  // namespace htu31d | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										56
									
								
								esphome/components/htu31d/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								esphome/components/htu31d/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | 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_TEMPERATURE, | ||||||
|  |     DEVICE_CLASS_HUMIDITY, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     UNIT_PERCENT, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | htu31d_ns = cg.esphome_ns.namespace("htu31d") | ||||||
|  | HTU31DComponent = htu31d_ns.class_( | ||||||
|  |     "HTU31DComponent", cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(HTU31DComponent), | ||||||
|  |             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |                 accuracy_decimals=1, | ||||||
|  |                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_PERCENT, | ||||||
|  |                 accuracy_decimals=1, | ||||||
|  |                 device_class=DEVICE_CLASS_HUMIDITY, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x40)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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(sens)) | ||||||
|  |  | ||||||
|  |     if humidity_config := config.get(CONF_HUMIDITY): | ||||||
|  |         sens = await sensor.new_sensor(humidity_config) | ||||||
|  |         cg.add(var.set_humidity(sens)) | ||||||
							
								
								
									
										9
									
								
								tests/components/htu31d/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								tests/components/htu31d/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | i2c: | ||||||
|  |  | ||||||
|  | sensor: | ||||||
|  |   - platform: htu31d | ||||||
|  |     temperature: | ||||||
|  |       name: Living Room Temperature 7 | ||||||
|  |     humidity: | ||||||
|  |       name: Living Room Humidity 7 | ||||||
|  |     update_interval: 15s | ||||||
							
								
								
									
										1
									
								
								tests/components/htu31d/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/htu31d/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										1
									
								
								tests/components/htu31d/test.esp32.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/htu31d/test.esp32.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										1
									
								
								tests/components/htu31d/test.esp8266.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/htu31d/test.esp8266.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <<: !include common.yaml | ||||||
		Reference in New Issue
	
	Block a user