1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-03 10:32:21 +01:00

Merge branch 'dev' into client_info_flash

This commit is contained in:
J. Nick Koston
2025-10-01 15:24:17 +02:00
committed by GitHub
8 changed files with 200 additions and 127 deletions

View File

@@ -7,24 +7,20 @@ namespace hdc1080 {
static const char *const TAG = "hdc1080"; static const char *const TAG = "hdc1080";
static const uint8_t HDC1080_ADDRESS = 0x40; // 0b1000000 from datasheet
static const uint8_t HDC1080_CMD_CONFIGURATION = 0x02; static const uint8_t HDC1080_CMD_CONFIGURATION = 0x02;
static const uint8_t HDC1080_CMD_TEMPERATURE = 0x00; static const uint8_t HDC1080_CMD_TEMPERATURE = 0x00;
static const uint8_t HDC1080_CMD_HUMIDITY = 0x01; static const uint8_t HDC1080_CMD_HUMIDITY = 0x01;
void HDC1080Component::setup() { void HDC1080Component::setup() {
const uint8_t data[2] = { const uint8_t config[2] = {0x00, 0x00}; // resolution 14bit for both humidity and temperature
0b00000000, // resolution 14bit for both humidity and temperature
0b00000000 // reserved
};
if (!this->write_bytes(HDC1080_CMD_CONFIGURATION, data, 2)) { // if configuration fails - there is a problem
// as instruction is same as powerup defaults (for now), interpret as warning if this fails if (this->write_register(HDC1080_CMD_CONFIGURATION, config, 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "HDC1080 initial config instruction error"); this->mark_failed();
this->status_set_warning();
return; return;
} }
} }
void HDC1080Component::dump_config() { void HDC1080Component::dump_config() {
ESP_LOGCONFIG(TAG, "HDC1080:"); ESP_LOGCONFIG(TAG, "HDC1080:");
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
@@ -35,39 +31,51 @@ void HDC1080Component::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Humidity", this->humidity_);
} }
void HDC1080Component::update() { void HDC1080Component::update() {
uint16_t raw_temp; // regardless of what sensor/s are defined in yaml configuration
// the hdc1080 setup configuration used, requires both temperature and humidity to be read
this->status_clear_warning();
if (this->write(&HDC1080_CMD_TEMPERATURE, 1) != i2c::ERROR_OK) { if (this->write(&HDC1080_CMD_TEMPERATURE, 1) != i2c::ERROR_OK) {
this->status_set_warning(); this->status_set_warning();
return; return;
} }
delay(20);
if (this->read(reinterpret_cast<uint8_t *>(&raw_temp), 2) != i2c::ERROR_OK) { this->set_timeout(20, [this]() {
uint16_t raw_temperature;
if (this->read(reinterpret_cast<uint8_t *>(&raw_temperature), 2) != i2c::ERROR_OK) {
this->status_set_warning(); this->status_set_warning();
return; return;
} }
raw_temp = i2c::i2ctohs(raw_temp);
float temp = raw_temp * 0.0025177f - 40.0f; // raw * 2^-16 * 165 - 40
this->temperature_->publish_state(temp);
uint16_t raw_humidity; if (this->temperature_ != nullptr) {
raw_temperature = i2c::i2ctohs(raw_temperature);
float temperature = raw_temperature * 0.0025177f - 40.0f; // raw * 2^-16 * 165 - 40
this->temperature_->publish_state(temperature);
}
if (this->write(&HDC1080_CMD_HUMIDITY, 1) != i2c::ERROR_OK) { if (this->write(&HDC1080_CMD_HUMIDITY, 1) != i2c::ERROR_OK) {
this->status_set_warning(); this->status_set_warning();
return; return;
} }
delay(20);
this->set_timeout(20, [this]() {
uint16_t raw_humidity;
if (this->read(reinterpret_cast<uint8_t *>(&raw_humidity), 2) != i2c::ERROR_OK) { if (this->read(reinterpret_cast<uint8_t *>(&raw_humidity), 2) != i2c::ERROR_OK) {
this->status_set_warning(); this->status_set_warning();
return; return;
} }
if (this->humidity_ != nullptr) {
raw_humidity = i2c::i2ctohs(raw_humidity); raw_humidity = i2c::i2ctohs(raw_humidity);
float humidity = raw_humidity * 0.001525879f; // raw * 2^-16 * 100 float humidity = raw_humidity * 0.001525879f; // raw * 2^-16 * 100
this->humidity_->publish_state(humidity); this->humidity_->publish_state(humidity);
}
ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temp, humidity); });
this->status_clear_warning(); });
} }
float HDC1080Component::get_setup_priority() const { return setup_priority::DATA; }
} // namespace hdc1080 } // namespace hdc1080
} // namespace esphome } // namespace esphome

