mirror of
https://github.com/esphome/esphome.git
synced 2025-10-27 05:03:48 +00:00
Merge branch 'integration' into memory_api
This commit is contained in:
@@ -201,6 +201,7 @@ esphome/components/havells_solar/* @sourabhjaiswal
|
|||||||
esphome/components/hbridge/fan/* @WeekendWarrior
|
esphome/components/hbridge/fan/* @WeekendWarrior
|
||||||
esphome/components/hbridge/light/* @DotNetDann
|
esphome/components/hbridge/light/* @DotNetDann
|
||||||
esphome/components/hbridge/switch/* @dwmw2
|
esphome/components/hbridge/switch/* @dwmw2
|
||||||
|
esphome/components/hdc2010/* @optimusprimespace @ssieb
|
||||||
esphome/components/he60r/* @clydebarrow
|
esphome/components/he60r/* @clydebarrow
|
||||||
esphome/components/heatpumpir/* @rob-deutsch
|
esphome/components/heatpumpir/* @rob-deutsch
|
||||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||||
|
|||||||
@@ -486,7 +486,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
|
|||||||
if (light->supports_effects()) {
|
if (light->supports_effects()) {
|
||||||
msg.effects.emplace_back("None");
|
msg.effects.emplace_back("None");
|
||||||
for (auto *effect : light->get_effects()) {
|
for (auto *effect : light->get_effects()) {
|
||||||
msg.effects.push_back(effect->get_name());
|
msg.effects.emplace_back(effect->get_name());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size,
|
return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||||
|
|||||||
1
esphome/components/hdc2010/__init__.py
Normal file
1
esphome/components/hdc2010/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CODEOWNERS = ["@optimusprimespace", "@ssieb"]
|
||||||
111
esphome/components/hdc2010/hdc2010.cpp
Normal file
111
esphome/components/hdc2010/hdc2010.cpp
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "hdc2010.h"
|
||||||
|
// https://github.com/vigsterkr/homebridge-hdc2010/blob/main/src/hdc2010.js
|
||||||
|
// https://github.com/lime-labs/HDC2080-Arduino/blob/master/src/HDC2080.cpp
|
||||||
|
namespace esphome {
|
||||||
|
namespace hdc2010 {
|
||||||
|
|
||||||
|
static const char *const TAG = "hdc2010";
|
||||||
|
|
||||||
|
static const uint8_t HDC2010_ADDRESS = 0x40; // 0b1000000 or 0b1000001 from datasheet
|
||||||
|
static const uint8_t HDC2010_CMD_CONFIGURATION_MEASUREMENT = 0x8F;
|
||||||
|
static const uint8_t HDC2010_CMD_START_MEASUREMENT = 0xF9;
|
||||||
|
static const uint8_t HDC2010_CMD_TEMPERATURE_LOW = 0x00;
|
||||||
|
static const uint8_t HDC2010_CMD_TEMPERATURE_HIGH = 0x01;
|
||||||
|
static const uint8_t HDC2010_CMD_HUMIDITY_LOW = 0x02;
|
||||||
|
static const uint8_t HDC2010_CMD_HUMIDITY_HIGH = 0x03;
|
||||||
|
static const uint8_t CONFIG = 0x0E;
|
||||||
|
static const uint8_t MEASUREMENT_CONFIG = 0x0F;
|
||||||
|
|
||||||
|
void HDC2010Component::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Running setup");
|
||||||
|
|
||||||
|
const uint8_t data[2] = {
|
||||||
|
0b00000000, // resolution 14bit for both humidity and temperature
|
||||||
|
0b00000000 // reserved
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!this->write_bytes(HDC2010_CMD_CONFIGURATION_MEASUREMENT, data, 2)) {
|
||||||
|
ESP_LOGW(TAG, "Initial config instruction error");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set measurement mode to temperature and humidity
|
||||||
|
uint8_t config_contents;
|
||||||
|
this->read_register(MEASUREMENT_CONFIG, &config_contents, 1);
|
||||||
|
config_contents = (config_contents & 0xF9); // Always set to TEMP_AND_HUMID mode
|
||||||
|
this->write_bytes(MEASUREMENT_CONFIG, &config_contents, 1);
|
||||||
|
|
||||||
|
// Set rate to manual
|
||||||
|
this->read_register(CONFIG, &config_contents, 1);
|
||||||
|
config_contents &= 0x8F;
|
||||||
|
this->write_bytes(CONFIG, &config_contents, 1);
|
||||||
|
|
||||||
|
// Set temperature resolution to 14bit
|
||||||
|
this->read_register(CONFIG, &config_contents, 1);
|
||||||
|
config_contents &= 0x3F;
|
||||||
|
this->write_bytes(CONFIG, &config_contents, 1);
|
||||||
|
|
||||||
|
// Set humidity resolution to 14bit
|
||||||
|
this->read_register(CONFIG, &config_contents, 1);
|
||||||
|
config_contents &= 0xCF;
|
||||||
|
this->write_bytes(CONFIG, &config_contents, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HDC2010Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "HDC2010:");
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||||
|
}
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HDC2010Component::update() {
|
||||||
|
// Trigger measurement
|
||||||
|
uint8_t config_contents;
|
||||||
|
this->read_register(CONFIG, &config_contents, 1);
|
||||||
|
config_contents |= 0x01;
|
||||||
|
this->write_bytes(MEASUREMENT_CONFIG, &config_contents, 1);
|
||||||
|
|
||||||
|
// 1ms delay after triggering the sample
|
||||||
|
set_timeout(1, [this]() {
|
||||||
|
if (this->temperature_sensor_ != nullptr) {
|
||||||
|
float temp = this->read_temp();
|
||||||
|
this->temperature_sensor_->publish_state(temp);
|
||||||
|
ESP_LOGD(TAG, "Temp=%.1f°C", temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->humidity_sensor_ != nullptr) {
|
||||||
|
float humidity = this->read_humidity();
|
||||||
|
this->humidity_sensor_->publish_state(humidity);
|
||||||
|
ESP_LOGD(TAG, "Humidity=%.1f%%", humidity);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
float HDC2010Component::read_temp() {
|
||||||
|
uint8_t byte[2];
|
||||||
|
|
||||||
|
this->read_register(HDC2010_CMD_TEMPERATURE_LOW, &byte[0], 1);
|
||||||
|
this->read_register(HDC2010_CMD_TEMPERATURE_HIGH, &byte[1], 1);
|
||||||
|
|
||||||
|
uint16_t temp = encode_uint16(byte[1], byte[0]);
|
||||||
|
return (float) temp * 0.0025177f - 40.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float HDC2010Component::read_humidity() {
|
||||||
|
uint8_t byte[2];
|
||||||
|
|
||||||
|
this->read_register(HDC2010_CMD_HUMIDITY_LOW, &byte[0], 1);
|
||||||
|
this->read_register(HDC2010_CMD_HUMIDITY_HIGH, &byte[1], 1);
|
||||||
|
|
||||||
|
uint16_t humidity = encode_uint16(byte[1], byte[0]);
|
||||||
|
return (float) humidity * 0.001525879f;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace hdc2010
|
||||||
|
} // namespace esphome
|
||||||
32
esphome/components/hdc2010/hdc2010.h
Normal file
32
esphome/components/hdc2010/hdc2010.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace hdc2010 {
|
||||||
|
|
||||||
|
class HDC2010Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
void set_temperature_sensor(sensor::Sensor *temperature) { this->temperature_sensor_ = temperature; }
|
||||||
|
|
||||||
|
void set_humidity_sensor(sensor::Sensor *humidity) { this->humidity_sensor_ = humidity; }
|
||||||
|
|
||||||
|
/// Setup the sensor and check for connection.
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
/// Retrieve the latest sensor values. This operation takes approximately 16ms.
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
float read_temp();
|
||||||
|
|
||||||
|
float read_humidity();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
sensor::Sensor *temperature_sensor_{nullptr};
|
||||||
|
sensor::Sensor *humidity_sensor_{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace hdc2010
|
||||||
|
} // namespace esphome
|
||||||
56
esphome/components/hdc2010/sensor.py
Normal file
56
esphome/components/hdc2010/sensor.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import i2c, sensor
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
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"]
|
||||||
|
|
||||||
|
hdc2010_ns = cg.esphome_ns.namespace("hdc2010")
|
||||||
|
HDC2010Component = hdc2010_ns.class_(
|
||||||
|
"HDC2010Component", cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(HDC2010Component),
|
||||||
|
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=0,
|
||||||
|
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_sensor(sens))
|
||||||
|
|
||||||
|
if humidity_config := config.get(CONF_HUMIDITY):
|
||||||
|
sens = await sensor.new_sensor(humidity_config)
|
||||||
|
cg.add(var.set_humidity_sensor(sens))
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <deque>
|
|
||||||
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
|
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -224,36 +224,37 @@ def resolve_ip_address(
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
# Process hosts
|
# Process hosts
|
||||||
cached_addresses: list[str] = []
|
|
||||||
uncached_hosts: list[str] = []
|
uncached_hosts: list[str] = []
|
||||||
has_cache = address_cache is not None
|
|
||||||
|
|
||||||
for h in hosts:
|
for h in hosts:
|
||||||
if is_ip_address(h):
|
if is_ip_address(h):
|
||||||
if has_cache:
|
_add_ip_addresses_to_addrinfo([h], port, res)
|
||||||
# If we have a cache, treat IPs as cached
|
|
||||||
cached_addresses.append(h)
|
|
||||||
else:
|
|
||||||
# If no cache, pass IPs through to resolver with hostnames
|
|
||||||
uncached_hosts.append(h)
|
|
||||||
elif address_cache and (cached := address_cache.get_addresses(h)):
|
elif address_cache and (cached := address_cache.get_addresses(h)):
|
||||||
# Found in cache
|
_add_ip_addresses_to_addrinfo(cached, port, res)
|
||||||
cached_addresses.extend(cached)
|
|
||||||
else:
|
else:
|
||||||
# Not cached, need to resolve
|
# Not cached, need to resolve
|
||||||
if address_cache and address_cache.has_cache():
|
if address_cache and address_cache.has_cache():
|
||||||
_LOGGER.info("Host %s not in cache, will need to resolve", h)
|
_LOGGER.info("Host %s not in cache, will need to resolve", h)
|
||||||
uncached_hosts.append(h)
|
uncached_hosts.append(h)
|
||||||
|
|
||||||
# Process cached addresses (includes direct IPs and cached lookups)
|
|
||||||
_add_ip_addresses_to_addrinfo(cached_addresses, port, res)
|
|
||||||
|
|
||||||
# If we have uncached hosts (only non-IP hostnames), resolve them
|
# If we have uncached hosts (only non-IP hostnames), resolve them
|
||||||
if uncached_hosts:
|
if uncached_hosts:
|
||||||
|
from aioesphomeapi.host_resolver import AddrInfo as AioAddrInfo
|
||||||
|
|
||||||
|
from esphome.core import EsphomeError
|
||||||
from esphome.resolver import AsyncResolver
|
from esphome.resolver import AsyncResolver
|
||||||
|
|
||||||
resolver = AsyncResolver(uncached_hosts, port)
|
resolver = AsyncResolver(uncached_hosts, port)
|
||||||
|
addr_infos: list[AioAddrInfo] = []
|
||||||
|
try:
|
||||||
addr_infos = resolver.resolve()
|
addr_infos = resolver.resolve()
|
||||||
|
except EsphomeError as err:
|
||||||
|
if not res:
|
||||||
|
# No pre-resolved addresses available, DNS resolution is fatal
|
||||||
|
raise
|
||||||
|
_LOGGER.info("%s (using %d already resolved IP addresses)", err, len(res))
|
||||||
|
|
||||||
# Convert aioesphomeapi AddrInfo to our format
|
# Convert aioesphomeapi AddrInfo to our format
|
||||||
for addr_info in addr_infos:
|
for addr_info in addr_infos:
|
||||||
sockaddr = addr_info.sockaddr
|
sockaddr = addr_info.sockaddr
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
|
|||||||
esptool==5.1.0
|
esptool==5.1.0
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
esphome-dashboard==20251013.0
|
esphome-dashboard==20251013.0
|
||||||
aioesphomeapi==42.2.0
|
aioesphomeapi==42.3.0
|
||||||
zeroconf==0.148.0
|
zeroconf==0.148.0
|
||||||
puremagic==1.30
|
puremagic==1.30
|
||||||
ruamel.yaml==0.18.15 # dashboard_import
|
ruamel.yaml==0.18.15 # dashboard_import
|
||||||
@@ -22,7 +22,7 @@ pillow==11.3.0
|
|||||||
cairosvg==2.8.2
|
cairosvg==2.8.2
|
||||||
freetype-py==2.5.1
|
freetype-py==2.5.1
|
||||||
jinja2==3.1.6
|
jinja2==3.1.6
|
||||||
bleak==1.0.1
|
bleak==1.1.1
|
||||||
|
|
||||||
# esp-idf >= 5.0 requires this
|
# esp-idf >= 5.0 requires this
|
||||||
pyparsing >= 3.0
|
pyparsing >= 3.0
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ ISOLATED_COMPONENTS = {
|
|||||||
"esphome": "Defines devices/areas in esphome: section that are referenced in other sections - breaks when merged",
|
"esphome": "Defines devices/areas in esphome: section that are referenced in other sections - breaks when merged",
|
||||||
"ethernet": "Defines ethernet: which conflicts with wifi: used by most components",
|
"ethernet": "Defines ethernet: which conflicts with wifi: used by most components",
|
||||||
"ethernet_info": "Related to ethernet component which conflicts with wifi",
|
"ethernet_info": "Related to ethernet component which conflicts with wifi",
|
||||||
|
"gps": "TinyGPSPlus library declares millis() function that creates ambiguity with ESPHome millis() macro when merged with components using millis() in lambdas",
|
||||||
"lvgl": "Defines multiple SDL displays on host platform that conflict when merged with other display configs",
|
"lvgl": "Defines multiple SDL displays on host platform that conflict when merged with other display configs",
|
||||||
"mapping": "Uses dict format for image/display sections incompatible with standard list format - ESPHome merge_config cannot handle",
|
"mapping": "Uses dict format for image/display sections incompatible with standard list format - ESPHome merge_config cannot handle",
|
||||||
"openthread": "Conflicts with wifi: used by most components",
|
"openthread": "Conflicts with wifi: used by most components",
|
||||||
|
|||||||
7
tests/components/hdc2010/common.yaml
Normal file
7
tests/components/hdc2010/common.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
sensor:
|
||||||
|
- platform: hdc2010
|
||||||
|
i2c_id: i2c_bus
|
||||||
|
temperature:
|
||||||
|
name: Temperature
|
||||||
|
humidity:
|
||||||
|
name: Humidity
|
||||||
4
tests/components/hdc2010/test.esp32-c3-idf.yaml
Normal file
4
tests/components/hdc2010/test.esp32-c3-idf.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
packages:
|
||||||
|
i2c: !include ../../test_build_components/common/i2c/esp32-c3-idf.yaml
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
||||||
4
tests/components/hdc2010/test.esp32-idf.yaml
Normal file
4
tests/components/hdc2010/test.esp32-idf.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
packages:
|
||||||
|
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
||||||
4
tests/components/hdc2010/test.esp8266-ard.yaml
Normal file
4
tests/components/hdc2010/test.esp8266-ard.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
packages:
|
||||||
|
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
||||||
4
tests/components/hdc2010/test.rp2040-ard.yaml
Normal file
4
tests/components/hdc2010/test.rp2040-ard.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
packages:
|
||||||
|
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
||||||
@@ -454,9 +454,27 @@ def test_resolve_ip_address_mixed_list() -> None:
|
|||||||
# Mix of IP and hostname - should use async resolver
|
# Mix of IP and hostname - should use async resolver
|
||||||
result = helpers.resolve_ip_address(["192.168.1.100", "test.local"], 6053)
|
result = helpers.resolve_ip_address(["192.168.1.100", "test.local"], 6053)
|
||||||
|
|
||||||
|
assert len(result) == 2
|
||||||
|
assert result[0][4][0] == "192.168.1.100"
|
||||||
|
assert result[1][4][0] == "192.168.1.200"
|
||||||
|
MockResolver.assert_called_once_with(["test.local"], 6053)
|
||||||
|
mock_resolver.resolve.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_ip_address_mixed_list_fail() -> None:
|
||||||
|
"""Test resolving a mix of IPs and hostnames with resolve failed."""
|
||||||
|
with patch("esphome.resolver.AsyncResolver") as MockResolver:
|
||||||
|
mock_resolver = MockResolver.return_value
|
||||||
|
mock_resolver.resolve.side_effect = EsphomeError(
|
||||||
|
"Error resolving IP address: [test.local]"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mix of IP and hostname - should use async resolver
|
||||||
|
result = helpers.resolve_ip_address(["192.168.1.100", "test.local"], 6053)
|
||||||
|
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
assert result[0][4][0] == "192.168.1.200"
|
assert result[0][4][0] == "192.168.1.100"
|
||||||
MockResolver.assert_called_once_with(["192.168.1.100", "test.local"], 6053)
|
MockResolver.assert_called_once_with(["test.local"], 6053)
|
||||||
mock_resolver.resolve.assert_called_once()
|
mock_resolver.resolve.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user