mirror of
https://github.com/esphome/esphome.git
synced 2024-12-01 03:04:12 +01:00
Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
commit
e85194e0b6
20 changed files with 182 additions and 27 deletions
|
@ -65,7 +65,7 @@ def choose_prompt(options, purpose: str = None):
|
||||||
f'Found multiple options{f" for {purpose}" if purpose else ""}, please choose one:'
|
f'Found multiple options{f" for {purpose}" if purpose else ""}, please choose one:'
|
||||||
)
|
)
|
||||||
for i, (desc, _) in enumerate(options):
|
for i, (desc, _) in enumerate(options):
|
||||||
safe_print(f" [{i+1}] {desc}")
|
safe_print(f" [{i + 1}] {desc}")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
opt = input("(number): ")
|
opt = input("(number): ")
|
||||||
|
|
|
@ -157,7 +157,7 @@ async def to_code(config):
|
||||||
pixels = list(frame.getdata())
|
pixels = list(frame.getdata())
|
||||||
if len(pixels) != height * width:
|
if len(pixels) != height * width:
|
||||||
raise core.EsphomeError(
|
raise core.EsphomeError(
|
||||||
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
|
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height * width})"
|
||||||
)
|
)
|
||||||
for pix, a in pixels:
|
for pix, a in pixels:
|
||||||
if transparent:
|
if transparent:
|
||||||
|
@ -180,7 +180,7 @@ async def to_code(config):
|
||||||
pixels = list(frame.getdata())
|
pixels = list(frame.getdata())
|
||||||
if len(pixels) != height * width:
|
if len(pixels) != height * width:
|
||||||
raise core.EsphomeError(
|
raise core.EsphomeError(
|
||||||
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
|
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height * width})"
|
||||||
)
|
)
|
||||||
for pix in pixels:
|
for pix in pixels:
|
||||||
data[pos] = pix[0]
|
data[pos] = pix[0]
|
||||||
|
@ -203,7 +203,7 @@ async def to_code(config):
|
||||||
pixels = list(frame.getdata())
|
pixels = list(frame.getdata())
|
||||||
if len(pixels) != height * width:
|
if len(pixels) != height * width:
|
||||||
raise core.EsphomeError(
|
raise core.EsphomeError(
|
||||||
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
|
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height * width})"
|
||||||
)
|
)
|
||||||
for r, g, b, a in pixels:
|
for r, g, b, a in pixels:
|
||||||
if transparent:
|
if transparent:
|
||||||
|
@ -232,7 +232,7 @@ async def to_code(config):
|
||||||
pixels = list(frame.getdata())
|
pixels = list(frame.getdata())
|
||||||
if len(pixels) != height * width:
|
if len(pixels) != height * width:
|
||||||
raise core.EsphomeError(
|
raise core.EsphomeError(
|
||||||
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
|
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height * width})"
|
||||||
)
|
)
|
||||||
for r, g, b, a in pixels:
|
for r, g, b, a in pixels:
|
||||||
R = r >> 3
|
R = r >> 3
|
||||||
|
|
|
@ -1147,6 +1147,9 @@ message MediaPlayerCommandRequest {
|
||||||
|
|
||||||
bool has_media_url = 6;
|
bool has_media_url = 6;
|
||||||
string media_url = 7;
|
string media_url = 7;
|
||||||
|
|
||||||
|
bool has_announcement = 8;
|
||||||
|
bool announcement = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== BLUETOOTH ====================
|
// ==================== BLUETOOTH ====================
|
||||||
|
|
|
@ -1002,7 +1002,11 @@ bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_pla
|
||||||
|
|
||||||
MediaPlayerStateResponse resp{};
|
MediaPlayerStateResponse resp{};
|
||||||
resp.key = media_player->get_object_id_hash();
|
resp.key = media_player->get_object_id_hash();
|
||||||
resp.state = static_cast<enums::MediaPlayerState>(media_player->state);
|
|
||||||
|
media_player::MediaPlayerState report_state = media_player->state == media_player::MEDIA_PLAYER_STATE_ANNOUNCING
|
||||||
|
? media_player::MEDIA_PLAYER_STATE_PLAYING
|
||||||
|
: media_player->state;
|
||||||
|
resp.state = static_cast<enums::MediaPlayerState>(report_state);
|
||||||
resp.volume = media_player->volume;
|
resp.volume = media_player->volume;
|
||||||
resp.muted = media_player->is_muted();
|
resp.muted = media_player->is_muted();
|
||||||
return this->send_media_player_state_response(resp);
|
return this->send_media_player_state_response(resp);
|
||||||
|
@ -1038,6 +1042,9 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
|
||||||
if (msg.has_media_url) {
|
if (msg.has_media_url) {
|
||||||
call.set_media_url(msg.media_url);
|
call.set_media_url(msg.media_url);
|
||||||
}
|
}
|
||||||
|
if (msg.has_announcement) {
|
||||||
|
call.set_announcement(msg.announcement);
|
||||||
|
}
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -5253,6 +5253,14 @@ bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||||
this->has_media_url = value.as_bool();
|
this->has_media_url = value.as_bool();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 8: {
|
||||||
|
this->has_announcement = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 9: {
|
||||||
|
this->announcement = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -5289,6 +5297,8 @@ void MediaPlayerCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_float(5, this->volume);
|
buffer.encode_float(5, this->volume);
|
||||||
buffer.encode_bool(6, this->has_media_url);
|
buffer.encode_bool(6, this->has_media_url);
|
||||||
buffer.encode_string(7, this->media_url);
|
buffer.encode_string(7, this->media_url);
|
||||||
|
buffer.encode_bool(8, this->has_announcement);
|
||||||
|
buffer.encode_bool(9, this->announcement);
|
||||||
}
|
}
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
void MediaPlayerCommandRequest::dump_to(std::string &out) const {
|
void MediaPlayerCommandRequest::dump_to(std::string &out) const {
|
||||||
|
@ -5323,6 +5333,14 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const {
|
||||||
out.append(" media_url: ");
|
out.append(" media_url: ");
|
||||||
out.append("'").append(this->media_url).append("'");
|
out.append("'").append(this->media_url).append("'");
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" has_announcement: ");
|
||||||
|
out.append(YESNO(this->has_announcement));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" announcement: ");
|
||||||
|
out.append(YESNO(this->announcement));
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1298,6 +1298,8 @@ class MediaPlayerCommandRequest : public ProtoMessage {
|
||||||
float volume{0.0f};
|
float volume{0.0f};
|
||||||
bool has_media_url{false};
|
bool has_media_url{false};
|
||||||
std::string media_url{};
|
std::string media_url{};
|
||||||
|
bool has_announcement{false};
|
||||||
|
bool announcement{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
#include "ble.h"
|
#include "ble.h"
|
||||||
|
|
||||||
|
#ifdef USE_ESP32_VARIANT_ESP32C6
|
||||||
|
#include "const_esp32c6.h"
|
||||||
|
#endif // USE_ESP32_VARIANT_ESP32C6
|
||||||
|
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
@ -114,7 +119,11 @@ bool ESP32BLE::ble_setup_() {
|
||||||
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
||||||
// start bt controller
|
// start bt controller
|
||||||
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
|
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
|
||||||
|
#ifdef USE_ESP32_VARIANT_ESP32C6
|
||||||
|
esp_bt_controller_config_t cfg = BT_CONTROLLER_CONFIG;
|
||||||
|
#else
|
||||||
esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||||
|
#endif
|
||||||
err = esp_bt_controller_init(&cfg);
|
err = esp_bt_controller_init(&cfg);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err));
|
ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err));
|
||||||
|
|
67
esphome/components/esp32_ble/const_esp32c6.h
Normal file
67
esphome/components/esp32_ble/const_esp32c6.h
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_ESP32_VARIANT_ESP32C6
|
||||||
|
|
||||||
|
#include <esp_bt.h>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace esp32_ble {
|
||||||
|
|
||||||
|
static const esp_bt_controller_config_t BT_CONTROLLER_CONFIG = {
|
||||||
|
.config_version = CONFIG_VERSION,
|
||||||
|
.ble_ll_resolv_list_size = CONFIG_BT_LE_LL_RESOLV_LIST_SIZE,
|
||||||
|
.ble_hci_evt_hi_buf_count = DEFAULT_BT_LE_HCI_EVT_HI_BUF_COUNT,
|
||||||
|
.ble_hci_evt_lo_buf_count = DEFAULT_BT_LE_HCI_EVT_LO_BUF_COUNT,
|
||||||
|
.ble_ll_sync_list_cnt = DEFAULT_BT_LE_MAX_PERIODIC_ADVERTISER_LIST,
|
||||||
|
.ble_ll_sync_cnt = DEFAULT_BT_LE_MAX_PERIODIC_SYNCS,
|
||||||
|
.ble_ll_rsp_dup_list_count = CONFIG_BT_LE_LL_DUP_SCAN_LIST_COUNT,
|
||||||
|
.ble_ll_adv_dup_list_count = CONFIG_BT_LE_LL_DUP_SCAN_LIST_COUNT,
|
||||||
|
.ble_ll_tx_pwr_dbm = BLE_LL_TX_PWR_DBM_N,
|
||||||
|
.rtc_freq = RTC_FREQ_N,
|
||||||
|
.ble_ll_sca = CONFIG_BT_LE_LL_SCA,
|
||||||
|
.ble_ll_scan_phy_number = BLE_LL_SCAN_PHY_NUMBER_N,
|
||||||
|
.ble_ll_conn_def_auth_pyld_tmo = BLE_LL_CONN_DEF_AUTH_PYLD_TMO_N,
|
||||||
|
.ble_ll_jitter_usecs = BLE_LL_JITTER_USECS_N,
|
||||||
|
.ble_ll_sched_max_adv_pdu_usecs = BLE_LL_SCHED_MAX_ADV_PDU_USECS_N,
|
||||||
|
.ble_ll_sched_direct_adv_max_usecs = BLE_LL_SCHED_DIRECT_ADV_MAX_USECS_N,
|
||||||
|
.ble_ll_sched_adv_max_usecs = BLE_LL_SCHED_ADV_MAX_USECS_N,
|
||||||
|
.ble_scan_rsp_data_max_len = DEFAULT_BT_LE_SCAN_RSP_DATA_MAX_LEN_N,
|
||||||
|
.ble_ll_cfg_num_hci_cmd_pkts = BLE_LL_CFG_NUM_HCI_CMD_PKTS_N,
|
||||||
|
.ble_ll_ctrl_proc_timeout_ms = BLE_LL_CTRL_PROC_TIMEOUT_MS_N,
|
||||||
|
.nimble_max_connections = DEFAULT_BT_LE_MAX_CONNECTIONS,
|
||||||
|
.ble_whitelist_size = DEFAULT_BT_NIMBLE_WHITELIST_SIZE, // NOLINT
|
||||||
|
.ble_acl_buf_size = DEFAULT_BT_LE_ACL_BUF_SIZE,
|
||||||
|
.ble_acl_buf_count = DEFAULT_BT_LE_ACL_BUF_COUNT,
|
||||||
|
.ble_hci_evt_buf_size = DEFAULT_BT_LE_HCI_EVT_BUF_SIZE,
|
||||||
|
.ble_multi_adv_instances = DEFAULT_BT_LE_MAX_EXT_ADV_INSTANCES,
|
||||||
|
.ble_ext_adv_max_size = DEFAULT_BT_LE_EXT_ADV_MAX_SIZE,
|
||||||
|
.controller_task_stack_size = NIMBLE_LL_STACK_SIZE,
|
||||||
|
.controller_task_prio = ESP_TASK_BT_CONTROLLER_PRIO,
|
||||||
|
.controller_run_cpu = 0,
|
||||||
|
.enable_qa_test = RUN_QA_TEST,
|
||||||
|
.enable_bqb_test = RUN_BQB_TEST,
|
||||||
|
.enable_uart_hci = HCI_UART_EN,
|
||||||
|
.ble_hci_uart_port = DEFAULT_BT_LE_HCI_UART_PORT,
|
||||||
|
.ble_hci_uart_baud = DEFAULT_BT_LE_HCI_UART_BAUD,
|
||||||
|
.ble_hci_uart_data_bits = DEFAULT_BT_LE_HCI_UART_DATA_BITS,
|
||||||
|
.ble_hci_uart_stop_bits = DEFAULT_BT_LE_HCI_UART_STOP_BITS,
|
||||||
|
.ble_hci_uart_flow_ctrl = DEFAULT_BT_LE_HCI_UART_FLOW_CTRL,
|
||||||
|
.ble_hci_uart_uart_parity = DEFAULT_BT_LE_HCI_UART_PARITY,
|
||||||
|
.enable_tx_cca = DEFAULT_BT_LE_TX_CCA_ENABLED,
|
||||||
|
.cca_rssi_thresh = 256 - DEFAULT_BT_LE_CCA_RSSI_THRESH,
|
||||||
|
.sleep_en = NIMBLE_SLEEP_ENABLE,
|
||||||
|
.coex_phy_coded_tx_rx_time_limit = DEFAULT_BT_LE_COEX_PHY_CODED_TX_RX_TLIM_EFF,
|
||||||
|
.dis_scan_backoff = NIMBLE_DISABLE_SCAN_BACKOFF,
|
||||||
|
.ble_scan_classify_filter_enable = 1,
|
||||||
|
.main_xtal_freq = CONFIG_XTAL_FREQ,
|
||||||
|
.version_num = (uint8_t) efuse_hal_chip_revision(),
|
||||||
|
.cpu_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ,
|
||||||
|
.ignore_wl_for_direct_adv = 0,
|
||||||
|
.enable_pcl = DEFAULT_BT_LE_POWER_CONTROL_ENABLED,
|
||||||
|
.config_magic = CONFIG_MAGIC,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esp32_ble
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_ESP32_VARIANT_ESP32C6
|
|
@ -10,6 +10,11 @@ namespace i2s_audio {
|
||||||
static const char *const TAG = "audio";
|
static const char *const TAG = "audio";
|
||||||
|
|
||||||
void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
|
void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
|
||||||
|
media_player::MediaPlayerState play_state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||||
|
if (call.get_announcement().has_value()) {
|
||||||
|
play_state = call.get_announcement().value() ? media_player::MEDIA_PLAYER_STATE_ANNOUNCING
|
||||||
|
: media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||||
|
}
|
||||||
if (call.get_media_url().has_value()) {
|
if (call.get_media_url().has_value()) {
|
||||||
this->current_url_ = call.get_media_url();
|
this->current_url_ = call.get_media_url();
|
||||||
if (this->i2s_state_ != I2S_STATE_STOPPED && this->audio_ != nullptr) {
|
if (this->i2s_state_ != I2S_STATE_STOPPED && this->audio_ != nullptr) {
|
||||||
|
@ -17,7 +22,7 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
|
||||||
this->audio_->stopSong();
|
this->audio_->stopSong();
|
||||||
}
|
}
|
||||||
this->audio_->connecttohost(this->current_url_.value().c_str());
|
this->audio_->connecttohost(this->current_url_.value().c_str());
|
||||||
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
this->state = play_state;
|
||||||
} else {
|
} else {
|
||||||
this->start();
|
this->start();
|
||||||
}
|
}
|
||||||
|
@ -35,7 +40,7 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
|
||||||
case media_player::MEDIA_PLAYER_COMMAND_PLAY:
|
case media_player::MEDIA_PLAYER_COMMAND_PLAY:
|
||||||
if (!this->audio_->isRunning())
|
if (!this->audio_->isRunning())
|
||||||
this->audio_->pauseResume();
|
this->audio_->pauseResume();
|
||||||
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
this->state = play_state;
|
||||||
break;
|
break;
|
||||||
case media_player::MEDIA_PLAYER_COMMAND_PAUSE:
|
case media_player::MEDIA_PLAYER_COMMAND_PAUSE:
|
||||||
if (this->audio_->isRunning())
|
if (this->audio_->isRunning())
|
||||||
|
@ -126,7 +131,9 @@ void I2SAudioMediaPlayer::loop() {
|
||||||
|
|
||||||
void I2SAudioMediaPlayer::play_() {
|
void I2SAudioMediaPlayer::play_() {
|
||||||
this->audio_->loop();
|
this->audio_->loop();
|
||||||
if (this->state == media_player::MEDIA_PLAYER_STATE_PLAYING && !this->audio_->isRunning()) {
|
if ((this->state == media_player::MEDIA_PLAYER_STATE_PLAYING ||
|
||||||
|
this->state == media_player::MEDIA_PLAYER_STATE_ANNOUNCING) &&
|
||||||
|
!this->audio_->isRunning()) {
|
||||||
this->stop();
|
this->stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,6 +171,10 @@ void I2SAudioMediaPlayer::start_() {
|
||||||
if (this->current_url_.has_value()) {
|
if (this->current_url_.has_value()) {
|
||||||
this->audio_->connecttohost(this->current_url_.value().c_str());
|
this->audio_->connecttohost(this->current_url_.value().c_str());
|
||||||
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||||
|
if (this->is_announcement_.has_value()) {
|
||||||
|
this->state = this->is_announcement_.value() ? media_player::MEDIA_PLAYER_STATE_ANNOUNCING
|
||||||
|
: media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||||
|
}
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,7 @@ class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer,
|
||||||
HighFrequencyLoopRequester high_freq_;
|
HighFrequencyLoopRequester high_freq_;
|
||||||
|
|
||||||
optional<std::string> current_url_{};
|
optional<std::string> current_url_{};
|
||||||
|
optional<bool> is_announcement_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace i2s_audio
|
} // namespace i2s_audio
|
||||||
|
|
|
@ -51,12 +51,16 @@ VolumeSetAction = media_player_ns.class_(
|
||||||
|
|
||||||
CONF_ON_PLAY = "on_play"
|
CONF_ON_PLAY = "on_play"
|
||||||
CONF_ON_PAUSE = "on_pause"
|
CONF_ON_PAUSE = "on_pause"
|
||||||
|
CONF_ON_ANNOUNCEMENT = "on_announcement"
|
||||||
CONF_MEDIA_URL = "media_url"
|
CONF_MEDIA_URL = "media_url"
|
||||||
|
|
||||||
StateTrigger = media_player_ns.class_("StateTrigger", automation.Trigger.template())
|
StateTrigger = media_player_ns.class_("StateTrigger", automation.Trigger.template())
|
||||||
IdleTrigger = media_player_ns.class_("IdleTrigger", automation.Trigger.template())
|
IdleTrigger = media_player_ns.class_("IdleTrigger", automation.Trigger.template())
|
||||||
PlayTrigger = media_player_ns.class_("PlayTrigger", automation.Trigger.template())
|
PlayTrigger = media_player_ns.class_("PlayTrigger", automation.Trigger.template())
|
||||||
PauseTrigger = media_player_ns.class_("PauseTrigger", automation.Trigger.template())
|
PauseTrigger = media_player_ns.class_("PauseTrigger", automation.Trigger.template())
|
||||||
|
AnnoucementTrigger = media_player_ns.class_(
|
||||||
|
"AnnouncementTrigger", automation.Trigger.template()
|
||||||
|
)
|
||||||
IsIdleCondition = media_player_ns.class_("IsIdleCondition", automation.Condition)
|
IsIdleCondition = media_player_ns.class_("IsIdleCondition", automation.Condition)
|
||||||
IsPlayingCondition = media_player_ns.class_("IsPlayingCondition", automation.Condition)
|
IsPlayingCondition = media_player_ns.class_("IsPlayingCondition", automation.Condition)
|
||||||
|
|
||||||
|
@ -75,6 +79,9 @@ async def setup_media_player_core_(var, config):
|
||||||
for conf in config.get(CONF_ON_PAUSE, []):
|
for conf in config.get(CONF_ON_PAUSE, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(trigger, [], conf)
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
for conf in config.get(CONF_ON_ANNOUNCEMENT, []):
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|
||||||
|
|
||||||
async def register_media_player(var, config):
|
async def register_media_player(var, config):
|
||||||
|
@ -106,6 +113,11 @@ MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PauseTrigger),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PauseTrigger),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_ON_ANNOUNCEMENT): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(AnnoucementTrigger),
|
||||||
|
}
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ class StateTrigger : public Trigger<> {
|
||||||
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(IdleTrigger, IDLE)
|
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(IdleTrigger, IDLE)
|
||||||
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PlayTrigger, PLAYING)
|
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PlayTrigger, PLAYING)
|
||||||
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PauseTrigger, PAUSED)
|
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PauseTrigger, PAUSED)
|
||||||
|
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(AnnouncementTrigger, ANNOUNCING)
|
||||||
|
|
||||||
template<typename... Ts> class IsIdleCondition : public Condition<Ts...>, public Parented<MediaPlayer> {
|
template<typename... Ts> class IsIdleCondition : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -15,6 +15,8 @@ const char *media_player_state_to_string(MediaPlayerState state) {
|
||||||
return "PLAYING";
|
return "PLAYING";
|
||||||
case MEDIA_PLAYER_STATE_PAUSED:
|
case MEDIA_PLAYER_STATE_PAUSED:
|
||||||
return "PAUSED";
|
return "PAUSED";
|
||||||
|
case MEDIA_PLAYER_STATE_ANNOUNCING:
|
||||||
|
return "ANNOUNCING";
|
||||||
case MEDIA_PLAYER_STATE_NONE:
|
case MEDIA_PLAYER_STATE_NONE:
|
||||||
default:
|
default:
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
|
@ -68,6 +70,9 @@ void MediaPlayerCall::perform() {
|
||||||
if (this->volume_.has_value()) {
|
if (this->volume_.has_value()) {
|
||||||
ESP_LOGD(TAG, " Volume: %.2f", this->volume_.value());
|
ESP_LOGD(TAG, " Volume: %.2f", this->volume_.value());
|
||||||
}
|
}
|
||||||
|
if (this->announcement_.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Announcement: %s", this->announcement_.value() ? "yes" : "no");
|
||||||
|
}
|
||||||
this->parent_->control(*this);
|
this->parent_->control(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +113,11 @@ MediaPlayerCall &MediaPlayerCall::set_volume(float volume) {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MediaPlayerCall &MediaPlayerCall::set_announcement(bool announce) {
|
||||||
|
this->announcement_ = announce;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
void MediaPlayer::add_on_state_callback(std::function<void()> &&callback) {
|
void MediaPlayer::add_on_state_callback(std::function<void()> &&callback) {
|
||||||
this->state_callback_.add(std::move(callback));
|
this->state_callback_.add(std::move(callback));
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ enum MediaPlayerState : uint8_t {
|
||||||
MEDIA_PLAYER_STATE_NONE = 0,
|
MEDIA_PLAYER_STATE_NONE = 0,
|
||||||
MEDIA_PLAYER_STATE_IDLE = 1,
|
MEDIA_PLAYER_STATE_IDLE = 1,
|
||||||
MEDIA_PLAYER_STATE_PLAYING = 2,
|
MEDIA_PLAYER_STATE_PLAYING = 2,
|
||||||
MEDIA_PLAYER_STATE_PAUSED = 3
|
MEDIA_PLAYER_STATE_PAUSED = 3,
|
||||||
|
MEDIA_PLAYER_STATE_ANNOUNCING = 4
|
||||||
};
|
};
|
||||||
const char *media_player_state_to_string(MediaPlayerState state);
|
const char *media_player_state_to_string(MediaPlayerState state);
|
||||||
|
|
||||||
|
@ -51,12 +52,14 @@ class MediaPlayerCall {
|
||||||
MediaPlayerCall &set_media_url(const std::string &url);
|
MediaPlayerCall &set_media_url(const std::string &url);
|
||||||
|
|
||||||
MediaPlayerCall &set_volume(float volume);
|
MediaPlayerCall &set_volume(float volume);
|
||||||
|
MediaPlayerCall &set_announcement(bool announce);
|
||||||
|
|
||||||
void perform();
|
void perform();
|
||||||
|
|
||||||
const optional<MediaPlayerCommand> &get_command() const { return command_; }
|
const optional<MediaPlayerCommand> &get_command() const { return command_; }
|
||||||
const optional<std::string> &get_media_url() const { return media_url_; }
|
const optional<std::string> &get_media_url() const { return media_url_; }
|
||||||
const optional<float> &get_volume() const { return volume_; }
|
const optional<float> &get_volume() const { return volume_; }
|
||||||
|
const optional<bool> &get_announcement() const { return announcement_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void validate_();
|
void validate_();
|
||||||
|
@ -64,6 +67,7 @@ class MediaPlayerCall {
|
||||||
optional<MediaPlayerCommand> command_;
|
optional<MediaPlayerCommand> command_;
|
||||||
optional<std::string> media_url_;
|
optional<std::string> media_url_;
|
||||||
optional<float> volume_;
|
optional<float> volume_;
|
||||||
|
optional<bool> announcement_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MediaPlayer : public EntityBase {
|
class MediaPlayer : public EntityBase {
|
||||||
|
|
|
@ -152,7 +152,7 @@ void VoiceAssistant::loop() {
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
this->set_state_(State::START_PIPELINE, State::START_MICROPHONE);
|
this->set_state_(State::START_MICROPHONE, State::START_PIPELINE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this->high_freq_.stop();
|
this->high_freq_.stop();
|
||||||
|
@ -318,7 +318,7 @@ void VoiceAssistant::loop() {
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_MEDIA_PLAYER
|
#ifdef USE_MEDIA_PLAYER
|
||||||
if (this->media_player_ != nullptr) {
|
if (this->media_player_ != nullptr) {
|
||||||
playing = (this->media_player_->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING);
|
playing = (this->media_player_->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (playing) {
|
if (playing) {
|
||||||
|
@ -514,7 +514,7 @@ void VoiceAssistant::request_start(bool continuous, bool silence_detection) {
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
this->set_state_(State::START_PIPELINE, State::START_MICROPHONE);
|
this->set_state_(State::START_MICROPHONE, State::START_PIPELINE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -640,7 +640,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
|
||||||
this->defer([this, url]() {
|
this->defer([this, url]() {
|
||||||
#ifdef USE_MEDIA_PLAYER
|
#ifdef USE_MEDIA_PLAYER
|
||||||
if (this->media_player_ != nullptr) {
|
if (this->media_player_ != nullptr) {
|
||||||
this->media_player_->make_call().set_media_url(url).perform();
|
this->media_player_->make_call().set_media_url(url).set_announcement(true).perform();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
this->tts_end_trigger_->trigger(url);
|
this->tts_end_trigger_->trigger(url);
|
||||||
|
|
|
@ -35,19 +35,19 @@ WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller)
|
||||||
def default_url(config):
|
def default_url(config):
|
||||||
config = config.copy()
|
config = config.copy()
|
||||||
if config[CONF_VERSION] == 1:
|
if config[CONF_VERSION] == 1:
|
||||||
if not (CONF_CSS_URL in config):
|
if CONF_CSS_URL not in config:
|
||||||
config[CONF_CSS_URL] = "https://esphome.io/_static/webserver-v1.min.css"
|
config[CONF_CSS_URL] = "https://esphome.io/_static/webserver-v1.min.css"
|
||||||
if not (CONF_JS_URL in config):
|
if CONF_JS_URL not in config:
|
||||||
config[CONF_JS_URL] = "https://esphome.io/_static/webserver-v1.min.js"
|
config[CONF_JS_URL] = "https://esphome.io/_static/webserver-v1.min.js"
|
||||||
if config[CONF_VERSION] == 2:
|
if config[CONF_VERSION] == 2:
|
||||||
if not (CONF_CSS_URL in config):
|
if CONF_CSS_URL not in config:
|
||||||
config[CONF_CSS_URL] = ""
|
config[CONF_CSS_URL] = ""
|
||||||
if not (CONF_JS_URL in config):
|
if CONF_JS_URL not in config:
|
||||||
config[CONF_JS_URL] = "https://oi.esphome.io/v2/www.js"
|
config[CONF_JS_URL] = "https://oi.esphome.io/v2/www.js"
|
||||||
if config[CONF_VERSION] == 3:
|
if config[CONF_VERSION] == 3:
|
||||||
if not (CONF_CSS_URL in config):
|
if CONF_CSS_URL not in config:
|
||||||
config[CONF_CSS_URL] = ""
|
config[CONF_CSS_URL] = ""
|
||||||
if not (CONF_JS_URL in config):
|
if CONF_JS_URL not in config:
|
||||||
config[CONF_JS_URL] = "https://oi.esphome.io/v3/www.js"
|
config[CONF_JS_URL] = "https://oi.esphome.io/v3/www.js"
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ from esphome.voluptuous_schema import ExtraKeysInvalid
|
||||||
from esphome.log import color, Fore
|
from esphome.log import color, Fore
|
||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.types import ConfigType, ConfigPathType, ConfigFragmentType
|
from esphome.types import ConfigType, ConfigFragmentType
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@ class Config(OrderedDict, fv.FinalValidateConfig):
|
||||||
return doc_range
|
return doc_range
|
||||||
|
|
||||||
def get_nested_item(
|
def get_nested_item(
|
||||||
self, path: ConfigPathType, raise_error: bool = False
|
self, path: ConfigPath, raise_error: bool = False
|
||||||
) -> ConfigFragmentType:
|
) -> ConfigFragmentType:
|
||||||
data = self
|
data = self
|
||||||
for item_index in path:
|
for item_index in path:
|
||||||
|
@ -244,7 +244,7 @@ class Config(OrderedDict, fv.FinalValidateConfig):
|
||||||
return path
|
return path
|
||||||
raise KeyError(f"ID {id} not found in configuration")
|
raise KeyError(f"ID {id} not found in configuration")
|
||||||
|
|
||||||
def get_config_for_path(self, path: ConfigPathType) -> ConfigFragmentType:
|
def get_config_for_path(self, path: ConfigPath) -> ConfigFragmentType:
|
||||||
return self.get_nested_item(path, raise_error=True)
|
return self.get_nested_item(path, raise_error=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -885,6 +885,9 @@ def _get_parent_name(path, config):
|
||||||
# Sub-item
|
# Sub-item
|
||||||
break
|
break
|
||||||
return domain
|
return domain
|
||||||
|
# When processing a list, skip back over the index
|
||||||
|
while len(path) > 1 and isinstance(path[-1], int):
|
||||||
|
path = path[:-1]
|
||||||
return path[-1]
|
return path[-1]
|
||||||
|
|
||||||
|
|
||||||
|
@ -1106,7 +1109,14 @@ def read_config(command_line_substitutions):
|
||||||
if errline:
|
if errline:
|
||||||
errstr += f" {errline}"
|
errstr += f" {errline}"
|
||||||
safe_print(errstr)
|
safe_print(errstr)
|
||||||
safe_print(indent(dump_dict(res, path)[0]))
|
split_dump = dump_dict(res, path)[0].splitlines()
|
||||||
|
# find the last error message
|
||||||
|
i = len(split_dump) - 1
|
||||||
|
while i > 10 and "\033[" not in split_dump[i]:
|
||||||
|
i = i - 1
|
||||||
|
# discard lines more than 4 beyond the last error
|
||||||
|
i = min(i + 4, len(split_dump))
|
||||||
|
safe_print(indent("\n".join(split_dump[:i])))
|
||||||
|
|
||||||
for err in res.errors:
|
for err in res.errors:
|
||||||
safe_print(color(Fore.BOLD_RED, err.msg))
|
safe_print(color(Fore.BOLD_RED, err.msg))
|
||||||
|
|
|
@ -394,7 +394,7 @@ async def to_code(config):
|
||||||
if project_conf := config.get(CONF_PROJECT):
|
if project_conf := config.get(CONF_PROJECT):
|
||||||
cg.add_define("ESPHOME_PROJECT_NAME", project_conf[CONF_NAME])
|
cg.add_define("ESPHOME_PROJECT_NAME", project_conf[CONF_NAME])
|
||||||
cg.add_define("ESPHOME_PROJECT_VERSION", project_conf[CONF_VERSION])
|
cg.add_define("ESPHOME_PROJECT_VERSION", project_conf[CONF_VERSION])
|
||||||
cg.add_define("ESPHOME_PROJECT_VERSION_30", project_conf[CONF_VERSION][:30])
|
cg.add_define("ESPHOME_PROJECT_VERSION_30", project_conf[CONF_VERSION][:29])
|
||||||
for conf in project_conf.get(CONF_ON_UPDATE, []):
|
for conf in project_conf.get(CONF_ON_UPDATE, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||||
await cg.register_component(trigger, conf)
|
await cg.register_component(trigger, conf)
|
||||||
|
|
|
@ -9,7 +9,7 @@ tornado==6.4
|
||||||
tzlocal==5.2 # from time
|
tzlocal==5.2 # from time
|
||||||
tzdata>=2021.1 # from time
|
tzdata>=2021.1 # from time
|
||||||
pyserial==3.5
|
pyserial==3.5
|
||||||
platformio==6.1.13 # When updating platformio, also update Dockerfile
|
platformio==6.1.15 # When updating platformio, also update Dockerfile
|
||||||
esptool==4.7.0
|
esptool==4.7.0
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
esphome-dashboard==20240412.0
|
esphome-dashboard==20240412.0
|
||||||
|
|
|
@ -5,7 +5,7 @@ pyupgrade==3.15.2 # also change in .pre-commit-config.yaml when updating
|
||||||
pre-commit
|
pre-commit
|
||||||
|
|
||||||
# Unit tests
|
# Unit tests
|
||||||
pytest==8.1.1
|
pytest==8.2.0
|
||||||
pytest-cov==4.1.0
|
pytest-cov==4.1.0
|
||||||
pytest-mock==3.14.0
|
pytest-mock==3.14.0
|
||||||
pytest-asyncio==0.23.6
|
pytest-asyncio==0.23.6
|
||||||
|
|
Loading…
Reference in a new issue