Merge branch 'dev' into lint-no-powf-core

This commit is contained in:
J. Nick Koston
2026-03-02 15:56:45 -10:00
committed by GitHub
6 changed files with 221 additions and 12 deletions
+1
View File
@@ -316,6 +316,7 @@ esphome/components/mcp9808/* @k7hpn
esphome/components/md5/* @esphome/core
esphome/components/mdns/* @esphome/core
esphome/components/media_player/* @jesserockz
esphome/components/media_source/* @kahrendt
esphome/components/micro_wake_word/* @jesserockz @kahrendt
esphome/components/micronova/* @edenhaus @jorre05
esphome/components/microphone/* @jesserockz @kahrendt
+17 -10
View File
@@ -474,15 +474,12 @@ void LD2450Component::handle_periodic_data_() {
is_moving = false;
// tx is used for further calculations, so always needs to be populated
tx = ld2450::decode_coordinate(this->buffer_data_[start], this->buffer_data_[start + 1]);
SAFE_PUBLISH_SENSOR(this->move_x_sensors_[index], tx);
// Y
start = TARGET_Y + index * 8;
ty = ld2450::decode_coordinate(this->buffer_data_[start], this->buffer_data_[start + 1]);
SAFE_PUBLISH_SENSOR(this->move_y_sensors_[index], ty);
// RESOLUTION
start = TARGET_RESOLUTION + index * 8;
res = (this->buffer_data_[start + 1] << 8) | this->buffer_data_[start];
SAFE_PUBLISH_SENSOR(this->move_resolution_sensors_[index], res);
#endif
// SPEED
start = TARGET_SPEED + index * 8;
@@ -491,9 +488,6 @@ void LD2450Component::handle_periodic_data_() {
is_moving = true;
moving_target_count++;
}
#ifdef USE_SENSOR
SAFE_PUBLISH_SENSOR(this->move_speed_sensors_[index], ts);
#endif
// DISTANCE
// Optimized: use already decoded tx and ty values, replace pow() with multiplication
int32_t x_squared = (int32_t) tx * tx;
@@ -503,10 +497,23 @@ void LD2450Component::handle_periodic_data_() {
target_count++;
}
#ifdef USE_SENSOR
SAFE_PUBLISH_SENSOR(this->move_distance_sensors_[index], td);
// ANGLE - atan2f computes angle from Y axis directly, no sqrt/division needed
angle = atan2f(static_cast<float>(-tx), static_cast<float>(ty)) * (180.0f / std::numbers::pi_v<float>);
SAFE_PUBLISH_SENSOR(this->move_angle_sensors_[index], angle);
if (td == 0) {
SAFE_PUBLISH_SENSOR_UNKNOWN(this->move_x_sensors_[index]);
SAFE_PUBLISH_SENSOR_UNKNOWN(this->move_y_sensors_[index]);
SAFE_PUBLISH_SENSOR_UNKNOWN(this->move_resolution_sensors_[index]);
SAFE_PUBLISH_SENSOR_UNKNOWN(this->move_speed_sensors_[index]);
SAFE_PUBLISH_SENSOR_UNKNOWN(this->move_distance_sensors_[index]);
SAFE_PUBLISH_SENSOR_UNKNOWN(this->move_angle_sensors_[index]);
} else {
SAFE_PUBLISH_SENSOR(this->move_x_sensors_[index], tx);
SAFE_PUBLISH_SENSOR(this->move_y_sensors_[index], ty);
SAFE_PUBLISH_SENSOR(this->move_resolution_sensors_[index], res);
SAFE_PUBLISH_SENSOR(this->move_speed_sensors_[index], ts);
SAFE_PUBLISH_SENSOR(this->move_distance_sensors_[index], td);
// ANGLE - atan2f computes angle from Y axis directly, no sqrt/division needed
angle = atan2f(static_cast<float>(-tx), static_cast<float>(ty)) * (180.0f / std::numbers::pi_v<float>);
SAFE_PUBLISH_SENSOR(this->move_angle_sensors_[index], angle);
}
#endif
#ifdef USE_TEXT_SENSOR
// DIRECTION
+3 -2
View File
@@ -8,6 +8,7 @@ static constexpr const char *const TAG = "lps22";
static constexpr uint8_t WHO_AM_I = 0x0F;
static constexpr uint8_t LPS22HB_ID = 0xB1;
static constexpr uint8_t LPS22HH_ID = 0xB3;
static constexpr uint8_t LPS22DF_ID = 0xB4;
static constexpr uint8_t CTRL_REG2 = 0x11;
static constexpr uint8_t CTRL_REG2_ONE_SHOT_MASK = 0b1;
static constexpr uint8_t STATUS = 0x27;
@@ -24,8 +25,8 @@ static constexpr float TEMPERATURE_SCALE = 0.01f;
void LPS22Component::setup() {
uint8_t value = 0x00;
this->read_register(WHO_AM_I, &value, 1);
if (value != LPS22HB_ID && value != LPS22HH_ID) {
ESP_LOGW(TAG, "device IDs as %02x, which isn't a known LPS22HB or LPS22HH ID", value);
if (value != LPS22HB_ID && value != LPS22HH_ID && value != LPS22DF_ID) {
ESP_LOGW(TAG, "device IDs as %02x, which isn't a known LPS22HB/HH/DF ID", value);
this->mark_failed();
}
}
@@ -0,0 +1,40 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.core import CORE
from esphome.coroutine import CoroPriority, coroutine_with_priority
from esphome.cpp_generator import MockObjClass
CODEOWNERS = ["@kahrendt"]
AUTO_LOAD = ["audio"]
IS_PLATFORM_COMPONENT = True
media_source_ns = cg.esphome_ns.namespace("media_source")
MediaSource = media_source_ns.class_("MediaSource")
async def register_media_source(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
CORE.register_platform_component("media_source", var)
return var
_MEDIA_SOURCE_SCHEMA = cv.Schema({})
def media_source_schema(
class_: MockObjClass,
) -> cv.Schema:
schema = {cv.GenerateID(CONF_ID): cv.declare_id(class_)}
return _MEDIA_SOURCE_SCHEMA.extend(schema)
@coroutine_with_priority(CoroPriority.CORE)
async def to_code(config):
cg.add_global(media_source_ns.using)
cg.add_define("USE_MEDIA_SOURCE")
@@ -0,0 +1,159 @@
#pragma once
#include "esphome/components/audio/audio.h"
#include "esphome/core/helpers.h"
#include <cstdint>
#include <string>
namespace esphome::media_source {
enum class MediaSourceState : uint8_t {
IDLE, // Not playing, ready to accept play_uri
PLAYING, // Currently playing media
PAUSED, // Playback paused, can be resumed
ERROR, // Error occurred during playback; sources are responsible for logging their own error details
};
/// @brief Commands that are sent from the orchestrator to a media source
enum class MediaSourceCommand : uint8_t {
// All sources should support these basic commands.
PLAY,
PAUSE,
STOP,
// Only sources with internal playlists will handle these; simple sources should ignore them.
NEXT,
PREVIOUS,
CLEAR_PLAYLIST,
REPEAT_ALL,
REPEAT_ONE,
REPEAT_OFF,
SHUFFLE,
UNSHUFFLE,
};
/// @brief Callbacks from a MediaSource to its orchestrator
class MediaSourceListener {
public:
virtual ~MediaSourceListener() = default;
// Callbacks that all sources use to send data and state changes to the orchestrator.
/// @brief Send audio data to the listener
virtual size_t write_audio(const uint8_t *data, size_t length, uint32_t timeout_ms,
const audio::AudioStreamInfo &stream_info) = 0;
/// @brief Notify listener of state changes
virtual void report_state(MediaSourceState state) = 0;
// Callbacks from smart sources requesting the orchestrator to change volume, mute, or start a new URI.
// Simple sources never invoke these.
/// @brief Request the orchestrator to change volume
virtual void request_volume(float volume) {}
/// @brief Request the orchestrator to change mute state
virtual void request_mute(bool is_muted) {}
/// @brief Request the orchestrator to play a new URI
virtual void request_play_uri(const std::string &uri) {}
};
/// @brief Abstract base class for media sources
/// MediaSource provides audio data to an orchestrator via the MediaSourceListener interface. It also receives commands
/// from the orchestrator to control playback.
class MediaSource {
public:
virtual ~MediaSource() = default;
// === Playback Control ===
/// @brief Start playing the given URI
/// Sources should validate the URI and state, returning false if the source is busy.
/// The orchestrator is responsible for stopping active sources before starting a new one.
/// @param uri URI to play; e.g., "http://stream_url"
/// @return true if playback started successfully, false otherwise
virtual bool play_uri(const std::string &uri) = 0;
/// @brief Handle playback commands (pause, stop, next, etc.)
/// @param command Command to execute
virtual void handle_command(MediaSourceCommand command) = 0;
/// @brief Whether this source manages its own playlist internally
/// Smart sources that handle next/previous/repeat/shuffle should override this to return true.
virtual bool has_internal_playlist() const { return false; }
// === State Access ===
/// @brief Get current playback state (must only be called from the main loop)
/// @return Current state of this source
MediaSourceState get_state() const { return this->state_; }
// === URI Matching ===
/// @brief Check if this source can handle the given URI
/// Each source must override this to match its supported URI scheme(s).
/// @param uri URI to check
/// @return true if this source can handle the URI
virtual bool can_handle(const std::string &uri) const = 0;
// === Listener: Source -> Orchestrator ===
/// @brief Set the listener that receives callbacks from this source
/// @param listener Pointer to the MediaSourceListener implementation
void set_listener(MediaSourceListener *listener) { this->listener_ = listener; }
/// @brief Check if a listener has been registered
bool has_listener() const { return this->listener_ != nullptr; }
/// @brief Write audio data to the listener
/// @param data Pointer to audio data buffer (not modified by this method)
/// @param length Number of bytes to write
/// @param timeout_ms Milliseconds to wait if the listener can't accept data immediately
/// @param stream_info Audio stream format information
/// @return Number of bytes written, or 0 if no listener is set
size_t write_output(const uint8_t *data, size_t length, uint32_t timeout_ms,
const audio::AudioStreamInfo &stream_info) {
if (this->listener_ != nullptr) {
return this->listener_->write_audio(data, length, timeout_ms, stream_info);
}
return 0;
}
// === Callbacks: Orchestrator -> Source ===
/// @brief Notify the source that volume changed
/// Simple sources ignore this. Override for smart sources that track volume state.
/// @param volume New volume level (0.0 to 1.0)
virtual void notify_volume_changed(float volume) {}
/// @brief Notify the source that mute state changed
/// Simple sources ignore this. Override for smart sources that track mute state.
/// @param is_muted New mute state
virtual void notify_mute_changed(bool is_muted) {}
/// @brief Notify the source about audio that has been played
/// Called when the speaker reports that audio frames have been written to the DAC.
/// Sources can override this to track playback progress for synchronization.
/// @param frames Number of audio frames that were played
/// @param timestamp System time in microseconds when the frames finished writing to the DAC
virtual void notify_audio_played(uint32_t frames, int64_t timestamp) {}
protected:
/// @brief Update state and notify listener (must only be called from the main loop)
/// This is the only way to change state_, ensuring listener notifications always fire.
/// Sources running FreeRTOS tasks should signal via event groups and call this from loop().
/// @param state New state to set
void set_state_(MediaSourceState state) {
if (this->state_ != state) {
this->state_ = state;
if (this->listener_ != nullptr) {
this->listener_->report_state(state);
}
}
}
private:
// Private to enforce the invariant that listener notifications always fire on state changes.
// All state transitions must go through set_state_() which couples the update with notification.
MediaSourceState state_{MediaSourceState::IDLE};
MediaSourceListener *listener_{nullptr};
};
} // namespace esphome::media_source
+1
View File
@@ -106,6 +106,7 @@
#define MDNS_DYNAMIC_TXT_COUNT 2
#define SNTP_SERVER_COUNT 3
#define USE_MEDIA_PLAYER
#define USE_MEDIA_SOURCE
#define USE_NEXTION_TFT_UPLOAD
#define USE_NUMBER
#define USE_OUTPUT