Merge remote-tracking branch 'upstream/dev' into integration

This commit is contained in:
J. Nick Koston
2026-01-04 10:56:13 -10:00
18 changed files with 161 additions and 69 deletions

View File

@@ -212,9 +212,8 @@ void CC1101Component::dump_config() {
XTAL_FREQUENCY / (1 << 16);
float symbol_rate = (((256.0f + this->state_.DRATE_M) * (1 << this->state_.DRATE_E)) / (1 << 28)) * XTAL_FREQUENCY;
float bw = XTAL_FREQUENCY / (8.0f * (4 + this->state_.CHANBW_M) * (1 << this->state_.CHANBW_E));
ESP_LOGCONFIG(TAG, "CC1101:");
LOG_PIN(" CS Pin: ", this->cs_);
ESP_LOGCONFIG(TAG,
"CC1101:\n"
" Chip ID: 0x%04X\n"
" Frequency: %" PRId32 " Hz\n"
" Channel: %u\n"
@@ -224,6 +223,7 @@ void CC1101Component::dump_config() {
" Output Power: %.1f dBm",
this->chip_id_, freq, this->state_.CHANNR, MODULATION_NAMES[this->state_.MOD_FORMAT & 0x07],
symbol_rate, bw, this->output_power_effective_);
LOG_PIN(" CS Pin: ", this->cs_);
}
void CC1101Component::begin_tx() {

View File

@@ -21,12 +21,14 @@ void CD74HC4067Component::setup() {
}
void CD74HC4067Component::dump_config() {
ESP_LOGCONFIG(TAG, "CD74HC4067 Multiplexer:");
ESP_LOGCONFIG(TAG,
"CD74HC4067 Multiplexer:\n"
" switch delay: %" PRIu32,
this->switch_delay_);
LOG_PIN(" S0 Pin: ", this->pin_s0_);
LOG_PIN(" S1 Pin: ", this->pin_s1_);
LOG_PIN(" S2 Pin: ", this->pin_s2_);
LOG_PIN(" S3 Pin: ", this->pin_s3_);
ESP_LOGCONFIG(TAG, "switch delay: %" PRIu32, this->switch_delay_);
}
void CD74HC4067Component::activate_pin(uint8_t pin) {

View File

@@ -146,8 +146,10 @@ void CurrentBasedCover::dump_config() {
if (this->close_obstacle_current_threshold_ != FLT_MAX) {
ESP_LOGCONFIG(TAG, " Close obstacle current threshold: %.11fA", this->close_obstacle_current_threshold_);
}
ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f);
ESP_LOGCONFIG(TAG, "Obstacle Rollback: %.1f%%", this->obstacle_rollback_ * 100);
ESP_LOGCONFIG(TAG,
" Close Duration: %.1fs\n"
"Obstacle Rollback: %.1f%%",
this->close_duration_ / 1e3f, this->obstacle_rollback_ * 100);
if (this->max_duration_ != UINT32_MAX) {
ESP_LOGCONFIG(TAG, "Maximum duration: %.1fs", this->max_duration_ / 1e3f);
}

View File

@@ -179,8 +179,10 @@ uint8_t DetRangeCfgCommand::on_message(std::string &message) {
ESP_LOGE(TAG, "Cannot configure range config. Sensor is not stopped!");
return 1; // Command done
} else if (message == "Done") {
ESP_LOGI(TAG, "Updated detection area config:");
ESP_LOGI(TAG, "Detection area 1 from %.02fm to %.02fm.", this->min1_, this->max1_);
ESP_LOGI(TAG,
"Updated detection area config:\n"
"Detection area 1 from %.02fm to %.02fm.",
this->min1_, this->max1_);
if (this->min2_ >= 0 && this->max2_ >= 0) {
ESP_LOGI(TAG, "Detection area 2 from %.02fm to %.02fm.", this->min2_, this->max2_);
}
@@ -209,9 +211,11 @@ uint8_t SetLatencyCommand::on_message(std::string &message) {
ESP_LOGE(TAG, "Cannot configure output latency. Sensor is not stopped!");
return 1; // Command done
} else if (message == "Done") {
ESP_LOGI(TAG, "Updated output latency config:");
ESP_LOGI(TAG, "Signal that someone was detected is delayed by %.03f s.", this->delay_after_detection_);
ESP_LOGI(TAG, "Signal that nobody is detected anymore is delayed by %.03f s.", this->delay_after_disappear_);
ESP_LOGI(TAG,
"Updated output latency config:\n"
"Signal that someone was detected is delayed by %.03f s.\n"
"Signal that nobody is detected anymore is delayed by %.03f s.",
this->delay_after_detection_, this->delay_after_disappear_);
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
return 1; // Command done
}

View File

@@ -153,8 +153,10 @@ void EmmetiClimate::reverse_add_(T val, size_t len, esphome::remote_base::Remote
bool EmmetiClimate::check_checksum_(uint8_t checksum) {
uint8_t expected = this->gen_checksum_();
ESP_LOGV(TAG, "Expected checksum: %X", expected);
ESP_LOGV(TAG, "Checksum received: %X", checksum);
ESP_LOGV(TAG,
"Expected checksum: %X\n"
"Checksum received: %X",
expected, checksum);
return checksum == expected;
}
@@ -264,8 +266,10 @@ bool EmmetiClimate::on_receive(remote_base::RemoteReceiveData data) {
}
}
ESP_LOGD(TAG, "Swing: %d", (curr_state.bitmap >> 1) & 0x01);
ESP_LOGD(TAG, "Sleep: %d", (curr_state.bitmap >> 2) & 0x01);
ESP_LOGD(TAG,
"Swing: %d\n"
"Sleep: %d",
(curr_state.bitmap >> 1) & 0x01, (curr_state.bitmap >> 2) & 0x01);
for (size_t pos = 0; pos < 4; pos++) {
if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) {
@@ -291,10 +295,13 @@ bool EmmetiClimate::on_receive(remote_base::RemoteReceiveData data) {
}
}
ESP_LOGD(TAG, "Turbo: %d", (curr_state.bitmap >> 3) & 0x01);
ESP_LOGD(TAG, "Light: %d", (curr_state.bitmap >> 4) & 0x01);
ESP_LOGD(TAG, "Tree: %d", (curr_state.bitmap >> 5) & 0x01);
ESP_LOGD(TAG, "Blow: %d", (curr_state.bitmap >> 6) & 0x01);
ESP_LOGD(TAG,
"Turbo: %d\n"
"Light: %d\n"
"Tree: %d\n"
"Blow: %d",
(curr_state.bitmap >> 3) & 0x01, (curr_state.bitmap >> 4) & 0x01, (curr_state.bitmap >> 5) & 0x01,
(curr_state.bitmap >> 6) & 0x01);
uint16_t control_data = 0;
for (size_t pos = 0; pos < 11; pos++) {

View File

@@ -151,14 +151,16 @@ void ENS160Component::update() {
}
// verbose status logging
ESP_LOGV(TAG, "Status: ENS160 STATAS bit 0x%x",
(ENS160_DATA_STATUS_STATAS & (status_value)) == ENS160_DATA_STATUS_STATAS);
ESP_LOGV(TAG, "Status: ENS160 STATER bit 0x%x",
(ENS160_DATA_STATUS_STATER & (status_value)) == ENS160_DATA_STATUS_STATER);
ESP_LOGV(TAG, "Status: ENS160 VALIDITY FLAG 0x%02x", (ENS160_DATA_STATUS_VALIDITY & status_value) >> 2);
ESP_LOGV(TAG, "Status: ENS160 NEWDAT bit 0x%x",
(ENS160_DATA_STATUS_NEWDAT & (status_value)) == ENS160_DATA_STATUS_NEWDAT);
ESP_LOGV(TAG, "Status: ENS160 NEWGPR bit 0x%x",
ESP_LOGV(TAG,
"Status: ENS160 STATAS bit 0x%x\n"
"Status: ENS160 STATER bit 0x%x\n"
"Status: ENS160 VALIDITY FLAG 0x%02x\n"
"Status: ENS160 NEWDAT bit 0x%x\n"
"Status: ENS160 NEWGPR bit 0x%x",
(ENS160_DATA_STATUS_STATAS & (status_value)) == ENS160_DATA_STATUS_STATAS,
(ENS160_DATA_STATUS_STATER & (status_value)) == ENS160_DATA_STATUS_STATER,
(ENS160_DATA_STATUS_VALIDITY & status_value) >> 2,
(ENS160_DATA_STATUS_NEWDAT & (status_value)) == ENS160_DATA_STATUS_NEWDAT,
(ENS160_DATA_STATUS_NEWGPR & (status_value)) == ENS160_DATA_STATUS_NEWGPR);
data_ready = ENS160_DATA_STATUS_NEWDAT & status_value;

View File

@@ -210,9 +210,11 @@ bool ES8388::set_dac_output(DacOutputLine line) {
return false;
};
ESP_LOGV(TAG, "Setting ES8388_DACPOWER to 0x%02X", dac_power);
ESP_LOGV(TAG, "Setting ES8388_DACCONTROL24 / ES8388_DACCONTROL25 to 0x%02X", reg_out1);
ESP_LOGV(TAG, "Setting ES8388_DACCONTROL26 / ES8388_DACCONTROL27 to 0x%02X", reg_out2);
ESP_LOGV(TAG,
"Setting ES8388_DACPOWER to 0x%02X\n"
"Setting ES8388_DACCONTROL24 / ES8388_DACCONTROL25 to 0x%02X\n"
"Setting ES8388_DACCONTROL26 / ES8388_DACCONTROL27 to 0x%02X",
dac_power, reg_out1, reg_out2);
ES8388_ERROR_CHECK(this->write_byte(ES8388_DACCONTROL24, reg_out1)); // LOUT1VOL
ES8388_ERROR_CHECK(this->write_byte(ES8388_DACCONTROL25, reg_out1)); // ROUT1VOL

View File

@@ -99,8 +99,9 @@ template<typename... Ts> class ESP32BLEStartScanAction : public Action<Ts...> {
void play(const Ts &...x) override {
this->parent_->set_scan_continuous(this->continuous_.value(x...));
// Only call start_scan() if scanner is IDLE
// For other states (STARTING, RUNNING, STOPPING, FAILED), the state machine
// will handle restarting when appropriate based on the continuous flag
// For other states (STARTING, RUNNING, STOPPING, FAILED), the normal state
// machine flow will eventually transition back to IDLE, at which point
// loop() will see scan_continuous_ and restart scanning if it is true.
if (this->parent_->get_scanner_state() == ScannerState::IDLE) {
this->parent_->start_scan();
}

View File

@@ -41,11 +41,13 @@ void Esp32HostedUpdate::setup() {
if (this->firmware_size_ >= app_desc_offset + sizeof(esp_app_desc_t)) {
esp_app_desc_t *app_desc = (esp_app_desc_t *) (this->firmware_data_ + app_desc_offset);
if (app_desc->magic_word == ESP_APP_DESC_MAGIC_WORD) {
ESP_LOGD(TAG, "Firmware version: %s", app_desc->version);
ESP_LOGD(TAG, "Project name: %s", app_desc->project_name);
ESP_LOGD(TAG, "Build date: %s", app_desc->date);
ESP_LOGD(TAG, "Build time: %s", app_desc->time);
ESP_LOGD(TAG, "IDF version: %s", app_desc->idf_ver);
ESP_LOGD(TAG,
"Firmware version: %s\n"
"Project name: %s\n"
"Build date: %s\n"
"Build time: %s\n"
"IDF version: %s",
app_desc->version, app_desc->project_name, app_desc->date, app_desc->time, app_desc->idf_ver);
this->update_info_.latest_version = app_desc->version;
if (this->update_info_.latest_version != this->update_info_.current_version) {
this->state_ = update::UPDATE_STATE_AVAILABLE;

View File

@@ -30,8 +30,8 @@ static bool was_power_cycled() {
void FactoryResetComponent::dump_config() {
uint8_t count = 0;
this->flash_.load(&count);
ESP_LOGCONFIG(TAG, "Factory Reset by Reset:");
ESP_LOGCONFIG(TAG,
"Factory Reset by Reset:\n"
" Max interval between resets: %u seconds\n"
" Current count: %u\n"
" Factory reset after %u resets",

View File

@@ -15,6 +15,7 @@ from esphome.const import (
CONF_ID,
CONF_LAMBDA,
CONF_OE_PIN,
CONF_ROTATION,
CONF_UPDATE_INTERVAL,
)
from esphome.core import ID
@@ -134,6 +135,14 @@ CLOCK_SPEEDS = {
"20MHZ": Hub75ClockSpeed.HZ_20M,
}
Hub75Rotation = cg.global_ns.enum("Hub75Rotation", is_class=True)
ROTATIONS = {
0: Hub75Rotation.ROTATE_0,
90: Hub75Rotation.ROTATE_90,
180: Hub75Rotation.ROTATE_180,
270: Hub75Rotation.ROTATE_270,
}
HUB75Display = hub75_ns.class_("HUB75Display", cg.PollingComponent, display.Display)
Hub75Config = cg.global_ns.struct("Hub75Config")
Hub75Pins = cg.global_ns.struct("Hub75Pins")
@@ -361,6 +370,8 @@ CONFIG_SCHEMA = cv.All(
display.FULL_DISPLAY_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(HUB75Display),
# Override rotation - store Hub75Rotation directly (driver handles rotation)
cv.Optional(CONF_ROTATION): cv.enum(ROTATIONS, int=True),
# Board preset (optional - provides default pin mappings)
cv.Optional(CONF_BOARD): cv.one_of(*BOARDS.keys(), lower=True),
# Panel dimensions
@@ -378,7 +389,7 @@ CONFIG_SCHEMA = cv.All(
# Display configuration
cv.Optional(CONF_DOUBLE_BUFFER): cv.boolean,
cv.Optional(CONF_BRIGHTNESS): cv.int_range(min=0, max=255),
cv.Optional(CONF_BIT_DEPTH): cv.int_range(min=6, max=12),
cv.Optional(CONF_BIT_DEPTH): cv.int_range(min=4, max=12),
cv.Optional(CONF_GAMMA_CORRECT): cv.enum(
{"LINEAR": 0, "CIE1931": 1, "GAMMA_2_2": 2}, upper=True
),
@@ -490,10 +501,11 @@ def _build_config_struct(
Fields must be added in declaration order (see hub75_types.h) to satisfy
C++ designated initializer requirements. The order is:
1. fields_before_pins (panel_width through layout)
2. pins
3. output_clock_speed
4. min_refresh_rate
5. fields_after_min_refresh (latch_blanking through brightness)
2. rotation
3. pins
4. output_clock_speed
5. min_refresh_rate
6. fields_after_min_refresh (latch_blanking through brightness)
"""
fields_before_pins = [
(CONF_PANEL_WIDTH, "panel_width"),
@@ -516,6 +528,10 @@ def _build_config_struct(
_append_config_fields(config, fields_before_pins, config_fields)
# Rotation - config already contains Hub75Rotation enum from cv.enum
if CONF_ROTATION in config:
config_fields.append(("rotation", config[CONF_ROTATION]))
config_fields.append(("pins", pins_struct))
if CONF_CLOCK_SPEED in config:
@@ -531,7 +547,7 @@ def _build_config_struct(
async def to_code(config: ConfigType) -> None:
add_idf_component(
name="esphome/esp-hub75",
ref="0.1.7",
ref="0.2.2",
)
# Set compile-time configuration via defines
@@ -570,6 +586,11 @@ async def to_code(config: ConfigType) -> None:
pins_struct = _build_pins_struct(pin_expressions, e_pin_num)
hub75_config = _build_config_struct(config, pins_struct, min_refresh)
# Rotation is handled by the hub75 driver (config_.rotation already set above).
# Force rotation to 0 for ESPHome's Display base class to avoid double-rotation.
if CONF_ROTATION in config:
config[CONF_ROTATION] = 0
# Create display and register
var = cg.new_Pvariable(config[CONF_ID], hub75_config)
await display.register_display(var, config)

View File

@@ -92,14 +92,25 @@ void HUB75Display::fill(Color color) {
if (!this->enabled_) [[unlikely]]
return;
// Special case: black (off) - use fast hardware clear
if (!color.is_on()) {
// Start with full display rect
display::Rect fill_rect(0, 0, this->get_width_internal(), this->get_height_internal());
// Apply clipping using Rect::shrink() to intersect
display::Rect clip = this->get_clipping();
if (clip.is_set()) {
fill_rect.shrink(clip);
if (!fill_rect.is_set())
return; // Completely clipped
}
// Fast path: black filling entire display
if (!color.is_on() && fill_rect.x == 0 && fill_rect.y == 0 && fill_rect.w == this->get_width_internal() &&
fill_rect.h == this->get_height_internal()) {
driver_->clear();
return;
}
// For non-black colors, fall back to base class (pixel-by-pixel)
Display::fill(color);
driver_->fill(fill_rect.x, fill_rect.y, fill_rect.w, fill_rect.h, color.r, color.g, color.b);
}
void HOT HUB75Display::draw_pixel_at(int x, int y, Color color) {

View File

@@ -39,8 +39,8 @@ class HUB75Display : public display::Display {
protected:
// Display internal methods
int get_width_internal() override { return config_.panel_width * config_.layout_cols; }
int get_height_internal() override { return config_.panel_height * config_.layout_rows; }
int get_width_internal() override { return this->driver_ != nullptr ? this->driver_->get_width() : 0; }
int get_height_internal() override { return this->driver_ != nullptr ? this->driver_->get_height() : 0; }
// Member variables
Hub75Driver *driver_{nullptr};

View File

@@ -1,3 +1,5 @@
import logging
from esphome import pins
import esphome.codegen as cg
from esphome.components import sensor
@@ -11,6 +13,8 @@ from esphome.const import (
UNIT_METER,
)
_LOGGER = logging.getLogger(__name__)
CONF_PULSE_TIME = "pulse_time"
ultrasonic_ns = cg.esphome_ns.namespace("ultrasonic")
@@ -30,7 +34,7 @@ CONFIG_SCHEMA = (
{
cv.Required(CONF_TRIGGER_PIN): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_ECHO_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_TIMEOUT, default="2m"): cv.distance,
cv.Optional(CONF_TIMEOUT): cv.distance,
cv.Optional(
CONF_PULSE_TIME, default="10us"
): cv.positive_time_period_microseconds,
@@ -49,5 +53,11 @@ async def to_code(config):
echo = await cg.gpio_pin_expression(config[CONF_ECHO_PIN])
cg.add(var.set_echo_pin(echo))
cg.add(var.set_timeout_us(config[CONF_TIMEOUT] / (0.000343 / 2)))
# Remove before 2026.8.0
if CONF_TIMEOUT in config:
_LOGGER.warning(
"'timeout' option is deprecated and will be removed in 2026.8.0. "
"The option has no effect and can be safely removed."
)
cg.add(var.set_pulse_time_us(config[CONF_PULSE_TIME]))

View File

@@ -6,8 +6,8 @@ namespace esphome::ultrasonic {
static const char *const TAG = "ultrasonic.sensor";
static constexpr uint32_t DEBOUNCE_US = 50; // Ignore edges within 50us (noise filtering)
static constexpr uint32_t TIMEOUT_MARGIN_US = 1000; // Extra margin for sensor processing time
static constexpr uint32_t DEBOUNCE_US = 50; // Ignore edges within 50us (noise filtering)
static constexpr uint32_t MEASUREMENT_TIMEOUT_US = 80000; // Maximum time to wait for measurement completion
void IRAM_ATTR UltrasonicSensorStore::gpio_intr(UltrasonicSensorStore *arg) {
uint32_t now = micros();
@@ -64,12 +64,8 @@ void UltrasonicSensorComponent::loop() {
}
uint32_t elapsed = micros() - this->measurement_start_us_;
if (elapsed >= this->timeout_us_ + TIMEOUT_MARGIN_US) {
ESP_LOGD(TAG,
"'%s' - Timeout after %" PRIu32 "us (measurement_start=%" PRIu32 ", echo_start=%" PRIu32
", echo_end=%" PRIu32 ")",
this->name_.c_str(), elapsed, this->measurement_start_us_, this->store_.echo_start_us,
this->store_.echo_end_us);
if (elapsed >= MEASUREMENT_TIMEOUT_US) {
ESP_LOGD(TAG, "'%s' - Measurement timed out after %" PRIu32 "us", this->name_.c_str(), elapsed);
this->publish_state(NAN);
this->measurement_pending_ = false;
}
@@ -79,10 +75,7 @@ void UltrasonicSensorComponent::dump_config() {
LOG_SENSOR("", "Ultrasonic Sensor", this);
LOG_PIN(" Echo Pin: ", this->echo_pin_);
LOG_PIN(" Trigger Pin: ", this->trigger_pin_);
ESP_LOGCONFIG(TAG,
" Pulse time: %" PRIu32 " us\n"
" Timeout: %" PRIu32 " us",
this->pulse_time_us_, this->timeout_us_);
ESP_LOGCONFIG(TAG, " Pulse time: %" PRIu32 " us", this->pulse_time_us_);
LOG_UPDATE_INTERVAL(this);
}

View File

@@ -22,9 +22,6 @@ class UltrasonicSensorComponent : public sensor::Sensor, public PollingComponent
void set_trigger_pin(InternalGPIOPin *trigger_pin) { this->trigger_pin_ = trigger_pin; }
void set_echo_pin(InternalGPIOPin *echo_pin) { this->echo_pin_ = echo_pin; }
/// Set the timeout for waiting for the echo in µs.
void set_timeout_us(uint32_t timeout_us) { this->timeout_us_ = timeout_us; }
void setup() override;
void loop() override;
void dump_config() override;
@@ -44,7 +41,6 @@ class UltrasonicSensorComponent : public sensor::Sensor, public PollingComponent
ISRInternalGPIOPin trigger_pin_isr_;
InternalGPIOPin *echo_pin_;
UltrasonicSensorStore store_;
uint32_t timeout_us_{};
uint32_t pulse_time_us_{};
uint32_t measurement_start_us_{0};

View File

@@ -28,6 +28,6 @@ dependencies:
rules:
- if: "target in [esp32s2, esp32s3, esp32p4]"
esphome/esp-hub75:
version: 0.1.7
version: 0.2.2
rules:
- if: "target in [esp32, esp32s2, esp32s3, esp32p4]"

View File

@@ -0,0 +1,39 @@
display:
- platform: hub75
id: my_hub75
board: apollo-automation-rev6
panel_width: 64
panel_height: 64
layout_rows: 1
layout_cols: 2
rotation: 90
bit_depth: 4
double_buffer: true
auto_clear_enabled: true
update_interval: 16ms
latch_blanking: 1
clock_speed: 20MHz
lambda: |-
// Test clipping: 8 columns x 4 rows of 16x16 colored squares
Color colors[32] = {
Color(255, 0, 0), Color(0, 255, 0), Color(0, 0, 255), Color(255, 255, 0),
Color(255, 0, 255), Color(0, 255, 255), Color(255, 128, 0), Color(128, 0, 255),
Color(0, 128, 255), Color(255, 0, 128), Color(128, 255, 0), Color(0, 255, 128),
Color(255, 128, 128), Color(128, 255, 128), Color(128, 128, 255), Color(255, 255, 128),
Color(255, 128, 255), Color(128, 255, 255), Color(192, 64, 0), Color(64, 192, 0),
Color(0, 64, 192), Color(192, 0, 64), Color(64, 0, 192), Color(0, 192, 64),
Color(128, 64, 64), Color(64, 128, 64), Color(64, 64, 128), Color(128, 128, 64),
Color(128, 64, 128), Color(64, 128, 128), Color(255, 255, 255), Color(128, 128, 128)
};
int idx = 0;
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 8; col++) {
// Clipping mode: clip to square bounds, then fill "entire screen"
it.start_clipping(col * 16, row * 16, (col + 1) * 16, (row + 1) * 16);
it.fill(colors[idx]);
it.end_clipping();
idx++;
}
}
<<: !include common.yaml