mirror of
https://github.com/esphome/esphome.git
synced 2024-11-22 06:58:11 +01:00
feat: Add HTU31D Support (#5805)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
77214a677b
commit
3abf2f1d14
9 changed files with 372 additions and 0 deletions
|
@ -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
|
Loading…
Reference in a new issue