Merge branch 'dev' into frenck/replace-voluptuous-with-probatio

This commit is contained in:
Jonathan Swoboda
2026-06-29 14:14:04 -04:00
109 changed files with 902 additions and 632 deletions
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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
View File
@@ -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 \
+3 -6
View File
@@ -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()
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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_;
+5 -5
View File
@@ -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();
}
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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) {
+1 -1
View File
@@ -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);
}
+1 -1
View File
@@ -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; }
+2 -2
View File
@@ -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;
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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);
}
+2 -2
View File
@@ -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");
+27 -27
View File
@@ -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;
}
+8 -8
View File
@@ -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;
}
+1 -1
View File
@@ -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;
+4 -4
View File
@@ -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;
+4 -4
View File
@@ -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
}
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+3 -1
View File
@@ -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);
+1 -1
View File
@@ -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();
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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);
}
+6 -6
View File
@@ -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;
+6 -6
View File
@@ -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;
+1 -1
View File
@@ -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();
}
+1 -1
View File
@@ -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();
+2 -1
View File
@@ -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 -3
View File
@@ -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;
+11 -11
View File
@@ -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);
}
+3 -1
View File
@@ -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)
-1
View File
@@ -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))
+136 -41
View File
@@ -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.
+33 -28
View File
@@ -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 &registers) {
return ModbusExceptionCode::ILLEGAL_FUNCTION;
};
virtual ServerResponseStatus on_modbus_read_input_registers(uint16_t start_address, uint16_t number_of_registers,
RegisterValues &registers) {
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 &registers) {
return this->on_modbus_read_registers(start_address, number_of_registers, registers);
};
virtual ServerResponseStatus on_modbus_write_registers(uint16_t start_address, const RegisterValues &registers) {
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
+32 -42
View File
@@ -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) {
+62 -9
View File
@@ -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 &registers) {
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 &registers) {
// 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,
&registers](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, &registers](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([&registers](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 &registers) 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 &registers) 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;
+3 -3
View File
@@ -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);
+2 -2
View File
@@ -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";
+1 -1
View File
@@ -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;
}
+5 -12
View File
@@ -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
"""
+1 -1
View File
@@ -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);
}
+1 -1
View File
@@ -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);
+3 -6
View File
@@ -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 ==================
+2 -1
View File
@@ -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) {
+1 -1
View File
@@ -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;
}
+2 -1
View File
@@ -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);
}
}
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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_;
+3 -3
View File
@@ -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 {
+2 -2
View File
@@ -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;
}
+1 -1
View File
@@ -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;
}
+4 -4
View File
@@ -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;
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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");
+1 -1
View File
@@ -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;
+19
View File
@@ -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
+1
View File
@@ -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"
+2
View File
@@ -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(
{
+5 -5
View File
@@ -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) {
+1 -1
View File
@@ -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;
+29 -95
View File
@@ -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
View File
@@ -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):
+1 -1
View File
@@ -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:
+2 -5
View File
@@ -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.
+27
View File
@@ -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
View File
@@ -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