View File

@@ -12,13 +12,11 @@ class HDC1080Component : public PollingComponent, public i2c::I2CDevice {
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
/// Setup the sensor and check for connection.
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
/// Retrieve the latest sensor values. This operation takes approximately 16ms.
void update() override; void update() override;
float get_setup_priority() const override; float get_setup_priority() const override { return setup_priority::DATA; }
protected: protected:
sensor::Sensor *temperature_{nullptr}; sensor::Sensor *temperature_{nullptr};

View File

@@ -62,6 +62,11 @@ SPIRAM_SPEEDS = {
} }
def supported() -> bool:
variant = get_esp32_variant()
return variant in SPIRAM_MODES
def validate_psram_mode(config): def validate_psram_mode(config):
esp32_config = fv.full_config.get()[PLATFORM_ESP32] esp32_config = fv.full_config.get()[PLATFORM_ESP32]
if config[CONF_SPEED] == "120MHZ": if config[CONF_SPEED] == "120MHZ":
@@ -95,7 +100,7 @@ def get_config_schema(config):
variant = get_esp32_variant() variant = get_esp32_variant()
speeds = [f"{s}MHZ" for s in SPIRAM_SPEEDS.get(variant, [])] speeds = [f"{s}MHZ" for s in SPIRAM_SPEEDS.get(variant, [])]
if not speeds: if not speeds:
return cv.Invalid("PSRAM is not supported on this chip") raise cv.Invalid("PSRAM is not supported on this chip")
modes = SPIRAM_MODES[variant] modes = SPIRAM_MODES[variant]
return cv.Schema( return cv.Schema(
{ {

View File

@@ -40,7 +40,13 @@ void RemoteTransmitterComponent::await_target_time_() {
if (this->target_time_ == 0) { if (this->target_time_ == 0) {
this->target_time_ = current_time; this->target_time_ = current_time;
} else if ((int32_t) (this->target_time_ - current_time) > 0) { } else if ((int32_t) (this->target_time_ - current_time) > 0) {
#if defined(USE_LIBRETINY)
// busy loop for libretiny is required (see the comment inside micros() in wiring.c)
while ((int32_t) (this->target_time_ - micros()) > 0)
;
#else
delayMicroseconds(this->target_time_ - current_time); delayMicroseconds(this->target_time_ - current_time);
#endif
} }
} }

View File

@@ -1,9 +1,10 @@
from ast import literal_eval
import logging import logging
import math import math
import re import re
import jinja2 as jinja import jinja2 as jinja
from jinja2.nativetypes import NativeEnvironment from jinja2.sandbox import SandboxedEnvironment
TemplateError = jinja.TemplateError TemplateError = jinja.TemplateError
TemplateSyntaxError = jinja.TemplateSyntaxError TemplateSyntaxError = jinja.TemplateSyntaxError
@@ -70,7 +71,7 @@ class Jinja:
""" """
def __init__(self, context_vars): def __init__(self, context_vars):
self.env = NativeEnvironment( self.env = SandboxedEnvironment(
trim_blocks=True, trim_blocks=True,
lstrip_blocks=True, lstrip_blocks=True,
block_start_string="<%", block_start_string="<%",
@@ -90,6 +91,15 @@ class Jinja:
**SAFE_GLOBAL_FUNCTIONS, **SAFE_GLOBAL_FUNCTIONS,
} }
def safe_eval(self, expr):
try:
result = literal_eval(expr)
if not isinstance(result, str):
return result
except (ValueError, SyntaxError, MemoryError, TypeError):
pass
return expr
def expand(self, content_str): def expand(self, content_str):
""" """
Renders a string that may contain Jinja expressions or statements Renders a string that may contain Jinja expressions or statements
@@ -106,7 +116,7 @@ class Jinja:
override_vars = content_str.upvalues override_vars = content_str.upvalues
try: try:
template = self.env.from_string(content_str) template = self.env.from_string(content_str)
result = template.render(override_vars) result = self.safe_eval(template.render(override_vars))
if isinstance(result, Undefined): if isinstance(result, Undefined):
# This happens when the expression is simply an undefined variable. Jinja does not # This happens when the expression is simply an undefined variable. Jinja does not
# raise an exception, instead we get "Undefined". # raise an exception, instead we get "Undefined".

View File

@@ -829,15 +829,28 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa
} }
auto call = obj->make_call(); auto call = obj->make_call();
if (match.method_equals("open")) {
call.set_command_open(); // Lookup table for cover methods
} else if (match.method_equals("close")) { static const struct {
call.set_command_close(); const char *name;
} else if (match.method_equals("stop")) { cover::CoverCall &(cover::CoverCall::*action)();
call.set_command_stop(); } METHODS[] = {
} else if (match.method_equals("toggle")) { {"open", &cover::CoverCall::set_command_open},
call.set_command_toggle(); {"close", &cover::CoverCall::set_command_close},
} else if (!match.method_equals("set")) { {"stop", &cover::CoverCall::set_command_stop},
{"toggle", &cover::CoverCall::set_command_toggle},
};
bool found = false;
for (const auto &method : METHODS) {
if (match.method_equals(method.name)) {
(call.*method.action)();
found = true;
break;
}
}
if (!found && !match.method_equals("set")) {
request->send(404); request->send(404);
return; return;
} }
@@ -1483,15 +1496,28 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa
} }
auto call = obj->make_call(); auto call = obj->make_call();
if (match.method_equals("open")) {
call.set_command_open(); // Lookup table for valve methods
} else if (match.method_equals("close")) { static const struct {
call.set_command_close(); const char *name;
} else if (match.method_equals("stop")) { valve::ValveCall &(valve::ValveCall::*action)();
call.set_command_stop(); } METHODS[] = {
} else if (match.method_equals("toggle")) { {"open", &valve::ValveCall::set_command_open},
call.set_command_toggle(); {"close", &valve::ValveCall::set_command_close},
} else if (!match.method_equals("set")) { {"stop", &valve::ValveCall::set_command_stop},
{"toggle", &valve::ValveCall::set_command_toggle},
};
bool found = false;
for (const auto &method : METHODS) {
if (match.method_equals(method.name)) {
(call.*method.action)();
found = true;
break;
}
}
if (!found && !match.method_equals("set")) {
request->send(404); request->send(404);
return; return;
} }
@@ -1555,17 +1581,28 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques
auto call = obj->make_call(); auto call = obj->make_call();
parse_string_param_(request, "code", call, &decltype(call)::set_code); parse_string_param_(request, "code", call, &decltype(call)::set_code);
if (match.method_equals("disarm")) { // Lookup table for alarm control panel methods
call.disarm(); static const struct {
} else if (match.method_equals("arm_away")) { const char *name;
call.arm_away(); alarm_control_panel::AlarmControlPanelCall &(alarm_control_panel::AlarmControlPanelCall::*action)();
} else if (match.method_equals("arm_home")) { } METHODS[] = {
call.arm_home(); {"disarm", &alarm_control_panel::AlarmControlPanelCall::disarm},
} else if (match.method_equals("arm_night")) { {"arm_away", &alarm_control_panel::AlarmControlPanelCall::arm_away},
call.arm_night(); {"arm_home", &alarm_control_panel::AlarmControlPanelCall::arm_home},
} else if (match.method_equals("arm_vacation")) { {"arm_night", &alarm_control_panel::AlarmControlPanelCall::arm_night},
call.arm_vacation(); {"arm_vacation", &alarm_control_panel::AlarmControlPanelCall::arm_vacation},
} else { };
bool found = false;
for (const auto &method : METHODS) {
if (match.method_equals(method.name)) {
(call.*method.action)();
found = true;
break;
}
}
if (!found) {
request->send(404); request->send(404);
return; return;
} }
@@ -1731,24 +1768,24 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) const {
const auto &url = request->url(); const auto &url = request->url();
const auto method = request->method(); const auto method = request->method();
// Simple URL checks // Static URL checks
if (url == "/") static const char *const STATIC_URLS[] = {
return true; "/",
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
if (url == "/events") "/events",
return true;
#endif #endif
#ifdef USE_WEBSERVER_CSS_INCLUDE #ifdef USE_WEBSERVER_CSS_INCLUDE
if (url == "/0.css") "/0.css",
return true;
#endif #endif
#ifdef USE_WEBSERVER_JS_INCLUDE #ifdef USE_WEBSERVER_JS_INCLUDE
if (url == "/0.js") "/0.js",
return true;
#endif #endif
};
for (const auto &static_url : STATIC_URLS) {
if (url == static_url)
return true;
}
#ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
if (method == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) if (method == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA))
@@ -1768,92 +1805,87 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) const {
if (!is_get_or_post) if (!is_get_or_post)
return false; return false;
// GET-only components // Use lookup tables for domain checks
if (is_get) { static const char *const GET_ONLY_DOMAINS[] = {
#ifdef USE_SENSOR #ifdef USE_SENSOR
if (match.domain_equals("sensor")) "sensor",
return true;
#endif #endif
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
if (match.domain_equals("binary_sensor")) "binary_sensor",
return true;
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
if (match.domain_equals("text_sensor")) "text_sensor",
return true;
#endif #endif
#ifdef USE_EVENT #ifdef USE_EVENT
if (match.domain_equals("event")) "event",
return true;
#endif #endif
} };
// GET+POST components static const char *const GET_POST_DOMAINS[] = {
if (is_get_or_post) {
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (match.domain_equals("switch")) "switch",
return true;
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
if (match.domain_equals("button")) "button",
return true;
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
if (match.domain_equals("fan")) "fan",
return true;
#endif #endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
if (match.domain_equals("light")) "light",
return true;
#endif #endif
#ifdef USE_COVER #ifdef USE_COVER
if (match.domain_equals("cover")) "cover",
return true;
#endif #endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
if (match.domain_equals("number")) "number",
return true;
#endif #endif
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
if (match.domain_equals("date")) "date",
return true;
#endif #endif
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
if (match.domain_equals("time")) "time",
return true;
#endif #endif
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
if (match.domain_equals("datetime")) "datetime",
return true;
#endif #endif
#ifdef USE_TEXT #ifdef USE_TEXT
if (match.domain_equals("text")) "text",
return true;
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
if (match.domain_equals("select")) "select",
return true;
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
if (match.domain_equals("climate")) "climate",
return true;
#endif #endif
#ifdef USE_LOCK #ifdef USE_LOCK
if (match.domain_equals("lock")) "lock",
return true;
#endif #endif
#ifdef USE_VALVE #ifdef USE_VALVE
if (match.domain_equals("valve")) "valve",
return true;
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
if (match.domain_equals("alarm_control_panel")) "alarm_control_panel",
return true;
#endif #endif
#ifdef USE_UPDATE #ifdef USE_UPDATE
if (match.domain_equals("update")) "update",
return true;
#endif #endif
};
// Check GET-only domains
if (is_get) {
for (const auto &domain : GET_ONLY_DOMAINS) {
if (match.domain_equals(domain))
return true;
}
}
// Check GET+POST domains
if (is_get_or_post) {
for (const auto &domain : GET_POST_DOMAINS) {
if (match.domain_equals(domain))
return true;
}
} }
return false; return false;

