mirror of
https://github.com/esphome/esphome.git
synced 2026-07-10 17:05:36 +00:00
Merge branch 'dev' into frenck/replace-voluptuous-with-probatio
This commit is contained in:
@@ -13,7 +13,7 @@ permissions:
|
||||
|
||||
env:
|
||||
DEFAULT_PYTHON: "3.12"
|
||||
PYUPGRADE_TARGET: "--py311-plus"
|
||||
PYUPGRADE_TARGET: "--py312-plus"
|
||||
|
||||
concurrency:
|
||||
# yamllint disable-line rule:line-length
|
||||
|
||||
@@ -40,7 +40,7 @@ repos:
|
||||
rev: v3.21.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py311-plus]
|
||||
args: [--py312-plus]
|
||||
- repo: https://github.com/adrienverge/yamllint.git
|
||||
rev: v1.37.1
|
||||
hooks:
|
||||
|
||||
@@ -9,7 +9,7 @@ This document provides essential context for AI models interacting with this pro
|
||||
|
||||
## 2. Core Technologies & Stack
|
||||
|
||||
* **Languages:** Python (>=3.11), C++ (gnu++20)
|
||||
* **Languages:** Python (>=3.12), C++ (gnu++20)
|
||||
* **Frameworks & Runtimes:** PlatformIO, Arduino, ESP-IDF.
|
||||
* **Build Systems:** PlatformIO is the primary build system. CMake is used as an alternative.
|
||||
* **Configuration:** YAML.
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ RUN \
|
||||
-r /requirements.txt
|
||||
|
||||
# Install the ESPHome Device Builder dashboard.
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.0.20
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.0.21
|
||||
|
||||
RUN \
|
||||
platformio settings set enable_telemetry No \
|
||||
|
||||
@@ -12,12 +12,9 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable
|
||||
import threading
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
class AsyncThreadRunner(threading.Thread, Generic[_T]):
|
||||
class AsyncThreadRunner[T](threading.Thread):
|
||||
"""Run an async coroutine in a daemon thread and expose its result.
|
||||
|
||||
The runner catches all exceptions from the coroutine and stores them in
|
||||
@@ -35,10 +32,10 @@ class AsyncThreadRunner(threading.Thread, Generic[_T]):
|
||||
result = runner.result
|
||||
"""
|
||||
|
||||
def __init__(self, coro_factory: Callable[[], Awaitable[_T]]) -> None:
|
||||
def __init__(self, coro_factory: Callable[[], Awaitable[T]]) -> None:
|
||||
super().__init__(daemon=True)
|
||||
self._coro_factory = coro_factory
|
||||
self.result: _T | None = None
|
||||
self.result: T | None = None
|
||||
self.exception: BaseException | None = None
|
||||
self.event = threading.Event()
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ void A01nyubComponent::check_buffer_() {
|
||||
if (this->buffer_[3] == checksum) {
|
||||
float distance = (this->buffer_[1] << 8) + this->buffer_[2];
|
||||
if (distance > 280) {
|
||||
float meters = distance / 1000.0;
|
||||
float meters = distance / 1000.0f;
|
||||
ESP_LOGV(TAG, "Distance from sensor: %f mm, %f m", distance, meters);
|
||||
this->publish_state(meters);
|
||||
} else {
|
||||
|
||||
@@ -216,7 +216,7 @@ void AcDimmer::setup() {
|
||||
}
|
||||
|
||||
void AcDimmer::write_state(float state) {
|
||||
state = std::acos(1 - (2 * state)) / std::numbers::pi; // RMS power compensation
|
||||
state = std::acos(1 - (2 * state)) / std::numbers::pi_v<float>; // RMS power compensation
|
||||
auto new_value = static_cast<uint16_t>(roundf(state * 65535));
|
||||
if (new_value != 0 && this->store_.value == 0)
|
||||
this->store_.init_cycle = this->init_with_half_cycle_;
|
||||
|
||||
@@ -114,13 +114,13 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
this->decoder_->decode(param->notify.value, param->notify.value_len);
|
||||
|
||||
if (this->decoder_->has_position()) {
|
||||
this->position = ((float) this->decoder_->position_ / 100.0);
|
||||
this->position = ((float) this->decoder_->position_ / 100.0f);
|
||||
if (!this->invert_position_)
|
||||
this->position = 1 - this->position;
|
||||
if (this->position > 0.97)
|
||||
this->position = 1.0;
|
||||
if (this->position < 0.02)
|
||||
this->position = 0.0;
|
||||
if (this->position > 0.97f)
|
||||
this->position = 1.0f;
|
||||
if (this->position < 0.02f)
|
||||
this->position = 0.0f;
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
namespace esphome::anova {
|
||||
|
||||
float ftoc(float f) { return (f - 32.0) * (5.0f / 9.0f); }
|
||||
float ftoc(float f) { return (f - 32.0f) * (5.0f / 9.0f); }
|
||||
|
||||
float ctof(float c) { return (c * 9.0f / 5.0f) + 32.0; }
|
||||
float ctof(float c) { return (c * 9.0f / 5.0f) + 32.0f; }
|
||||
|
||||
AnovaPacket *AnovaCodec::clean_packet_() {
|
||||
this->packet_.length = strlen((char *) this->packet_.data);
|
||||
|
||||
@@ -395,7 +395,7 @@ async def to_code(config):
|
||||
)
|
||||
if data.mp3_support:
|
||||
cg.add_define("USE_AUDIO_MP3_SUPPORT")
|
||||
add_idf_component(name="esphome/micro-mp3", ref="0.3.0")
|
||||
add_idf_component(name="esphome/micro-mp3", ref="0.4.0")
|
||||
_emit_memory_pair(
|
||||
data.mp3.buffer_memory,
|
||||
"CONFIG_MICRO_MP3_PREFER_PSRAM",
|
||||
|
||||
@@ -112,7 +112,7 @@ float BinarySensorMap::bayesian_predicate_(bool sensor_state, float prior, float
|
||||
prob_state_source_false = 1 - prob_given_false;
|
||||
}
|
||||
|
||||
return prob_state_source_true / (prior * prob_state_source_true + (1.0 - prior) * prob_state_source_false);
|
||||
return prob_state_source_true / (prior * prob_state_source_true + (1.0f - prior) * prob_state_source_false);
|
||||
}
|
||||
|
||||
void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) {
|
||||
|
||||
@@ -205,7 +205,7 @@ void BL0906::read_data_(const uint8_t address, const float reference, sensor::Se
|
||||
// Chip temperature
|
||||
if (reference == BL0906_TREF) {
|
||||
value = (float) to_int32_t(data_s24);
|
||||
value = (value - 64) * 12.5 / 59 - 40;
|
||||
value = (value - 64) * 12.5f / 59 - 40;
|
||||
}
|
||||
sensor->publish_state(value);
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ float BL0940::calculate_power_reference_() {
|
||||
float BL0940::calculate_energy_reference_() {
|
||||
// formula: 3600000 * 4046 * RL * R1 * 1000 / (1638.4 * 256) / Vref² / (R1 + R2)
|
||||
// or: power_reference_ * 3600000 / (1638.4 * 256)
|
||||
return this->power_reference_cal_ * 3600000 / (1638.4 * 256);
|
||||
return this->power_reference_cal_ * 3600000 / (1638.4f * 256);
|
||||
}
|
||||
|
||||
float BL0940::calculate_calibration_value_(float state) { return (100 + state) / 100; }
|
||||
|
||||
@@ -124,14 +124,14 @@ void BL0942::setup() {
|
||||
// If either current or voltage references are set explicitly by the user,
|
||||
// calculate the power reference from it unless that is also explicitly set.
|
||||
if ((this->current_reference_set_ || this->voltage_reference_set_) && !this->power_reference_set_) {
|
||||
this->power_reference_ = (this->voltage_reference_ * this->current_reference_ * 3537.0 / 305978.0) / 73989.0;
|
||||
this->power_reference_ = (this->voltage_reference_ * this->current_reference_ * 3537.0f / 305978.0f) / 73989.0f;
|
||||
this->power_reference_set_ = true;
|
||||
}
|
||||
|
||||
// Similarly for energy reference, if the power reference was set by the user
|
||||
// either implicitly or explicitly.
|
||||
if (this->power_reference_set_ && !this->energy_reference_set_) {
|
||||
this->energy_reference_ = this->power_reference_ * 3600000 / 419430.4;
|
||||
this->energy_reference_ = this->power_reference_ * 3600000 / 419430.4f;
|
||||
this->energy_reference_set_ = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ void MedianCombinationComponent::handle_new_value(float value) {
|
||||
median = sensor_states[sensor_states_size / 2];
|
||||
} else {
|
||||
// Even number of measurements, use the average of the two middle measurements
|
||||
median = (sensor_states[sensor_states_size / 2] + sensor_states[sensor_states_size / 2 - 1]) / 2.0;
|
||||
median = (sensor_states[sensor_states_size / 2] + sensor_states[sensor_states_size / 2 - 1]) / 2.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ void CurrentBasedCover::control(const CoverCall &call) {
|
||||
auto opt_pos = call.get_position();
|
||||
if (opt_pos.has_value()) {
|
||||
auto pos = *opt_pos;
|
||||
if (fabsf(this->position - pos) < 0.01) {
|
||||
if (fabsf(this->position - pos) < 0.01f) {
|
||||
// already at target
|
||||
} else {
|
||||
auto op = pos < this->position ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING;
|
||||
|
||||
@@ -151,7 +151,7 @@ uint8_t DaikinBrcClimate::temperature_() {
|
||||
// Temperature in remote is in F
|
||||
if (this->fahrenheit_) {
|
||||
temperature = (uint8_t) roundf(
|
||||
clamp<float>(((this->target_temperature * 1.8) + 32), DAIKIN_BRC_TEMP_MIN_F, DAIKIN_BRC_TEMP_MAX_F));
|
||||
clamp<float>(((this->target_temperature * 1.8f) + 32), DAIKIN_BRC_TEMP_MIN_F, DAIKIN_BRC_TEMP_MAX_F));
|
||||
} else {
|
||||
temperature = ((uint8_t) roundf(this->target_temperature) - 9) << 1;
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ float DallasTemperatureSensor::get_temp_c_() {
|
||||
if (this->scratch_pad_[7] == 0) {
|
||||
return NAN;
|
||||
}
|
||||
return (temp >> 1) + (this->scratch_pad_[7] - this->scratch_pad_[6]) / float(this->scratch_pad_[7]) - 0.25;
|
||||
return (temp >> 1) + (this->scratch_pad_[7] - this->scratch_pad_[6]) / float(this->scratch_pad_[7]) - 0.25f;
|
||||
}
|
||||
switch (this->resolution_) {
|
||||
case 9:
|
||||
|
||||
@@ -15,7 +15,7 @@ class DemoSensor final : public sensor::Sensor, public PollingComponent {
|
||||
float base = std::isnan(this->state) ? 0.0f : this->state;
|
||||
this->publish_state(base + val * 10);
|
||||
} else {
|
||||
if (val < 0.1) {
|
||||
if (val < 0.1f) {
|
||||
this->publish_state(NAN);
|
||||
} else {
|
||||
this->publish_state(val * 100);
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace esphome::demo {
|
||||
class DemoSwitch final : public switch_::Switch, public Component {
|
||||
public:
|
||||
void setup() override {
|
||||
bool initial = random_float() < 0.5;
|
||||
bool initial = random_float() < 0.5f;
|
||||
this->publish_state(initial);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ class DemoTextSensor final : public text_sensor::TextSensor, public PollingCompo
|
||||
public:
|
||||
void update() override {
|
||||
float val = random_float();
|
||||
if (val < 0.33) {
|
||||
if (val < 0.33f) {
|
||||
this->publish_state("foo");
|
||||
} else if (val < 0.66) {
|
||||
} else if (val < 0.66f) {
|
||||
this->publish_state("bar");
|
||||
} else {
|
||||
this->publish_state("foobar");
|
||||
|
||||
@@ -121,51 +121,51 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
|
||||
|
||||
this->cmd_ = "detRangeCfg -1 0 0";
|
||||
} else if (min2 < 0 || max2 < 0) {
|
||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||
this->min1_ = min1 = roundf(min1 / 0.15f) * 0.15f;
|
||||
this->max1_ = max1 = roundf(max1 / 0.15f) * 0.15f;
|
||||
this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 =
|
||||
this->max4_ = max4 = -1;
|
||||
|
||||
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f", min1 / 0.15, max1 / 0.15);
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f", min1 / 0.15f, max1 / 0.15f);
|
||||
this->cmd_ = buf;
|
||||
} else if (min3 < 0 || max3 < 0) {
|
||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||
this->min2_ = min2 = round(min2 / 0.15) * 0.15;
|
||||
this->max2_ = max2 = round(max2 / 0.15) * 0.15;
|
||||
this->min1_ = min1 = roundf(min1 / 0.15f) * 0.15f;
|
||||
this->max1_ = max1 = roundf(max1 / 0.15f) * 0.15f;
|
||||
this->min2_ = min2 = roundf(min2 / 0.15f) * 0.15f;
|
||||
this->max2_ = max2 = roundf(max2 / 0.15f) * 0.15f;
|
||||
this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 = this->max4_ = max4 = -1;
|
||||
|
||||
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15,
|
||||
max2 / 0.15);
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15f, max1 / 0.15f, min2 / 0.15f,
|
||||
max2 / 0.15f);
|
||||
this->cmd_ = buf;
|
||||
} else if (min4 < 0 || max4 < 0) {
|
||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||
this->min2_ = min2 = round(min2 / 0.15) * 0.15;
|
||||
this->max2_ = max2 = round(max2 / 0.15) * 0.15;
|
||||
this->min3_ = min3 = round(min3 / 0.15) * 0.15;
|
||||
this->max3_ = max3 = round(max3 / 0.15) * 0.15;
|
||||
this->min1_ = min1 = roundf(min1 / 0.15f) * 0.15f;
|
||||
this->max1_ = max1 = roundf(max1 / 0.15f) * 0.15f;
|
||||
this->min2_ = min2 = roundf(min2 / 0.15f) * 0.15f;
|
||||
this->max2_ = max2 = roundf(max2 / 0.15f) * 0.15f;
|
||||
this->min3_ = min3 = roundf(min3 / 0.15f) * 0.15f;
|
||||
this->max3_ = max3 = roundf(max3 / 0.15f) * 0.15f;
|
||||
this->min4_ = min4 = this->max4_ = max4 = -1;
|
||||
|
||||
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15,
|
||||
max2 / 0.15, min3 / 0.15, max3 / 0.15);
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15f, max1 / 0.15f, min2 / 0.15f,
|
||||
max2 / 0.15f, min3 / 0.15f, max3 / 0.15f);
|
||||
this->cmd_ = buf;
|
||||
} else {
|
||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||
this->min2_ = min2 = round(min2 / 0.15) * 0.15;
|
||||
this->max2_ = max2 = round(max2 / 0.15) * 0.15;
|
||||
this->min3_ = min3 = round(min3 / 0.15) * 0.15;
|
||||
this->max3_ = max3 = round(max3 / 0.15) * 0.15;
|
||||
this->min4_ = min4 = round(min4 / 0.15) * 0.15;
|
||||
this->max4_ = max4 = round(max4 / 0.15) * 0.15;
|
||||
this->min1_ = min1 = roundf(min1 / 0.15f) * 0.15f;
|
||||
this->max1_ = max1 = roundf(max1 / 0.15f) * 0.15f;
|
||||
this->min2_ = min2 = roundf(min2 / 0.15f) * 0.15f;
|
||||
this->max2_ = max2 = roundf(max2 / 0.15f) * 0.15f;
|
||||
this->min3_ = min3 = roundf(min3 / 0.15f) * 0.15f;
|
||||
this->max3_ = max3 = roundf(max3 / 0.15f) * 0.15f;
|
||||
this->min4_ = min4 = roundf(min4 / 0.15f) * 0.15f;
|
||||
this->max4_ = max4 = roundf(max4 / 0.15f) * 0.15f;
|
||||
|
||||
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15,
|
||||
min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15, min4 / 0.15, max4 / 0.15);
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15f, max1 / 0.15f,
|
||||
min2 / 0.15f, max2 / 0.15f, min3 / 0.15f, max3 / 0.15f, min4 / 0.15f, max4 / 0.15f);
|
||||
this->cmd_ = buf;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,10 +42,10 @@ void Display::line_at_angle(int x, int y, int angle, int length, Color color) {
|
||||
|
||||
void Display::line_at_angle(int x, int y, int angle, int start_radius, int stop_radius, Color color) {
|
||||
// Calculate start and end points
|
||||
int x1 = (start_radius * cos(angle * M_PI / 180)) + x;
|
||||
int y1 = (start_radius * sin(angle * M_PI / 180)) + y;
|
||||
int x2 = (stop_radius * cos(angle * M_PI / 180)) + x;
|
||||
int y2 = (stop_radius * sin(angle * M_PI / 180)) + y;
|
||||
int x1 = (start_radius * std::cos(angle * std::numbers::pi_v<float> / 180)) + x;
|
||||
int y1 = (start_radius * std::sin(angle * std::numbers::pi_v<float> / 180)) + y;
|
||||
int x2 = (stop_radius * std::cos(angle * std::numbers::pi_v<float> / 180)) + x;
|
||||
int y2 = (stop_radius * std::sin(angle * std::numbers::pi_v<float> / 180)) + y;
|
||||
|
||||
// Draw line
|
||||
this->line(x1, y1, x2, y2, color);
|
||||
@@ -228,7 +228,7 @@ void Display::filled_gauge(int center_x, int center_y, int radius1, int radius2,
|
||||
int e2max, e2min;
|
||||
progress = std::max(0, std::min(progress, 100)); // 0..100
|
||||
int draw_progress = progress > 50 ? (100 - progress) : progress;
|
||||
float tan_a = (progress == 50) ? 65535 : tan(float(draw_progress) * M_PI / 100); // slope
|
||||
float tan_a = (progress == 50) ? 65535 : tanf(float(draw_progress) * std::numbers::pi_v<float> / 100); // slope
|
||||
|
||||
do {
|
||||
// outer dots
|
||||
@@ -444,15 +444,15 @@ void HOT Display::get_regular_polygon_vertex(int vertex_id, int *vertex_x, int *
|
||||
// hence we rotate the shape by 270° to orient the polygon up.
|
||||
rotation_degrees += ROTATION_270_DEGREES;
|
||||
// Convert the rotation to radians, easier to use in trigonometrical calculations
|
||||
float rotation_radians = rotation_degrees * std::numbers::pi / 180;
|
||||
float rotation_radians = rotation_degrees * std::numbers::pi_v<float> / 180;
|
||||
// A pointy top variation means the first vertex of the polygon is at the top center of the shape, this requires no
|
||||
// additional rotation of the shape.
|
||||
// A flat top variation means the first point of the polygon has to be rotated so that the first edge is horizontal,
|
||||
// this requires to rotate the shape by π/edges radians counter-clockwise so that the first point is located on the
|
||||
// left side of the first horizontal edge.
|
||||
rotation_radians -= (variation == VARIATION_FLAT_TOP) ? std::numbers::pi / edges : 0.0;
|
||||
rotation_radians -= (variation == VARIATION_FLAT_TOP) ? std::numbers::pi_v<float> / edges : 0.0f;
|
||||
|
||||
float vertex_angle = ((float) vertex_id) / edges * 2 * std::numbers::pi + rotation_radians;
|
||||
float vertex_angle = ((float) vertex_id) / edges * 2 * std::numbers::pi_v<float> + rotation_radians;
|
||||
*vertex_x = (int) std::round(std::cos(vertex_angle) * radius) + center_x;
|
||||
*vertex_y = (int) std::round(std::sin(vertex_angle) * radius) + center_y;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class DS2484OneWireBus final : public one_wire::OneWireBus, public i2c::I2CDevic
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::BUS - 1.0; }
|
||||
float get_setup_priority() const override { return setup_priority::BUS - 1.0f; }
|
||||
|
||||
bool reset_device();
|
||||
int reset_int() override;
|
||||
|
||||
@@ -169,14 +169,14 @@ bool ES7210::configure_mic_gain_() {
|
||||
|
||||
uint8_t ES7210::es7210_gain_reg_value_(float mic_gain) {
|
||||
// reg: 12 - 34.5dB, 13 - 36dB, 14 - 37.5dB
|
||||
mic_gain += 0.5;
|
||||
if (mic_gain <= 33.0) {
|
||||
mic_gain += 0.5f;
|
||||
if (mic_gain <= 33.0f) {
|
||||
return (uint8_t) (mic_gain / 3);
|
||||
}
|
||||
if (mic_gain < 36.0) {
|
||||
if (mic_gain < 36.0f) {
|
||||
return 12;
|
||||
}
|
||||
if (mic_gain < 37.0) {
|
||||
if (mic_gain < 37.0f) {
|
||||
return 13;
|
||||
}
|
||||
return 14;
|
||||
|
||||
@@ -105,14 +105,14 @@ bool ES7243E::configure_mic_gain_() {
|
||||
|
||||
uint8_t ES7243E::es7243e_gain_reg_value_(float mic_gain) {
|
||||
// reg: 12 - 34.5dB, 13 - 36dB, 14 - 37.5dB
|
||||
mic_gain += 0.5;
|
||||
if (mic_gain <= 33.0) {
|
||||
mic_gain += 0.5f;
|
||||
if (mic_gain <= 33.0f) {
|
||||
return (uint8_t) mic_gain / 3;
|
||||
}
|
||||
if (mic_gain < 36.0) {
|
||||
if (mic_gain < 36.0f) {
|
||||
return 12;
|
||||
}
|
||||
if (mic_gain < 37.0) {
|
||||
if (mic_gain < 37.0f) {
|
||||
return 13;
|
||||
}
|
||||
return 14;
|
||||
|
||||
@@ -303,7 +303,9 @@ void ESPNowComponent::loop() {
|
||||
ESP_LOGV(TAG, ">>> [%s] %s", addr_buf, LOG_STR_ARG(espnow_error_to_str(packet->packet_.sent.status)));
|
||||
#endif
|
||||
if (this->current_send_packet_ != nullptr) {
|
||||
this->current_send_packet_->callback_(packet->packet_.sent.status);
|
||||
if (this->current_send_packet_->callback_ != nullptr) {
|
||||
this->current_send_packet_->callback_(packet->packet_.sent.status);
|
||||
}
|
||||
this->send_packet_pool_.release(this->current_send_packet_);
|
||||
this->current_send_packet_ = nullptr; // Reset current packet after sending
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color colo
|
||||
/// Draw grid
|
||||
if (!std::isnan(this->gridspacing_y_)) {
|
||||
for (int y = yn; y <= ym; y++) {
|
||||
int16_t py = (int16_t) roundf((this->height_ - 1) * (1.0 - (float) (y - yn) / (ym - yn)));
|
||||
int16_t py = (int16_t) roundf((this->height_ - 1) * (1.0f - (float) (y - yn) / (ym - yn)));
|
||||
for (uint32_t x = 0; x < this->width_; x += 2) {
|
||||
buff->draw_pixel_at(x_offset + x, y_offset + py, color);
|
||||
}
|
||||
@@ -177,7 +177,7 @@ void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color colo
|
||||
uint8_t bit = 1 << ((i % (thick * LineType::PATTERN_LENGTH)) / thick);
|
||||
bool b = (trace->get_line_type() & bit) == bit;
|
||||
if (b) {
|
||||
int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2 + y_offset;
|
||||
int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0f - v)) - thick / 2 + y_offset;
|
||||
auto draw_pixel_at = [&buff, c, y_offset, this](int16_t x, int16_t y) {
|
||||
if (y >= y_offset && static_cast<uint32_t>(y) < y_offset + this->height_)
|
||||
buff->draw_pixel_at(x, y, c);
|
||||
|
||||
@@ -122,7 +122,7 @@ void GroveMotorDriveTB6612FNG::stepper_run(StepperModeTypeT mode, int16_t steps,
|
||||
|
||||
rpm = clamp<uint16_t>(rpm, 1, 300);
|
||||
|
||||
ms_per_step = (uint16_t) (3000.0 / (float) rpm);
|
||||
ms_per_step = (uint16_t) (3000.0f / (float) rpm);
|
||||
buffer_[0] = mode;
|
||||
buffer_[1] = cw; //(cw=1) => cw; (cw=0) => ccw
|
||||
buffer_[2] = steps;
|
||||
@@ -153,7 +153,7 @@ void GroveMotorDriveTB6612FNG::stepper_keep_run(StepperModeTypeT mode, uint16_t
|
||||
uint16_t ms_per_step = 0;
|
||||
|
||||
rpm = clamp<uint16_t>(rpm, 1, 300);
|
||||
ms_per_step = (uint16_t) (3000.0 / (float) rpm);
|
||||
ms_per_step = (uint16_t) (3000.0f / (float) rpm);
|
||||
|
||||
buffer_[0] = mode;
|
||||
buffer_[1] = cw; //(cw=1) => cw; (cw=0) => ccw
|
||||
|
||||
@@ -607,7 +607,7 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
|
||||
if (climate_control.target_temperature.has_value()) {
|
||||
float target_temp = climate_control.target_temperature.value();
|
||||
out_data->set_point = ((int) target_temp) - 16; // set the temperature with offset 16
|
||||
out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0;
|
||||
out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49f) ? 1 : 0;
|
||||
}
|
||||
if (out_data->ac_power == 0) {
|
||||
// If AC is off - no presets allowed
|
||||
|
||||
@@ -341,7 +341,7 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() {
|
||||
if (climate_control.target_temperature.has_value()) {
|
||||
float target_temp = climate_control.target_temperature.value();
|
||||
out_data->set_point = ((int) target_temp) - 16; // set the temperature with offset 16
|
||||
out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0;
|
||||
out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49f) ? 1 : 0;
|
||||
}
|
||||
if (out_data->ac_power == 0) {
|
||||
// If AC is off - no presets allowed
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#include <numbers>
|
||||
|
||||
namespace esphome::hmc5883l {
|
||||
|
||||
static const char *const TAG = "hmc5883l";
|
||||
@@ -126,7 +128,7 @@ void HMC5883LComponent::update() {
|
||||
const float y = int16_t(raw_y) * mg_per_bit * 0.1f;
|
||||
const float z = int16_t(raw_z) * mg_per_bit * 0.1f;
|
||||
|
||||
float heading = atan2f(0.0f - x, y) * 180.0f / M_PI;
|
||||
float heading = atan2f(0.0f - x, y) * 180.0f / std::numbers::pi_v<float>;
|
||||
ESP_LOGD(TAG, "Got x=%0.02fµT y=%0.02fµT z=%0.02fµT heading=%0.01f°", x, y, z, heading);
|
||||
|
||||
if (this->x_sensor_ != nullptr)
|
||||
|
||||
@@ -55,7 +55,9 @@ float HONEYWELLABPSensor::countstopressure_(const int counts, const float min_pr
|
||||
|
||||
// Converts a digital temperature measurement in counts to temperature in C
|
||||
// This will be invalid if sensore daoes not have temperature measurement capability
|
||||
float HONEYWELLABPSensor::countstotemperatures_(const int counts) { return (((float) counts / 2047.0) * 200.0) - 50.0; }
|
||||
float HONEYWELLABPSensor::countstotemperatures_(const int counts) {
|
||||
return (((float) counts / 2047.0f) * 200.0f) - 50.0f;
|
||||
}
|
||||
|
||||
// Pressure value from the most recent reading in units
|
||||
float HONEYWELLABPSensor::read_pressure_() {
|
||||
@@ -69,9 +71,9 @@ void HONEYWELLABPSensor::update() {
|
||||
ESP_LOGV(TAG, "Update Honeywell ABP Sensor");
|
||||
if (readsensor_() == 0) {
|
||||
if (this->pressure_sensor_ != nullptr)
|
||||
this->pressure_sensor_->publish_state(read_pressure_() * 1.0);
|
||||
this->pressure_sensor_->publish_state(read_pressure_() * 1.0f);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(read_temperature_() * 1.0);
|
||||
this->temperature_sensor_->publish_state(read_temperature_() * 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ void HrxlMaxsonarWrComponent::check_buffer_() {
|
||||
millimeters = millimeters * 10;
|
||||
}
|
||||
|
||||
float meters = float(millimeters) / 1000.0;
|
||||
float meters = float(millimeters) / 1000.0f;
|
||||
ESP_LOGV(TAG, "Distance from sensor: %d mm, %f m", millimeters, meters);
|
||||
this->publish_state(meters);
|
||||
} else {
|
||||
|
||||
@@ -139,7 +139,7 @@ void I2SAudioSpeakerBase::set_volume(float volume) {
|
||||
this->volume_ = volume;
|
||||
#ifdef USE_AUDIO_DAC
|
||||
if (this->audio_dac_ != nullptr) {
|
||||
if (volume > 0.0) {
|
||||
if (volume > 0.0f) {
|
||||
this->audio_dac_->set_mute_off();
|
||||
}
|
||||
this->audio_dac_->set_volume(volume);
|
||||
|
||||
@@ -119,7 +119,7 @@ void INA219Component::setup() {
|
||||
}
|
||||
|
||||
this->calibration_lsb_ = lsb;
|
||||
auto calibration = uint32_t(0.04096f / (0.000001 * lsb * this->shunt_resistance_ohm_));
|
||||
auto calibration = uint32_t(0.04096f / (0.000001f * lsb * this->shunt_resistance_ohm_));
|
||||
ESP_LOGV(TAG, " Using LSB=%" PRIu32 " calibration=%" PRIu32, lsb, calibration);
|
||||
if (!this->write_byte_16(INA219_REGISTER_CALIBRATION, calibration)) {
|
||||
this->mark_failed();
|
||||
|
||||
@@ -70,7 +70,7 @@ void INA226Component::setup() {
|
||||
|
||||
this->calibration_lsb_ = lsb;
|
||||
|
||||
auto calibration = uint32_t(0.00512 / (lsb * this->shunt_resistance_ohm_ / 1000000.0f));
|
||||
auto calibration = uint32_t(0.00512f / (lsb * this->shunt_resistance_ohm_ / 1000000.0f));
|
||||
|
||||
ESP_LOGV(TAG, " Using LSB=%" PRIu32 " calibration=%" PRIu32, lsb, calibration);
|
||||
|
||||
|
||||
@@ -315,14 +315,14 @@ class LightColorValues {
|
||||
if (this->color_temperature_ <= 0) {
|
||||
return this->color_temperature_;
|
||||
}
|
||||
return 1000000.0 / this->color_temperature_;
|
||||
return 1000000.0f / this->color_temperature_;
|
||||
}
|
||||
/// Set the color temperature property of these light color values in kelvin.
|
||||
void set_color_temperature_kelvin(float color_temperature) {
|
||||
if (color_temperature <= 0) {
|
||||
return;
|
||||
}
|
||||
this->color_temperature_ = 1000000.0 / color_temperature;
|
||||
this->color_temperature_ = 1000000.0f / color_temperature;
|
||||
}
|
||||
|
||||
/// Get the cold white property of these light color values. In range 0.0 to 1.0.
|
||||
|
||||
@@ -47,7 +47,7 @@ class LightTransitionTransformer : public LightTransformer {
|
||||
LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_;
|
||||
LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->end_values_;
|
||||
if (this->changing_color_mode_)
|
||||
p = p < 0.5f ? p * 2 : (p - 0.5) * 2;
|
||||
p = p < 0.5f ? p * 2 : (p - 0.5f) * 2;
|
||||
|
||||
float v = LightTransformer::smoothed_progress(p);
|
||||
return LightColorValues::lerp(start, end, v);
|
||||
|
||||
@@ -75,7 +75,7 @@ void LTR390Component::read_als_() {
|
||||
uint32_t als = *val;
|
||||
|
||||
if (this->light_sensor_ != nullptr) {
|
||||
float lux = ((0.6 * als) / (GAINVALUES[this->gain_als_] * RESOLUTIONVALUE[this->res_als_])) * this->wfac_;
|
||||
float lux = ((0.6f * als) / (GAINVALUES[this->gain_als_] * RESOLUTIONVALUE[this->res_als_])) * this->wfac_;
|
||||
this->light_sensor_->publish_state(lux);
|
||||
}
|
||||
|
||||
|
||||
@@ -500,12 +500,12 @@ void LTRAlsPs501Component::apply_lux_calculation_(AlsReadings &data) {
|
||||
// method from
|
||||
// https://github.com/fards/Ainol_fire_kernel/blob/83832cf8a3082fd8e963230f4b1984479d1f1a84/customer/drivers/lightsensor/ltr501als.c#L295
|
||||
|
||||
if (ratio < 0.45) {
|
||||
lux = 1.7743 * ch0 + 1.1059 * ch1;
|
||||
} else if (ratio < 0.64) {
|
||||
lux = 3.7725 * ch0 - 1.3363 * ch1;
|
||||
} else if (ratio < 0.85) {
|
||||
lux = 1.6903 * ch0 - 0.1693 * ch1;
|
||||
if (ratio < 0.45f) {
|
||||
lux = 1.7743f * ch0 + 1.1059f * ch1;
|
||||
} else if (ratio < 0.64f) {
|
||||
lux = 3.7725f * ch0 - 1.3363f * ch1;
|
||||
} else if (ratio < 0.85f) {
|
||||
lux = 1.6903f * ch0 - 0.1693f * ch1;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Impossible ch1/(ch0 + ch1) ratio");
|
||||
lux = 0.0f;
|
||||
|
||||
@@ -480,12 +480,12 @@ void LTRAlsPsComponent::apply_lux_calculation_(AlsReadings &data) {
|
||||
float inv_pfactor = this->glass_attenuation_factor_;
|
||||
float lux = 0.0f;
|
||||
|
||||
if (ratio < 0.45) {
|
||||
lux = (1.7743 * ch0 + 1.1059 * ch1);
|
||||
} else if (ratio < 0.64 && ratio >= 0.45) {
|
||||
lux = (4.2785 * ch0 - 1.9548 * ch1);
|
||||
} else if (ratio < 0.85 && ratio >= 0.64) {
|
||||
lux = (0.5926 * ch0 + 0.1185 * ch1);
|
||||
if (ratio < 0.45f) {
|
||||
lux = (1.7743f * ch0 + 1.1059f * ch1);
|
||||
} else if (ratio < 0.64f && ratio >= 0.45f) {
|
||||
lux = (4.2785f * ch0 - 1.9548f * ch1);
|
||||
} else if (ratio < 0.85f && ratio >= 0.64f) {
|
||||
lux = (0.5926f * ch0 + 0.1185f * ch1);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Impossible ch1/(ch0 + ch1) ratio");
|
||||
lux = 0.0f;
|
||||
|
||||
@@ -23,7 +23,7 @@ void MAX17043Component::update() {
|
||||
if (!this->read_byte_16(MAX17043_VCELL, &raw_voltage)) {
|
||||
this->status_set_warning(LOG_STR("Unable to read MAX17043_VCELL"));
|
||||
} else {
|
||||
float voltage = (1.25 * (float) (raw_voltage >> 4)) / 1000.0;
|
||||
float voltage = (1.25f * (float) (raw_voltage >> 4)) / 1000.0f;
|
||||
this->voltage_sensor_->publish_state(voltage);
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ float MCP3204::read_data(uint8_t pin, bool differential) {
|
||||
this->disable();
|
||||
|
||||
uint16_t digital_value = encode_uint16(b0, b1) >> 4;
|
||||
return float(digital_value) / 4096.000 * this->reference_voltage_; // in V
|
||||
return float(digital_value) / 4096.000f * this->reference_voltage_; // in V
|
||||
}
|
||||
|
||||
} // namespace esphome::mcp3204
|
||||
|
||||
@@ -29,7 +29,9 @@ void Mcp4461Wiper::write_state(float state) {
|
||||
}
|
||||
}
|
||||
|
||||
float Mcp4461Wiper::read_state() { return (static_cast<float>(this->parent_->get_wiper_level_(this->wiper_)) / 256.0); }
|
||||
float Mcp4461Wiper::read_state() {
|
||||
return (static_cast<float>(this->parent_->get_wiper_level_(this->wiper_)) / 256.0f);
|
||||
}
|
||||
|
||||
float Mcp4461Wiper::update_state() {
|
||||
this->state_ = this->read_state();
|
||||
|
||||
@@ -24,7 +24,8 @@ void MCP4725::dump_config() {
|
||||
|
||||
// https://learn.sparkfun.com/tutorials/mcp4725-digital-to-analog-converter-hookup-guide?_ga=2.176055202.1402343014.1607953301-893095255.1606753886
|
||||
void MCP4725::write_state(float state) {
|
||||
const uint16_t value = (uint16_t) round(state * (pow(2, MCP4725_RES) - 1));
|
||||
constexpr uint16_t max_value = (1U << MCP4725_RES) - 1;
|
||||
const uint16_t value = (uint16_t) roundf(state * max_value);
|
||||
|
||||
this->write_byte_16(64, value << 4);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
static const uint8_t MCP4725_ADDR = 0x60;
|
||||
static const uint8_t MCP4725_RES = 12;
|
||||
|
||||
namespace esphome::mcp4725 {
|
||||
|
||||
static constexpr uint8_t MCP4725_ADDR = 0x60;
|
||||
static constexpr uint8_t MCP4725_RES = 12;
|
||||
|
||||
class MCP4725 final : public Component, public output::FloatOutput, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
@@ -71,10 +71,10 @@ void MICS4514Component::update() {
|
||||
float co = 0.0f;
|
||||
if (red_f > 3.4f) {
|
||||
co = 0.0;
|
||||
} else if (red_f < 0.01) {
|
||||
} else if (red_f < 0.01f) {
|
||||
co = 1000.0;
|
||||
} else {
|
||||
co = 4.2 / pow(red_f, 1.2);
|
||||
co = 4.2f / powf(red_f, 1.2f);
|
||||
}
|
||||
this->carbon_monoxide_sensor_->publish_state(co);
|
||||
}
|
||||
@@ -84,47 +84,47 @@ void MICS4514Component::update() {
|
||||
if (ox_f < 0.3f) {
|
||||
nitrogendioxide = 0.0;
|
||||
} else {
|
||||
nitrogendioxide = 0.164 * pow(ox_f, 0.975);
|
||||
nitrogendioxide = 0.164f * powf(ox_f, 0.975f);
|
||||
}
|
||||
this->nitrogen_dioxide_sensor_->publish_state(nitrogendioxide);
|
||||
}
|
||||
|
||||
if (this->methane_sensor_ != nullptr) {
|
||||
float methane = 0.0f;
|
||||
if (red_f > 0.9f || red_f < 0.5) { // outside the range->unlikely
|
||||
if (red_f > 0.9f || red_f < 0.5f) { // outside the range->unlikely
|
||||
methane = 0.0;
|
||||
} else {
|
||||
methane = 630 / pow(red_f, 4.4);
|
||||
methane = 630 / powf(red_f, 4.4f);
|
||||
}
|
||||
this->methane_sensor_->publish_state(methane);
|
||||
}
|
||||
|
||||
if (this->ethanol_sensor_ != nullptr) {
|
||||
float ethanol = 0.0f;
|
||||
if (red_f > 1.0f || red_f < 0.02) { // outside the range->unlikely
|
||||
if (red_f > 1.0f || red_f < 0.02f) { // outside the range->unlikely
|
||||
ethanol = 0.0;
|
||||
} else {
|
||||
ethanol = 1.52 / pow(red_f, 1.55);
|
||||
ethanol = 1.52f / powf(red_f, 1.55f);
|
||||
}
|
||||
this->ethanol_sensor_->publish_state(ethanol);
|
||||
}
|
||||
|
||||
if (this->hydrogen_sensor_ != nullptr) {
|
||||
float hydrogen = 0.0f;
|
||||
if (red_f > 0.9f || red_f < 0.02) { // outside the range->unlikely
|
||||
if (red_f > 0.9f || red_f < 0.02f) { // outside the range->unlikely
|
||||
hydrogen = 0.0;
|
||||
} else {
|
||||
hydrogen = 0.85 / pow(red_f, 1.75);
|
||||
hydrogen = 0.85f / powf(red_f, 1.75f);
|
||||
}
|
||||
this->hydrogen_sensor_->publish_state(hydrogen);
|
||||
}
|
||||
|
||||
if (this->ammonia_sensor_ != nullptr) {
|
||||
float ammonia = 0.0f;
|
||||
if (red_f > 0.98f || red_f < 0.2532) { // outside the ammonia range->unlikely
|
||||
if (red_f > 0.98f || red_f < 0.2532f) { // outside the ammonia range->unlikely
|
||||
ammonia = 0.0;
|
||||
} else {
|
||||
ammonia = 0.9 / pow(red_f, 4.6);
|
||||
ammonia = 0.9f / powf(red_f, 4.6f);
|
||||
}
|
||||
this->ammonia_sensor_->publish_state(ammonia);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "mmc5603.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <numbers>
|
||||
|
||||
namespace esphome::mmc5603 {
|
||||
|
||||
static const char *const TAG = "mmc5603";
|
||||
@@ -143,7 +145,7 @@ void MMC5603Component::update() {
|
||||
|
||||
const float z = 0.00625 * (raw_z - 524288);
|
||||
|
||||
const float heading = atan2f(0.0f - x, y) * 180.0f / M_PI;
|
||||
const float heading = atan2f(0.0f - x, y) * 180.0f / std::numbers::pi_v<float>;
|
||||
ESP_LOGD(TAG, "Got x=%0.02fµT y=%0.02fµT z=%0.02fµT heading=%0.01f°", x, y, z, heading);
|
||||
|
||||
if (this->x_sensor_ != nullptr)
|
||||
|
||||
@@ -124,7 +124,6 @@ async def register_modbus_client_device(var, config):
|
||||
|
||||
async def register_modbus_server_device(var, config):
|
||||
parent = await cg.get_variable(config[CONF_MODBUS_ID])
|
||||
cg.add(var.set_parent(parent))
|
||||
cg.add(var.set_address(config[CONF_ADDRESS]))
|
||||
cg.add(parent.register_device(var))
|
||||
|
||||
|
||||
@@ -92,14 +92,10 @@ int32_t Modbus::tx_delay_remaining() {
|
||||
|
||||
int32_t ModbusClientHub::tx_delay_remaining() {
|
||||
const uint32_t now = millis();
|
||||
// Turnaround delay only applies after a broadcast: no response is expected, so we must give listening devices
|
||||
// quiet time to process it before the next request. For normal unicast request/response the received reply already
|
||||
// provides the inter-frame timing, so adding turnaround there just throttles throughput.
|
||||
const uint16_t turnaround = this->last_send_was_broadcast_ ? this->turnaround_delay_ms_ : 0;
|
||||
return std::max(
|
||||
{(int32_t) 0,
|
||||
(int32_t) (this->last_send_tx_offset_ + this->frame_delay_ms_ + turnaround - (now - this->last_send_)),
|
||||
(int32_t) (this->frame_delay_ms_ + turnaround - (now - this->last_modbus_byte_))});
|
||||
return std::max({(int32_t) 0,
|
||||
(int32_t) (this->last_send_tx_offset_ + this->frame_delay_ms_ + this->turnaround_delay_ms_ -
|
||||
(now - this->last_send_)),
|
||||
(int32_t) (this->frame_delay_ms_ + this->turnaround_delay_ms_ - (now - this->last_modbus_byte_))});
|
||||
}
|
||||
|
||||
bool Modbus::tx_blocked() {
|
||||
@@ -258,7 +254,7 @@ bool ModbusServerHub::parse_modbus_client_frame_() {
|
||||
std::memcpy(data, this->rx_buffer_.data() + data_offset, data_len);
|
||||
this->clear_rx_buffer_(LOG_STR("parse succeeded"), false, frame_length);
|
||||
|
||||
this->process_modbus_client_frame_(address, function_code, data, data_len);
|
||||
this->process_modbus_client_frame_(address, function_code, data);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -321,10 +317,8 @@ void ModbusClientHub::process_modbus_server_frame(uint8_t address, uint8_t funct
|
||||
}
|
||||
|
||||
void ModbusServerHub::process_modbus_server_frame(uint8_t address, uint8_t function_code, const uint8_t *, uint16_t) {
|
||||
for (auto *device : this->devices_) {
|
||||
if (device->address_ == address) {
|
||||
ESP_LOGE(TAG, "Unexpected response from address %" PRIu8 ", which is mapped to this device.", address);
|
||||
}
|
||||
if (this->find_device_(address) != nullptr) {
|
||||
ESP_LOGE(TAG, "Unexpected response from address %" PRIu8 ", which is mapped to this device.", address);
|
||||
}
|
||||
|
||||
if (this->expecting_peer_response_ == address) {
|
||||
@@ -338,31 +332,124 @@ void ModbusServerHub::process_modbus_server_frame(uint8_t address, uint8_t funct
|
||||
this->expecting_peer_response_ = 0;
|
||||
}
|
||||
|
||||
void ModbusServerHub::process_modbus_client_frame_(uint8_t address, uint8_t function_code, const uint8_t *data,
|
||||
uint16_t len) {
|
||||
bool found = false;
|
||||
|
||||
ModbusServerDevice *ModbusServerHub::find_device_(uint8_t address) {
|
||||
for (auto *device : this->devices_) {
|
||||
if (device->address_ == address) {
|
||||
found = true;
|
||||
|
||||
if (static_cast<ModbusFunctionCode>(function_code) == ModbusFunctionCode::READ_HOLDING_REGISTERS ||
|
||||
static_cast<ModbusFunctionCode>(function_code) == ModbusFunctionCode::READ_INPUT_REGISTERS) {
|
||||
device->on_modbus_read_registers(function_code, helpers::get_data<uint16_t>(data, 0),
|
||||
helpers::get_data<uint16_t>(data, 2));
|
||||
} else if (static_cast<ModbusFunctionCode>(function_code) == ModbusFunctionCode::WRITE_SINGLE_REGISTER ||
|
||||
static_cast<ModbusFunctionCode>(function_code) == ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS) {
|
||||
device->on_modbus_write_registers(function_code, std::vector<uint8_t>(data, data + len));
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unsupported function code %" PRIu8, function_code);
|
||||
device->send_error(function_code, ModbusExceptionCode::ILLEGAL_FUNCTION);
|
||||
}
|
||||
if (device->get_address() == address) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
bool ModbusServerHub::check_register_range_(uint8_t address, uint8_t function_code, uint16_t start_address,
|
||||
uint16_t number_of_registers) {
|
||||
if ((uint32_t) start_address + number_of_registers > 0x10000u) {
|
||||
ESP_LOGW(TAG, "Register address out of range - start: %" PRIu16 " num: %" PRIu16, start_address,
|
||||
number_of_registers);
|
||||
this->send_exception_(address, function_code, ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModbusServerHub::process_modbus_client_frame_(uint8_t address, uint8_t function_code, const uint8_t *data) {
|
||||
ModbusServerDevice *device = this->find_device_(address);
|
||||
if (device == nullptr) {
|
||||
this->expecting_peer_response_ = address;
|
||||
ESP_LOGV(TAG, "Request to peer %" PRIu8 " received", address);
|
||||
return;
|
||||
}
|
||||
|
||||
ServerResponseStatus status;
|
||||
uint8_t response_buffer[modbus::MAX_RAW_SIZE];
|
||||
const uint8_t *response_data = response_buffer;
|
||||
uint16_t response_len = 0;
|
||||
|
||||
switch (static_cast<ModbusFunctionCode>(function_code)) {
|
||||
case ModbusFunctionCode::READ_HOLDING_REGISTERS:
|
||||
case ModbusFunctionCode::READ_INPUT_REGISTERS: {
|
||||
// PDU data: start address(2) + quantity(2).
|
||||
uint16_t start_address = helpers::get_data<uint16_t>(data, 0);
|
||||
uint16_t number_of_registers = helpers::get_data<uint16_t>(data, 2);
|
||||
if (number_of_registers == 0 || number_of_registers > MAX_NUM_OF_REGISTERS_TO_READ) {
|
||||
ESP_LOGW(TAG, "Invalid number of registers %" PRIu16, number_of_registers);
|
||||
this->send_exception_(address, function_code, ModbusExceptionCode::ILLEGAL_DATA_VALUE);
|
||||
return;
|
||||
}
|
||||
if (!this->check_register_range_(address, function_code, start_address, number_of_registers)) {
|
||||
return;
|
||||
}
|
||||
RegisterValues registers;
|
||||
if (static_cast<ModbusFunctionCode>(function_code) == ModbusFunctionCode::READ_HOLDING_REGISTERS) {
|
||||
status = device->on_modbus_read_holding_registers(start_address, number_of_registers, registers);
|
||||
} else {
|
||||
status = device->on_modbus_read_input_registers(start_address, number_of_registers, registers);
|
||||
}
|
||||
|
||||
// A handler that returns an exception leaves registers partially filled, so check the exception
|
||||
// first and forward it before validating the register count on the success path.
|
||||
if (status.has_value()) {
|
||||
this->send_exception_(address, function_code, status.value());
|
||||
return;
|
||||
}
|
||||
|
||||
if (registers.size() != number_of_registers) {
|
||||
ESP_LOGE(TAG, "Incorrect response %" PRIu16 " requested, %zu returned", number_of_registers, registers.size());
|
||||
this->send_exception_(address, function_code, ModbusExceptionCode::SERVICE_DEVICE_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
response_buffer[response_len++] = static_cast<uint8_t>(number_of_registers * 2); // actual byte count
|
||||
for (auto r : registers) {
|
||||
auto register_bytes = decode_value(r);
|
||||
response_buffer[response_len++] = register_bytes[0];
|
||||
response_buffer[response_len++] = register_bytes[1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ModbusFunctionCode::WRITE_SINGLE_REGISTER:
|
||||
case ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS: {
|
||||
// PDU data: start address(2) [+ quantity(2) + byte count(1)] + register values.
|
||||
// A single-register write always targets one register; for a multiple-register write the
|
||||
// quantity is in the frame and its byte count must equal quantity * 2. The register values are
|
||||
// assembled into registers below so the handler doesn't have to know the request framing.
|
||||
uint16_t start_address = helpers::get_data<uint16_t>(data, 0);
|
||||
uint16_t number_of_registers = 1;
|
||||
uint16_t values_offset = 2; // single write: values follow the 2-byte start address
|
||||
if (static_cast<ModbusFunctionCode>(function_code) == ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS) {
|
||||
number_of_registers = helpers::get_data<uint16_t>(data, 2);
|
||||
uint8_t number_of_bytes = helpers::get_data<uint8_t>(data, 4);
|
||||
values_offset = 5; // multiple write: values follow start address(2) + quantity(2) + byte count(1)
|
||||
if (number_of_registers == 0 || number_of_registers > MAX_NUM_OF_REGISTERS_TO_WRITE ||
|
||||
number_of_registers * 2 != number_of_bytes) {
|
||||
ESP_LOGW(TAG, "Invalid number of registers %" PRIu16 " or bytes %" PRIu8, number_of_registers,
|
||||
number_of_bytes);
|
||||
this->send_exception_(address, function_code, ModbusExceptionCode::ILLEGAL_DATA_VALUE);
|
||||
return;
|
||||
}
|
||||
if (!this->check_register_range_(address, function_code, start_address, number_of_registers)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Assemble the register values (host byte order) so the handler never sees wire framing.
|
||||
RegisterValues registers;
|
||||
for (uint16_t i = 0; i < number_of_registers; i++) {
|
||||
registers.push_back(helpers::get_data<uint16_t>(data, values_offset + i * 2));
|
||||
}
|
||||
status = device->on_modbus_write_registers(start_address, registers);
|
||||
response_data = data; // echo the request header per Modbus 6.6, 6.12
|
||||
response_len = 4;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unsupported function code %" PRIu8, function_code);
|
||||
this->send_exception_(address, function_code, ModbusExceptionCode::ILLEGAL_FUNCTION);
|
||||
return;
|
||||
}
|
||||
if (status.has_value()) {
|
||||
this->send_exception_(address, function_code, status.value());
|
||||
} else {
|
||||
this->send_response_(address, function_code, response_data, response_len);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,7 +487,6 @@ bool Modbus::send_frame_(const ModbusFrame &frame) {
|
||||
format_hex_pretty_to(hex_buf, frame.data.get(), frame.size), now - this->last_send_,
|
||||
now - this->last_modbus_byte_);
|
||||
this->last_send_ = now;
|
||||
this->last_send_was_broadcast_ = frame.size > 0 && frame.data[0] == 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -416,8 +502,7 @@ void ModbusClientHub::send_next_frame_() {
|
||||
ModbusDeviceCommand &command = this->tx_buffer_.front();
|
||||
|
||||
if (this->send_frame_(command.frame)) {
|
||||
if (!this->last_send_was_broadcast_)
|
||||
this->waiting_for_response_ = std::move(command);
|
||||
this->waiting_for_response_ = std::move(command);
|
||||
} else {
|
||||
if (command.device)
|
||||
command.device->on_modbus_not_sent();
|
||||
@@ -455,17 +540,27 @@ float Modbus::get_setup_priority() const {
|
||||
return setup_priority::BUS - 1.0f;
|
||||
}
|
||||
|
||||
void ModbusServerHub::send(uint8_t address, uint8_t function_code, const std::vector<uint8_t> &payload) {
|
||||
const uint16_t len = static_cast<uint16_t>(2 + payload.size());
|
||||
if (len > MAX_RAW_SIZE) {
|
||||
ESP_LOGE(TAG, "Server send frame too large (%" PRIu16 " bytes)", len);
|
||||
void ModbusServerHub::send_response_(uint8_t address, uint8_t function_code, const uint8_t *payload,
|
||||
uint16_t payload_len) {
|
||||
// Build the raw frame (address + function code + payload) in a stack buffer; it's consumed
|
||||
// immediately by send_raw_ and a full raw frame never exceeds MAX_RAW_SIZE.
|
||||
if (payload_len + 2 > MAX_RAW_SIZE) {
|
||||
ESP_LOGE(TAG, "Server response too large (%" PRIu16 " bytes)", static_cast<uint16_t>(payload_len + 2));
|
||||
return;
|
||||
}
|
||||
uint8_t raw_frame[MAX_RAW_SIZE];
|
||||
raw_frame[0] = address;
|
||||
raw_frame[1] = function_code;
|
||||
std::memcpy(raw_frame + 2, payload.data(), payload.size());
|
||||
this->send_raw_(raw_frame, len);
|
||||
std::memcpy(raw_frame + 2, payload, payload_len);
|
||||
this->send_raw_(raw_frame, payload_len + 2);
|
||||
}
|
||||
|
||||
void ModbusServerHub::send_exception_(uint8_t address, uint8_t function_code, ModbusExceptionCode exception_code) {
|
||||
uint8_t raw_frame[3];
|
||||
raw_frame[0] = address;
|
||||
raw_frame[1] = function_code | FUNCTION_CODE_EXCEPTION_MASK;
|
||||
raw_frame[2] = static_cast<uint8_t>(exception_code);
|
||||
this->send_raw_(raw_frame, 3);
|
||||
}
|
||||
|
||||
// Raw send for client: pushes to tx queue. Everything except the CRC must be contained in payload.
|
||||
|
||||
@@ -63,7 +63,6 @@ class Modbus : public uart::UARTDevice, public Component {
|
||||
uint32_t last_receive_check_{0};
|
||||
uint32_t last_send_{0};
|
||||
uint32_t last_send_tx_offset_{0};
|
||||
bool last_send_was_broadcast_{false};
|
||||
uint16_t frame_delay_ms_{5};
|
||||
uint16_t long_rx_buffer_delay_ms_{0};
|
||||
|
||||
@@ -130,22 +129,22 @@ class ModbusServerHub : public Modbus {
|
||||
public:
|
||||
ModbusServerHub() = default;
|
||||
void dump_config() override;
|
||||
void send(uint8_t address, uint8_t function_code, const std::vector<uint8_t> &payload);
|
||||
ESPDEPRECATED("Use ModbusServerDevice::send_raw instead. Removed in 2026.10.0", "2026.4.0")
|
||||
void send_raw(const std::vector<uint8_t> &payload) {
|
||||
this->send_raw_(payload.data(), static_cast<uint16_t>(payload.size()));
|
||||
};
|
||||
void register_device(ModbusServerDevice *device) { this->devices_.push_back(device); }
|
||||
|
||||
protected:
|
||||
friend class ModbusServerDevice;
|
||||
|
||||
void parse_modbus_frames() override;
|
||||
bool parse_modbus_client_frame_();
|
||||
// Parsers need to handle standard (ModbusFunctionCode) and custom (uint8_t) function codes, so we use uint8_t here.
|
||||
void process_modbus_server_frame(uint8_t address, uint8_t function_code, const uint8_t *data, uint16_t len) override;
|
||||
void process_modbus_client_frame_(uint8_t address, uint8_t function_code, const uint8_t *data, uint16_t len);
|
||||
void process_modbus_client_frame_(uint8_t address, uint8_t function_code, const uint8_t *data);
|
||||
ModbusServerDevice *find_device_(uint8_t address);
|
||||
// Returns true if [start_address, start_address + number_of_registers) fits in the 16-bit address space.
|
||||
// On failure, logs and sends an ILLEGAL_DATA_ADDRESS exception to the client.
|
||||
bool check_register_range_(uint8_t address, uint8_t function_code, uint16_t start_address,
|
||||
uint16_t number_of_registers);
|
||||
void send_raw_(const uint8_t *payload, uint16_t len);
|
||||
void send_exception_(uint8_t address, uint8_t function_code, ModbusExceptionCode exception_code);
|
||||
void send_response_(uint8_t address, uint8_t function_code, const uint8_t *payload, uint16_t payload_len);
|
||||
uint8_t expecting_peer_response_{0};
|
||||
std::vector<ModbusServerDevice *> devices_;
|
||||
|
||||
@@ -200,35 +199,41 @@ class ModbusClientDevice {
|
||||
// This is for compatibility with external components using the former class name
|
||||
using ModbusDevice = ModbusClientDevice;
|
||||
|
||||
// Result of a server register handler: std::nullopt means success, otherwise the Modbus exception code to return.
|
||||
using ServerResponseStatus = std::optional<ModbusExceptionCode>;
|
||||
// Register values exchanged with server handlers, in host byte order. Sized at the larger of the two protocol
|
||||
// maxima (read = 125 / 0x7D, write = 123 / 0x7B); the per-direction count limit is enforced by the hub, not by
|
||||
// the capacity of this type.
|
||||
using RegisterValues = StaticVector<uint16_t, MAX_NUM_OF_REGISTERS_TO_READ>;
|
||||
|
||||
class ModbusServerDevice {
|
||||
public:
|
||||
ModbusServerDevice() = default;
|
||||
ModbusServerDevice(ModbusServerHub *parent, uint8_t address) : parent_(parent), address_(address) {}
|
||||
virtual ~ModbusServerDevice() = default;
|
||||
ModbusServerDevice() = default;
|
||||
// Polymorphic base: non-copyable and non-movable to prevent slicing (Rule of Five).
|
||||
ModbusServerDevice(const ModbusServerDevice &) = delete;
|
||||
ModbusServerDevice &operator=(const ModbusServerDevice &) = delete;
|
||||
ModbusServerDevice(ModbusServerDevice &&) = delete;
|
||||
ModbusServerDevice &operator=(ModbusServerDevice &&) = delete;
|
||||
void set_parent(ModbusServerHub *parent) { this->parent_ = parent; }
|
||||
void set_address(uint8_t address) { this->address_ = address; }
|
||||
virtual void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers){};
|
||||
virtual void on_modbus_write_registers(uint8_t function_code, const std::vector<uint8_t> &data){};
|
||||
void send(uint8_t function, const std::vector<uint8_t> &payload) {
|
||||
this->parent_->send(this->address_, function, payload);
|
||||
}
|
||||
void send_raw(const std::vector<uint8_t> &payload) {
|
||||
this->parent_->send_raw_(payload.data(), static_cast<uint16_t>(payload.size()));
|
||||
}
|
||||
void send_error(uint8_t function_code, ModbusExceptionCode exception_code) {
|
||||
uint8_t error_response[3] = {this->address_, uint8_t(function_code | FUNCTION_CODE_EXCEPTION_MASK),
|
||||
static_cast<uint8_t>(exception_code)};
|
||||
this->parent_->send_raw_(error_response, 3);
|
||||
}
|
||||
uint8_t get_address() const { return this->address_; }
|
||||
virtual ServerResponseStatus on_modbus_read_registers(uint16_t start_address, uint16_t number_of_registers,
|
||||
RegisterValues ®isters) {
|
||||
return ModbusExceptionCode::ILLEGAL_FUNCTION;
|
||||
};
|
||||
virtual ServerResponseStatus on_modbus_read_input_registers(uint16_t start_address, uint16_t number_of_registers,
|
||||
RegisterValues ®isters) {
|
||||
return this->on_modbus_read_registers(start_address, number_of_registers, registers);
|
||||
};
|
||||
virtual ServerResponseStatus on_modbus_read_holding_registers(uint16_t start_address, uint16_t number_of_registers,
|
||||
RegisterValues ®isters) {
|
||||
return this->on_modbus_read_registers(start_address, number_of_registers, registers);
|
||||
};
|
||||
virtual ServerResponseStatus on_modbus_write_registers(uint16_t start_address, const RegisterValues ®isters) {
|
||||
return ModbusExceptionCode::ILLEGAL_FUNCTION;
|
||||
};
|
||||
|
||||
protected:
|
||||
friend ModbusServerHub;
|
||||
|
||||
ModbusServerHub *parent_{nullptr};
|
||||
uint8_t address_{0};
|
||||
};
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ static constexpr uint8_t MAX_NUM_OF_REGISTERS_TO_READ = 125; // 0x7D
|
||||
// Smallest possible frame is 4 bytes (custom function with no data): address(1) + function(1) + CRC(2)
|
||||
static constexpr uint16_t MIN_FRAME_SIZE = 4;
|
||||
static constexpr uint16_t MAX_PDU_SIZE = 253; // Max PDU size is 256 - address(1) - CRC(2) = 253
|
||||
static constexpr uint16_t MAX_RAW_SIZE = 254; // Max RAW size is 255 - CRC(2) = 254
|
||||
static constexpr uint16_t MAX_RAW_SIZE = 254; // Max RAW size is 256 - CRC(2) = 254
|
||||
static constexpr uint16_t MAX_FRAME_SIZE = 256;
|
||||
/// End of Modbus definitions
|
||||
} // namespace esphome::modbus
|
||||
|
||||
@@ -101,53 +101,19 @@ static size_t required_payload_size(SensorValueType sensor_value_type) {
|
||||
}
|
||||
}
|
||||
|
||||
void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type) {
|
||||
switch (value_type) {
|
||||
case SensorValueType::U_WORD:
|
||||
case SensorValueType::S_WORD:
|
||||
data.push_back(value & 0xFFFF);
|
||||
break;
|
||||
case SensorValueType::U_DWORD:
|
||||
case SensorValueType::S_DWORD:
|
||||
case SensorValueType::FP32:
|
||||
data.push_back((value & 0xFFFF0000) >> 16);
|
||||
data.push_back(value & 0xFFFF);
|
||||
break;
|
||||
case SensorValueType::U_DWORD_R:
|
||||
case SensorValueType::S_DWORD_R:
|
||||
case SensorValueType::FP32_R:
|
||||
data.push_back(value & 0xFFFF);
|
||||
data.push_back((value & 0xFFFF0000) >> 16);
|
||||
break;
|
||||
case SensorValueType::U_QWORD:
|
||||
case SensorValueType::S_QWORD:
|
||||
data.push_back((value & 0xFFFF000000000000) >> 48);
|
||||
data.push_back((value & 0xFFFF00000000) >> 32);
|
||||
data.push_back((value & 0xFFFF0000) >> 16);
|
||||
data.push_back(value & 0xFFFF);
|
||||
break;
|
||||
case SensorValueType::U_QWORD_R:
|
||||
case SensorValueType::S_QWORD_R:
|
||||
data.push_back(value & 0xFFFF);
|
||||
data.push_back((value & 0xFFFF0000) >> 16);
|
||||
data.push_back((value & 0xFFFF00000000) >> 32);
|
||||
data.push_back((value & 0xFFFF000000000000) >> 48);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Invalid data type for modbus number to payload conversion: %d", static_cast<uint16_t>(value_type));
|
||||
break;
|
||||
}
|
||||
void log_unsupported_value_type(SensorValueType value_type) {
|
||||
ESP_LOGE(TAG, "Invalid data type for modbus number to payload conversion: %d", static_cast<uint16_t>(value_type));
|
||||
}
|
||||
|
||||
int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
|
||||
int64_t payload_to_number(const uint8_t *data, size_t size, SensorValueType sensor_value_type, uint8_t offset,
|
||||
uint32_t bitmask, bool *error_return) {
|
||||
int64_t value = 0; // int64_t because it can hold signed and unsigned 32 bits
|
||||
|
||||
// Validate offset against the buffer for all types, including RAW/unsupported, so
|
||||
// a malformed or misconfigured frame still produces an error log.
|
||||
if (static_cast<size_t>(offset) > data.size()) {
|
||||
if (static_cast<size_t>(offset) > size) {
|
||||
ESP_LOGE(TAG, "not enough data for value type=%u offset=%u size=%zu", static_cast<unsigned int>(sensor_value_type),
|
||||
static_cast<unsigned int>(offset), data.size());
|
||||
static_cast<unsigned int>(offset), size);
|
||||
if (error_return)
|
||||
*error_return = true;
|
||||
return value;
|
||||
@@ -158,10 +124,9 @@ int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sens
|
||||
return value;
|
||||
}
|
||||
|
||||
if (data.size() - offset < required_size) {
|
||||
if (size - offset < required_size) {
|
||||
ESP_LOGE(TAG, "not enough data for value type=%u offset=%u size=%zu required=%zu",
|
||||
static_cast<unsigned int>(sensor_value_type), static_cast<unsigned int>(offset), data.size(),
|
||||
required_size);
|
||||
static_cast<unsigned int>(sensor_value_type), static_cast<unsigned int>(offset), size, required_size);
|
||||
if (error_return)
|
||||
*error_return = true;
|
||||
return value;
|
||||
@@ -214,6 +179,31 @@ int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sens
|
||||
return value;
|
||||
}
|
||||
|
||||
int64_t registers_to_number(const uint16_t *registers, size_t count, SensorValueType sensor_value_type,
|
||||
bool *error_return) {
|
||||
const size_t required_size = required_payload_size(sensor_value_type);
|
||||
if (required_size == 0) {
|
||||
return 0; // RAW/unsupported: nothing to read
|
||||
}
|
||||
const size_t required_words = required_size / 2;
|
||||
if (required_words > count) {
|
||||
ESP_LOGE(TAG, "not enough registers for value type=%u count=%zu required=%zu",
|
||||
static_cast<unsigned int>(sensor_value_type), count, required_words);
|
||||
if (error_return)
|
||||
*error_return = true;
|
||||
return 0;
|
||||
}
|
||||
// Serialize the needed words back to big-endian bytes and reuse the audited byte decoder so the
|
||||
// sign-extension behaviour stays identical to the wire path.
|
||||
uint8_t bytes[8]; // at most 4 registers (QWORD)
|
||||
for (size_t i = 0; i < required_words; i++) {
|
||||
uint16_t reg = registers[i];
|
||||
bytes[i * 2] = static_cast<uint8_t>(reg >> 8);
|
||||
bytes[i * 2 + 1] = static_cast<uint8_t>(reg & 0xFF);
|
||||
}
|
||||
return payload_to_number(bytes, required_size, sensor_value_type, 0, 0xFFFFFFFF, error_return);
|
||||
}
|
||||
|
||||
StaticVector<uint8_t, MAX_PDU_SIZE> create_client_pdu(ModbusFunctionCode function_code, uint16_t start_address,
|
||||
uint16_t number_of_entities, const uint8_t *values,
|
||||
size_t values_len) {
|
||||
|
||||
@@ -224,24 +224,77 @@ template<typename N> N mask_and_shift_by_rightbit(N data, uint32_t mask) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Convert float value to vector<uint16_t> suitable for sending
|
||||
* @param data target for payload
|
||||
* @param value float value to convert
|
||||
* @param value_type defines if 16/32 or FP32 is used
|
||||
* @return vector containing the modbus register words in correct order
|
||||
*/
|
||||
void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type);
|
||||
// Logs an error for an unsupported value type. Defined in the .cpp so logging stays out of headers.
|
||||
void log_unsupported_value_type(SensorValueType value_type);
|
||||
|
||||
/** Convert vector<uint8_t> response payload to number.
|
||||
/** Append the Modbus register words for value to data.
|
||||
* Works with any container exposing push_back(uint16_t) (e.g. std::vector or StaticVector).
|
||||
*/
|
||||
template<typename Container> void number_to_payload(Container &data, int64_t value, SensorValueType value_type) {
|
||||
switch (value_type) {
|
||||
case SensorValueType::U_WORD:
|
||||
case SensorValueType::S_WORD:
|
||||
data.push_back(value & 0xFFFF);
|
||||
break;
|
||||
case SensorValueType::U_DWORD:
|
||||
case SensorValueType::S_DWORD:
|
||||
case SensorValueType::FP32:
|
||||
data.push_back((value & 0xFFFF0000) >> 16);
|
||||
data.push_back(value & 0xFFFF);
|
||||
break;
|
||||
case SensorValueType::U_DWORD_R:
|
||||
case SensorValueType::S_DWORD_R:
|
||||
case SensorValueType::FP32_R:
|
||||
data.push_back(value & 0xFFFF);
|
||||
data.push_back((value & 0xFFFF0000) >> 16);
|
||||
break;
|
||||
case SensorValueType::U_QWORD:
|
||||
case SensorValueType::S_QWORD:
|
||||
data.push_back((value & 0xFFFF000000000000) >> 48);
|
||||
data.push_back((value & 0xFFFF00000000) >> 32);
|
||||
data.push_back((value & 0xFFFF0000) >> 16);
|
||||
data.push_back(value & 0xFFFF);
|
||||
break;
|
||||
case SensorValueType::U_QWORD_R:
|
||||
case SensorValueType::S_QWORD_R:
|
||||
data.push_back(value & 0xFFFF);
|
||||
data.push_back((value & 0xFFFF0000) >> 16);
|
||||
data.push_back((value & 0xFFFF00000000) >> 32);
|
||||
data.push_back((value & 0xFFFF000000000000) >> 48);
|
||||
break;
|
||||
default:
|
||||
log_unsupported_value_type(value_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** Convert a raw response payload to a number.
|
||||
* @param data payload with the data to convert
|
||||
* @param size number of bytes available in data
|
||||
* @param sensor_value_type defines if 16/32/64 bits or FP32 is used
|
||||
* @param offset offset to the data in data
|
||||
* @param bitmask bitmask used for masking and shifting
|
||||
* @return 64-bit number of the payload
|
||||
*/
|
||||
int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
|
||||
int64_t payload_to_number(const uint8_t *data, size_t size, SensorValueType sensor_value_type, uint8_t offset,
|
||||
uint32_t bitmask, bool *error_return = nullptr);
|
||||
|
||||
/** Convert vector<uint8_t> response payload to number. */
|
||||
inline int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
|
||||
uint32_t bitmask, bool *error_return = nullptr) {
|
||||
return payload_to_number(data.data(), data.size(), sensor_value_type, offset, bitmask, error_return);
|
||||
}
|
||||
|
||||
/** Reconstruct a number from register words (host byte order). Inverse of number_to_payload.
|
||||
* Decodes the value at the start of the given span; advance the pointer to read successive values.
|
||||
* @param registers register values in host byte order
|
||||
* @param count number of registers available in registers
|
||||
* @param sensor_value_type defines if 16/32/64 bits or FP32 is used
|
||||
* @return 64-bit number of the registers
|
||||
*/
|
||||
int64_t registers_to_number(const uint16_t *registers, size_t count, SensorValueType sensor_value_type,
|
||||
bool *error_return = nullptr);
|
||||
|
||||
/** Create a modbus clinet pdu for reading/writing single/multiple coils/register/inputs.
|
||||
* @param function_code the modbus function code to use. One of:
|
||||
* READ_COILS
|
||||
|
||||
@@ -3,26 +3,18 @@
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::modbus_server {
|
||||
using modbus::ModbusFunctionCode;
|
||||
using modbus::ModbusExceptionCode;
|
||||
using modbus::helpers::payload_to_number;
|
||||
using modbus::helpers::registers_to_number;
|
||||
|
||||
static const char *const TAG = "modbus_server";
|
||||
|
||||
void ModbusServer::on_modbus_read_registers(uint8_t function_code, uint16_t start_address,
|
||||
uint16_t number_of_registers) {
|
||||
modbus::ServerResponseStatus ModbusServer::on_modbus_read_registers(uint16_t start_address,
|
||||
uint16_t number_of_registers,
|
||||
modbus::RegisterValues ®isters) {
|
||||
ESP_LOGV(TAG,
|
||||
"Received read holding/input registers for device 0x%X. FC: 0x%X. Start address: 0x%X. Number of registers: "
|
||||
"0x%X.",
|
||||
this->address_, function_code, start_address, number_of_registers);
|
||||
"Received read holding/input registers for device 0x%X. Start address: 0x%X. Number of registers: 0x%X.",
|
||||
this->address_, start_address, number_of_registers);
|
||||
|
||||
if (number_of_registers == 0 || number_of_registers > modbus::MAX_NUM_OF_REGISTERS_TO_READ) {
|
||||
ESP_LOGW(TAG, "Invalid number of registers %" PRIu16 ". Sending exception response.", number_of_registers);
|
||||
this->send_error(function_code, ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<uint16_t> sixteen_bit_response;
|
||||
for (uint16_t current_address = start_address; current_address < start_address + number_of_registers;) {
|
||||
bool found = false;
|
||||
for (auto *server_register : this->server_registers_) {
|
||||
@@ -36,10 +28,7 @@ void ModbusServer::on_modbus_read_registers(uint8_t function_code, uint16_t star
|
||||
server_register->address, static_cast<size_t>(server_register->value_type),
|
||||
server_register->register_count, server_register->format_value(value, value_buf, sizeof(value_buf)));
|
||||
|
||||
std::vector<uint16_t> payload;
|
||||
payload.reserve(server_register->register_count * 2);
|
||||
modbus::helpers::number_to_payload(payload, value, server_register->value_type);
|
||||
sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend());
|
||||
modbus::helpers::number_to_payload(registers, value, server_register->value_type);
|
||||
current_address += server_register->register_count;
|
||||
found = true;
|
||||
break;
|
||||
@@ -53,92 +42,37 @@ void ModbusServer::on_modbus_read_registers(uint8_t function_code, uint16_t star
|
||||
"Could not match any register to address 0x%02X, but default allowed. "
|
||||
"Returning default value: %" PRIu16 ".",
|
||||
current_address, this->server_courtesy_response_.register_value);
|
||||
sixteen_bit_response.push_back(this->server_courtesy_response_.register_value);
|
||||
registers.push_back(this->server_courtesy_response_.register_value);
|
||||
current_address += 1; // Just increment by 1, as the default response is a single register
|
||||
} else {
|
||||
ESP_LOGW(TAG,
|
||||
"Could not match any register to address 0x%02X and default not allowed. Sending exception response.",
|
||||
current_address);
|
||||
this->send_error(function_code, ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
return;
|
||||
return ModbusExceptionCode::ILLEGAL_DATA_ADDRESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> response;
|
||||
if (number_of_registers != sixteen_bit_response.size())
|
||||
ESP_LOGW(TAG, "Response size not matched to request register count.");
|
||||
response.push_back(sixteen_bit_response.size() * 2); // actual byte count
|
||||
for (auto v : sixteen_bit_response) {
|
||||
auto decoded_value = decode_value(v);
|
||||
response.push_back(decoded_value[0]);
|
||||
response.push_back(decoded_value[1]);
|
||||
}
|
||||
this->send(function_code, response);
|
||||
return {};
|
||||
}
|
||||
|
||||
void ModbusServer::on_modbus_write_registers(uint8_t function_code, const std::vector<uint8_t> &data) {
|
||||
uint16_t number_of_registers;
|
||||
uint16_t payload_offset;
|
||||
modbus::ServerResponseStatus ModbusServer::on_modbus_write_registers(uint16_t start_address,
|
||||
const modbus::RegisterValues ®isters) {
|
||||
// registers holds the values to write in host byte order; its size is the register count.
|
||||
ESP_LOGV(TAG, "Received write registers for device 0x%X. Start address: 0x%X. Number of registers: 0x%zX.",
|
||||
this->address_, start_address, registers.size());
|
||||
|
||||
if (static_cast<ModbusFunctionCode>(function_code) == ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS) {
|
||||
if (data.size() < 5) {
|
||||
ESP_LOGW(TAG, "Write multiple registers data too short (%zu bytes)", data.size());
|
||||
this->send_error(function_code, ModbusExceptionCode::ILLEGAL_DATA_VALUE);
|
||||
return;
|
||||
}
|
||||
number_of_registers = uint16_t(data[3]) | (uint16_t(data[2]) << 8);
|
||||
if (number_of_registers == 0 || number_of_registers > modbus::MAX_NUM_OF_REGISTERS_TO_WRITE) {
|
||||
ESP_LOGW(TAG, "Invalid number of registers %" PRIu16 ". Sending exception response.", number_of_registers);
|
||||
this->send_error(function_code, ModbusExceptionCode::ILLEGAL_DATA_VALUE);
|
||||
return;
|
||||
}
|
||||
uint16_t payload_size = data[4];
|
||||
if (payload_size != number_of_registers * 2) {
|
||||
ESP_LOGW(TAG,
|
||||
"Payload size of %" PRIu16 " bytes is not 2 times the number of registers (%" PRIu16
|
||||
"). Sending exception response.",
|
||||
payload_size, number_of_registers);
|
||||
this->send_error(function_code, ModbusExceptionCode::ILLEGAL_DATA_VALUE);
|
||||
return;
|
||||
}
|
||||
if (data.size() < 5 + payload_size) {
|
||||
ESP_LOGW(TAG, "Write multiple registers payload truncated (%zu bytes, expected %u)", data.size(),
|
||||
5 + payload_size);
|
||||
this->send_error(function_code, ModbusExceptionCode::ILLEGAL_DATA_VALUE);
|
||||
return;
|
||||
}
|
||||
payload_offset = 5;
|
||||
} else if (static_cast<ModbusFunctionCode>(function_code) == ModbusFunctionCode::WRITE_SINGLE_REGISTER) {
|
||||
if (data.size() < 4) {
|
||||
ESP_LOGW(TAG, "Write single register data too short (%zu bytes)", data.size());
|
||||
this->send_error(function_code, ModbusExceptionCode::ILLEGAL_DATA_VALUE);
|
||||
return;
|
||||
}
|
||||
number_of_registers = 1;
|
||||
payload_offset = 2;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Invalid function code 0x%X. Sending exception response.", function_code);
|
||||
this->send_error(function_code, ModbusExceptionCode::ILLEGAL_FUNCTION);
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t start_address = uint16_t(data[1]) | (uint16_t(data[0]) << 8);
|
||||
ESP_LOGD(TAG,
|
||||
"Received write holding registers for device 0x%X. FC: 0x%X. Start address: 0x%X. Number of registers: "
|
||||
"0x%X.",
|
||||
this->address_, function_code, start_address, number_of_registers);
|
||||
|
||||
auto for_each_register = [this, start_address, number_of_registers, payload_offset](
|
||||
const std::function<bool(ServerRegister *, uint16_t offset)> &callback) -> bool {
|
||||
uint16_t offset = payload_offset;
|
||||
for (uint16_t current_address = start_address; current_address < start_address + number_of_registers;) {
|
||||
auto for_each_register =
|
||||
[this, start_address,
|
||||
®isters](const std::function<bool(ServerRegister *, uint16_t register_offset)> &callback) -> bool {
|
||||
uint16_t register_offset = 0;
|
||||
for (uint32_t current_address = start_address; current_address < start_address + registers.size();) {
|
||||
bool ok = false;
|
||||
for (auto *server_register : this->server_registers_) {
|
||||
if (server_register->address == current_address) {
|
||||
ok = callback(server_register, offset);
|
||||
ok = callback(server_register, register_offset);
|
||||
current_address += server_register->register_count;
|
||||
offset += server_register->register_count * sizeof(uint16_t);
|
||||
register_offset += server_register->register_count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -150,36 +84,41 @@ void ModbusServer::on_modbus_write_registers(uint8_t function_code, const std::v
|
||||
return true;
|
||||
};
|
||||
|
||||
// check all registers are writable before writing to any of them:
|
||||
if (!for_each_register([](ServerRegister *server_register, uint16_t offset) -> bool {
|
||||
return server_register->write_lambda != nullptr;
|
||||
})) {
|
||||
ESP_LOGW(TAG, "Invalid register address. Sending exception response.");
|
||||
this->send_error(function_code, ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
return;
|
||||
}
|
||||
|
||||
// Actually write to the registers:
|
||||
if (!for_each_register([&data](ServerRegister *server_register, uint16_t offset) {
|
||||
bool error = false;
|
||||
int64_t number = payload_to_number(data, server_register->value_type, offset, 0xFFFFFFFF, &error);
|
||||
if (error) {
|
||||
return false;
|
||||
} else {
|
||||
return server_register->write_lambda(number);
|
||||
// Pre-flight: every targeted register must be writable AND have its full value present in the request,
|
||||
// so we never apply a partial write before discovering a problem. The commit pass below re-runs
|
||||
// registers_to_number rather than caching the decoded values: using the same function for the check and
|
||||
// the write keeps a single source of truth for the decode bound, independent of how register_count was set.
|
||||
ModbusExceptionCode precheck = ModbusExceptionCode::ILLEGAL_DATA_ADDRESS; // unmatched or unwritable register
|
||||
if (!for_each_register([&precheck, ®isters](ServerRegister *server_register, uint16_t register_offset) -> bool {
|
||||
if (server_register->write_lambda == nullptr) {
|
||||
return false; // unwritable -> ILLEGAL_DATA_ADDRESS
|
||||
}
|
||||
bool error = false;
|
||||
registers_to_number(registers.data() + register_offset, registers.size() - register_offset,
|
||||
server_register->value_type, &error);
|
||||
if (error) {
|
||||
precheck = ModbusExceptionCode::ILLEGAL_DATA_VALUE; // request doesn't supply the full value
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})) {
|
||||
ESP_LOGW(TAG, "Could not write all registers. Sending exception response.");
|
||||
this->send_error(function_code, ModbusExceptionCode::SERVICE_DEVICE_FAILURE);
|
||||
return;
|
||||
ESP_LOGW(TAG, "Write request rejected before applying any register. Sending exception response.");
|
||||
return precheck;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> response;
|
||||
response.reserve(6);
|
||||
response.push_back(this->address_);
|
||||
response.push_back(function_code);
|
||||
response.insert(response.end(), data.begin(), data.begin() + 4);
|
||||
this->send_raw(response);
|
||||
// Commit: every value is known writable and decodable, so the only failure now is a user write callback
|
||||
// rejecting the value at runtime -- which cannot be rolled back.
|
||||
if (!for_each_register([®isters](ServerRegister *server_register, uint16_t register_offset) {
|
||||
int64_t number = registers_to_number(registers.data() + register_offset, registers.size() - register_offset,
|
||||
server_register->value_type);
|
||||
return server_register->write_lambda(number);
|
||||
})) {
|
||||
ESP_LOGW(TAG, "A register write callback failed mid-sequence; earlier writes were already applied.");
|
||||
return ModbusExceptionCode::SERVICE_DEVICE_FAILURE;
|
||||
}
|
||||
|
||||
// Success: the caller builds the write response (an echo of the request header).
|
||||
return {};
|
||||
}
|
||||
|
||||
void ModbusServer::dump_config() {
|
||||
|
||||
@@ -98,9 +98,11 @@ class ModbusServer : public Component, public modbus::ModbusServerDevice {
|
||||
/// Registers a server register with the controller. Called by esphomes code generator
|
||||
void add_server_register(ServerRegister *server_register) { server_registers_.push_back(server_register); }
|
||||
/// called when a modbus request (function code 0x03 or 0x04) was parsed without errors
|
||||
void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers) final;
|
||||
modbus::ServerResponseStatus on_modbus_read_registers(uint16_t start_address, uint16_t number_of_registers,
|
||||
modbus::RegisterValues ®isters) final;
|
||||
/// called when a modbus request (function code 0x06 or 0x10) was parsed without errors
|
||||
void on_modbus_write_registers(uint8_t function_code, const std::vector<uint8_t> &data) final;
|
||||
modbus::ServerResponseStatus on_modbus_write_registers(uint16_t start_address,
|
||||
const modbus::RegisterValues ®isters) final;
|
||||
/// Called by esphome generated code to set the server courtesy response object
|
||||
void set_server_courtesy_response(const ServerCourtesyResponse &server_courtesy_response) {
|
||||
this->server_courtesy_response_ = server_courtesy_response;
|
||||
|
||||
@@ -75,16 +75,16 @@ void MPL3115A2Component::update() {
|
||||
float altitude = 0, pressure = 0;
|
||||
if (this->altitude_ != nullptr) {
|
||||
int32_t alt = encode_uint32(buffer[0], buffer[1], buffer[2], 0);
|
||||
altitude = float(alt) / 65536.0;
|
||||
altitude = float(alt) / 65536.0f;
|
||||
this->altitude_->publish_state(altitude);
|
||||
} else {
|
||||
uint32_t p = encode_uint32(0, buffer[0], buffer[1], buffer[2]);
|
||||
pressure = float(p) / 6400.0;
|
||||
pressure = float(p) / 6400.0f;
|
||||
if (this->pressure_ != nullptr)
|
||||
this->pressure_->publish_state(pressure);
|
||||
}
|
||||
int16_t t = encode_uint16(buffer[3], buffer[4]);
|
||||
float temperature = float(t) / 256.0;
|
||||
float temperature = float(t) / 256.0f;
|
||||
if (this->temperature_ != nullptr)
|
||||
this->temperature_->publish_state(temperature);
|
||||
|
||||
|
||||
@@ -115,9 +115,9 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
|
||||
// max_temp
|
||||
root[MQTT_MAX_TEMP] = traits.get_visual_max_temperature();
|
||||
// target_temp_step
|
||||
root[MQTT_TARGET_TEMPERATURE_STEP] = roundf(traits.get_visual_target_temperature_step() * 10) * 0.1;
|
||||
root[MQTT_TARGET_TEMPERATURE_STEP] = roundf(traits.get_visual_target_temperature_step() * 10) * 0.1f;
|
||||
// current_temp_step
|
||||
root[MQTT_CURRENT_TEMPERATURE_STEP] = roundf(traits.get_visual_current_temperature_step() * 10) * 0.1;
|
||||
root[MQTT_CURRENT_TEMPERATURE_STEP] = roundf(traits.get_visual_current_temperature_step() * 10) * 0.1f;
|
||||
// temperature units are always coerced to Celsius internally
|
||||
root[MQTT_TEMPERATURE_UNIT] = "C";
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ static const char *const TAG = "msa3xx";
|
||||
const uint8_t MSA_3XX_PART_ID = 0x13;
|
||||
|
||||
const float GRAVITY_EARTH = 9.80665f;
|
||||
const float LSB_COEFF = 1000.0f / (GRAVITY_EARTH * 3.9); // LSB to 1 LSB = 3.9mg = 0.0039g
|
||||
const float LSB_COEFF = 1000.0f / (GRAVITY_EARTH * 3.9f); // LSB to 1 LSB = 3.9mg = 0.0039g
|
||||
const float G_OFFSET_MIN = -4.5f; // -127...127 LSB = +- 0.4953g = +- 4.857 m/s^2 => +- 4.5 for the safe
|
||||
const float G_OFFSET_MAX = 4.5f; // -127...127 LSB = +- 0.4953g = +- 4.857 m/s^2 => +- 4.5 for the safe
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ void Nextion::goto_page(const char *page) { this->add_no_result_to_queue_with_pr
|
||||
void Nextion::goto_page(uint8_t page) { this->add_no_result_to_queue_with_printf_("page", "page %i", page); }
|
||||
|
||||
void Nextion::set_backlight_brightness(float brightness) {
|
||||
if (brightness < 0 || brightness > 1.0) {
|
||||
if (brightness < 0 || brightness > 1.0f) {
|
||||
ESP_LOGD(TAG, "Brightness out of bounds (0-1.0)");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -54,22 +54,15 @@ def _get_toolchain_path(version: str) -> Path:
|
||||
return _get_tools_path() / "toolchains" / version
|
||||
|
||||
|
||||
# onexc/dir_fd were added to shutil.rmtree in 3.12; the 3.11 branch uses onerror.
|
||||
_SITECUSTOMIZE = """\
|
||||
import os, stat, shutil, sys
|
||||
import os, stat, shutil
|
||||
_orig = shutil.rmtree
|
||||
def _handler(func, path, exc):
|
||||
os.chmod(path, stat.S_IWRITE); func(path)
|
||||
if sys.version_info >= (3, 12):
|
||||
def _rmtree(path, ignore_errors=False, onerror=None, *, onexc=None, dir_fd=None):
|
||||
if onerror is None and onexc is None:
|
||||
onexc = _handler
|
||||
return _orig(path, ignore_errors=ignore_errors, onerror=onerror, onexc=onexc, dir_fd=dir_fd)
|
||||
else:
|
||||
def _rmtree(path, ignore_errors=False, onerror=None):
|
||||
if onerror is None:
|
||||
onerror = _handler
|
||||
return _orig(path, ignore_errors=ignore_errors, onerror=onerror)
|
||||
def _rmtree(path, ignore_errors=False, onerror=None, *, onexc=None, dir_fd=None):
|
||||
if onerror is None and onexc is None:
|
||||
onexc = _handler
|
||||
return _orig(path, ignore_errors=ignore_errors, onerror=onerror, onexc=onexc, dir_fd=dir_fd)
|
||||
shutil.rmtree = _rmtree
|
||||
"""
|
||||
|
||||
|
||||
@@ -541,7 +541,7 @@ void OpenTherm::debug_error(OpenThermError &error) const {
|
||||
error.capture, error.bit_pos);
|
||||
}
|
||||
|
||||
float OpenthermData::f88() { return ((float) this->s16()) / 256.0; }
|
||||
float OpenthermData::f88() { return ((float) this->s16()) / 256.0f; }
|
||||
|
||||
void OpenthermData::f88(float value) { this->s16((int16_t) (value * 256)); }
|
||||
|
||||
|
||||
@@ -12,8 +12,9 @@ void opentherm::OpenthermOutput::write_state(float state) {
|
||||
#else
|
||||
bool zero_means_zero = false;
|
||||
#endif
|
||||
this->state =
|
||||
state < 0.003 && zero_means_zero ? 0.0 : clamp(std::lerp(min_value_, max_value_, state), min_value_, max_value_);
|
||||
this->state = state < 0.003f && zero_means_zero
|
||||
? 0.0f
|
||||
: clamp(std::lerp(min_value_, max_value_, state), min_value_, max_value_);
|
||||
this->has_state_ = true;
|
||||
ESP_LOGD(TAG, "Output %s set to %.2f", this->id_, this->state);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ class OpenThreadSrpComponent final : public Component {
|
||||
public:
|
||||
void set_mdns(esphome::mdns::MDNSComponent *mdns);
|
||||
// This has to run after the mdns component or else no services are available to advertise
|
||||
float get_setup_priority() const override { return this->mdns_->get_setup_priority() - 1.0; }
|
||||
float get_setup_priority() const override { return this->mdns_->get_setup_priority() - 1.0f; }
|
||||
void setup() override;
|
||||
static void srp_callback(otError err, const otSrpClientHostInfo *host_info, const otSrpClientService *services,
|
||||
const otSrpClientService *removed_services, void *context);
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
#include "pid_autotuner.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cinttypes>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.1415926535897932384626433
|
||||
#endif
|
||||
#include <numbers>
|
||||
|
||||
namespace esphome::pid {
|
||||
|
||||
@@ -126,7 +123,7 @@ PIDAutotuner::PIDAutotuneResult PIDAutotuner::update(float setpoint, float proce
|
||||
float osc_ampl = this->amplitude_detector_.get_mean_oscillation_amplitude();
|
||||
float d = (this->relay_function_.output_positive - this->relay_function_.output_negative) / 2.0f;
|
||||
ESP_LOGVV(TAG, " Relay magnitude: %f", d);
|
||||
this->ku_ = 4.0f * d / float(M_PI * osc_ampl);
|
||||
this->ku_ = 4.0f * d / (std::numbers::pi_v<float> * osc_ampl);
|
||||
this->pu_ = this->frequency_detector_.get_mean_oscillation_period();
|
||||
|
||||
this->state_ = AUTOTUNE_SUCCEEDED;
|
||||
@@ -300,7 +297,7 @@ bool PIDAutotuner::OscillationFrequencyDetector::is_increase_decrease_symmetrica
|
||||
min_interval = std::min(min_interval, interval);
|
||||
}
|
||||
float ratio = min_interval / float(max_interval);
|
||||
return ratio >= 0.66;
|
||||
return ratio >= 0.66f;
|
||||
}
|
||||
|
||||
// ================== OscillationAmplitudeDetector ==================
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include <cmath>
|
||||
#include <numbers>
|
||||
|
||||
namespace esphome::qmc5883l {
|
||||
|
||||
@@ -173,7 +174,7 @@ void QMC5883LComponent::read_sensor_() {
|
||||
const float y = int16_t(raw[1]) * mg_per_bit * 0.1f;
|
||||
const float z = int16_t(raw[2]) * mg_per_bit * 0.1f;
|
||||
|
||||
float heading = atan2f(0.0f - x, y) * 180.0f / M_PI;
|
||||
float heading = atan2f(0.0f - x, y) * 180.0f / std::numbers::pi_v<float>;
|
||||
|
||||
float temp = NAN;
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
|
||||
@@ -276,7 +276,7 @@ void QMP6988Component::write_oversampling_temperature_(QMP6988Oversampling overs
|
||||
|
||||
void QMP6988Component::calculate_altitude_(float pressure, float temp) {
|
||||
float altitude;
|
||||
altitude = (pow((101325 / pressure), 1 / 5.257) - 1) * (temp + 273.15) / 0.0065;
|
||||
altitude = (powf((101325 / pressure), 1 / 5.257f) - 1) * (temp + 273.15f) / 0.0065f;
|
||||
this->qmp6988_data_.altitude = altitude;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cmath>
|
||||
#include <numbers>
|
||||
|
||||
namespace esphome::rd03d {
|
||||
|
||||
@@ -233,7 +234,7 @@ void RD03DComponent::publish_target_(uint8_t target_num, int16_t x, int16_t y, i
|
||||
// Angle is measured from the Y axis (radar forward direction)
|
||||
if (target.angle != nullptr) {
|
||||
if (valid) {
|
||||
float angle = std::atan2(static_cast<float>(x), static_cast<float>(y)) * 180.0f / M_PI;
|
||||
float angle = std::atan2(static_cast<float>(x), static_cast<float>(y)) * 180.0f / std::numbers::pi_v<float>;
|
||||
target.angle->publish_state(angle);
|
||||
} else {
|
||||
target.angle->publish_state(NAN);
|
||||
|
||||
@@ -95,7 +95,7 @@ void RuntimeStatsCollector::log_stats_() {
|
||||
ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.3fms, max=%.2fms, total=%.1fms",
|
||||
LOG_STR_ARG(sorted[i]->get_component_log_str()), stats.total_count,
|
||||
stats.total_count > 0 ? stats.total_time_us / (float) stats.total_count / 1000.0f : 0.0f,
|
||||
stats.total_max_time_us / 1000.0f, stats.total_time_us / 1000.0);
|
||||
stats.total_max_time_us / 1000.0f, stats.total_time_us / 1000.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ void Servo::write(float value) {
|
||||
void Servo::internal_write(float value) {
|
||||
value = clamp(value, -1.0f, 1.0f);
|
||||
float level;
|
||||
if (value < 0.0) {
|
||||
if (value < 0.0f) {
|
||||
level = std::lerp(this->idle_level_, this->min_level_, -value);
|
||||
} else {
|
||||
level = std::lerp(this->idle_level_, this->max_level_, value);
|
||||
|
||||
@@ -207,7 +207,7 @@ bool ShellyDimmer::upgrade_firmware_() {
|
||||
|
||||
uint16_t ShellyDimmer::convert_brightness_(float brightness) {
|
||||
// Special case for zero as only zero means turn off completely.
|
||||
if (brightness == 0.0) {
|
||||
if (brightness == 0.0f) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ void SoundLevelComponent::loop() {
|
||||
if (this->sample_count_ == samples_in_window) {
|
||||
// Processed enough samples for the measurement window, compute and publish the sensor values
|
||||
if (this->peak_sensor_ != nullptr) {
|
||||
const float peak_db = 10.0f * log10(static_cast<float>(this->squared_peak_) / MAX_SAMPLE_SQUARED_DENOMINATOR);
|
||||
const float peak_db = 10.0f * log10f(static_cast<float>(this->squared_peak_) / MAX_SAMPLE_SQUARED_DENOMINATOR);
|
||||
this->peak_sensor_->publish_state(peak_db);
|
||||
|
||||
this->squared_peak_ = 0; // reset accumulator
|
||||
|
||||
@@ -224,7 +224,7 @@ bool SPA06Component::soft_reset_() {
|
||||
}
|
||||
|
||||
// Temperature conversion formula. See datasheet pg. 14
|
||||
float SPA06Component::convert_temperature_(const float &t_raw_sc) { return this->c0_ * 0.5 + this->c1_ * t_raw_sc; }
|
||||
float SPA06Component::convert_temperature_(const float &t_raw_sc) { return this->c0_ * 0.5f + this->c1_ * t_raw_sc; }
|
||||
// Pressure conversion formula. See datasheet pg. 14
|
||||
float SPA06Component::convert_pressure_(const float &p_raw_sc, const float &t_raw_sc) {
|
||||
float p2_raw_sc = p_raw_sc * p_raw_sc;
|
||||
|
||||
@@ -612,7 +612,7 @@ void SpeakerMediaPlayer::set_volume_(float volume, bool publish) {
|
||||
}
|
||||
|
||||
// Turn on the mute state if the volume is effectively zero, off otherwise
|
||||
if (volume < 0.001) {
|
||||
if (volume < 0.001f) {
|
||||
this->set_mute_state_(true);
|
||||
} else {
|
||||
this->set_mute_state_(false);
|
||||
|
||||
@@ -831,7 +831,7 @@ void SpeakerSourceMediaPlayer::set_volume_(float volume, bool publish) {
|
||||
|
||||
// Turn on the mute state if the volume is effectively zero, off otherwise.
|
||||
// Pass publish=false to avoid saving twice.
|
||||
if (volume < 0.001) {
|
||||
if (volume < 0.001f) {
|
||||
this->set_mute_state_(true, false);
|
||||
} else {
|
||||
this->set_mute_state_(false, false);
|
||||
|
||||
@@ -215,7 +215,7 @@ void SX126x::configure() {
|
||||
// configure modem
|
||||
if (this->modulation_ == PACKET_TYPE_LORA) {
|
||||
// set modulation params
|
||||
float duration = 1000.0f * std::pow(2, this->spreading_factor_) / BW_HZ[this->bandwidth_];
|
||||
float duration = 1000.0f * (1UL << this->spreading_factor_) / BW_HZ[this->bandwidth_];
|
||||
buf[0] = this->spreading_factor_;
|
||||
buf[1] = BW_LORA[this->bandwidth_ - SX126X_BW_7810];
|
||||
buf[2] = this->coding_rate_;
|
||||
|
||||
@@ -201,8 +201,8 @@ void SX127x::configure_fsk_ook_() {
|
||||
this->write_register_(REG_OOK_AVG, OOK_AVG_RESERVED | OOK_THRESH_DEC_1_8);
|
||||
|
||||
// set rx floor
|
||||
this->write_register_(REG_OOK_FIX, 256 + int(this->rx_floor_ * 2.0));
|
||||
this->write_register_(REG_RSSI_THRESH, std::abs(int(this->rx_floor_ * 2.0)));
|
||||
this->write_register_(REG_OOK_FIX, 256 + int(this->rx_floor_ * 2.0f));
|
||||
this->write_register_(REG_RSSI_THRESH, std::abs(int(this->rx_floor_ * 2.0f)));
|
||||
}
|
||||
|
||||
void SX127x::configure_lora_() {
|
||||
@@ -225,7 +225,7 @@ void SX127x::configure_lora_() {
|
||||
}
|
||||
|
||||
// optimize detection
|
||||
float duration = 1000.0f * std::pow(2, this->spreading_factor_) / BW_HZ[this->bandwidth_];
|
||||
float duration = 1000.0f * (1UL << this->spreading_factor_) / BW_HZ[this->bandwidth_];
|
||||
if (duration > 16) {
|
||||
this->write_register_(REG_MODEM_CONFIG3, MODEM_AGC_AUTO_ON | LOW_DATA_RATE_OPTIMIZE_ON);
|
||||
} else {
|
||||
|
||||
@@ -256,7 +256,7 @@ void TCS34725Component::update() {
|
||||
// increase only if not already maximum
|
||||
// do not use max gain, as ist will not get better
|
||||
if (this->gain_reg_ < 3) {
|
||||
if (((float) raw_c / 655.35 < 20.f) && (this->integration_time_ > 600.f)) {
|
||||
if (((float) raw_c / 655.35f < 20.f) && (this->integration_time_ > 600.f)) {
|
||||
gain_reg_val_new = this->gain_reg_ + 1;
|
||||
// update integration time to new situation
|
||||
integration_time_ideal = integration_time_ideal / 4;
|
||||
@@ -265,7 +265,7 @@ void TCS34725Component::update() {
|
||||
|
||||
// decrease gain, if very high clear values and integration times alreadey low
|
||||
if (this->gain_reg_ > 0) {
|
||||
if (70 < ((float) raw_c / 655.35) && (this->integration_time_ < 200)) {
|
||||
if (70 < ((float) raw_c / 655.35f) && (this->integration_time_ < 200)) {
|
||||
gain_reg_val_new = this->gain_reg_ - 1;
|
||||
// update integration time to new situation
|
||||
integration_time_ideal = integration_time_ideal * 4;
|
||||
|
||||
@@ -196,7 +196,7 @@ static optional<ParseResult> parse_tp3(const uint8_t *data, std::size_t data_siz
|
||||
result.humidity = static_cast<float>(data[3]);
|
||||
|
||||
// battery level, 2 bits (0-2)
|
||||
result.battery_level = static_cast<float>(data[4] & 0x3) * 50.0;
|
||||
result.battery_level = static_cast<float>(data[4] & 0x3) * 50.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -593,7 +593,7 @@ void ToshibaClimate::transmit_rac_pt1411hwru_() {
|
||||
message[3] = ~message[2];
|
||||
// Byte 4u: Temp
|
||||
if (this->model_ == MODEL_RAC_PT1411HWRU_F) {
|
||||
temperature = (temperature * 1.8) + 32;
|
||||
temperature = (temperature * 1.8f) + 32;
|
||||
temp_adjd = temperature - TOSHIBA_RAC_PT1411HWRU_TEMP_F_MIN;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,19 +70,19 @@ float UFireISEComponent::measure_ph_(float temperature) {
|
||||
if (mv == -1)
|
||||
return -1;
|
||||
|
||||
ph = fabs(7.0 - (mv / PROBE_MV_TO_PH));
|
||||
ph = fabsf(7.0f - (mv / PROBE_MV_TO_PH));
|
||||
|
||||
// Determine the temperature correction
|
||||
float distance_from_7 = std::abs(7 - roundf(ph));
|
||||
float distance_from_25 = std::floor(std::abs(25 - roundf(temperature)) / 10);
|
||||
float temp_multiplier = (distance_from_25 * distance_from_7) * PROBE_TMP_CORRECTION;
|
||||
if ((ph >= 8.0) && (temperature >= 35))
|
||||
if ((ph >= 8.0f) && (temperature >= 35))
|
||||
temp_multiplier *= -1;
|
||||
if ((ph <= 6.0) && (temperature <= 15))
|
||||
if ((ph <= 6.0f) && (temperature <= 15))
|
||||
temp_multiplier *= -1;
|
||||
|
||||
ph += temp_multiplier;
|
||||
if ((ph <= 0.0) || (ph > 14.0))
|
||||
if ((ph <= 0.0f) || (ph > 14.0f))
|
||||
ph = -1;
|
||||
if (std::isinf(ph))
|
||||
ph = -1;
|
||||
|
||||
@@ -215,7 +215,7 @@ void VEML3235Sensor::dump_config() {
|
||||
" Auto-gain upper threshold: %f%%\n"
|
||||
" Auto-gain lower threshold: %f%%\n"
|
||||
" Values below will be used as initial values only",
|
||||
this->auto_gain_threshold_high_ * 100.0, this->auto_gain_threshold_low_ * 100.0);
|
||||
this->auto_gain_threshold_high_ * 100.0f, this->auto_gain_threshold_low_ * 100.0f);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Digital gain: %uX\n"
|
||||
|
||||
@@ -380,7 +380,7 @@ void VEML7700Component::apply_lux_compensation_(Readings &data) {
|
||||
// if this light level is exceeded"
|
||||
auto compensate = [&local_data](float &lux) {
|
||||
auto calculate_high_lux_compensation = [](float lux_veml) -> float {
|
||||
return (((6.0135e-13 * lux_veml - 9.3924e-9) * lux_veml + 8.1488e-5) * lux_veml + 1.0023) * lux_veml;
|
||||
return (((6.0135e-13f * lux_veml - 9.3924e-9f) * lux_veml + 8.1488e-5f) * lux_veml + 1.0023f) * lux_veml;
|
||||
};
|
||||
|
||||
if (lux > 1000.0f || local_data.actual_gain == Gain::X_1_8 || local_data.actual_gain == Gain::X_1_4) {
|
||||
|
||||
@@ -179,12 +179,53 @@ void WiFiComponent::wifi_lazy_init_() {
|
||||
// nor re-register the default WiFi handlers.
|
||||
if (s_sta_netif == nullptr)
|
||||
s_sta_netif = esp_netif_create_default_wifi_sta();
|
||||
if (s_sta_netif == nullptr) {
|
||||
// Allocation failed; leave wifi_initialized_ false so a later enable() retries.
|
||||
ESP_LOGE(TAG, "esp_netif_create_default_wifi_sta failed");
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_AP
|
||||
if (s_ap_netif == nullptr)
|
||||
s_ap_netif = esp_netif_create_default_wifi_ap();
|
||||
#endif // USE_WIFI_AP
|
||||
|
||||
// The WiFi driver was started (e.g. by ESP-NOW with the wifi component disabled at
|
||||
// boot) before our STA netif existed. The default WIFI_EVENT_STA_START handler
|
||||
// therefore ran with no netif and never called esp_wifi_register_if_rxcb() -- the
|
||||
// only thing that points the driver's RX path at a netif (it sets
|
||||
// s_wifi_netifs[WIFI_IF_STA]). A bare esp_netif_action_start() would stop the
|
||||
// immediate crash (#17232) but leaves RX unbound, so the first association
|
||||
// associates at L2 yet never receives DHCP replies and times out (#17239). Restart
|
||||
// the driver now that the netif exists so STA_START re-runs the default handler and
|
||||
// wires RX correctly. ESP-NOW survives the stop/start (its peer state persists).
|
||||
// This also matches a self-retry: if esp_wifi_set_storage() below failed on a
|
||||
// previous wifi_lazy_init_() it returned without setting wifi_initialized_, and
|
||||
// esp_wifi_init() has since run, so esp_wifi_get_mode() now succeeds here too.
|
||||
wifi_mode_t mode;
|
||||
if (esp_wifi_get_mode(&mode) == ESP_OK) {
|
||||
ESP_LOGD(TAG, "WiFi driver already started without STA netif; restarting to bind it");
|
||||
esp_err_t err = esp_wifi_stop();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "esp_wifi_stop failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
// Re-apply RAM storage; the normal init path does this, but it is skipped on
|
||||
// the self-retry case above, which would otherwise let the driver persist
|
||||
// credentials to NVS for the rest of the boot.
|
||||
err = esp_wifi_set_storage(WIFI_STORAGE_RAM);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "esp_wifi_set_storage failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
err = esp_wifi_start();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_wifi_start failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
s_wifi_started = true;
|
||||
this->wifi_initialized_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
if (global_preferences->nvs_handle == 0) {
|
||||
ESP_LOGW(TAG, "starting wifi without nvs");
|
||||
|
||||
@@ -44,7 +44,7 @@ void X9cOutput::setup() {
|
||||
this->ud_pin_->get_pin();
|
||||
this->ud_pin_->setup();
|
||||
|
||||
if (this->initial_value_ <= 0.50) {
|
||||
if (this->initial_value_ <= 0.50f) {
|
||||
this->trim_value(-101); // Set min value (beyond 0)
|
||||
this->trim_value(lroundf(this->initial_value_ * 100));
|
||||
} else {
|
||||
|
||||
@@ -49,7 +49,7 @@ bool XiaomiLYWSD03MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device
|
||||
}
|
||||
if (res->humidity.has_value() && this->humidity_ != nullptr) {
|
||||
// see https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595948254
|
||||
*res->humidity = trunc(*res->humidity);
|
||||
*res->humidity = truncf(*res->humidity);
|
||||
}
|
||||
if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) {
|
||||
continue;
|
||||
|
||||
@@ -49,7 +49,7 @@ bool XiaomiMHOC401::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
}
|
||||
if (res->humidity.has_value() && this->humidity_ != nullptr) {
|
||||
// see https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595948254
|
||||
*res->humidity = trunc(*res->humidity);
|
||||
*res->humidity = truncf(*res->humidity);
|
||||
}
|
||||
if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) {
|
||||
continue;
|
||||
|
||||
@@ -49,7 +49,7 @@ bool XiaomiXMWSDJ04MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &devic
|
||||
}
|
||||
if (res->humidity.has_value() && this->humidity_ != nullptr) {
|
||||
// see https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595948254
|
||||
*res->humidity = trunc(*res->humidity);
|
||||
*res->humidity = truncf(*res->humidity);
|
||||
}
|
||||
if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) {
|
||||
continue;
|
||||
|
||||
@@ -20,6 +20,7 @@ from esphome.const import (
|
||||
CONF_ESPHOME,
|
||||
CONF_EXTERNAL_COMPONENTS,
|
||||
CONF_ID,
|
||||
CONF_MERGE_WARNINGS,
|
||||
CONF_MIN_VERSION,
|
||||
CONF_PACKAGES,
|
||||
CONF_PLATFORM,
|
||||
@@ -1184,6 +1185,24 @@ def validate_config(
|
||||
)
|
||||
return result
|
||||
|
||||
# Warn about any keys silently dropped by `<<` merge includes (shallow,
|
||||
# first-wins). The esphome: section is now known, so we can honor its
|
||||
# `merge_warnings:` opt-out. Always drain the queue to keep it from leaking
|
||||
# into a later run.
|
||||
if (dropped := yaml_util.take_dropped_merge_keys()) and (
|
||||
not isinstance(esphome_conf := config[CONF_ESPHOME], dict)
|
||||
or esphome_conf.get(CONF_MERGE_WARNINGS, True)
|
||||
):
|
||||
for key, location in dict.fromkeys(dropped):
|
||||
_LOGGER.warning(
|
||||
"Key '%s' (%s) was dropped while processing a '<<' merge because it "
|
||||
"is already defined. Merge keys don't combine sections - the first "
|
||||
"definition wins. Use 'packages:' to merge sections, or set "
|
||||
"'esphome: { merge_warnings: false }' to silence this.",
|
||||
key,
|
||||
location,
|
||||
)
|
||||
|
||||
# Snapshot the user's config before any schema validation defaults are
|
||||
# applied. preload_core_config and later validation steps rewrite entries
|
||||
# in-place with defaulted values; deep-copying here preserves the
|
||||
|
||||
@@ -613,6 +613,7 @@ CONF_MEASUREMENT_SEQUENCE_NUMBER = "measurement_sequence_number"
|
||||
CONF_MEDIA_PLAYER = "media_player"
|
||||
CONF_MEDIUM = "medium"
|
||||
CONF_MEMORY_BLOCKS = "memory_blocks"
|
||||
CONF_MERGE_WARNINGS = "merge_warnings"
|
||||
CONF_MESSAGE = "message"
|
||||
CONF_METHANE = "methane"
|
||||
CONF_METHOD = "method"
|
||||
|
||||
@@ -26,6 +26,7 @@ from esphome.const import (
|
||||
CONF_INCLUDES,
|
||||
CONF_INCLUDES_C,
|
||||
CONF_LIBRARIES,
|
||||
CONF_MERGE_WARNINGS,
|
||||
CONF_MIN_VERSION,
|
||||
CONF_NAME,
|
||||
CONF_NAME_ADD_MAC_SUFFIX,
|
||||
@@ -316,6 +317,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_INCLUDES_C, default=[]): cv.ensure_list(valid_include),
|
||||
cv.Optional(CONF_LIBRARIES, default=[]): cv.ensure_list(cv.string_strict),
|
||||
cv.Optional(CONF_NAME_ADD_MAC_SUFFIX, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MERGE_WARNINGS, default=True): cv.boolean,
|
||||
cv.Optional(CONF_DEBUG_SCHEDULER, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PROJECT): cv.Schema(
|
||||
{
|
||||
|
||||
@@ -669,11 +669,11 @@ void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation,
|
||||
if (delta == 0) {
|
||||
hue = 0;
|
||||
} else if (max_color_value == red) {
|
||||
hue = int(fmod(((60 * ((green - blue) / delta)) + 360), 360));
|
||||
hue = int(fmodf((60.0f * ((green - blue) / delta)) + 360.0f, 360.0f));
|
||||
} else if (max_color_value == green) {
|
||||
hue = int(fmod(((60 * ((blue - red) / delta)) + 120), 360));
|
||||
hue = int(fmodf((60.0f * ((blue - red) / delta)) + 120.0f, 360.0f));
|
||||
} else if (max_color_value == blue) {
|
||||
hue = int(fmod(((60 * ((red - green) / delta)) + 240), 360));
|
||||
hue = int(fmodf((60.0f * ((red - green) / delta)) + 240.0f, 360.0f));
|
||||
}
|
||||
|
||||
if (max_color_value == 0) {
|
||||
@@ -686,8 +686,8 @@ void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation,
|
||||
}
|
||||
void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue) {
|
||||
float chroma = value * saturation;
|
||||
float hue_prime = fmod(hue / 60.0, 6);
|
||||
float intermediate = chroma * (1 - fabs(fmod(hue_prime, 2) - 1));
|
||||
float hue_prime = fmodf(hue / 60.0f, 6.0f);
|
||||
float intermediate = chroma * (1.0f - fabsf(fmodf(hue_prime, 2.0f) - 1.0f));
|
||||
float delta = value - chroma;
|
||||
|
||||
if (0 <= hue_prime && hue_prime < 1) {
|
||||
|
||||
@@ -356,7 +356,7 @@ void HOT Scheduler::set_retry_common_(Component *component, NameType name_type,
|
||||
}
|
||||
#endif
|
||||
|
||||
if (backoff_increase_factor < 0.0001) {
|
||||
if (backoff_increase_factor < 0.0001f) {
|
||||
ESP_LOGE(TAG, "set_retry: backoff_factor %0.1f too small, using 1.0: %s", backoff_increase_factor,
|
||||
(name_type == NameType::STATIC_STRING && static_name) ? static_name : "");
|
||||
backoff_increase_factor = 1;
|
||||
|
||||
@@ -239,22 +239,19 @@ def _tar_extract_all(
|
||||
"""
|
||||
Extract a TAR archive to the specified directory.
|
||||
|
||||
Implementation is inspired by Python 3.12's tarfile data filtering logic.
|
||||
This can be replaced with the standard library implementation once
|
||||
support for Python 3.11 is no longer required.
|
||||
Path-traversal, link, permission and ownership sanitization is delegated to
|
||||
the stdlib ``tarfile.data_filter`` (PEP 706). We keep the wrapper-directory
|
||||
stripping (no stdlib equivalent) and the absolute-path reject (data_filter's
|
||||
check is os.path-dependent and would miss a Windows drive path when
|
||||
extracting on POSIX).
|
||||
|
||||
Args:
|
||||
data: File-like object containing the TAR archive
|
||||
extract_dir: Directory to extract contents to
|
||||
progress_header: If set, show a progress bar with this header
|
||||
"""
|
||||
import stat
|
||||
import tarfile
|
||||
|
||||
# Tar extraction safety: os.path.realpath / commonpath / normpath have no
|
||||
# pathlib equivalents and Path.resolve() would follow symlinks unsafely.
|
||||
# Use os.path for the security-sensitive parts; the simple checks move to
|
||||
# Path.
|
||||
extract_dir = os.fspath(extract_dir)
|
||||
abs_dest = os.path.abspath(extract_dir) # noqa: PTH100
|
||||
|
||||
@@ -269,18 +266,14 @@ def _tar_extract_all(
|
||||
safe_members = []
|
||||
|
||||
for member in all_members:
|
||||
name = member.name
|
||||
|
||||
# 1. Strip leading slashes
|
||||
name = name.lstrip("/" + os.sep)
|
||||
|
||||
# 2. Reject absolute paths (incl. Windows drive)
|
||||
# Strip leading slashes, then reject absolute / Windows-drive paths
|
||||
name = member.name.lstrip("/" + os.sep)
|
||||
if Path(name).is_absolute() or (
|
||||
os.name == "nt" and ":" in name.split(os.sep)[0] # noqa: PTH206
|
||||
):
|
||||
continue
|
||||
|
||||
# 3. Strip wrapper directory if one was detected
|
||||
# Strip wrapper directory if one was detected
|
||||
if strip_prefix is not None:
|
||||
norm = name.replace("\\", "/")
|
||||
if norm in (strip_root, strip_prefix):
|
||||
@@ -288,88 +281,29 @@ def _tar_extract_all(
|
||||
if not norm.startswith(strip_prefix):
|
||||
continue
|
||||
name = norm[len(strip_prefix) :]
|
||||
|
||||
# 4. Compute final path
|
||||
target_path = os.path.realpath(os.path.join(abs_dest, name)) # noqa: PTH118
|
||||
if os.path.commonpath([abs_dest, target_path]) != abs_dest:
|
||||
continue
|
||||
|
||||
# 5. Validate links properly
|
||||
if member.issym() or member.islnk():
|
||||
linkname = member.linkname
|
||||
|
||||
# Reject absolute link targets
|
||||
if Path(linkname).is_absolute():
|
||||
continue
|
||||
|
||||
if member.islnk() and strip_prefix is not None:
|
||||
# Hard-link linknames reference another archive member
|
||||
# by its archive name. We've stripped the wrapper prefix
|
||||
# from member.name above (step 3); strip it here too so
|
||||
# tarfile._find_link_target can resolve the target during
|
||||
# extraction. Symlink linknames are filesystem-relative
|
||||
# paths, not archive-member references, so they don't
|
||||
# need this treatment.
|
||||
norm_link = linkname.replace("\\", "/")
|
||||
if norm_link in (strip_root, strip_prefix):
|
||||
continue
|
||||
if not norm_link.startswith(strip_prefix):
|
||||
continue
|
||||
linkname = norm_link[len(strip_prefix) :]
|
||||
|
||||
# Strip leading slashes
|
||||
linkname = os.path.normpath(linkname)
|
||||
|
||||
if member.issym():
|
||||
link_target = os.path.join( # noqa: PTH118
|
||||
abs_dest,
|
||||
os.path.dirname(name), # noqa: PTH120
|
||||
linkname,
|
||||
)
|
||||
else:
|
||||
link_target = os.path.join(abs_dest, linkname) # noqa: PTH118
|
||||
link_target = os.path.realpath(link_target)
|
||||
|
||||
if os.path.commonpath([abs_dest, link_target]) != abs_dest:
|
||||
continue
|
||||
|
||||
# write back normalized linkname
|
||||
member.linkname = linkname
|
||||
|
||||
# 6. Sanitize permissions
|
||||
mode = member.mode
|
||||
if mode is not None:
|
||||
# Strip high bits & group/other write bits
|
||||
mode &= (
|
||||
stat.S_IRWXU
|
||||
| stat.S_IRGRP
|
||||
| stat.S_IXGRP
|
||||
| stat.S_IROTH
|
||||
| stat.S_IXOTH
|
||||
)
|
||||
if member.isfile() or member.islnk():
|
||||
# remove exec bits unless explicitly user-executable
|
||||
if not (mode & stat.S_IXUSR):
|
||||
mode &= ~(stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
||||
mode |= stat.S_IRUSR | stat.S_IWUSR
|
||||
elif not (member.isdir() or member.issym()):
|
||||
# Block special files. Directories and symlinks keep
|
||||
# their masked-original mode — passing None here would
|
||||
# crash tarfile.extract on Python <3.12 (its chmod
|
||||
# path calls os.chmod unconditionally).
|
||||
continue
|
||||
|
||||
member.mode = mode
|
||||
|
||||
# 7. Strip ownership
|
||||
member.uid = None
|
||||
member.gid = None
|
||||
member.uname = None
|
||||
member.gname = None
|
||||
|
||||
# 8. Assign sanitized name back
|
||||
member.name = name
|
||||
|
||||
# Hard-link linknames reference another archive member by its
|
||||
# archive name; strip the wrapper prefix here too so
|
||||
# tarfile._find_link_target can resolve the target during
|
||||
# extraction. Symlink linknames are filesystem-relative paths,
|
||||
# not archive-member references, so they don't need this.
|
||||
if member.islnk() and strip_prefix is not None:
|
||||
norm_link = member.linkname.replace("\\", "/")
|
||||
if norm_link in (strip_root, strip_prefix):
|
||||
continue
|
||||
if not norm_link.startswith(strip_prefix):
|
||||
continue
|
||||
member.linkname = norm_link[len(strip_prefix) :]
|
||||
|
||||
# Delegate traversal, link, permission and ownership sanitization
|
||||
# to the stdlib data filter; it raises FilterError for unsafe
|
||||
# members (path traversal, links outside dest, special files).
|
||||
try:
|
||||
member = tarfile.data_filter(member, abs_dest)
|
||||
except tarfile.FilterError:
|
||||
continue
|
||||
|
||||
safe_members.append(member)
|
||||
|
||||
total = len(safe_members)
|
||||
|
||||
+3
-7
@@ -397,17 +397,13 @@ def rmtree(path: Path | str) -> None:
|
||||
read-only flag and retrying.
|
||||
"""
|
||||
|
||||
def _onerror(func, path, exc_info):
|
||||
def _onexc(func, path, exc):
|
||||
if os.access(path, os.W_OK):
|
||||
raise exc_info[1].with_traceback(exc_info[2])
|
||||
raise exc
|
||||
Path(path).chmod(stat.S_IWUSR | stat.S_IRUSR)
|
||||
func(path)
|
||||
|
||||
# ``onerror`` is deprecated in 3.12 in favour of ``onexc`` (different
|
||||
# callable signature); keep the existing handler shape for now and
|
||||
# silence the lint locally so this PR doesn't bundle an unrelated
|
||||
# migration.
|
||||
shutil.rmtree(path, onerror=_onerror) # pylint: disable=deprecated-argument
|
||||
shutil.rmtree(path, onexc=_onexc)
|
||||
|
||||
|
||||
def walk_files(path: Path):
|
||||
|
||||
@@ -12,7 +12,7 @@ dependencies:
|
||||
esphome/micro-flac:
|
||||
version: 0.2.0
|
||||
esphome/micro-mp3:
|
||||
version: 0.3.0
|
||||
version: 0.4.0
|
||||
esphome/micro-opus:
|
||||
version: 0.4.1
|
||||
esphome/micro-wav:
|
||||
|
||||
@@ -24,7 +24,7 @@ import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import tempfile
|
||||
from typing import Any, TypeVar
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse, urlsplit, urlunsplit
|
||||
|
||||
from esphome import git
|
||||
@@ -195,10 +195,7 @@ class LibraryBackend:
|
||||
emit: Callable[["ConvertedLibrary"], None]
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def ensure_list(obj: T | list[T]) -> list[T]:
|
||||
def ensure_list[T](obj: T | list[T]) -> list[T]:
|
||||
"""
|
||||
Convert an object to a list if it isn't already a list.
|
||||
|
||||
|
||||
@@ -51,6 +51,29 @@ _load_listeners: list[Callable[[Path], None]] = []
|
||||
|
||||
DocumentPath = list[str | int]
|
||||
|
||||
# Key under CORE.data used to accumulate keys that a `<<` merge silently
|
||||
# dropped. The warning is emitted later (see esphome.config.validate_config),
|
||||
# because the esphome: option that suppresses it isn't known while parsing.
|
||||
_MERGE_WARNINGS_KEY = "yaml_dropped_merge_keys"
|
||||
|
||||
|
||||
def _record_dropped_merge_key(parent_file: Path, key: Any) -> None:
|
||||
"""Record a mapping key that a ``<<`` merge silently dropped.
|
||||
|
||||
Merge keys follow the YAML spec's shallow, first-wins semantics: a key that
|
||||
already exists in the mapping (or came from an earlier merge) is discarded
|
||||
rather than deep-merged the way ``packages:`` would combine it. We collect
|
||||
these so a single warning can be emitted once the config is loaded.
|
||||
"""
|
||||
esp_range = getattr(key, "esp_range", None)
|
||||
location = str(esp_range.start_mark) if esp_range is not None else str(parent_file)
|
||||
CORE.data.setdefault(_MERGE_WARNINGS_KEY, []).append((str(key), location))
|
||||
|
||||
|
||||
def take_dropped_merge_keys() -> list[tuple[str, str]]:
|
||||
"""Return and clear the keys dropped during ``<<`` merges so far."""
|
||||
return CORE.data.pop(_MERGE_WARNINGS_KEY, [])
|
||||
|
||||
|
||||
class SensitiveStr(str):
|
||||
"""Marker subclass for validated strings that should be masked in
|
||||
@@ -551,6 +574,10 @@ class ESPHomeLoaderMixin:
|
||||
# is expected to contain mapping nodes and each of these nodes is merged in
|
||||
# turn according to its order in the sequence. Keys in mapping nodes earlier
|
||||
# in the sequence override keys specified in later mapping nodes."
|
||||
#
|
||||
# This is a silent shallow drop (unlike `packages:`, which deep-merges).
|
||||
# Record it so a warning can be emitted after the config loads.
|
||||
_record_dropped_merge_key(self.name, key)
|
||||
continue
|
||||
pairs.append((key, value))
|
||||
# Add key node to seen keys, for sequence merge values.
|
||||
|
||||
+1
-1
@@ -555,7 +555,7 @@ def lint_constants_usage():
|
||||
# Maximum allowed CONF_ constants in esphome/const.py.
|
||||
# This file is frozen — new constants go in esphome/components/const/__init__.py.
|
||||
# Decrease this number when constants are moved out of const.py.
|
||||
CONST_PY_MAX_CONF = 1013
|
||||
CONST_PY_MAX_CONF = 1014
|
||||
|
||||
|
||||
@lint_content_check(include=["esphome/const.py"])
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user