diff --git a/CODEOWNERS b/CODEOWNERS index e2b29547cb..3cb38fce06 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -88,6 +88,7 @@ esphome/components/honeywellabp/* @RubyBailey esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/hydreon_rgxx/* @functionpointer esphome/components/i2c/* @esphome/core +esphome/components/i2s_audio/* @jesserockz esphome/components/improv_serial/* @esphome/core esphome/components/ina260/* @MrEditor97 esphome/components/inkbird_ibsth1_mini/* @fkirill @@ -102,6 +103,7 @@ esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lock/* @esphome/core esphome/components/logger/* @esphome/core esphome/components/ltr390/* @sjtrny +esphome/components/max31865/* @DAVe3283 esphome/components/max44009/* @berfenger esphome/components/max7219digit/* @rspaargaren esphome/components/max9611/* @mckaymatthew @@ -119,6 +121,7 @@ esphome/components/mcp47a1/* @jesserockz esphome/components/mcp9808/* @k7hpn esphome/components/md5/* @esphome/core esphome/components/mdns/* @esphome/core +esphome/components/media_player/* @jesserockz esphome/components/midea/* @dudanov esphome/components/midea_ir/* @dudanov esphome/components/mitsubishi/* @RubyBailey @@ -178,6 +181,7 @@ esphome/components/sen5x/* @martgras esphome/components/sensirion_common/* @martgras esphome/components/sensor/* @esphome/core esphome/components/sgp40/* @SenexCrenshaw +esphome/components/sgp4x/* @SenexCrenshaw @martgras esphome/components/shelly_dimmer/* @edge90 @rnauber esphome/components/sht4x/* @sjtrny esphome/components/shutdown/* @esphome/core @jsuanet @@ -222,6 +226,7 @@ esphome/components/tsl2591/* @wjcarpenter esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/climate/* @jesserockz esphome/components/tuya/number/* @frankiboy1 +esphome/components/tuya/select/* @bearpawmaxim esphome/components/tuya/sensor/* @jesserockz esphome/components/tuya/switch/* @jesserockz esphome/components/tuya/text_sensor/* @dentra diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index e9af828a9d..1d0cd8d0ab 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -121,7 +121,11 @@ void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() { // calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic // also take into account min_power auto min_us = this->cycle_time_us * this->min_power / 1000; - this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535); + // calculate required value to provide a true RMS voltage output + this->enable_time_us = + std::max((uint32_t) 1, (uint32_t)((65535 - (acos(1 - (2 * this->value / 65535.0)) / 3.14159 * 65535)) * + (this->cycle_time_us - min_us)) / + 65535); if (this->method == DIM_METHOD_LEADING_PULSE) { // Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone // this is for brightness near 99% diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index ca87c8d11f..daa1d3c5cf 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -15,10 +15,21 @@ namespace esphome { namespace adc { static const char *const TAG = "adc"; -// 13 bits for S3 / 12 bit for all other esp32 variants -// create a const to avoid the repated cast to enum + +// 13bit for S2, and 12bit for all other esp32 variants #ifdef USE_ESP32 static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast(ADC_WIDTH_MAX - 1); + +#ifndef SOC_ADC_RTC_MAX_BITWIDTH +#if USE_ESP32_VARIANT_ESP32S2 +static const int SOC_ADC_RTC_MAX_BITWIDTH = 13; +#else +static const int SOC_ADC_RTC_MAX_BITWIDTH = 12; +#endif +#endif + +static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1; // 4095 (12 bit) or 8191 (13 bit) +static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1; // 2048 (12 bit) or 4096 (13 bit) #endif void ADCSensor::setup() { @@ -75,16 +86,16 @@ void ADCSensor::dump_config() { } else { switch (this->attenuation_) { case ADC_ATTEN_DB_0: - ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); + ESP_LOGCONFIG(TAG, " Attenuation: 0db"); break; case ADC_ATTEN_DB_2_5: - ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); + ESP_LOGCONFIG(TAG, " Attenuation: 2.5db"); break; case ADC_ATTEN_DB_6: - ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); + ESP_LOGCONFIG(TAG, " Attenuation: 6db"); break; case ADC_ATTEN_DB_11: - ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); + ESP_LOGCONFIG(TAG, " Attenuation: 11db"); break; default: // This is to satisfy the unused ADC_ATTEN_MAX break; @@ -129,16 +140,16 @@ float ADCSensor::sample() { return mv / 1000.0f; } - int raw11, raw6 = 4095, raw2 = 4095, raw0 = 4095; + int raw11, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX; adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11); raw11 = adc1_get_raw(channel_); - if (raw11 < 4095) { + if (raw11 < ADC_MAX) { adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6); raw6 = adc1_get_raw(channel_); - if (raw6 < 4095) { + if (raw6 < ADC_MAX) { adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5); raw2 = adc1_get_raw(channel_); - if (raw2 < 4095) { + if (raw2 < ADC_MAX) { adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0); raw0 = adc1_get_raw(channel_); } @@ -154,15 +165,15 @@ float ADCSensor::sample() { uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]); uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]); - // Contribution of each value, in range 0-2048 - uint32_t c11 = std::min(raw11, 2048); - uint32_t c6 = 2048 - std::abs(raw6 - 2048); - uint32_t c2 = 2048 - std::abs(raw2 - 2048); - uint32_t c0 = std::min(4095 - raw0, 2048); - // max theoretical csum value is 2048*4 = 8192 + // Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC) + uint32_t c11 = std::min(raw11, ADC_HALF); + uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF); + uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF); + uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF); + // max theoretical csum value is 4096*4 = 16384 uint32_t csum = c11 + c6 + c2 + c0; - // each mv is max 3900; so max value is 3900*2048*4, fits in unsigned + // each mv is max 3900; so max value is 3900*4096*4, fits in unsigned32 uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0); return mv_scaled / (float) (csum * 1000U); } diff --git a/esphome/components/addressable_light/addressable_light_display.h b/esphome/components/addressable_light/addressable_light_display.h index 163faf27b0..dfafe9d98d 100644 --- a/esphome/components/addressable_light/addressable_light_display.h +++ b/esphome/components/addressable_light/addressable_light_display.h @@ -40,6 +40,8 @@ class AddressableLightDisplay : public display::DisplayBuffer, public PollingCom void setup() override; void display(); + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } + protected: int get_width_internal() override; int get_height_internal() override; diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index bd39893825..3e9a62f3d8 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -42,6 +42,7 @@ service APIConnection { rpc select_command (SelectCommandRequest) returns (void) {} rpc button_command (ButtonCommandRequest) returns (void) {} rpc lock_command (LockCommandRequest) returns (void) {} + rpc media_player_command (MediaPlayerCommandRequest) returns (void) {} } @@ -991,7 +992,7 @@ message ListEntitiesLockResponse { bool supports_open = 9; bool requires_code = 10; - # Not yet implemented: + // Not yet implemented: string code_format = 11; } message LockStateResponse { @@ -1010,7 +1011,7 @@ message LockCommandRequest { fixed32 key = 1; LockCommand command = 2; - # Not yet implemented: + // Not yet implemented: bool has_code = 3; string code = 4; } @@ -1040,3 +1041,60 @@ message ButtonCommandRequest { fixed32 key = 1; } +// ==================== MEDIA PLAYER ==================== +enum MediaPlayerState { + MEDIA_PLAYER_STATE_NONE = 0; + MEDIA_PLAYER_STATE_IDLE = 1; + MEDIA_PLAYER_STATE_PLAYING = 2; + MEDIA_PLAYER_STATE_PAUSED = 3; +} +enum MediaPlayerCommand { + MEDIA_PLAYER_COMMAND_PLAY = 0; + MEDIA_PLAYER_COMMAND_PAUSE = 1; + MEDIA_PLAYER_COMMAND_STOP = 2; + MEDIA_PLAYER_COMMAND_MUTE = 3; + MEDIA_PLAYER_COMMAND_UNMUTE = 4; +} +message ListEntitiesMediaPlayerResponse { + option (id) = 63; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_MEDIA_PLAYER"; + + string object_id = 1; + fixed32 key = 2; + string name = 3; + string unique_id = 4; + + string icon = 5; + bool disabled_by_default = 6; + EntityCategory entity_category = 7; + + bool supports_pause = 8; +} +message MediaPlayerStateResponse { + option (id) = 64; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_MEDIA_PLAYER"; + option (no_delay) = true; + fixed32 key = 1; + MediaPlayerState state = 2; + float volume = 3; + bool muted = 4; +} +message MediaPlayerCommandRequest { + option (id) = 65; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_MEDIA_PLAYER"; + option (no_delay) = true; + + fixed32 key = 1; + + bool has_command = 2; + MediaPlayerCommand command = 3; + + bool has_volume = 4; + float volume = 5; + + bool has_media_url = 6; + string media_url = 7; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 81f2465b74..9028034c90 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -12,9 +12,6 @@ #ifdef USE_HOMEASSISTANT_TIME #include "esphome/components/homeassistant/time/homeassistant_time.h" #endif -#ifdef USE_FAN -#include "esphome/components/fan/fan_helpers.h" -#endif namespace esphome { namespace api { @@ -253,9 +250,6 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) { #endif #ifdef USE_FAN -// Shut-up about usage of deprecated speed_level_to_enum/speed_enum_to_level functions for a bit. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" bool APIConnection::send_fan_state(fan::Fan *fan) { if (!this->state_subscription_) return false; @@ -268,7 +262,6 @@ bool APIConnection::send_fan_state(fan::Fan *fan) { resp.oscillating = fan->oscillating; if (traits.supports_speed()) { resp.speed_level = fan->speed; - resp.speed = static_cast(fan::speed_level_to_enum(fan->speed, traits.supported_speed_count())); } if (traits.supports_direction()) resp.direction = static_cast(fan->direction); @@ -295,8 +288,6 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { if (fan == nullptr) return; - auto traits = fan->get_traits(); - auto call = fan->make_call(); if (msg.has_state) call.set_state(msg.state); @@ -305,14 +296,11 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { if (msg.has_speed_level) { // Prefer level call.set_speed(msg.speed_level); - } else if (msg.has_speed) { - call.set_speed(fan::speed_enum_to_level(static_cast(msg.speed), traits.supported_speed_count())); } if (msg.has_direction) call.set_direction(static_cast(msg.direction)); call.perform(); } -#pragma GCC diagnostic pop #endif #ifdef USE_LIGHT @@ -745,6 +733,52 @@ void APIConnection::lock_command(const LockCommandRequest &msg) { } #endif +#ifdef USE_MEDIA_PLAYER +bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) { + if (!this->state_subscription_) + return false; + + MediaPlayerStateResponse resp{}; + resp.key = media_player->get_object_id_hash(); + resp.state = static_cast(media_player->state); + resp.volume = media_player->volume; + resp.muted = media_player->is_muted(); + return this->send_media_player_state_response(resp); +} +bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_player) { + ListEntitiesMediaPlayerResponse msg; + msg.key = media_player->get_object_id_hash(); + msg.object_id = media_player->get_object_id(); + msg.name = media_player->get_name(); + msg.unique_id = get_default_unique_id("media_player", media_player); + msg.icon = media_player->get_icon(); + msg.disabled_by_default = media_player->is_disabled_by_default(); + msg.entity_category = static_cast(media_player->get_entity_category()); + + auto traits = media_player->get_traits(); + msg.supports_pause = traits.get_supports_pause(); + + return this->send_list_entities_media_player_response(msg); +} +void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { + media_player::MediaPlayer *media_player = App.get_media_player_by_key(msg.key); + if (media_player == nullptr) + return; + + auto call = media_player->make_call(); + if (msg.has_command) { + call.set_command(static_cast(msg.command)); + } + if (msg.has_volume) { + call.set_volume(msg.volume); + } + if (msg.has_media_url) { + call.set_media_url(msg.media_url); + } + call.perform(); +} +#endif + #ifdef USE_ESP32_CAMERA void APIConnection::send_camera_state(std::shared_ptr image) { if (!this->state_subscription_) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 10f0becc54..0787d2f7eb 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -82,6 +82,11 @@ class APIConnection : public APIServerConnection { bool send_lock_state(lock::Lock *a_lock, lock::LockState state); bool send_lock_info(lock::Lock *a_lock); void lock_command(const LockCommandRequest &msg) override; +#endif +#ifdef USE_MEDIA_PLAYER + bool send_media_player_state(media_player::MediaPlayer *media_player); + bool send_media_player_info(media_player::MediaPlayer *media_player); + void media_player_command(const MediaPlayerCommandRequest &msg) override; #endif bool send_log_message(int level, const char *tag, const char *line); void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 5a78587473..70f909c07a 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -308,6 +308,36 @@ template<> const char *proto_enum_to_string(enums::LockComma return "UNKNOWN"; } } +template<> const char *proto_enum_to_string(enums::MediaPlayerState value) { + switch (value) { + case enums::MEDIA_PLAYER_STATE_NONE: + return "MEDIA_PLAYER_STATE_NONE"; + case enums::MEDIA_PLAYER_STATE_IDLE: + return "MEDIA_PLAYER_STATE_IDLE"; + case enums::MEDIA_PLAYER_STATE_PLAYING: + return "MEDIA_PLAYER_STATE_PLAYING"; + case enums::MEDIA_PLAYER_STATE_PAUSED: + return "MEDIA_PLAYER_STATE_PAUSED"; + default: + return "UNKNOWN"; + } +} +template<> const char *proto_enum_to_string(enums::MediaPlayerCommand value) { + switch (value) { + case enums::MEDIA_PLAYER_COMMAND_PLAY: + return "MEDIA_PLAYER_COMMAND_PLAY"; + case enums::MEDIA_PLAYER_COMMAND_PAUSE: + return "MEDIA_PLAYER_COMMAND_PAUSE"; + case enums::MEDIA_PLAYER_COMMAND_STOP: + return "MEDIA_PLAYER_COMMAND_STOP"; + case enums::MEDIA_PLAYER_COMMAND_MUTE: + return "MEDIA_PLAYER_COMMAND_MUTE"; + case enums::MEDIA_PLAYER_COMMAND_UNMUTE: + return "MEDIA_PLAYER_COMMAND_UNMUTE"; + default: + return "UNKNOWN"; + } +} bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -4574,6 +4604,254 @@ void ButtonCommandRequest::dump_to(std::string &out) const { out.append("}"); } #endif +bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 6: { + this->disabled_by_default = value.as_bool(); + return true; + } + case 7: { + this->entity_category = value.as_enum(); + return true; + } + case 8: { + this->supports_pause = value.as_bool(); + return true; + } + default: + return false; + } +} +bool ListEntitiesMediaPlayerResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + this->object_id = value.as_string(); + return true; + } + case 3: { + this->name = value.as_string(); + return true; + } + case 4: { + this->unique_id = value.as_string(); + return true; + } + case 5: { + this->icon = value.as_string(); + return true; + } + default: + return false; + } +} +bool ListEntitiesMediaPlayerResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 2: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->object_id); + buffer.encode_fixed32(2, this->key); + buffer.encode_string(3, this->name); + buffer.encode_string(4, this->unique_id); + buffer.encode_string(5, this->icon); + buffer.encode_bool(6, this->disabled_by_default); + buffer.encode_enum(7, this->entity_category); + buffer.encode_bool(8, this->supports_pause); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("ListEntitiesMediaPlayerResponse {\n"); + out.append(" object_id: "); + out.append("'").append(this->object_id).append("'"); + out.append("\n"); + + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" name: "); + out.append("'").append(this->name).append("'"); + out.append("\n"); + + out.append(" unique_id: "); + out.append("'").append(this->unique_id).append("'"); + out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); + + out.append(" supports_pause: "); + out.append(YESNO(this->supports_pause)); + out.append("\n"); + out.append("}"); +} +#endif +bool MediaPlayerStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->state = value.as_enum(); + return true; + } + case 4: { + this->muted = value.as_bool(); + return true; + } + default: + return false; + } +} +bool MediaPlayerStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + case 3: { + this->volume = value.as_float(); + return true; + } + default: + return false; + } +} +void MediaPlayerStateResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_fixed32(1, this->key); + buffer.encode_enum(2, this->state); + buffer.encode_float(3, this->volume); + buffer.encode_bool(4, this->muted); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void MediaPlayerStateResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("MediaPlayerStateResponse {\n"); + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" state: "); + out.append(proto_enum_to_string(this->state)); + out.append("\n"); + + out.append(" volume: "); + sprintf(buffer, "%g", this->volume); + out.append(buffer); + out.append("\n"); + + out.append(" muted: "); + out.append(YESNO(this->muted)); + out.append("\n"); + out.append("}"); +} +#endif +bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->has_command = value.as_bool(); + return true; + } + case 3: { + this->command = value.as_enum(); + return true; + } + case 4: { + this->has_volume = value.as_bool(); + return true; + } + case 6: { + this->has_media_url = value.as_bool(); + return true; + } + default: + return false; + } +} +bool MediaPlayerCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 7: { + this->media_url = value.as_string(); + return true; + } + default: + return false; + } +} +bool MediaPlayerCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + case 5: { + this->volume = value.as_float(); + return true; + } + default: + return false; + } +} +void MediaPlayerCommandRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_fixed32(1, this->key); + buffer.encode_bool(2, this->has_command); + buffer.encode_enum(3, this->command); + buffer.encode_bool(4, this->has_volume); + buffer.encode_float(5, this->volume); + buffer.encode_bool(6, this->has_media_url); + buffer.encode_string(7, this->media_url); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void MediaPlayerCommandRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("MediaPlayerCommandRequest {\n"); + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" has_command: "); + out.append(YESNO(this->has_command)); + out.append("\n"); + + out.append(" command: "); + out.append(proto_enum_to_string(this->command)); + out.append("\n"); + + out.append(" has_volume: "); + out.append(YESNO(this->has_volume)); + out.append("\n"); + + out.append(" volume: "); + sprintf(buffer, "%g", this->volume); + out.append(buffer); + out.append("\n"); + + out.append(" has_media_url: "); + out.append(YESNO(this->has_media_url)); + out.append("\n"); + + out.append(" media_url: "); + out.append("'").append(this->media_url).append("'"); + out.append("\n"); + out.append("}"); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 28c0a7ce88..ec1cdc35ac 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -141,6 +141,19 @@ enum LockCommand : uint32_t { LOCK_LOCK = 1, LOCK_OPEN = 2, }; +enum MediaPlayerState : uint32_t { + MEDIA_PLAYER_STATE_NONE = 0, + MEDIA_PLAYER_STATE_IDLE = 1, + MEDIA_PLAYER_STATE_PLAYING = 2, + MEDIA_PLAYER_STATE_PAUSED = 3, +}; +enum MediaPlayerCommand : uint32_t { + MEDIA_PLAYER_COMMAND_PLAY = 0, + MEDIA_PLAYER_COMMAND_PAUSE = 1, + MEDIA_PLAYER_COMMAND_STOP = 2, + MEDIA_PLAYER_COMMAND_MUTE = 3, + MEDIA_PLAYER_COMMAND_UNMUTE = 4, +}; } // namespace enums @@ -1146,6 +1159,60 @@ class ButtonCommandRequest : public ProtoMessage { protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; }; +class ListEntitiesMediaPlayerResponse : public ProtoMessage { + public: + std::string object_id{}; + uint32_t key{0}; + std::string name{}; + std::string unique_id{}; + std::string icon{}; + bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; + bool supports_pause{false}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class MediaPlayerStateResponse : public ProtoMessage { + public: + uint32_t key{0}; + enums::MediaPlayerState state{}; + float volume{0.0f}; + bool muted{false}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class MediaPlayerCommandRequest : public ProtoMessage { + public: + uint32_t key{0}; + bool has_command{false}; + enums::MediaPlayerCommand command{}; + bool has_volume{false}; + float volume{0.0f}; + bool has_media_url{false}; + std::string media_url{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index d981a3bf4e..bd146cb54d 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -310,6 +310,24 @@ bool APIServerConnectionBase::send_list_entities_button_response(const ListEntit #endif #ifdef USE_BUTTON #endif +#ifdef USE_MEDIA_PLAYER +bool APIServerConnectionBase::send_list_entities_media_player_response(const ListEntitiesMediaPlayerResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_list_entities_media_player_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 63); +} +#endif +#ifdef USE_MEDIA_PLAYER +bool APIServerConnectionBase::send_media_player_state_response(const MediaPlayerStateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_media_player_state_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 64); +} +#endif +#ifdef USE_MEDIA_PLAYER +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { case 1: { @@ -563,6 +581,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str()); #endif this->on_button_command_request(msg); +#endif + break; + } + case 65: { +#ifdef USE_MEDIA_PLAYER + MediaPlayerCommandRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_media_player_command_request: %s", msg.dump().c_str()); +#endif + this->on_media_player_command_request(msg); #endif break; } @@ -813,6 +842,19 @@ void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) this->lock_command(msg); } #endif +#ifdef USE_MEDIA_PLAYER +void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->media_player_command(msg); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 5aaf831c91..28ad3fbd15 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -144,6 +144,15 @@ class APIServerConnectionBase : public ProtoService { #endif #ifdef USE_BUTTON virtual void on_button_command_request(const ButtonCommandRequest &value){}; +#endif +#ifdef USE_MEDIA_PLAYER + bool send_list_entities_media_player_response(const ListEntitiesMediaPlayerResponse &msg); +#endif +#ifdef USE_MEDIA_PLAYER + bool send_media_player_state_response(const MediaPlayerStateResponse &msg); +#endif +#ifdef USE_MEDIA_PLAYER + virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){}; #endif protected: bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; @@ -192,6 +201,9 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_LOCK virtual void lock_command(const LockCommandRequest &msg) = 0; +#endif +#ifdef USE_MEDIA_PLAYER + virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; @@ -236,6 +248,9 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_LOCK void on_lock_command_request(const LockCommandRequest &msg) override; #endif +#ifdef USE_MEDIA_PLAYER + void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; +#endif }; } // namespace api diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 1f2800f298..8375a82313 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -272,6 +272,15 @@ void APIServer::on_lock_update(lock::Lock *obj) { } #endif +#ifdef USE_MEDIA_PLAYER +void APIServer::on_media_player_update(media_player::MediaPlayer *obj) { + if (obj->is_internal()) + return; + for (auto &c : this->clients_) + c->send_media_player_state(obj); +} +#endif + float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; } void APIServer::set_port(uint16_t port) { this->port_ = port; } APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index f03a83fc7b..6997e23cac 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -68,6 +68,9 @@ class APIServer : public Component, public Controller { #endif #ifdef USE_LOCK void on_lock_update(lock::Lock *obj) override; +#endif +#ifdef USE_MEDIA_PLAYER + void on_media_player_update(media_player::MediaPlayer *obj) override; #endif void send_homeassistant_service_call(const HomeassistantServiceResponse &call); void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index 9f55fda617..85d4cd61ef 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -64,5 +64,11 @@ bool ListEntitiesIterator::on_number(number::Number *number) { return this->clie bool ListEntitiesIterator::on_select(select::Select *select) { return this->client_->send_select_info(select); } #endif +#ifdef USE_MEDIA_PLAYER +bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *media_player) { + return this->client_->send_media_player_info(media_player); +} +#endif + } // namespace api } // namespace esphome diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index 51c343eb03..4fbaa509a2 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -51,6 +51,9 @@ class ListEntitiesIterator : public ComponentIterator { #endif #ifdef USE_LOCK bool on_lock(lock::Lock *a_lock) override; +#endif +#ifdef USE_MEDIA_PLAYER + bool on_media_player(media_player::MediaPlayer *media_player) override; #endif bool on_end() override; diff --git a/esphome/components/api/subscribe_state.cpp b/esphome/components/api/subscribe_state.cpp index ba277502c8..1d1ba0245e 100644 --- a/esphome/components/api/subscribe_state.cpp +++ b/esphome/components/api/subscribe_state.cpp @@ -50,6 +50,11 @@ bool InitialStateIterator::on_select(select::Select *select) { #ifdef USE_LOCK bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); } #endif +#ifdef USE_MEDIA_PLAYER +bool InitialStateIterator::on_media_player(media_player::MediaPlayer *media_player) { + return this->client_->send_media_player_state(media_player); +} +#endif InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {} } // namespace api diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index 515e1a2d07..7a7ba697c0 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -48,6 +48,9 @@ class InitialStateIterator : public ComponentIterator { #endif #ifdef USE_LOCK bool on_lock(lock::Lock *a_lock) override; +#endif +#ifdef USE_MEDIA_PLAYER + bool on_media_player(media_player::MediaPlayer *media_player) override; #endif protected: APIConnection *client_; diff --git a/esphome/components/bedjet/bedjet.cpp b/esphome/components/bedjet/bedjet.cpp index 38ed6206a8..60eef52334 100644 --- a/esphome/components/bedjet/bedjet.cpp +++ b/esphome/components/bedjet/bedjet.cpp @@ -36,6 +36,14 @@ static uint8_t bedjet_fan_speed_to_step(const std::string &fan_step_percent) { return -1; } +static BedjetButton heat_button(BedjetHeatMode mode) { + BedjetButton btn = BTN_HEAT; + if (mode == HEAT_MODE_EXTENDED) { + btn = BTN_EXTHT; + } + return btn; +} + void Bedjet::upgrade_firmware() { auto *pkt = this->codec_->get_button_request(MAGIC_UPDATE); auto status = this->write_bedjet_packet_(pkt); @@ -117,7 +125,7 @@ void Bedjet::control(const ClimateCall &call) { pkt = this->codec_->get_button_request(BTN_OFF); break; case climate::CLIMATE_MODE_HEAT: - pkt = this->codec_->get_button_request(BTN_HEAT); + pkt = this->codec_->get_button_request(heat_button(this->heating_mode_)); break; case climate::CLIMATE_MODE_FAN_ONLY: pkt = this->codec_->get_button_request(BTN_COOL); @@ -186,6 +194,8 @@ void Bedjet::control(const ClimateCall &call) { pkt = this->codec_->get_button_request(BTN_M2); } else if (preset == "M3") { pkt = this->codec_->get_button_request(BTN_M3); + } else if (preset == "LTD HT") { + pkt = this->codec_->get_button_request(BTN_HEAT); } else if (preset == "EXT HT") { pkt = this->codec_->get_button_request(BTN_EXTHT); } else { @@ -298,7 +308,7 @@ void Bedjet::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc #ifdef USE_TIME if (this->time_id_.has_value()) { - this->send_local_time_(); + this->send_local_time(); } #endif break; @@ -453,40 +463,47 @@ uint8_t Bedjet::write_notify_config_descriptor_(bool enable) { #ifdef USE_TIME /** Attempts to sync the local time (via `time_id`) to the BedJet device. */ -void Bedjet::send_local_time_() { - if (this->node_state != espbt::ClientState::ESTABLISHED) { - ESP_LOGV(TAG, "[%s] Not connected, cannot send time.", this->get_name().c_str()); - return; - } - auto *time_id = *this->time_id_; - time::ESPTime now = time_id->now(); - if (now.is_valid()) { - uint8_t hour = now.hour; - uint8_t minute = now.minute; - BedjetPacket *pkt = this->codec_->get_set_time_request(hour, minute); - auto status = this->write_bedjet_packet_(pkt); - if (status) { - ESP_LOGW(TAG, "Failed setting BedJet clock: %d", status); - } else { - ESP_LOGD(TAG, "[%s] BedJet clock set to: %d:%02d", this->get_name().c_str(), hour, minute); +void Bedjet::send_local_time() { + if (this->time_id_.has_value()) { + auto *time_id = *this->time_id_; + time::ESPTime now = time_id->now(); + if (now.is_valid()) { + this->set_clock(now.hour, now.minute); + ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute); } + } else { + ESP_LOGI(TAG, "`time_id` is not configured: will not sync BedJet clock."); } } /** Initializes time sync callbacks to support syncing current time to the BedJet. */ void Bedjet::setup_time_() { if (this->time_id_.has_value()) { - this->send_local_time_(); + this->send_local_time(); auto *time_id = *this->time_id_; - time_id->add_on_time_sync_callback([this] { this->send_local_time_(); }); - time::ESPTime now = time_id->now(); - ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute); + time_id->add_on_time_sync_callback([this] { this->send_local_time(); }); } else { ESP_LOGI(TAG, "`time_id` is not configured: will not sync BedJet clock."); } } #endif +/** Attempt to set the BedJet device's clock to the specified time. */ +void Bedjet::set_clock(uint8_t hour, uint8_t minute) { + if (this->node_state != espbt::ClientState::ESTABLISHED) { + ESP_LOGV(TAG, "[%s] Not connected, cannot send time.", this->get_name().c_str()); + return; + } + + BedjetPacket *pkt = this->codec_->get_set_time_request(hour, minute); + auto status = this->write_bedjet_packet_(pkt); + if (status) { + ESP_LOGW(TAG, "Failed setting BedJet clock: %d", status); + } else { + ESP_LOGD(TAG, "[%s] BedJet clock set to: %d:%02d", this->get_name().c_str(), hour, minute); + } +} + /** Writes one BedjetPacket to the BLE client on the BEDJET_COMMAND_UUID. */ uint8_t Bedjet::write_bedjet_packet_(BedjetPacket *pkt) { if (this->node_state != espbt::ClientState::ESTABLISHED) { @@ -557,11 +574,25 @@ bool Bedjet::update_status_() { break; case MODE_HEAT: + this->mode = climate::CLIMATE_MODE_HEAT; + this->action = climate::CLIMATE_ACTION_HEATING; + this->preset.reset(); + if (this->heating_mode_ == HEAT_MODE_EXTENDED) { + this->set_custom_preset_("LTD HT"); + } else { + this->custom_preset.reset(); + } + break; + case MODE_EXTHT: this->mode = climate::CLIMATE_MODE_HEAT; this->action = climate::CLIMATE_ACTION_HEATING; - this->custom_preset.reset(); this->preset.reset(); + if (this->heating_mode_ == HEAT_MODE_EXTENDED) { + this->custom_preset.reset(); + } else { + this->set_custom_preset_("EXT HT"); + } break; case MODE_COOL: diff --git a/esphome/components/bedjet/bedjet.h b/esphome/components/bedjet/bedjet.h index 0565be6045..5c2930420c 100644 --- a/esphome/components/bedjet/bedjet.h +++ b/esphome/components/bedjet/bedjet.h @@ -4,6 +4,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/climate/climate.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "bedjet_base.h" @@ -37,8 +38,12 @@ class Bedjet : public climate::Climate, public esphome::ble_client::BLEClientNod #ifdef USE_TIME void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; } + void send_local_time(); #endif + void set_clock(uint8_t hour, uint8_t minute); void set_status_timeout(uint32_t timeout) { this->timeout_ = timeout; } + /** Sets the default strategy to use for climate::CLIMATE_MODE_HEAT. */ + void set_heating_mode(BedjetHeatMode mode) { this->heating_mode_ = mode; } /** Attempts to check for and apply firmware updates. */ void upgrade_firmware(); @@ -73,6 +78,11 @@ class Bedjet : public climate::Climate, public esphome::ble_client::BLEClientNod "M2", "M3", }); + if (this->heating_mode_ == HEAT_MODE_EXTENDED) { + traits.add_supported_custom_preset("LTD HT"); + } else { + traits.add_supported_custom_preset("EXT HT"); + } traits.set_visual_min_temperature(19.0); traits.set_visual_max_temperature(43.0); traits.set_visual_temperature_step(1.0); @@ -84,11 +94,11 @@ class Bedjet : public climate::Climate, public esphome::ble_client::BLEClientNod #ifdef USE_TIME void setup_time_(); - void send_local_time_(); optional time_id_{}; #endif uint32_t timeout_{DEFAULT_STATUS_TIMEOUT}; + BedjetHeatMode heating_mode_ = HEAT_MODE_HEAT; static const uint32_t MIN_NOTIFY_THROTTLE = 5000; static const uint32_t NOTIFY_WARN_THRESHOLD = 300000; diff --git a/esphome/components/bedjet/bedjet_const.h b/esphome/components/bedjet/bedjet_const.h index e6bfa45d3a..16f73717c6 100644 --- a/esphome/components/bedjet/bedjet_const.h +++ b/esphome/components/bedjet/bedjet_const.h @@ -24,6 +24,14 @@ enum BedjetMode : uint8_t { MODE_WAIT = 6, }; +/** Optional heating strategies to use for climate::CLIMATE_MODE_HEAT. */ +enum BedjetHeatMode { + /// HVACMode.HEAT is handled using BTN_HEAT (default) + HEAT_MODE_HEAT, + /// HVACMode.HEAT is handled using BTN_EXTHT + HEAT_MODE_EXTENDED, +}; + enum BedjetButton : uint8_t { /// Turn BedJet off BTN_OFF = 0x1, @@ -66,8 +74,8 @@ enum BedjetCommand : uint8_t { #define BEDJET_FAN_STEP_NAMES_ \ { \ - " 5%", " 10%", " 15%", " 20%", " 25%", " 30%", " 35%", " 40%", " 45%", " 50%", " 55%", " 60%", " 65%", " 70%", \ - " 75%", " 80%", " 85%", " 90%", " 95%", "100%" \ + "5%", "10%", "15%", "20%", "25%", "30%", "35%", "40%", "45%", "50%", "55%", "60%", "65%", "70%", "75%", "80%", \ + "85%", "90%", "95%", "100%" \ } static const char *const BEDJET_FAN_STEP_NAMES[20] = BEDJET_FAN_STEP_NAMES_; diff --git a/esphome/components/bedjet/climate.py b/esphome/components/bedjet/climate.py index 49353934f6..d718ba9969 100644 --- a/esphome/components/bedjet/climate.py +++ b/esphome/components/bedjet/climate.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import climate, ble_client, time from esphome.const import ( + CONF_HEAT_MODE, CONF_ID, CONF_RECEIVE_TIMEOUT, CONF_TIME_ID, @@ -14,11 +15,19 @@ bedjet_ns = cg.esphome_ns.namespace("bedjet") Bedjet = bedjet_ns.class_( "Bedjet", climate.Climate, ble_client.BLEClientNode, cg.PollingComponent ) +BedjetHeatMode = bedjet_ns.enum("BedjetHeatMode") +BEDJET_HEAT_MODES = { + "heat": BedjetHeatMode.HEAT_MODE_HEAT, + "extended": BedjetHeatMode.HEAT_MODE_EXTENDED, +} CONFIG_SCHEMA = ( climate.CLIMATE_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(Bedjet), + cv.Optional(CONF_HEAT_MODE, default="heat"): cv.enum( + BEDJET_HEAT_MODES, lower=True + ), cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), cv.Optional( CONF_RECEIVE_TIMEOUT, default="0s" @@ -35,6 +44,7 @@ async def to_code(config): await cg.register_component(var, config) await climate.register_climate(var, config) await ble_client.register_ble_node(var, config) + cg.add(var.set_heating_mode(config[CONF_HEAT_MODE])) if CONF_TIME_ID in config: time_ = await cg.get_variable(config[CONF_TIME_ID]) cg.add(var.set_time_id(time_)) diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index 02735feaae..07217eebed 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -69,7 +69,6 @@ void BinarySensor::add_filters(const std::vector &filters) { } } bool BinarySensor::has_state() const { return this->has_state_; } -uint32_t BinarySensor::hash_base() { return 1210250844UL; } bool BinarySensor::is_status_binary_sensor() const { return false; } } // namespace binary_sensor diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index b5d1244bce..87d87b8eb4 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -76,8 +76,6 @@ class BinarySensor : public EntityBase { virtual std::string device_class(); protected: - uint32_t hash_base() override; - CallbackManager state_callback_{}; optional device_class_{}; ///< Stores the override of the device class Filter *filter_list_{nullptr}; diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index f8eee43137..ee5afd3f7b 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -11,8 +11,6 @@ namespace ble_client { static const char *const TAG = "ble_sensor"; -uint32_t BLESensor::hash_base() { return 343459825UL; } - void BLESensor::loop() {} void BLESensor::dump_config() { diff --git a/esphome/components/ble_client/sensor/ble_sensor.h b/esphome/components/ble_client/sensor/ble_sensor.h index d9f310b575..d3d037572a 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.h +++ b/esphome/components/ble_client/sensor/ble_sensor.h @@ -37,7 +37,6 @@ class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClie uint16_t handle; protected: - uint32_t hash_base() override; float parse_data_(uint8_t *value, uint16_t value_len); optional data_to_value_func_{}; bool notify_; diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp index c4d175faa4..1a71cd6cd8 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -14,8 +14,6 @@ static const char *const TAG = "ble_text_sensor"; static const std::string EMPTY = ""; -uint32_t BLETextSensor::hash_base() { return 193967603UL; } - void BLETextSensor::loop() {} void BLETextSensor::dump_config() { diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.h b/esphome/components/ble_client/text_sensor/ble_text_sensor.h index 37537307de..cb34043b46 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.h +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.h @@ -35,7 +35,6 @@ class BLETextSensor : public text_sensor::TextSensor, public PollingComponent, p uint16_t handle; protected: - uint32_t hash_base() override; bool notify_; espbt::ESPBTUUID service_uuid_; espbt::ESPBTUUID char_uuid_; diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp index 0a5c6567bc..3d58780d6d 100644 --- a/esphome/components/button/button.cpp +++ b/esphome/components/button/button.cpp @@ -15,7 +15,6 @@ void Button::press() { this->press_callback_.call(); } void Button::add_on_press_callback(std::function &&callback) { this->press_callback_.add(std::move(callback)); } -uint32_t Button::hash_base() { return 1495763804UL; } void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } std::string Button::get_device_class() { return this->device_class_; } diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h index f60c2a2bc5..398d398cd9 100644 --- a/esphome/components/button/button.h +++ b/esphome/components/button/button.h @@ -47,8 +47,6 @@ class Button : public EntityBase { */ virtual void press_action() = 0; - uint32_t hash_base() override; - CallbackManager press_callback_{}; std::string device_class_{}; }; diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 65b5ef4eb4..776a54f59d 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -419,7 +419,6 @@ void Climate::publish_state() { // Save state this->save_state_(); } -uint32_t Climate::hash_base() { return 3104134496UL; } ClimateTraits Climate::get_traits() { auto traits = this->traits(); diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 852b76686c..d508bb31b0 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -282,7 +282,6 @@ class Climate : public EntityBase { */ void save_state_(); - uint32_t hash_base() override; void dump_traits_(const char *tag); CallbackManager state_callback_{}; diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 21f35f14de..3d3abffae2 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -33,8 +33,6 @@ const char *cover_operation_to_str(CoverOperation op) { Cover::Cover(const std::string &name) : EntityBase(name), position{COVER_OPEN} {} -uint32_t Cover::hash_base() { return 1727367479UL; } - CoverCall::CoverCall(Cover *parent) : parent_(parent) {} CoverCall &CoverCall::set_command(const char *command) { if (strcasecmp(command, "OPEN") == 0) { diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 1b5d3a8fa1..27a821215a 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -177,7 +177,6 @@ class Cover : public EntityBase { virtual std::string device_class(); optional restore_state_(); - uint32_t hash_base() override; CallbackManager state_callback_{}; optional device_class_override_{}; diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 33f55aa3d1..dc6bbdf350 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -85,6 +85,12 @@ enum ImageType { IMAGE_TYPE_RGB565 = 4, }; +enum DisplayType { + DISPLAY_TYPE_BINARY = 1, + DISPLAY_TYPE_GRAYSCALE = 2, + DISPLAY_TYPE_COLOR = 3, +}; + enum DisplayRotation { DISPLAY_ROTATION_0_DEGREES = 0, DISPLAY_ROTATION_90_DEGREES = 90, @@ -361,6 +367,11 @@ class DisplayBuffer { virtual int get_width_internal() = 0; DisplayRotation get_rotation() const { return this->rotation_; } + /** Get the type of display that the buffer corresponds to. In case of dynamically configurable displays, + * returns the type the display is currently configured to. + */ + virtual DisplayType get_display_type() = 0; + protected: void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg); diff --git a/esphome/components/display/display_color_utils.h b/esphome/components/display/display_color_utils.h index 202de912de..bf6d5445f1 100644 --- a/esphome/components/display/display_color_utils.h +++ b/esphome/components/display/display_color_utils.h @@ -66,6 +66,9 @@ class ColorUtil { } return color_return; } + static inline Color rgb332_to_color(uint8_t rgb332_color) { + return to_color((uint32_t) rgb332_color, COLOR_ORDER_RGB, COLOR_BITNESS_332); + } static uint8_t color_to_332(Color color, ColorOrder color_order = ColorOrder::COLOR_ORDER_RGB) { uint16_t red_color, green_color, blue_color; @@ -100,11 +103,57 @@ class ColorUtil { } return 0; } - static uint32_t color_to_grayscale4(Color color) { uint32_t gs4 = esp_scale8(color.white, 15); return gs4; } + /*** + * Converts a Color value to an 8bit index using a 24bit 888 palette. + * Uses euclidiean distance to calculate the linear distance between + * two points in an RGB cube, then iterates through the full palette + * returning the closest match. + * @param[in] color The target color. + * @param[in] palette The 256*3 byte RGB palette. + * @return The 8 bit index of the closest color (e.g. for display buffer). + */ + // static uint8_t color_to_index8_palette888(Color color, uint8_t *palette) { + static uint8_t color_to_index8_palette888(Color color, const uint8_t *palette) { + uint8_t closest_index = 0; + uint32_t minimum_dist2 = UINT32_MAX; // Smallest distance^2 to the target + // so far + // int8_t(*plt)[][3] = palette; + int16_t tgt_r = color.r; + int16_t tgt_g = color.g; + int16_t tgt_b = color.b; + uint16_t x, y, z; + // Loop through each row of the palette + for (uint16_t i = 0; i < 256; i++) { + // Get the pallet rgb color + int16_t plt_r = (int16_t) palette[i * 3 + 0]; + int16_t plt_g = (int16_t) palette[i * 3 + 1]; + int16_t plt_b = (int16_t) palette[i * 3 + 2]; + // Calculate euclidian distance (linear distance in rgb cube). + x = (uint32_t) std::abs(tgt_r - plt_r); + y = (uint32_t) std::abs(tgt_g - plt_g); + z = (uint32_t) std::abs(tgt_b - plt_b); + uint32_t dist2 = x * x + y * y + z * z; + if (dist2 < minimum_dist2) { + minimum_dist2 = dist2; + closest_index = (uint8_t) i; + } + } + return closest_index; + } + /*** + * Converts an 8bit palette index (e.g. from a display buffer) to a color. + * @param[in] index The index to look up. + * @param[in] palette The 256*3 byte RGB palette. + * @return The RGBW Color object looked up by the palette. + */ + static Color index8_to_color_palette888(uint8_t index, const uint8_t *palette) { + Color color = Color(palette[index * 3 + 0], palette[index * 3 + 1], palette[index * 3 + 2], 0); + return color; + } }; } // namespace display } // namespace esphome diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index a78159825e..aa03c5acc7 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -156,7 +156,7 @@ class ESP32Preferences : public ESPPreferences { ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err)); return true; } - return to_save.data == stored_data.data; + return to_save.data != stored_data.data; } }; diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 912e705766..753b6ed9da 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome import automation from esphome import pins from esphome.const import ( CONF_FREQUENCY, @@ -12,6 +13,7 @@ from esphome.const import ( CONF_RESOLUTION, CONF_BRIGHTNESS, CONF_CONTRAST, + CONF_TRIGGER_ID, ) from esphome.core import CORE from esphome.components.esp32 import add_idf_sdkconfig_option @@ -23,7 +25,14 @@ AUTO_LOAD = ["psram"] esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) - +ESP32CameraStreamStartTrigger = esp32_camera_ns.class_( + "ESP32CameraStreamStartTrigger", + automation.Trigger.template(), +) +ESP32CameraStreamStopTrigger = esp32_camera_ns.class_( + "ESP32CameraStreamStopTrigger", + automation.Trigger.template(), +) ESP32CameraFrameSize = esp32_camera_ns.enum("ESP32CameraFrameSize") FRAME_SIZES = { "160X120": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_160X120, @@ -111,6 +120,10 @@ CONF_TEST_PATTERN = "test_pattern" CONF_MAX_FRAMERATE = "max_framerate" CONF_IDLE_FRAMERATE = "idle_framerate" +# stream trigger +CONF_ON_STREAM_START = "on_stream_start" +CONF_ON_STREAM_STOP = "on_stream_stop" + camera_range_param = cv.int_range(min=-2, max=2) CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( @@ -178,6 +191,20 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All( cv.framerate, cv.Range(min=0, max=1) ), + cv.Optional(CONF_ON_STREAM_START): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + ESP32CameraStreamStartTrigger + ), + } + ), + cv.Optional(CONF_ON_STREAM_STOP): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + ESP32CameraStreamStopTrigger + ), + } + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -238,3 +265,11 @@ async def to_code(config): if CORE.using_esp_idf: cg.add_library("espressif/esp32-camera", "1.0.0") add_idf_sdkconfig_option("CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC", True) + + for conf in config.get(CONF_ON_STREAM_START, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_STREAM_STOP, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 851926b083..165c00c960 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -282,8 +282,20 @@ void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) { void ESP32Camera::add_image_callback(std::function)> &&f) { this->new_image_callback_.add(std::move(f)); } -void ESP32Camera::start_stream(CameraRequester requester) { this->stream_requesters_ |= (1U << requester); } -void ESP32Camera::stop_stream(CameraRequester requester) { this->stream_requesters_ &= ~(1U << requester); } +void ESP32Camera::add_stream_start_callback(std::function &&callback) { + this->stream_start_callback_.add(std::move(callback)); +} +void ESP32Camera::add_stream_stop_callback(std::function &&callback) { + this->stream_stop_callback_.add(std::move(callback)); +} +void ESP32Camera::start_stream(CameraRequester requester) { + this->stream_start_callback_.call(); + this->stream_requesters_ |= (1U << requester); +} +void ESP32Camera::stop_stream(CameraRequester requester) { + this->stream_stop_callback_.call(); + this->stream_requesters_ &= ~(1U << requester); +} void ESP32Camera::request_image(CameraRequester requester) { this->single_requesters_ |= (1U << requester); } void ESP32Camera::update_camera_parameters() { sensor_t *s = esp_camera_sensor_get(); @@ -310,7 +322,6 @@ void ESP32Camera::update_camera_parameters() { } /* ---------------- Internal methods ---------------- */ -uint32_t ESP32Camera::hash_base() { return 3010542557UL; } bool ESP32Camera::has_requested_image_() const { return this->single_requesters_ || this->stream_requesters_; } bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; } void ESP32Camera::framebuffer_task(void *pv) { diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 743b5bde5f..87c5b0ba4a 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -2,6 +2,7 @@ #ifdef USE_ESP32 +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" @@ -145,9 +146,11 @@ class ESP32Camera : public Component, public EntityBase { void request_image(CameraRequester requester); void update_camera_parameters(); + void add_stream_start_callback(std::function &&callback); + void add_stream_stop_callback(std::function &&callback); + protected: /* internal methods */ - uint32_t hash_base() override; bool has_requested_image_() const; bool can_return_image_() const; @@ -187,6 +190,8 @@ class ESP32Camera : public Component, public EntityBase { QueueHandle_t framebuffer_get_queue_; QueueHandle_t framebuffer_return_queue_; CallbackManager)> new_image_callback_; + CallbackManager stream_start_callback_{}; + CallbackManager stream_stop_callback_{}; uint32_t last_idle_request_{0}; uint32_t last_update_{0}; @@ -195,6 +200,23 @@ class ESP32Camera : public Component, public EntityBase { // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern ESP32Camera *global_esp32_camera; +class ESP32CameraStreamStartTrigger : public Trigger<> { + public: + explicit ESP32CameraStreamStartTrigger(ESP32Camera *parent) { + parent->add_stream_start_callback([this]() { this->trigger(); }); + } + + protected: +}; +class ESP32CameraStreamStopTrigger : public Trigger<> { + public: + explicit ESP32CameraStreamStopTrigger(ESP32Camera *parent) { + parent->add_stream_stop_callback([this]() { this->trigger(); }); + } + + protected: +}; + } // namespace esp32_camera } // namespace esphome diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index 5f9660f6d6..961ed6142a 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -1,5 +1,4 @@ #include "fan.h" -#include "fan_helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -61,22 +60,6 @@ void FanCall::validate_() { } } -// This whole method is deprecated, don't warn about usage of deprecated methods inside of it. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -FanCall &FanCall::set_speed(const char *legacy_speed) { - const auto supported_speed_count = this->parent_.get_traits().supported_speed_count(); - if (strcasecmp(legacy_speed, "low") == 0) { - this->set_speed(fan::speed_enum_to_level(FAN_SPEED_LOW, supported_speed_count)); - } else if (strcasecmp(legacy_speed, "medium") == 0) { - this->set_speed(fan::speed_enum_to_level(FAN_SPEED_MEDIUM, supported_speed_count)); - } else if (strcasecmp(legacy_speed, "high") == 0) { - this->set_speed(fan::speed_enum_to_level(FAN_SPEED_HIGH, supported_speed_count)); - } - return *this; -} -#pragma GCC diagnostic pop - FanCall FanRestoreState::to_call(Fan &fan) { auto call = fan.make_call(); call.set_state(this->state); @@ -169,7 +152,6 @@ void Fan::dump_traits_(const char *tag, const char *prefix) { if (this->get_traits().supports_direction()) ESP_LOGCONFIG(tag, "%s Direction: YES", prefix); } -uint32_t Fan::hash_base() { return 418001110UL; } } // namespace fan } // namespace esphome diff --git a/esphome/components/fan/fan.h b/esphome/components/fan/fan.h index cafb5843d1..337bb3935a 100644 --- a/esphome/components/fan/fan.h +++ b/esphome/components/fan/fan.h @@ -16,13 +16,6 @@ namespace fan { (obj)->dump_traits_(TAG, prefix); \ } -/// Simple enum to represent the speed of a fan. - DEPRECATED - Will be deleted soon -enum ESPDEPRECATED("FanSpeed is deprecated.", "2021.9") FanSpeed { - FAN_SPEED_LOW = 0, ///< The fan is running on low speed. - FAN_SPEED_MEDIUM = 1, ///< The fan is running on medium speed. - FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed. -}; - /// Simple enum to represent the direction of a fan. enum class FanDirection { FORWARD = 0, REVERSE = 1 }; @@ -143,7 +136,6 @@ class Fan : public EntityBase { void save_state_(); void dump_traits_(const char *tag, const char *prefix); - uint32_t hash_base() override; CallbackManager state_callback_{}; ESPPreferenceObject rtc_; diff --git a/esphome/components/fan/fan_helpers.cpp b/esphome/components/fan/fan_helpers.cpp deleted file mode 100644 index 34883617e6..0000000000 --- a/esphome/components/fan/fan_helpers.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include "fan_helpers.h" - -namespace esphome { -namespace fan { - -// This whole file is deprecated, don't warn about usage of deprecated types in here. -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - -FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels) { - const auto speed_ratio = static_cast(speed_level) / (supported_speed_levels + 1); - const auto legacy_level = clamp(static_cast(ceilf(speed_ratio * 3)), 1, 3); - return static_cast(legacy_level - 1); -} - -int speed_enum_to_level(FanSpeed speed, int supported_speed_levels) { - const auto enum_level = static_cast(speed) + 1; - const auto speed_level = roundf(enum_level / 3.0f * supported_speed_levels); - return static_cast(speed_level); -} - -} // namespace fan -} // namespace esphome diff --git a/esphome/components/fan/fan_helpers.h b/esphome/components/fan/fan_helpers.h deleted file mode 100644 index 8e8e3859bd..0000000000 --- a/esphome/components/fan/fan_helpers.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "fan.h" - -namespace esphome { -namespace fan { - -// Shut-up about usage of deprecated FanSpeed for a bit. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - -ESPDEPRECATED("FanSpeed and speed_level_to_enum() are deprecated.", "2021.9") -FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels); -ESPDEPRECATED("FanSpeed and speed_enum_to_level() are deprecated.", "2021.9") -int speed_enum_to_level(FanSpeed speed, int supported_speed_levels); - -#pragma GCC diagnostic pop - -} // namespace fan -} // namespace esphome diff --git a/esphome/components/hbridge/fan/hbridge_fan.cpp b/esphome/components/hbridge/fan/hbridge_fan.cpp index 52d2b3d8b7..44cf5ae049 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.cpp +++ b/esphome/components/hbridge/fan/hbridge_fan.cpp @@ -1,5 +1,4 @@ #include "hbridge_fan.h" -#include "esphome/components/fan/fan_helpers.h" #include "esphome/core/log.h" namespace esphome { diff --git a/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp b/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp index 3ed65831ae..25d2617677 100644 --- a/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp +++ b/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp @@ -195,7 +195,7 @@ void HydreonRGxxComponent::process_line_() { if (n == std::string::npos) { continue; } - int data = strtol(this->buffer_.substr(n + strlen(PROTOCOL_NAMES[i])).c_str(), nullptr, 10); + float data = strtof(this->buffer_.substr(n + strlen(PROTOCOL_NAMES[i])).c_str(), nullptr); this->sensors_[i]->publish_state(data); ESP_LOGD(TAG, "Received %s: %f", PROTOCOL_NAMES[i], this->sensors_[i]->get_raw_state()); this->sensors_received_ |= (1 << i); diff --git a/esphome/components/hydreon_rgxx/sensor.py b/esphome/components/hydreon_rgxx/sensor.py index 409500305a..c604f8d3c1 100644 --- a/esphome/components/hydreon_rgxx/sensor.py +++ b/esphome/components/hydreon_rgxx/sensor.py @@ -37,7 +37,7 @@ SUPPORTED_SENSORS = { PROTOCOL_NAMES = { CONF_MOISTURE: "R", CONF_ACC: "Acc", - CONF_R_INT: "Rint", + CONF_R_INT: "RInt", CONF_EVENT_ACC: "EventAcc", CONF_TOTAL_ACC: "TotalAcc", } diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/i2s_audio/i2s_audio_media_player.cpp b/esphome/components/i2s_audio/i2s_audio_media_player.cpp new file mode 100644 index 0000000000..9ddc8419bf --- /dev/null +++ b/esphome/components/i2s_audio/i2s_audio_media_player.cpp @@ -0,0 +1,140 @@ +#include "i2s_audio_media_player.h" + +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#include "esphome/core/log.h" + +namespace esphome { +namespace i2s_audio { + +static const char *const TAG = "audio"; + +void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) { + if (call.get_media_url().has_value()) { + if (this->audio_->isRunning()) + this->audio_->stopSong(); + this->high_freq_.start(); + this->audio_->connecttohost(call.get_media_url().value().c_str()); + this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; + } + if (call.get_volume().has_value()) { + this->volume = call.get_volume().value(); + this->set_volume_(volume); + this->unmute_(); + } + if (call.get_command().has_value()) { + switch (call.get_command().value()) { + case media_player::MEDIA_PLAYER_COMMAND_PLAY: + if (!this->audio_->isRunning()) + this->audio_->pauseResume(); + this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; + break; + case media_player::MEDIA_PLAYER_COMMAND_PAUSE: + if (this->audio_->isRunning()) + this->audio_->pauseResume(); + this->state = media_player::MEDIA_PLAYER_STATE_PAUSED; + break; + case media_player::MEDIA_PLAYER_COMMAND_STOP: + this->stop_(); + break; + case media_player::MEDIA_PLAYER_COMMAND_MUTE: + this->mute_(); + break; + case media_player::MEDIA_PLAYER_COMMAND_UNMUTE: + this->unmute_(); + break; + case media_player::MEDIA_PLAYER_COMMAND_TOGGLE: + this->audio_->pauseResume(); + if (this->audio_->isRunning()) { + this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; + } else { + this->state = media_player::MEDIA_PLAYER_STATE_PAUSED; + } + break; + } + } + this->publish_state(); +} + +void I2SAudioMediaPlayer::mute_() { + if (this->mute_pin_ != nullptr) { + this->mute_pin_->digital_write(true); + } else { + this->set_volume_(0.0f, false); + } + this->muted_ = true; +} +void I2SAudioMediaPlayer::unmute_() { + if (this->mute_pin_ != nullptr) { + this->mute_pin_->digital_write(false); + } else { + this->set_volume_(this->volume, false); + } + this->muted_ = false; +} +void I2SAudioMediaPlayer::set_volume_(float volume, bool publish) { + this->audio_->setVolume(remap(volume, 0.0f, 1.0f, 0, 21)); + if (publish) + this->volume = volume; +} + +void I2SAudioMediaPlayer::stop_() { + if (this->audio_->isRunning()) + this->audio_->stopSong(); + this->high_freq_.stop(); + this->state = media_player::MEDIA_PLAYER_STATE_IDLE; +} + +void I2SAudioMediaPlayer::setup() { + ESP_LOGCONFIG(TAG, "Setting up Audio..."); + if (this->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) { + this->audio_ = make_unique