View File

@@ -5,6 +5,9 @@ substitutions:
var21: '79' var21: '79'
value: 33 value: 33
values: 44 values: 44
position:
x: 79
y: 82
esphome: esphome:
name: test name: test
@@ -26,3 +29,7 @@ test_list:
- Literal $values ${are not substituted} - Literal $values ${are not substituted}
- ["list $value", "${is not}", "${substituted}"] - ["list $value", "${is not}", "${substituted}"]
- {"$dictionary": "$value", "${is not}": "${substituted}"} - {"$dictionary": "$value", "${is not}": "${substituted}"}
- |-
{{{ "x", "79"}, { "y", "82"}}}
- '{{{"AA"}}}'
- '"HELLO"'

View File

@@ -8,6 +8,9 @@ substitutions:
var21: "79" var21: "79"
value: 33 value: 33
values: 44 values: 44
position:
x: 79
y: 82
test_list: test_list:
- "$var1" - "$var1"
@@ -27,3 +30,7 @@ test_list:
- !literal Literal $values ${are not substituted} - !literal Literal $values ${are not substituted}
- !literal ["list $value", "${is not}", "${substituted}"] - !literal ["list $value", "${is not}", "${substituted}"]
- !literal {"$dictionary": "$value", "${is not}": "${substituted}"} - !literal {"$dictionary": "$value", "${is not}": "${substituted}"}
- |- # Test parsing things that look like a python set of sets when rendered:
{{{ "x", "${ position.x }"}, { "y", "${ position.y }"}}}
- ${ '{{{"AA"}}}' }
- ${ '"HELLO"' }