mirror of
https://github.com/esphome/esphome.git
synced 2026-07-10 17:05:36 +00:00
Merge branch 'dev' into lint-no-powf-core
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user