mirror of
https://github.com/esphome/esphome.git
synced 2026-09-17 18:18:43 +00:00
Merge branch 'configure_entity' into integration
# Conflicts: # esphome/components/api/api_connection.cpp # esphome/components/api/api_connection.h # esphome/core/application.h # esphome/core/config.py # esphome/core/entity_base.h
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
//
|
||||
// Used by:
|
||||
// - codeowner-review-request.yml
|
||||
// - codeowner-approved-label.yml + codeowner-approved-label-update.yml
|
||||
// - codeowner-approved-label-update.yml
|
||||
// - auto-label-pr/detectors.js (detectCodeOwner)
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
# Fallback for fork PRs: phase 1 (codeowner-approved-label.yml) handles
|
||||
# non-fork PRs directly but can't write labels on fork PRs (read-only token).
|
||||
# This workflow re-determines the action and applies it if needed.
|
||||
# Adds/removes a 'code-owner-approved' label when a component-specific
|
||||
# codeowner approves (or dismisses) a PR.
|
||||
#
|
||||
# Uses pull_request_target so that fork PRs do not require workflow approval.
|
||||
# The label is reconciled on every PR update; for review events specifically,
|
||||
# this means the label is applied on the next push after a codeowner review.
|
||||
|
||||
name: Codeowner Approved Label Update
|
||||
name: Codeowner Approved Label
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Codeowner Approved Label"]
|
||||
types: [completed]
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
@@ -15,51 +17,23 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update-label:
|
||||
codeowner-approved:
|
||||
name: Run
|
||||
if: >
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event == 'pull_request_review'
|
||||
if: ${{ github.repository == 'esphome/esphome' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get PR details
|
||||
id: pr
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
pr_data=$(gh pr list --repo "$REPO" --state open --search "$HEAD_SHA" \
|
||||
--json number,baseRefName --jq '.[0] // empty')
|
||||
|
||||
if [ -z "$pr_data" ]; then
|
||||
echo "No open PR found for SHA $HEAD_SHA, skipping"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
pr_number=$(echo "$pr_data" | jq -r '.number')
|
||||
base_ref=$(echo "$pr_data" | jq -r '.baseRefName')
|
||||
|
||||
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
|
||||
echo "base_ref=$base_ref" >> "$GITHUB_OUTPUT"
|
||||
echo "Found PR #$pr_number targeting $base_ref"
|
||||
|
||||
- name: Checkout base repository
|
||||
if: steps.pr.outputs.skip != 'true'
|
||||
- name: Checkout base branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ github.repository }}
|
||||
ref: ${{ steps.pr.outputs.base_ref }}
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
sparse-checkout: |
|
||||
.github/scripts/codeowners.js
|
||||
CODEOWNERS
|
||||
|
||||
- name: Update label
|
||||
if: steps.pr.outputs.skip != 'true'
|
||||
- name: Check codeowner approval and update label
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
with:
|
||||
script: |
|
||||
const { loadCodeowners, determineLabelAction, LabelAction } = require('./.github/scripts/codeowners.js');
|
||||
@@ -76,6 +50,11 @@ jobs:
|
||||
github, owner, repo, pr_number, codeownersPatterns, LABEL_NAME
|
||||
);
|
||||
|
||||
if (action === LabelAction.NONE) {
|
||||
console.log('No label change needed');
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === LabelAction.ADD) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner, repo, issue_number: pr_number, labels: [LABEL_NAME]
|
||||
@@ -90,6 +69,4 @@ jobs:
|
||||
} catch (error) {
|
||||
if (error.status !== 404) throw error;
|
||||
}
|
||||
} else {
|
||||
console.log('No label change needed');
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
# Adds/removes a 'code-owner-approved' label when a component-specific
|
||||
# codeowner approves (or dismisses) a PR.
|
||||
#
|
||||
# Handles non-fork PRs directly. For fork PRs the GITHUB_TOKEN is read-only,
|
||||
# so label writes are deferred to codeowner-approved-label-update.yml which
|
||||
# triggers via workflow_run with write permissions.
|
||||
|
||||
name: Codeowner Approved Label
|
||||
|
||||
on:
|
||||
pull_request_review:
|
||||
types: [submitted, dismissed]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: read
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
codeowner-approved:
|
||||
name: Run
|
||||
if: ${{ github.repository == 'esphome/esphome' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout base branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
sparse-checkout: |
|
||||
.github/scripts/codeowners.js
|
||||
CODEOWNERS
|
||||
|
||||
- name: Check codeowner approval and update label
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
with:
|
||||
script: |
|
||||
const { loadCodeowners, determineLabelAction, LabelAction } = require('./.github/scripts/codeowners.js');
|
||||
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const pr_number = parseInt(process.env.PR_NUMBER, 10);
|
||||
const LABEL_NAME = 'code-owner-approved';
|
||||
|
||||
console.log(`Processing PR #${pr_number} for codeowner approval label`);
|
||||
|
||||
const codeownersPatterns = loadCodeowners();
|
||||
const action = await determineLabelAction(
|
||||
github, owner, repo, pr_number, codeownersPatterns, LABEL_NAME
|
||||
);
|
||||
|
||||
if (action === LabelAction.NONE) {
|
||||
console.log('No label change needed');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (action === LabelAction.ADD) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner, repo, issue_number: pr_number, labels: [LABEL_NAME]
|
||||
});
|
||||
console.log(`Added '${LABEL_NAME}' label`);
|
||||
} else if (action === LabelAction.REMOVE) {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner, repo, issue_number: pr_number, name: LABEL_NAME
|
||||
});
|
||||
console.log(`Removed '${LABEL_NAME}' label`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.status === 403) {
|
||||
console.log('Fork PR: deferring label write to phase 2 workflow');
|
||||
} else if (error.status === 404) {
|
||||
console.log('Label already removed');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -397,6 +397,48 @@ uint16_t APIConnection::fill_and_encode_entity_info_with_device_class(EntityBase
|
||||
return fill_and_encode_entity_info(entity, msg, size_fn, encode_fn, conn, remaining_size);
|
||||
}
|
||||
|
||||
uint16_t APIConnection::fill_and_encode_entity_info(EntityBase *entity, InfoResponseProtoMessage &msg,
|
||||
uint8_t message_type, APIConnection *conn,
|
||||
uint32_t remaining_size) {
|
||||
// Set common fields that are shared by all entity types
|
||||
msg.key = entity->get_object_id_hash();
|
||||
|
||||
// API 1.14+ clients compute object_id client-side from the entity name
|
||||
// For older clients, we must send object_id for backward compatibility
|
||||
// See: https://github.com/esphome/backlog/issues/76
|
||||
// TODO: Remove this backward compat code before 2026.7.0 - all clients should support API 1.14 by then
|
||||
// Buffer must remain in scope until encode_message_to_buffer is called
|
||||
char object_id_buf[OBJECT_ID_MAX_LEN];
|
||||
if (!conn->client_supports_api_version(1, 14)) {
|
||||
msg.object_id = entity->get_object_id_to(object_id_buf);
|
||||
}
|
||||
|
||||
if (entity->has_own_name()) {
|
||||
msg.name = entity->get_name();
|
||||
}
|
||||
|
||||
// Set common EntityBase properties
|
||||
#ifdef USE_ENTITY_ICON
|
||||
char icon_buf[MAX_ICON_LENGTH];
|
||||
msg.icon = StringRef(entity->get_icon_to(icon_buf));
|
||||
#endif
|
||||
msg.disabled_by_default = entity->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category());
|
||||
#ifdef USE_DEVICES
|
||||
msg.device_id = entity->get_device_id();
|
||||
#endif
|
||||
return encode_message_to_buffer(msg, message_type, conn, remaining_size);
|
||||
}
|
||||
|
||||
uint16_t APIConnection::fill_and_encode_entity_info_with_device_class(EntityBase *entity, InfoResponseProtoMessage &msg,
|
||||
StringRef &device_class_field,
|
||||
uint8_t message_type, APIConnection *conn,
|
||||
uint32_t remaining_size) {
|
||||
char dc_buf[MAX_DEVICE_CLASS_LENGTH];
|
||||
device_class_field = StringRef(entity->get_device_class_to(dc_buf));
|
||||
return fill_and_encode_entity_info(entity, msg, message_type, conn, remaining_size);
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) {
|
||||
return this->send_message_smart_(binary_sensor, BinarySensorStateResponse::MESSAGE_TYPE,
|
||||
|
||||
@@ -54,8 +54,10 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet
|
||||
|
||||
int32_t output_offset = (universe - first_universe_) * get_lights_per_universe();
|
||||
// limit amount of lights per universe and received
|
||||
// packet.count is the number of DMX bytes including start code; divide by channels to get the number of lights
|
||||
int lights_in_packet = (packet.count > 0) ? (packet.count - 1) / channels_ : 0;
|
||||
int output_end =
|
||||
std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1));
|
||||
std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + lights_in_packet));
|
||||
auto *input_data = packet.values + 1;
|
||||
|
||||
auto effect_name = get_name();
|
||||
|
||||
@@ -464,6 +464,8 @@ def only_on_variant(*, supported=None, unsupported=None, msg_prefix="This featur
|
||||
unsupported = [unsupported]
|
||||
|
||||
def validator_(obj):
|
||||
if not CORE.is_esp32:
|
||||
raise cv.Invalid(f"{msg_prefix} is only available on ESP32")
|
||||
variant = get_esp32_variant()
|
||||
if supported is not None and variant not in supported:
|
||||
raise cv.Invalid(
|
||||
|
||||
@@ -33,7 +33,7 @@ def esp32_p4_validate_supports(value):
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 54:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-54)")
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-54)")
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
|
||||
@@ -29,7 +29,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
def esp32_s3_validate_gpio_pin(value):
|
||||
if value < 0 or value > 48:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)")
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-48)")
|
||||
|
||||
if value in _ESP_32S3_SPI_PSRAM_PINS:
|
||||
raise cv.Invalid(
|
||||
@@ -55,7 +55,7 @@ def esp32_s3_validate_supports(value):
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 48:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)")
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-48)")
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
|
||||
@@ -133,24 +133,22 @@ void HlkFm22xComponent::recv_command_() {
|
||||
checksum ^= byte;
|
||||
length |= byte;
|
||||
|
||||
if (length > HLK_FM22X_MAX_RESPONSE_SIZE) {
|
||||
ESP_LOGE(TAG, "Response too large: %u bytes", length);
|
||||
// Discard exactly the remaining payload and checksum for this frame
|
||||
for (uint16_t i = 0; i < length + 1 && this->available() > 0; ++i)
|
||||
this->read();
|
||||
return;
|
||||
}
|
||||
|
||||
// Read up to buffer size; discard excess bytes while still computing checksum
|
||||
// GET_ALL_FACE_IDS can return all enrolled face data (hundreds of bytes)
|
||||
// but handlers only need the first few bytes
|
||||
size_t to_store = std::min(static_cast<size_t>(length), HLK_FM22X_MAX_RESPONSE_SIZE);
|
||||
for (uint16_t idx = 0; idx < length; ++idx) {
|
||||
byte = this->read();
|
||||
checksum ^= byte;
|
||||
this->recv_buf_[idx] = byte;
|
||||
if (idx < to_store) {
|
||||
this->recv_buf_[idx] = byte;
|
||||
}
|
||||
}
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
char hex_buf[format_hex_pretty_size(HLK_FM22X_MAX_RESPONSE_SIZE)];
|
||||
ESP_LOGV(TAG, "Recv type: 0x%.2X, data: %s", response_type,
|
||||
format_hex_pretty_to(hex_buf, this->recv_buf_.data(), length));
|
||||
format_hex_pretty_to(hex_buf, this->recv_buf_.data(), to_store));
|
||||
#endif
|
||||
|
||||
byte = this->read();
|
||||
@@ -160,10 +158,10 @@ void HlkFm22xComponent::recv_command_() {
|
||||
}
|
||||
switch (response_type) {
|
||||
case HlkFm22xResponseType::NOTE:
|
||||
this->handle_note_(this->recv_buf_.data(), length);
|
||||
this->handle_note_(this->recv_buf_.data(), to_store);
|
||||
break;
|
||||
case HlkFm22xResponseType::REPLY:
|
||||
this->handle_reply_(this->recv_buf_.data(), length);
|
||||
this->handle_reply_(this->recv_buf_.data(), to_store);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unexpected response type: 0x%.2X", response_type);
|
||||
|
||||
@@ -111,6 +111,8 @@ void MAX6956::write_brightness_mode() {
|
||||
}
|
||||
|
||||
void MAX6956::set_pin_brightness(uint8_t pin, float brightness) {
|
||||
if (pin < MAX6956_MIN || pin > MAX6956_MAX)
|
||||
return;
|
||||
uint8_t reg_addr = MAX6956_CURRENT_START + (pin - MAX6956_MIN) / 2;
|
||||
uint8_t config = 0;
|
||||
uint8_t shift = 4 * (pin % 2);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace mipi_dsi {
|
||||
static constexpr size_t MIPI_DSI_MAX_CMD_LOG_BYTES = 64;
|
||||
|
||||
static bool notify_refresh_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) {
|
||||
auto *sem = static_cast<SemaphoreHandle_t *>(user_ctx);
|
||||
auto sem = static_cast<SemaphoreHandle_t>(user_ctx);
|
||||
BaseType_t need_yield = pdFALSE;
|
||||
xSemaphoreGiveFromISR(sem, &need_yield);
|
||||
return (need_yield == pdTRUE);
|
||||
@@ -190,6 +190,7 @@ void MIPI_DSI::draw_pixels_at(int x_start, int y_start, int w, int h, const uint
|
||||
if (bitness != this->color_depth_) {
|
||||
display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
|
||||
x_pad);
|
||||
return;
|
||||
}
|
||||
this->write_to_display_(x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad);
|
||||
}
|
||||
|
||||
@@ -29,11 +29,6 @@ static constexpr uint8_t REPEATING_NOTE_GAP_MS = 10;
|
||||
static constexpr uint16_t SAMPLE_BUFFER_SIZE = 2048;
|
||||
static constexpr uint16_t SAMPLE_RATE = 16000;
|
||||
|
||||
struct SpeakerSample {
|
||||
int8_t left{0};
|
||||
int8_t right{0};
|
||||
};
|
||||
|
||||
inline double deg2rad(double degrees) {
|
||||
static constexpr double PI_ON_180 = M_PI / 180.0;
|
||||
return degrees * PI_ON_180;
|
||||
@@ -108,6 +103,9 @@ void Rtttl::loop() {
|
||||
}
|
||||
} else if (this->state_ == State::INIT) {
|
||||
if (this->speaker_->is_stopped()) {
|
||||
audio::AudioStreamInfo audio_stream_info = audio::AudioStreamInfo(16, 1, SAMPLE_RATE);
|
||||
this->speaker_->set_audio_stream_info(audio_stream_info);
|
||||
this->speaker_->set_volume(this->gain_);
|
||||
this->speaker_->start();
|
||||
this->set_state_(State::STARTING);
|
||||
}
|
||||
@@ -120,35 +118,27 @@ void Rtttl::loop() {
|
||||
return;
|
||||
}
|
||||
if (this->samples_sent_ != this->samples_count_) {
|
||||
SpeakerSample sample[SAMPLE_BUFFER_SIZE + 2];
|
||||
int16_t sample[SAMPLE_BUFFER_SIZE];
|
||||
uint16_t sample_index = 0;
|
||||
double rem = 0.0;
|
||||
|
||||
while (true) {
|
||||
while (sample_index < SAMPLE_BUFFER_SIZE && this->samples_sent_ < this->samples_count_) {
|
||||
// Try and send out the remainder of the existing note, one per `loop()`
|
||||
if (this->samples_per_wave_ != 0 && this->samples_sent_ >= this->samples_gap_) { // Play note
|
||||
rem = ((this->samples_sent_ << 10) % this->samples_per_wave_) * (360.0 / this->samples_per_wave_);
|
||||
|
||||
int8_t val = (127 * this->gain_) * sin(deg2rad(rem));
|
||||
|
||||
sample[sample_index].left = val;
|
||||
sample[sample_index].right = val;
|
||||
sample[sample_index] = INT16_MAX * sin(deg2rad(rem));
|
||||
} else {
|
||||
sample[sample_index].left = 0;
|
||||
sample[sample_index].right = 0;
|
||||
}
|
||||
|
||||
if (sample_index >= SAMPLE_BUFFER_SIZE || this->samples_sent_ >= this->samples_count_) {
|
||||
break;
|
||||
sample[sample_index] = 0;
|
||||
}
|
||||
this->samples_sent_++;
|
||||
sample_index++;
|
||||
}
|
||||
if (sample_index > 0) {
|
||||
size_t bytes_to_send = sample_index * sizeof(SpeakerSample);
|
||||
size_t send = this->speaker_->play((uint8_t *) (&sample), bytes_to_send);
|
||||
if (send != bytes_to_send) {
|
||||
this->samples_sent_ -= (sample_index - (send / sizeof(SpeakerSample)));
|
||||
size_t bytes = sample_index * sizeof(int16_t);
|
||||
size_t sent_bytes = this->speaker_->play((uint8_t *) (&sample), bytes);
|
||||
size_t samples_sent = sent_bytes / sizeof(int16_t);
|
||||
if (samples_sent != sample_index) {
|
||||
this->samples_sent_ -= (sample_index - samples_sent);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -408,11 +398,7 @@ void Rtttl::finish_() {
|
||||
|
||||
#ifdef USE_SPEAKER
|
||||
if (this->speaker_ != nullptr) {
|
||||
SpeakerSample sample[2];
|
||||
sample[0].left = 0;
|
||||
sample[0].right = 0;
|
||||
sample[1].left = 0;
|
||||
sample[1].right = 0;
|
||||
int16_t sample[2] = {0, 0};
|
||||
this->speaker_->play((uint8_t *) (&sample), sizeof(sample));
|
||||
this->speaker_->finish();
|
||||
this->set_state_(State::STOPPING);
|
||||
|
||||
@@ -35,13 +35,9 @@ void SGP4xComponent::setup() {
|
||||
this->self_test_time_ = SPG40_SELFTEST_TIME;
|
||||
this->measure_time_ = SGP40_MEASURE_TIME;
|
||||
if (this->nox_sensor_) {
|
||||
ESP_LOGE(TAG, "SGP41 required for NOx");
|
||||
// disable the sensor
|
||||
this->nox_sensor_->set_disabled_by_default(true);
|
||||
// make sure it's not visible in HA
|
||||
this->nox_sensor_->set_internal(true);
|
||||
this->nox_sensor_->state = NAN;
|
||||
// remove pointer to sensor
|
||||
ESP_LOGE(TAG, "SGP41 required for NOx, disabling NOx sensor");
|
||||
// Drop the pointer so update() never publishes to it.
|
||||
// The entity remains registered but will never receive state updates.
|
||||
this->nox_sensor_ = nullptr;
|
||||
}
|
||||
} else if (featureset == SGP41_FEATURESET) {
|
||||
|
||||
@@ -155,7 +155,8 @@ void SX126x::configure() {
|
||||
}
|
||||
|
||||
// check silicon version to make sure hw is ok
|
||||
this->read_register_(REG_VERSION_STRING, (uint8_t *) this->version_, 16);
|
||||
this->read_register_(REG_VERSION_STRING, (uint8_t *) this->version_, sizeof(this->version_));
|
||||
this->version_[sizeof(this->version_) - 1] = '\0';
|
||||
if (strncmp(this->version_, "SX126", 5) != 0 && strncmp(this->version_, "LLCC68", 6) != 0) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
|
||||
@@ -260,6 +260,11 @@ SX127xError SX127x::transmit_packet(const std::vector<uint8_t> &packet) {
|
||||
return SX127xError::INVALID_PARAMS;
|
||||
}
|
||||
|
||||
if (this->dio0_pin_ == nullptr) {
|
||||
ESP_LOGE(TAG, "DIO0 pin not configured, cannot wait for transmit completion");
|
||||
return SX127xError::INVALID_PARAMS;
|
||||
}
|
||||
|
||||
SX127xError ret = SX127xError::NONE;
|
||||
if (this->modulation_ == MOD_LORA) {
|
||||
this->set_mode_standby();
|
||||
|
||||
@@ -224,9 +224,11 @@ else:
|
||||
FRIENDLY_NAME_MAX_LEN = 120
|
||||
|
||||
# Max device class string length (47 chars + null = 48-byte PROGMEM buffer)
|
||||
# Keep in sync with MAX_DEVICE_CLASS_LENGTH in esphome/core/entity_base.h
|
||||
# Keep in sync with MAX_DEVICE_CLASS_LENGTH in esphome/core/entity_base.h:
|
||||
# DEVICE_CLASS_MAX_LENGTH == MAX_DEVICE_CLASS_LENGTH - 1 (C++ includes the null)
|
||||
DEVICE_CLASS_MAX_LENGTH = 47
|
||||
|
||||
|
||||
# Max icon string length (63 chars + null = 64-byte PROGMEM buffer)
|
||||
# Keep in sync with MAX_ICON_LENGTH in esphome/core/entity_base.h
|
||||
ICON_MAX_LENGTH = 63
|
||||
|
||||
@@ -11,7 +11,7 @@ static const char *const TAG = "entity_base";
|
||||
// Entity Name
|
||||
const StringRef &EntityBase::get_name() const { return this->name_; }
|
||||
|
||||
void EntityBase::configure_entity(const char *name, uint32_t object_id_hash, uint32_t entity_strings_packed) {
|
||||
void EntityBase::configure_entity_(const char *name, uint32_t object_id_hash, uint32_t entity_strings_packed) {
|
||||
this->name_ = StringRef(name);
|
||||
if (this->name_.empty()) {
|
||||
#ifdef USE_DEVICES
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
#include "device.h"
|
||||
#endif
|
||||
|
||||
// Forward declarations for friend access from codegen-generated setup()
|
||||
void setup(); // NOLINT(readability-redundant-declaration) - may be declared in Arduino.h
|
||||
void original_setup(); // NOLINT(readability-redundant-declaration) - used by cpp unit tests
|
||||
|
||||
namespace esphome {
|
||||
|
||||
// Extern lookup functions for entity string tables.
|
||||
@@ -57,9 +61,6 @@ class EntityBase {
|
||||
// Get the name of this Entity
|
||||
const StringRef &get_name() const;
|
||||
|
||||
/// Combined entity setup from codegen: set name, object_id hash, and entity string indices.
|
||||
void configure_entity(const char *name, uint32_t object_id_hash, uint32_t entity_strings_packed);
|
||||
|
||||
// Get whether this Entity has its own name or it should use the device friendly_name.
|
||||
bool has_own_name() const { return this->flags_.has_own_name; }
|
||||
|
||||
@@ -104,7 +105,7 @@ class EntityBase {
|
||||
}
|
||||
|
||||
// Get this entity's device class into a stack buffer.
|
||||
// On ESP32: returns pointer to PROGMEM string directly (buffer unused).
|
||||
// On non-ESP8266: returns pointer to PROGMEM string directly (buffer unused).
|
||||
// On ESP8266: copies from PROGMEM to buffer, returns buffer pointer.
|
||||
const char *get_device_class_to(std::span<char, MAX_DEVICE_CLASS_LENGTH> buffer) const;
|
||||
|
||||
@@ -224,6 +225,12 @@ class EntityBase {
|
||||
}
|
||||
|
||||
protected:
|
||||
friend void ::setup();
|
||||
friend void ::original_setup();
|
||||
|
||||
/// Combined entity setup from codegen: set name, object_id hash, and entity string indices.
|
||||
void configure_entity_(const char *name, uint32_t object_id_hash, uint32_t entity_strings_packed);
|
||||
|
||||
/// Non-template helper for make_entity_preference() to avoid code bloat.
|
||||
/// When preference hash algorithm changes, migration logic goes here.
|
||||
ESPPreferenceObject make_entity_preference_(size_t size, uint32_t version);
|
||||
|
||||
@@ -34,7 +34,7 @@ _KEY_ICON_IDX = "_entity_icon_idx"
|
||||
_KEY_ENTITY_NAME = "_entity_name"
|
||||
_KEY_OBJECT_ID_HASH = "_entity_object_id_hash"
|
||||
|
||||
# Bit layout for entity_strings_packed in configure_entity() — must match C++ in entity_base.h:
|
||||
# Bit layout for entity_strings_packed in configure_entity_() — must match C++ in entity_base.h:
|
||||
# [23..16] icon (8 bits) | [15..8] UoM (8 bits) | [7..0] device_class (8 bits)
|
||||
_DC_SHIFT = 0
|
||||
_UOM_SHIFT = 8
|
||||
@@ -221,7 +221,7 @@ def setup_unit_of_measurement(config: ConfigType) -> None:
|
||||
|
||||
|
||||
def finalize_entity_strings(var: MockObj, config: ConfigType) -> None:
|
||||
"""Emit a single configure_entity() call with name, hash, and packed string indices.
|
||||
"""Emit a single configure_entity_() call with name, hash, and packed string indices.
|
||||
|
||||
Call this at the end of each component's setup function, after
|
||||
setup_entity() and any register_device_class/register_unit_of_measurement calls.
|
||||
@@ -232,7 +232,7 @@ def finalize_entity_strings(var: MockObj, config: ConfigType) -> None:
|
||||
uom_idx = config.get(_KEY_UOM_IDX, 0)
|
||||
icon_idx = config.get(_KEY_ICON_IDX, 0)
|
||||
packed = (dc_idx << _DC_SHIFT) | (uom_idx << _UOM_SHIFT) | (icon_idx << _ICON_SHIFT)
|
||||
add(var.configure_entity(entity_name, object_id_hash, packed))
|
||||
add(var.configure_entity_(entity_name, object_id_hash, packed))
|
||||
|
||||
|
||||
def get_base_entity_object_id(
|
||||
@@ -334,7 +334,7 @@ async def _setup_entity_impl(var: MockObj, config: ConfigType, platform: str) ->
|
||||
device: MockObj = await get_variable(device_id_obj)
|
||||
add(var.set_device(device))
|
||||
|
||||
# Pre-compute entity name and object_id hash for configure_entity()
|
||||
# Pre-compute entity name and object_id hash for configure_entity_()
|
||||
# which is emitted later by finalize_entity_strings().
|
||||
# For named entities: pre-compute hash from entity name
|
||||
# For empty-name entities: pass 0, C++ calculates hash at runtime from
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(USE_ESP32)
|
||||
#if defined(USE_ESP32) || defined(USE_ZEPHYR)
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
|
||||
@@ -29,7 +29,7 @@ def test_binary_sensor_sets_mandatory_fields(generate_main):
|
||||
)
|
||||
|
||||
# Then
|
||||
assert 'bs_1->configure_entity("test bs1",' in main_cpp
|
||||
assert 'bs_1->configure_entity_("test bs1",' in main_cpp
|
||||
assert "bs_1->set_pin(" in main_cpp
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ def test_button_sets_mandatory_fields(generate_main):
|
||||
main_cpp = generate_main("tests/component_tests/button/test_button.yaml")
|
||||
|
||||
# Then
|
||||
assert 'wol_1->configure_entity("wol_test_1",' in main_cpp
|
||||
assert 'wol_1->configure_entity_("wol_test_1",' in main_cpp
|
||||
assert "wol_2->set_macaddr(18, 52, 86, 120, 144, 171);" in main_cpp
|
||||
|
||||
|
||||
|
||||
@@ -11,4 +11,4 @@ def test_sensor_device_class_set(generate_main):
|
||||
main_cpp = generate_main("tests/component_tests/sensor/test_sensor.yaml")
|
||||
|
||||
# Then
|
||||
assert "s_1->configure_entity(" in main_cpp
|
||||
assert "s_1->configure_entity_(" in main_cpp
|
||||
|
||||
@@ -25,7 +25,7 @@ def test_text_sets_mandatory_fields(generate_main):
|
||||
main_cpp = generate_main("tests/component_tests/text/test_text.yaml")
|
||||
|
||||
# Then
|
||||
assert 'it_1->configure_entity("test 1 text",' in main_cpp
|
||||
assert 'it_1->configure_entity_("test 1 text",' in main_cpp
|
||||
|
||||
|
||||
def test_text_config_value_internal_set(generate_main):
|
||||
|
||||
@@ -25,9 +25,9 @@ def test_text_sensor_sets_mandatory_fields(generate_main):
|
||||
main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml")
|
||||
|
||||
# Then
|
||||
assert 'ts_1->configure_entity("Template Text Sensor 1",' in main_cpp
|
||||
assert 'ts_2->configure_entity("Template Text Sensor 2",' in main_cpp
|
||||
assert 'ts_3->configure_entity("Template Text Sensor 3",' in main_cpp
|
||||
assert 'ts_1->configure_entity_("Template Text Sensor 1",' in main_cpp
|
||||
assert 'ts_2->configure_entity_("Template Text Sensor 2",' in main_cpp
|
||||
assert 'ts_3->configure_entity_("Template Text Sensor 3",' in main_cpp
|
||||
|
||||
|
||||
def test_text_sensor_config_value_internal_set(generate_main):
|
||||
@@ -54,5 +54,5 @@ def test_text_sensor_device_class_set(generate_main):
|
||||
main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml")
|
||||
|
||||
# Then
|
||||
assert "ts_2->configure_entity(" in main_cpp
|
||||
assert "ts_3->configure_entity(" in main_cpp
|
||||
assert "ts_2->configure_entity_(" in main_cpp
|
||||
assert "ts_3->configure_entity_(" in main_cpp
|
||||
|
||||
@@ -32,10 +32,10 @@ from esphome.helpers import sanitize, snake_case
|
||||
|
||||
from .common import load_config_from_fixture
|
||||
|
||||
# Pre-compiled regex pattern for extracting names from configure_entity/set_name calls
|
||||
# Matches: .configure_entity("name", ...) or .set_name("name", ...)
|
||||
# Pre-compiled regex pattern for extracting names from configure_entity_/set_name calls
|
||||
# Matches: .configure_entity_("name", ...) or .set_name("name", ...)
|
||||
ENTITY_NAME_PATTERN = re.compile(
|
||||
r'\.(?:configure_entity|set_name)\(["\']([^"\']*)["\']'
|
||||
r'\.(?:configure_entity_|set_name)\(["\']([^"\']*)["\']'
|
||||
)
|
||||
|
||||
FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "core" / "entity_helpers"
|
||||
@@ -292,7 +292,7 @@ def extract_object_id_from_config(config: dict[str, Any]) -> str | None:
|
||||
|
||||
|
||||
def extract_object_id_from_expressions(expressions: list[str]) -> str | None:
|
||||
"""Extract the object ID from configure_entity() calls in generated expressions."""
|
||||
"""Extract the object ID from configure_entity_() calls in generated expressions."""
|
||||
for expr in expressions:
|
||||
if match := ENTITY_NAME_PATTERN.search(expr):
|
||||
name = match.group(1)
|
||||
@@ -971,7 +971,7 @@ async def test_setup_entity_direct_call(setup_test_environment: list[str]) -> No
|
||||
# Direct call mode: await setup_entity(var, config, "camera")
|
||||
await setup_entity(var, config, "camera")
|
||||
|
||||
# Should have emitted configure_entity
|
||||
# Should have emitted configure_entity_
|
||||
object_id = extract_object_id_from_expressions(added_expressions)
|
||||
assert object_id == "my_camera"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user