diff --git a/.github/actions/build-image/action.yaml b/.github/actions/build-image/action.yaml index 87ea28fd20..d36bd65bb6 100644 --- a/.github/actions/build-image/action.yaml +++ b/.github/actions/build-image/action.yaml @@ -34,6 +34,16 @@ runs: echo $l >> $GITHUB_OUTPUT done + # set cache-to only if dev branch + - id: cache-to + shell: bash + run: |- + if [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then + echo "value=type=gha,mode=max" >> $GITHUB_OUTPUT + else + echo "value=" >> $GITHUB_OUTPUT + fi + - name: Build and push to ghcr by digest id: build-ghcr uses: docker/build-push-action@v5.3.0 @@ -43,7 +53,7 @@ runs: platforms: ${{ inputs.platform }} target: ${{ inputs.target }} cache-from: type=gha - cache-to: type=gha,mode=max + cache-to: ${{ steps.cache-to.outputs.value }} build-args: | BASEIMGTYPE=${{ inputs.baseimg }} BUILD_VERSION=${{ inputs.version }} @@ -66,7 +76,7 @@ runs: platforms: ${{ inputs.platform }} target: ${{ inputs.target }} cache-from: type=gha - cache-to: type=gha,mode=max + cache-to: ${{ steps.cache-to.outputs.value }} build-args: | BASEIMGTYPE=${{ inputs.baseimg }} BUILD_VERSION=${{ inputs.version }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbebc55676..ba0a8a363c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -364,12 +364,20 @@ jobs: with: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} + - name: Cache platformio + if: github.ref == 'refs/heads/dev' uses: actions/cache@v4.0.2 with: path: ~/.platformio - # yamllint disable-line rule:line-length - key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} + key: platformio-${{ matrix.pio_cache_key }} + + - name: Cache platformio + if: github.ref != 'refs/heads/dev' + uses: actions/cache/restore@v4.0.2 + with: + path: ~/.platformio + key: platformio-${{ matrix.pio_cache_key }} - name: Install clang-tidy run: sudo apt-get install clang-tidy-14 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6f4bb52104..ce226846e3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,3 +40,10 @@ repos: hooks: - id: clang-format types_or: [c, c++] + - repo: local + hooks: + - id: pylint + name: pylint + entry: pylint + language: system + types: [python] diff --git a/CODEOWNERS b/CODEOWNERS index c630db7948..88f875f368 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -135,6 +135,7 @@ esphome/components/fs3000/* @kahrendt esphome/components/ft5x06/* @clydebarrow esphome/components/ft63x6/* @gpambrozio esphome/components/gcja5/* @gcormier +esphome/components/gdk101/* @Szewcson esphome/components/globals/* @esphome/core esphome/components/gp8403/* @jesserockz esphome/components/gpio/* @esphome/core diff --git a/esphome/__main__.py b/esphome/__main__.py index 54c1aa112a..daf74eebb0 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -65,7 +65,7 @@ def choose_prompt(options, purpose: str = None): f'Found multiple options{f" for {purpose}" if purpose else ""}, please choose one:' ) for i, (desc, _) in enumerate(options): - safe_print(f" [{i+1}] {desc}") + safe_print(f" [{i + 1}] {desc}") while True: opt = input("(number): ") diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 9151d6e56d..a7a955bead 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -157,7 +157,7 @@ async def to_code(config): pixels = list(frame.getdata()) if len(pixels) != height * width: 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: if transparent: @@ -180,7 +180,7 @@ async def to_code(config): pixels = list(frame.getdata()) if len(pixels) != height * width: 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: data[pos] = pix[0] @@ -203,7 +203,7 @@ async def to_code(config): pixels = list(frame.getdata()) if len(pixels) != height * width: 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: if transparent: @@ -232,7 +232,7 @@ async def to_code(config): pixels = list(frame.getdata()) if len(pixels) != height * width: 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: R = r >> 3 diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index b8073abc19..774ca7ed9b 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1147,6 +1147,9 @@ message MediaPlayerCommandRequest { bool has_media_url = 6; string media_url = 7; + + bool has_announcement = 8; + bool announcement = 9; } // ==================== BLUETOOTH ==================== diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index b31212bbdb..2804dba31f 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1002,7 +1002,11 @@ bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_pla MediaPlayerStateResponse resp{}; resp.key = media_player->get_object_id_hash(); - resp.state = static_cast(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(report_state); resp.volume = media_player->volume; resp.muted = media_player->is_muted(); 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) { call.set_media_url(msg.media_url); } + if (msg.has_announcement) { + call.set_announcement(msg.announcement); + } call.perform(); } #endif diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 6ec1870d72..a48087e348 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -5253,6 +5253,14 @@ bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt val this->has_media_url = value.as_bool(); return true; } + case 8: { + this->has_announcement = value.as_bool(); + return true; + } + case 9: { + this->announcement = value.as_bool(); + return true; + } default: return false; } @@ -5289,6 +5297,8 @@ void MediaPlayerCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(5, this->volume); buffer.encode_bool(6, this->has_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 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("'").append(this->media_url).append("'"); 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("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 14fd95df37..807b150d82 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1298,6 +1298,8 @@ class MediaPlayerCommandRequest : public ProtoMessage { float volume{0.0f}; bool has_media_url{false}; std::string media_url{}; + bool has_announcement{false}; + bool announcement{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp index d2b8cc81f1..9e59810c7e 100644 --- a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +++ b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp @@ -15,6 +15,7 @@ void CST816Touchscreen::continue_setup_() { } switch (this->chip_id_) { case CST820_CHIP_ID: + case CST826_CHIP_ID: case CST716_CHIP_ID: case CST816S_CHIP_ID: case CST816D_CHIP_ID: @@ -90,6 +91,9 @@ void CST816Touchscreen::dump_config() { case CST820_CHIP_ID: name = "CST820"; break; + case CST826_CHIP_ID: + name = "CST826"; + break; case CST816S_CHIP_ID: name = "CST816S"; break; diff --git a/esphome/components/cst816/touchscreen/cst816_touchscreen.h b/esphome/components/cst816/touchscreen/cst816_touchscreen.h index 0d987f2739..24e664e7ee 100644 --- a/esphome/components/cst816/touchscreen/cst816_touchscreen.h +++ b/esphome/components/cst816/touchscreen/cst816_touchscreen.h @@ -24,6 +24,7 @@ static const uint8_t REG_SLEEP = 0xE5; static const uint8_t REG_IRQ_CTL = 0xFA; static const uint8_t IRQ_EN_MOTION = 0x70; +static const uint8_t CST826_CHIP_ID = 0x11; static const uint8_t CST820_CHIP_ID = 0xB7; static const uint8_t CST816S_CHIP_ID = 0xB4; static const uint8_t CST816D_CHIP_ID = 0xB6; diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index f22a8a2e5d..cbd4249d92 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -8,62 +8,16 @@ #include #include -#ifdef USE_ESP32 - -#include -#include - -#include -#if defined(USE_ESP32_VARIANT_ESP32) -#include -#elif defined(USE_ESP32_VARIANT_ESP32C3) -#include -#elif defined(USE_ESP32_VARIANT_ESP32C6) -#include -#elif defined(USE_ESP32_VARIANT_ESP32S2) -#include -#elif defined(USE_ESP32_VARIANT_ESP32S3) -#include -#endif - -#endif // USE_ESP32 - -#ifdef USE_ARDUINO -#ifdef USE_RP2040 -#include -#elif defined(USE_ESP32) || defined(USE_ESP8266) -#include -#endif -#endif - namespace esphome { namespace debug { static const char *const TAG = "debug"; -static uint32_t get_free_heap() { -#if defined(USE_ESP8266) - return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) -#elif defined(USE_ESP32) - return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); -#elif defined(USE_RP2040) - return rp2040.getFreeHeap(); -#elif defined(USE_LIBRETINY) - return lt_heap_get_free(); -#elif defined(USE_HOST) - return INT_MAX; -#endif -} - void DebugComponent::dump_config() { #ifndef ESPHOME_LOG_HAS_DEBUG return; // Can't log below if debug logging is disabled #endif - std::string device_info; - std::string reset_reason; - device_info.reserve(256); - ESP_LOGCONFIG(TAG, "Debug component:"); #ifdef USE_TEXT_SENSOR LOG_TEXT_SENSOR(" ", "Device info", this->device_info_); @@ -76,305 +30,15 @@ void DebugComponent::dump_config() { #endif // defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) #endif // USE_SENSOR + std::string device_info; + device_info.reserve(256); ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); device_info += ESPHOME_VERSION; - this->free_heap_ = get_free_heap(); + this->free_heap_ = get_free_heap_(); ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_); -#if defined(USE_ARDUINO) && (defined(USE_ESP32) || defined(USE_ESP8266)) - const char *flash_mode; - switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance) - case FM_QIO: - flash_mode = "QIO"; - break; - case FM_QOUT: - flash_mode = "QOUT"; - break; - case FM_DIO: - flash_mode = "DIO"; - break; - case FM_DOUT: - flash_mode = "DOUT"; - break; -#ifdef USE_ESP32 - case FM_FAST_READ: - flash_mode = "FAST_READ"; - break; - case FM_SLOW_READ: - flash_mode = "SLOW_READ"; - break; -#endif - default: - flash_mode = "UNKNOWN"; - } - ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", - ESP.getFlashChipSize() / 1024, // NOLINT - ESP.getFlashChipSpeed() / 1000000, flash_mode); // NOLINT - device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT - "kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT - device_info += flash_mode; -#endif // USE_ARDUINO && (USE_ESP32 || USE_ESP8266) - -#ifdef USE_ESP32 - esp_chip_info_t info; - esp_chip_info(&info); - const char *model; -#if defined(USE_ESP32_VARIANT_ESP32) - model = "ESP32"; -#elif defined(USE_ESP32_VARIANT_ESP32C3) - model = "ESP32-C3"; -#elif defined(USE_ESP32_VARIANT_ESP32C6) - model = "ESP32-C6"; -#elif defined(USE_ESP32_VARIANT_ESP32S2) - model = "ESP32-S2"; -#elif defined(USE_ESP32_VARIANT_ESP32S3) - model = "ESP32-S3"; -#elif defined(USE_ESP32_VARIANT_ESP32H2) - model = "ESP32-H2"; -#else - model = "UNKNOWN"; -#endif - std::string features; - if (info.features & CHIP_FEATURE_EMB_FLASH) { - features += "EMB_FLASH,"; - info.features &= ~CHIP_FEATURE_EMB_FLASH; - } - if (info.features & CHIP_FEATURE_WIFI_BGN) { - features += "WIFI_BGN,"; - info.features &= ~CHIP_FEATURE_WIFI_BGN; - } - if (info.features & CHIP_FEATURE_BLE) { - features += "BLE,"; - info.features &= ~CHIP_FEATURE_BLE; - } - if (info.features & CHIP_FEATURE_BT) { - features += "BT,"; - info.features &= ~CHIP_FEATURE_BT; - } - if (info.features & CHIP_FEATURE_EMB_PSRAM) { - features += "EMB_PSRAM,"; - info.features &= ~CHIP_FEATURE_EMB_PSRAM; - } - if (info.features) - features += "Other:" + format_hex(info.features); - ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores, - info.revision); - device_info += "|Chip: "; - device_info += model; - device_info += " Features:"; - device_info += features; - device_info += " Cores:" + to_string(info.cores); - device_info += " Revision:" + to_string(info.revision); - - ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version()); - device_info += "|ESP-IDF: "; - device_info += esp_get_idf_version(); - - std::string mac = get_mac_address_pretty(); - ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str()); - device_info += "|EFuse MAC: "; - device_info += mac; - - switch (rtc_get_reset_reason(0)) { - case POWERON_RESET: - reset_reason = "Power On Reset"; - break; -#if defined(USE_ESP32_VARIANT_ESP32) - case SW_RESET: -#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - case RTC_SW_SYS_RESET: -#endif - reset_reason = "Software Reset Digital Core"; - break; -#if defined(USE_ESP32_VARIANT_ESP32) - case OWDT_RESET: - reset_reason = "Watch Dog Reset Digital Core"; - break; -#endif - case DEEPSLEEP_RESET: - reset_reason = "Deep Sleep Reset Digital Core"; - break; -#if defined(USE_ESP32_VARIANT_ESP32) - case SDIO_RESET: - reset_reason = "SLC Module Reset Digital Core"; - break; -#endif - case TG0WDT_SYS_RESET: - reset_reason = "Timer Group 0 Watch Dog Reset Digital Core"; - break; - case TG1WDT_SYS_RESET: - reset_reason = "Timer Group 1 Watch Dog Reset Digital Core"; - break; - case RTCWDT_SYS_RESET: - reset_reason = "RTC Watch Dog Reset Digital Core"; - break; -#if !defined(USE_ESP32_VARIANT_ESP32C6) - case INTRUSION_RESET: - reset_reason = "Intrusion Reset CPU"; - break; -#endif -#if defined(USE_ESP32_VARIANT_ESP32) - case TGWDT_CPU_RESET: - reset_reason = "Timer Group Reset CPU"; - break; -#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - case TG0WDT_CPU_RESET: - reset_reason = "Timer Group 0 Reset CPU"; - break; -#endif -#if defined(USE_ESP32_VARIANT_ESP32) - case SW_CPU_RESET: -#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - case RTC_SW_CPU_RESET: -#endif - reset_reason = "Software Reset CPU"; - break; - case RTCWDT_CPU_RESET: - reset_reason = "RTC Watch Dog Reset CPU"; - break; -#if defined(USE_ESP32_VARIANT_ESP32) - case EXT_CPU_RESET: - reset_reason = "External CPU Reset"; - break; -#endif - case RTCWDT_BROWN_OUT_RESET: - reset_reason = "Voltage Unstable Reset"; - break; - case RTCWDT_RTC_RESET: - reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module"; - break; -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - case TG1WDT_CPU_RESET: - reset_reason = "Timer Group 1 Reset CPU"; - break; - case SUPER_WDT_RESET: - reset_reason = "Super Watchdog Reset Digital Core And RTC Module"; - break; - case GLITCH_RTC_RESET: - reset_reason = "Glitch Reset Digital Core And RTC Module"; - break; - case EFUSE_RESET: - reset_reason = "eFuse Reset Digital Core"; - break; -#endif -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) - case USB_UART_CHIP_RESET: - reset_reason = "USB UART Reset Digital Core"; - break; - case USB_JTAG_CHIP_RESET: - reset_reason = "USB JTAG Reset Digital Core"; - break; - case POWER_GLITCH_RESET: - reset_reason = "Power Glitch Reset Digital Core And RTC Module"; - break; -#endif - default: - reset_reason = "Unknown Reset Reason"; - } - ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); - device_info += "|Reset: "; - device_info += reset_reason; - - const char *wakeup_reason; - switch (rtc_get_wakeup_cause()) { - case NO_SLEEP: - wakeup_reason = "No Sleep"; - break; - case EXT_EVENT0_TRIG: - wakeup_reason = "External Event 0"; - break; - case EXT_EVENT1_TRIG: - wakeup_reason = "External Event 1"; - break; - case GPIO_TRIG: - wakeup_reason = "GPIO"; - break; - case TIMER_EXPIRE: - wakeup_reason = "Wakeup Timer"; - break; - case SDIO_TRIG: - wakeup_reason = "SDIO"; - break; - case MAC_TRIG: - wakeup_reason = "MAC"; - break; - case UART0_TRIG: - wakeup_reason = "UART0"; - break; - case UART1_TRIG: - wakeup_reason = "UART1"; - break; - case TOUCH_TRIG: - wakeup_reason = "Touch"; - break; - case SAR_TRIG: - wakeup_reason = "SAR"; - break; - case BT_TRIG: - wakeup_reason = "BT"; - break; - default: - wakeup_reason = "Unknown"; - } - ESP_LOGD(TAG, "Wakeup Reason: %s", wakeup_reason); - device_info += "|Wakeup: "; - device_info += wakeup_reason; -#endif - -#if defined(USE_ESP8266) && !defined(CLANG_TIDY) - ESP_LOGD(TAG, "Chip ID: 0x%08X", ESP.getChipId()); - ESP_LOGD(TAG, "SDK Version: %s", ESP.getSdkVersion()); - ESP_LOGD(TAG, "Core Version: %s", ESP.getCoreVersion().c_str()); - ESP_LOGD(TAG, "Boot Version=%u Mode=%u", ESP.getBootVersion(), ESP.getBootMode()); - ESP_LOGD(TAG, "CPU Frequency: %u", ESP.getCpuFreqMHz()); - ESP_LOGD(TAG, "Flash Chip ID=0x%08X", ESP.getFlashChipId()); - ESP_LOGD(TAG, "Reset Reason: %s", ESP.getResetReason().c_str()); - ESP_LOGD(TAG, "Reset Info: %s", ESP.getResetInfo().c_str()); - - device_info += "|Chip: 0x" + format_hex(ESP.getChipId()); - device_info += "|SDK: "; - device_info += ESP.getSdkVersion(); - device_info += "|Core: "; - device_info += ESP.getCoreVersion().c_str(); - device_info += "|Boot: "; - device_info += to_string(ESP.getBootVersion()); - device_info += "|Mode: " + to_string(ESP.getBootMode()); - device_info += "|CPU: " + to_string(ESP.getCpuFreqMHz()); - device_info += "|Flash: 0x" + format_hex(ESP.getFlashChipId()); - device_info += "|Reset: "; - device_info += ESP.getResetReason().c_str(); - device_info += "|"; - device_info += ESP.getResetInfo().c_str(); - - reset_reason = ESP.getResetReason().c_str(); -#endif - -#ifdef USE_RP2040 - ESP_LOGD(TAG, "CPU Frequency: %u", rp2040.f_cpu()); - device_info += "CPU Frequency: " + to_string(rp2040.f_cpu()); -#endif // USE_RP2040 - -#ifdef USE_LIBRETINY - ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version()); - ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz()); - ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id()); - ESP_LOGD(TAG, "Board: %s", lt_get_board_code()); - ESP_LOGD(TAG, "Flash: %u KiB / RAM: %u KiB", lt_flash_get_size() / 1024, lt_ram_get_size() / 1024); - ESP_LOGD(TAG, "Reset Reason: %s", lt_get_reboot_reason_name(lt_get_reboot_reason())); - - device_info += "|Version: "; - device_info += LT_BANNER_STR + 10; - device_info += "|Reset Reason: "; - device_info += lt_get_reboot_reason_name(lt_get_reboot_reason()); - device_info += "|Chip Name: "; - device_info += lt_cpu_get_model_name(); - device_info += "|Chip ID: 0x" + format_hex(lt_cpu_get_mac_id()); - device_info += "|Flash: " + to_string(lt_flash_get_size() / 1024) + " KiB"; - device_info += "|RAM: " + to_string(lt_ram_get_size() / 1024) + " KiB"; - - reset_reason = lt_get_reboot_reason_name(lt_get_reboot_reason()); -#endif // USE_LIBRETINY + get_device_info_(device_info); #ifdef USE_TEXT_SENSOR if (this->device_info_ != nullptr) { @@ -383,14 +47,14 @@ void DebugComponent::dump_config() { this->device_info_->publish_state(device_info); } if (this->reset_reason_ != nullptr) { - this->reset_reason_->publish_state(reset_reason); + this->reset_reason_->publish_state(get_reset_reason_()); } #endif // USE_TEXT_SENSOR } void DebugComponent::loop() { // log when free heap space has halved - uint32_t new_free_heap = get_free_heap(); + uint32_t new_free_heap = get_free_heap_(); if (new_free_heap < this->free_heap_ / 2) { this->free_heap_ = new_free_heap; ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_); @@ -411,38 +75,16 @@ void DebugComponent::loop() { void DebugComponent::update() { #ifdef USE_SENSOR if (this->free_sensor_ != nullptr) { - this->free_sensor_->publish_state(get_free_heap()); + this->free_sensor_->publish_state(get_free_heap_()); } - if (this->block_sensor_ != nullptr) { -#if defined(USE_ESP8266) - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - this->block_sensor_->publish_state(ESP.getMaxFreeBlockSize()); -#elif defined(USE_ESP32) - this->block_sensor_->publish_state(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL)); -#elif defined(USE_LIBRETINY) - this->block_sensor_->publish_state(lt_heap_get_max_alloc()); -#endif - } - -#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) - if (this->fragmentation_sensor_ != nullptr) { - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - this->fragmentation_sensor_->publish_state(ESP.getHeapFragmentation()); - } -#endif - if (this->loop_time_sensor_ != nullptr) { this->loop_time_sensor_->publish_state(this->max_loop_time_); this->max_loop_time_ = 0; } -#ifdef USE_ESP32 - if (this->psram_sensor_ != nullptr) { - this->psram_sensor_->publish_state(heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); - } -#endif // USE_ESP32 #endif // USE_SENSOR + update_platform_(); } float DebugComponent::get_setup_priority() const { return setup_priority::LATE; } diff --git a/esphome/components/debug/debug_component.h b/esphome/components/debug/debug_component.h index 93e3ba4857..2b54406603 100644 --- a/esphome/components/debug/debug_component.h +++ b/esphome/components/debug/debug_component.h @@ -59,6 +59,11 @@ class DebugComponent : public PollingComponent { text_sensor::TextSensor *device_info_{nullptr}; text_sensor::TextSensor *reset_reason_{nullptr}; #endif // USE_TEXT_SENSOR + + std::string get_reset_reason_(); + uint32_t get_free_heap_(); + void get_device_info_(std::string &device_info); + void update_platform_(); }; } // namespace debug diff --git a/esphome/components/debug/debug_esp32.cpp b/esphome/components/debug/debug_esp32.cpp new file mode 100644 index 0000000000..cfdfdd2a61 --- /dev/null +++ b/esphome/components/debug/debug_esp32.cpp @@ -0,0 +1,287 @@ +#include "debug_component.h" +#ifdef USE_ESP32 +#include "esphome/core/log.h" + +#include +#include +#include + +#if defined(USE_ESP32_VARIANT_ESP32) +#include +#elif defined(USE_ESP32_VARIANT_ESP32C3) +#include +#elif defined(USE_ESP32_VARIANT_ESP32C6) +#include +#elif defined(USE_ESP32_VARIANT_ESP32S2) +#include +#elif defined(USE_ESP32_VARIANT_ESP32S3) +#include +#endif +#ifdef USE_ARDUINO +#include +#endif + +namespace esphome { +namespace debug { + +static const char *const TAG = "debug"; + +std::string DebugComponent::get_reset_reason_() { + std::string reset_reason; + switch (rtc_get_reset_reason(0)) { + case POWERON_RESET: + reset_reason = "Power On Reset"; + break; +#if defined(USE_ESP32_VARIANT_ESP32) + case SW_RESET: +#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + case RTC_SW_SYS_RESET: +#endif + reset_reason = "Software Reset Digital Core"; + break; +#if defined(USE_ESP32_VARIANT_ESP32) + case OWDT_RESET: + reset_reason = "Watch Dog Reset Digital Core"; + break; +#endif + case DEEPSLEEP_RESET: + reset_reason = "Deep Sleep Reset Digital Core"; + break; +#if defined(USE_ESP32_VARIANT_ESP32) + case SDIO_RESET: + reset_reason = "SLC Module Reset Digital Core"; + break; +#endif + case TG0WDT_SYS_RESET: + reset_reason = "Timer Group 0 Watch Dog Reset Digital Core"; + break; + case TG1WDT_SYS_RESET: + reset_reason = "Timer Group 1 Watch Dog Reset Digital Core"; + break; + case RTCWDT_SYS_RESET: + reset_reason = "RTC Watch Dog Reset Digital Core"; + break; +#if !defined(USE_ESP32_VARIANT_ESP32C6) + case INTRUSION_RESET: + reset_reason = "Intrusion Reset CPU"; + break; +#endif +#if defined(USE_ESP32_VARIANT_ESP32) + case TGWDT_CPU_RESET: + reset_reason = "Timer Group Reset CPU"; + break; +#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + case TG0WDT_CPU_RESET: + reset_reason = "Timer Group 0 Reset CPU"; + break; +#endif +#if defined(USE_ESP32_VARIANT_ESP32) + case SW_CPU_RESET: +#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + case RTC_SW_CPU_RESET: +#endif + reset_reason = "Software Reset CPU"; + break; + case RTCWDT_CPU_RESET: + reset_reason = "RTC Watch Dog Reset CPU"; + break; +#if defined(USE_ESP32_VARIANT_ESP32) + case EXT_CPU_RESET: + reset_reason = "External CPU Reset"; + break; +#endif + case RTCWDT_BROWN_OUT_RESET: + reset_reason = "Voltage Unstable Reset"; + break; + case RTCWDT_RTC_RESET: + reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module"; + break; +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + case TG1WDT_CPU_RESET: + reset_reason = "Timer Group 1 Reset CPU"; + break; + case SUPER_WDT_RESET: + reset_reason = "Super Watchdog Reset Digital Core And RTC Module"; + break; + case GLITCH_RTC_RESET: + reset_reason = "Glitch Reset Digital Core And RTC Module"; + break; + case EFUSE_RESET: + reset_reason = "eFuse Reset Digital Core"; + break; +#endif +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) + case USB_UART_CHIP_RESET: + reset_reason = "USB UART Reset Digital Core"; + break; + case USB_JTAG_CHIP_RESET: + reset_reason = "USB JTAG Reset Digital Core"; + break; + case POWER_GLITCH_RESET: + reset_reason = "Power Glitch Reset Digital Core And RTC Module"; + break; +#endif + default: + reset_reason = "Unknown Reset Reason"; + } + ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); + return reset_reason; +} + +uint32_t DebugComponent::get_free_heap_() { return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); } + +void DebugComponent::get_device_info_(std::string &device_info) { +#if defined(USE_ARDUINO) + const char *flash_mode; + switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance) + case FM_QIO: + flash_mode = "QIO"; + break; + case FM_QOUT: + flash_mode = "QOUT"; + break; + case FM_DIO: + flash_mode = "DIO"; + break; + case FM_DOUT: + flash_mode = "DOUT"; + break; + case FM_FAST_READ: + flash_mode = "FAST_READ"; + break; + case FM_SLOW_READ: + flash_mode = "SLOW_READ"; + break; + default: + flash_mode = "UNKNOWN"; + } + ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", + ESP.getFlashChipSize() / 1024, // NOLINT + ESP.getFlashChipSpeed() / 1000000, flash_mode); // NOLINT + device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT + "kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT + device_info += flash_mode; +#endif + + esp_chip_info_t info; + esp_chip_info(&info); + const char *model; +#if defined(USE_ESP32_VARIANT_ESP32) + model = "ESP32"; +#elif defined(USE_ESP32_VARIANT_ESP32C3) + model = "ESP32-C3"; +#elif defined(USE_ESP32_VARIANT_ESP32C6) + model = "ESP32-C6"; +#elif defined(USE_ESP32_VARIANT_ESP32S2) + model = "ESP32-S2"; +#elif defined(USE_ESP32_VARIANT_ESP32S3) + model = "ESP32-S3"; +#elif defined(USE_ESP32_VARIANT_ESP32H2) + model = "ESP32-H2"; +#else + model = "UNKNOWN"; +#endif + std::string features; + if (info.features & CHIP_FEATURE_EMB_FLASH) { + features += "EMB_FLASH,"; + info.features &= ~CHIP_FEATURE_EMB_FLASH; + } + if (info.features & CHIP_FEATURE_WIFI_BGN) { + features += "WIFI_BGN,"; + info.features &= ~CHIP_FEATURE_WIFI_BGN; + } + if (info.features & CHIP_FEATURE_BLE) { + features += "BLE,"; + info.features &= ~CHIP_FEATURE_BLE; + } + if (info.features & CHIP_FEATURE_BT) { + features += "BT,"; + info.features &= ~CHIP_FEATURE_BT; + } + if (info.features & CHIP_FEATURE_EMB_PSRAM) { + features += "EMB_PSRAM,"; + info.features &= ~CHIP_FEATURE_EMB_PSRAM; + } + if (info.features) + features += "Other:" + format_hex(info.features); + ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores, + info.revision); + device_info += "|Chip: "; + device_info += model; + device_info += " Features:"; + device_info += features; + device_info += " Cores:" + to_string(info.cores); + device_info += " Revision:" + to_string(info.revision); + + ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version()); + device_info += "|ESP-IDF: "; + device_info += esp_get_idf_version(); + + std::string mac = get_mac_address_pretty(); + ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str()); + device_info += "|EFuse MAC: "; + device_info += mac; + + device_info += "|Reset: "; + device_info += get_reset_reason_(); + + const char *wakeup_reason; + switch (rtc_get_wakeup_cause()) { + case NO_SLEEP: + wakeup_reason = "No Sleep"; + break; + case EXT_EVENT0_TRIG: + wakeup_reason = "External Event 0"; + break; + case EXT_EVENT1_TRIG: + wakeup_reason = "External Event 1"; + break; + case GPIO_TRIG: + wakeup_reason = "GPIO"; + break; + case TIMER_EXPIRE: + wakeup_reason = "Wakeup Timer"; + break; + case SDIO_TRIG: + wakeup_reason = "SDIO"; + break; + case MAC_TRIG: + wakeup_reason = "MAC"; + break; + case UART0_TRIG: + wakeup_reason = "UART0"; + break; + case UART1_TRIG: + wakeup_reason = "UART1"; + break; + case TOUCH_TRIG: + wakeup_reason = "Touch"; + break; + case SAR_TRIG: + wakeup_reason = "SAR"; + break; + case BT_TRIG: + wakeup_reason = "BT"; + break; + default: + wakeup_reason = "Unknown"; + } + ESP_LOGD(TAG, "Wakeup Reason: %s", wakeup_reason); + device_info += "|Wakeup: "; + device_info += wakeup_reason; +} + +void DebugComponent::update_platform_() { +#ifdef USE_SENSOR + if (this->block_sensor_ != nullptr) { + this->block_sensor_->publish_state(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL)); + } + if (this->psram_sensor_ != nullptr) { + this->psram_sensor_->publish_state(heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + } +#endif +} + +} // namespace debug +} // namespace esphome +#endif diff --git a/esphome/components/debug/debug_esp8266.cpp b/esphome/components/debug/debug_esp8266.cpp new file mode 100644 index 0000000000..3395d9db12 --- /dev/null +++ b/esphome/components/debug/debug_esp8266.cpp @@ -0,0 +1,94 @@ +#include "debug_component.h" +#ifdef USE_ESP8266 +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace debug { + +static const char *const TAG = "debug"; + +std::string DebugComponent::get_reset_reason_() { +#if !defined(CLANG_TIDY) + return ESP.getResetReason().c_str(); +#else + return ""; +#endif +} + +uint32_t DebugComponent::get_free_heap_() { + return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) +} + +void DebugComponent::get_device_info_(std::string &device_info) { + const char *flash_mode; + switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance) + case FM_QIO: + flash_mode = "QIO"; + break; + case FM_QOUT: + flash_mode = "QOUT"; + break; + case FM_DIO: + flash_mode = "DIO"; + break; + case FM_DOUT: + flash_mode = "DOUT"; + break; + default: + flash_mode = "UNKNOWN"; + } + ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", + ESP.getFlashChipSize() / 1024, // NOLINT + ESP.getFlashChipSpeed() / 1000000, flash_mode); // NOLINT + device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT + "kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT + device_info += flash_mode; + +#if !defined(CLANG_TIDY) + auto reset_reason = get_reset_reason_(); + ESP_LOGD(TAG, "Chip ID: 0x%08X", ESP.getChipId()); + ESP_LOGD(TAG, "SDK Version: %s", ESP.getSdkVersion()); + ESP_LOGD(TAG, "Core Version: %s", ESP.getCoreVersion().c_str()); + ESP_LOGD(TAG, "Boot Version=%u Mode=%u", ESP.getBootVersion(), ESP.getBootMode()); + ESP_LOGD(TAG, "CPU Frequency: %u", ESP.getCpuFreqMHz()); + ESP_LOGD(TAG, "Flash Chip ID=0x%08X", ESP.getFlashChipId()); + ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); + ESP_LOGD(TAG, "Reset Info: %s", ESP.getResetInfo().c_str()); + + device_info += "|Chip: 0x" + format_hex(ESP.getChipId()); + device_info += "|SDK: "; + device_info += ESP.getSdkVersion(); + device_info += "|Core: "; + device_info += ESP.getCoreVersion().c_str(); + device_info += "|Boot: "; + device_info += to_string(ESP.getBootVersion()); + device_info += "|Mode: " + to_string(ESP.getBootMode()); + device_info += "|CPU: " + to_string(ESP.getCpuFreqMHz()); + device_info += "|Flash: 0x" + format_hex(ESP.getFlashChipId()); + device_info += "|Reset: "; + device_info += reset_reason; + device_info += "|"; + device_info += ESP.getResetInfo().c_str(); +#endif +} + +void DebugComponent::update_platform_() { +#ifdef USE_SENSOR + if (this->block_sensor_ != nullptr) { + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + this->block_sensor_->publish_state(ESP.getMaxFreeBlockSize()); + } +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) + if (this->fragmentation_sensor_ != nullptr) { + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + this->fragmentation_sensor_->publish_state(ESP.getHeapFragmentation()); + } +#endif + +#endif +} + +} // namespace debug +} // namespace esphome +#endif diff --git a/esphome/components/debug/debug_host.cpp b/esphome/components/debug/debug_host.cpp new file mode 100644 index 0000000000..09ad34ef88 --- /dev/null +++ b/esphome/components/debug/debug_host.cpp @@ -0,0 +1,18 @@ +#include "debug_component.h" +#ifdef USE_HOST +#include + +namespace esphome { +namespace debug { + +std::string DebugComponent::get_reset_reason_() { return ""; } + +uint32_t DebugComponent::get_free_heap_() { return INT_MAX; } + +void DebugComponent::get_device_info_(std::string &device_info) {} + +void DebugComponent::update_platform_() {} + +} // namespace debug +} // namespace esphome +#endif diff --git a/esphome/components/debug/debug_libretiny.cpp b/esphome/components/debug/debug_libretiny.cpp new file mode 100644 index 0000000000..725cd870ca --- /dev/null +++ b/esphome/components/debug/debug_libretiny.cpp @@ -0,0 +1,44 @@ +#include "debug_component.h" +#ifdef USE_LIBRETINY +#include "esphome/core/log.h" + +namespace esphome { +namespace debug { + +static const char *const TAG = "debug"; + +std::string DebugComponent::get_reset_reason_() { return lt_get_reboot_reason_name(lt_get_reboot_reason()); } + +uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); } + +void DebugComponent::get_device_info_(std::string &device_info) { + reset_reason = get_reset_reason_(); + ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version()); + ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz()); + ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id()); + ESP_LOGD(TAG, "Board: %s", lt_get_board_code()); + ESP_LOGD(TAG, "Flash: %u KiB / RAM: %u KiB", lt_flash_get_size() / 1024, lt_ram_get_size() / 1024); + ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); + + device_info += "|Version: "; + device_info += LT_BANNER_STR + 10; + device_info += "|Reset Reason: "; + device_info += reset_reason; + device_info += "|Chip Name: "; + device_info += lt_cpu_get_model_name(); + device_info += "|Chip ID: 0x" + format_hex(lt_cpu_get_mac_id()); + device_info += "|Flash: " + to_string(lt_flash_get_size() / 1024) + " KiB"; + device_info += "|RAM: " + to_string(lt_ram_get_size() / 1024) + " KiB"; +} + +void DebugComponent::update_platform_() { +#ifdef USE_SENSOR + if (this->block_sensor_ != nullptr) { + this->block_sensor_->publish_state(lt_heap_get_max_alloc()); + } +#endif +} + +} // namespace debug +} // namespace esphome +#endif diff --git a/esphome/components/debug/debug_rp2040.cpp b/esphome/components/debug/debug_rp2040.cpp new file mode 100644 index 0000000000..497547e30d --- /dev/null +++ b/esphome/components/debug/debug_rp2040.cpp @@ -0,0 +1,23 @@ +#include "debug_component.h" +#ifdef USE_RP2040 +#include "esphome/core/log.h" +#include +namespace esphome { +namespace debug { + +static const char *const TAG = "debug"; + +std::string DebugComponent::get_reset_reason_() { return ""; } + +uint32_t DebugComponent::get_free_heap_() { return rp2040.getFreeHeap(); } + +void DebugComponent::get_device_info_(std::string &device_info) { + ESP_LOGD(TAG, "CPU Frequency: %u", rp2040.f_cpu()); + device_info += "CPU Frequency: " + to_string(rp2040.f_cpu()); +} + +void DebugComponent::update_platform_() {} + +} // namespace debug +} // namespace esphome +#endif diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 3797f3221e..ceb6516a02 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -1,6 +1,11 @@ #ifdef USE_ESP32 #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/log.h" @@ -114,7 +119,11 @@ bool ESP32BLE::ble_setup_() { if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { // start bt controller 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(); +#endif err = esp_bt_controller_init(&cfg); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err)); diff --git a/esphome/components/esp32_ble/const_esp32c6.h b/esphome/components/esp32_ble/const_esp32c6.h new file mode 100644 index 0000000000..69f9adcf6b --- /dev/null +++ b/esphome/components/esp32_ble/const_esp32c6.h @@ -0,0 +1,67 @@ +#pragma once + +#ifdef USE_ESP32_VARIANT_ESP32C6 + +#include + +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 diff --git a/esphome/components/gdk101/__init__.py b/esphome/components/gdk101/__init__.py new file mode 100644 index 0000000000..0d90257964 --- /dev/null +++ b/esphome/components/gdk101/__init__.py @@ -0,0 +1,32 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c +from esphome.const import CONF_ID + +CODEOWNERS = ["@Szewcson"] + +DEPENDENCIES = ["i2c"] +MULTI_CONF = True + +CONF_GDK101_ID = "gdk101_id" + +gdk101_ns = cg.esphome_ns.namespace("gdk101") +GDK101Component = gdk101_ns.class_( + "GDK101Component", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(GDK101Component), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x18)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/gdk101/binary_sensor.py b/esphome/components/gdk101/binary_sensor.py new file mode 100644 index 0000000000..2a3d6f07eb --- /dev/null +++ b/esphome/components/gdk101/binary_sensor.py @@ -0,0 +1,29 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + CONF_VIBRATIONS, + DEVICE_CLASS_VIBRATION, + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_VIBRATE, +) +from . import CONF_GDK101_ID, GDK101Component + +DEPENDENCIES = ["gdk101"] + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_GDK101_ID): cv.use_id(GDK101Component), + cv.Required(CONF_VIBRATIONS): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_VIBRATION, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + icon=ICON_VIBRATE, + ), + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_GDK101_ID]) + var = await binary_sensor.new_binary_sensor(config[CONF_VIBRATIONS]) + cg.add(hub.set_vibration_binary_sensor(var)) diff --git a/esphome/components/gdk101/gdk101.cpp b/esphome/components/gdk101/gdk101.cpp new file mode 100644 index 0000000000..93f3c20fa8 --- /dev/null +++ b/esphome/components/gdk101/gdk101.cpp @@ -0,0 +1,189 @@ +#include "gdk101.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace gdk101 { + +static const char *const TAG = "gdk101"; +static const uint8_t NUMBER_OF_READ_RETRIES = 5; + +void GDK101Component::update() { + uint8_t data[2]; + if (!this->read_dose_1m_(data)) { + this->status_set_warning("Failed to read dose 1m"); + return; + } + + if (!this->read_dose_10m_(data)) { + this->status_set_warning("Failed to read dose 10m"); + return; + } + + if (!this->read_status_(data)) { + this->status_set_warning("Failed to read status"); + return; + } + + if (!this->read_measurement_duration_(data)) { + this->status_set_warning("Failed to read measurement duration"); + return; + } + this->status_clear_warning(); +} + +void GDK101Component::setup() { + uint8_t data[2]; + ESP_LOGCONFIG(TAG, "Setting up GDK101..."); + // first, reset the sensor + if (!this->reset_sensor_(data)) { + this->status_set_error("Reset failed!"); + this->mark_failed(); + return; + } + // sensor should acknowledge success of the reset procedure + if (data[0] != 1) { + this->status_set_error("Reset not acknowledged!"); + this->mark_failed(); + return; + } + delay(10); + // read firmware version + if (!this->read_fw_version_(data)) { + this->status_set_error("Failed to read firmware version"); + this->mark_failed(); + return; + } +} + +void GDK101Component::dump_config() { + ESP_LOGCONFIG(TAG, "GDK101:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with GDK101 failed!"); + } +#ifdef USE_SENSOR + LOG_SENSOR(" ", "Firmware Version", this->fw_version_sensor_); + LOG_SENSOR(" ", "Average Radaition Dose per 1 minute", this->rad_1m_sensor_); + LOG_SENSOR(" ", "Average Radaition Dose per 10 minutes", this->rad_10m_sensor_); + LOG_SENSOR(" ", "Status", this->status_sensor_); + LOG_SENSOR(" ", "Measurement Duration", this->measurement_duration_sensor_); +#endif // USE_SENSOR + +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "Vibration Status", this->vibration_binary_sensor_); +#endif // USE_BINARY_SENSOR +} + +float GDK101Component::get_setup_priority() const { return setup_priority::DATA; } + +bool GDK101Component::read_bytes_with_retry_(uint8_t a_register, uint8_t *data, uint8_t len) { + uint8_t retry = NUMBER_OF_READ_RETRIES; + bool status = false; + while (!status && retry) { + status = this->read_bytes(a_register, data, len); + retry--; + } + return status; +} + +bool GDK101Component::reset_sensor_(uint8_t *data) { + // It looks like reset is not so well designed in that sensor + // After sending reset command it looks that sensor start performing reset and is unresponsible during read + // after a while we can send another reset command and read "0x01" as confirmation + // Documentation not going in to such details unfortunately + if (!this->read_bytes_with_retry_(GDK101_REG_RESET, data, 2)) { + ESP_LOGE(TAG, "Updating GDK101 failed!"); + return false; + } + + return true; +} + +bool GDK101Component::read_dose_1m_(uint8_t *data) { +#ifdef USE_SENSOR + if (this->rad_1m_sensor_ != nullptr) { + if (!this->read_bytes(GDK101_REG_READ_1MIN_AVG, data, 2)) { + ESP_LOGE(TAG, "Updating GDK101 failed!"); + return false; + } + + const float dose = data[0] + (data[1] / 100.0f); + + this->rad_1m_sensor_->publish_state(dose); + } +#endif // USE_SENSOR + return true; +} + +bool GDK101Component::read_dose_10m_(uint8_t *data) { +#ifdef USE_SENSOR + if (this->rad_10m_sensor_ != nullptr) { + if (!this->read_bytes(GDK101_REG_READ_10MIN_AVG, data, 2)) { + ESP_LOGE(TAG, "Updating GDK101 failed!"); + return false; + } + + const float dose = data[0] + (data[1] / 100.0f); + + this->rad_10m_sensor_->publish_state(dose); + } +#endif // USE_SENSOR + return true; +} + +bool GDK101Component::read_status_(uint8_t *data) { + if (!this->read_bytes(GDK101_REG_READ_STATUS, data, 2)) { + ESP_LOGE(TAG, "Updating GDK101 failed!"); + return false; + } + +#ifdef USE_SENSOR + if (this->status_sensor_ != nullptr) { + this->status_sensor_->publish_state(data[0]); + } +#endif // USE_SENSOR + +#ifdef USE_BINARY_SENSOR + if (this->vibration_binary_sensor_ != nullptr) { + this->vibration_binary_sensor_->publish_state(data[1]); + } +#endif // USE_BINARY_SENSOR + + return true; +} + +bool GDK101Component::read_fw_version_(uint8_t *data) { +#ifdef USE_SENSOR + if (this->fw_version_sensor_ != nullptr) { + if (!this->read_bytes(GDK101_REG_READ_FIRMWARE, data, 2)) { + ESP_LOGE(TAG, "Updating GDK101 failed!"); + return false; + } + + const float fw_version = data[0] + (data[1] / 10.0f); + + this->fw_version_sensor_->publish_state(fw_version); + } +#endif // USE_SENSOR + return true; +} + +bool GDK101Component::read_measurement_duration_(uint8_t *data) { +#ifdef USE_SENSOR + if (this->measurement_duration_sensor_ != nullptr) { + if (!this->read_bytes(GDK101_REG_READ_MEASURING_TIME, data, 2)) { + ESP_LOGE(TAG, "Updating GDK101 failed!"); + return false; + } + + const float meas_time = (data[0] * 60) + data[1]; + + this->measurement_duration_sensor_->publish_state(meas_time); + } +#endif // USE_SENSOR + return true; +} + +} // namespace gdk101 +} // namespace esphome diff --git a/esphome/components/gdk101/gdk101.h b/esphome/components/gdk101/gdk101.h new file mode 100644 index 0000000000..460e72ac89 --- /dev/null +++ b/esphome/components/gdk101/gdk101.h @@ -0,0 +1,52 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif // USE_SENSOR +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif // USE_BINARY_SENSOR +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace gdk101 { + +static const uint8_t GDK101_REG_READ_FIRMWARE = 0xB4; // Firmware version +static const uint8_t GDK101_REG_RESET = 0xA0; // Reset register - reading its value triggers reset +static const uint8_t GDK101_REG_READ_STATUS = 0xB0; // Status register +static const uint8_t GDK101_REG_READ_MEASURING_TIME = 0xB1; // Mesuring time +static const uint8_t GDK101_REG_READ_10MIN_AVG = 0xB2; // Average radiation dose per 10 min +static const uint8_t GDK101_REG_READ_1MIN_AVG = 0xB3; // Average radiation dose per 1 min + +class GDK101Component : public PollingComponent, public i2c::I2CDevice { +#ifdef USE_SENSOR + SUB_SENSOR(rad_1m) + SUB_SENSOR(rad_10m) + SUB_SENSOR(status) + SUB_SENSOR(fw_version) + SUB_SENSOR(measurement_duration) +#endif // USE_SENSOR +#ifdef USE_BINARY_SENSOR + SUB_BINARY_SENSOR(vibration) +#endif // USE_BINARY_SENSOR + + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + + protected: + bool read_bytes_with_retry_(uint8_t a_register, uint8_t *data, uint8_t len); + bool reset_sensor_(uint8_t *data); + bool read_dose_1m_(uint8_t *data); + bool read_dose_10m_(uint8_t *data); + bool read_status_(uint8_t *data); + bool read_fw_version_(uint8_t *data); + bool read_measurement_duration_(uint8_t *data); +}; + +} // namespace gdk101 +} // namespace esphome diff --git a/esphome/components/gdk101/sensor.py b/esphome/components/gdk101/sensor.py new file mode 100644 index 0000000000..f782264615 --- /dev/null +++ b/esphome/components/gdk101/sensor.py @@ -0,0 +1,83 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + DEVICE_CLASS_DURATION, + DEVICE_CLASS_EMPTY, + ENTITY_CATEGORY_DIAGNOSTIC, + CONF_MEASUREMENT_DURATION, + CONF_STATUS, + CONF_VERSION, + ICON_RADIOACTIVE, + ICON_TIMER, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + UNIT_MICROSILVERTS_PER_HOUR, + UNIT_SECOND, +) +from . import CONF_GDK101_ID, GDK101Component + +CONF_RADIATION_DOSE_PER_1M = "radiation_dose_per_1m" +CONF_RADIATION_DOSE_PER_10M = "radiation_dose_per_10m" + +DEPENDENCIES = ["gdk101"] + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_GDK101_ID): cv.use_id(GDK101Component), + cv.Optional(CONF_RADIATION_DOSE_PER_1M): sensor.sensor_schema( + icon=ICON_RADIOACTIVE, + unit_of_measurement=UNIT_MICROSILVERTS_PER_HOUR, + accuracy_decimals=2, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_RADIATION_DOSE_PER_10M): sensor.sensor_schema( + icon=ICON_RADIOACTIVE, + unit_of_measurement=UNIT_MICROSILVERTS_PER_HOUR, + accuracy_decimals=2, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_VERSION): sensor.sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + accuracy_decimals=1, + ), + cv.Optional(CONF_STATUS): sensor.sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + accuracy_decimals=0, + ), + cv.Optional(CONF_MEASUREMENT_DURATION): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + icon=ICON_TIMER, + accuracy_decimals=0, + state_class=STATE_CLASS_TOTAL_INCREASING, + device_class=DEVICE_CLASS_DURATION, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_GDK101_ID]) + + if radiation_dose_per_1m := config.get(CONF_RADIATION_DOSE_PER_1M): + sens = await sensor.new_sensor(radiation_dose_per_1m) + cg.add(hub.set_rad_1m_sensor(sens)) + + if radiation_dose_per_10m := config.get(CONF_RADIATION_DOSE_PER_10M): + sens = await sensor.new_sensor(radiation_dose_per_10m) + cg.add(hub.set_rad_10m_sensor(sens)) + + if version_config := config.get(CONF_VERSION): + sens = await sensor.new_sensor(version_config) + cg.add(hub.set_fw_version_sensor(sens)) + + if status_config := config.get(CONF_STATUS): + sens = await sensor.new_sensor(status_config) + cg.add(hub.set_status_sensor(sens)) + + if measurement_duration_config := config.get(CONF_MEASUREMENT_DURATION): + sens = await sensor.new_sensor(measurement_duration_config) + cg.add(hub.set_measurement_duration_sensor(sens)) diff --git a/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp index 6e07983920..1890e27bdf 100644 --- a/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp +++ b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp @@ -10,6 +10,11 @@ namespace i2s_audio { static const char *const TAG = "audio"; 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()) { this->current_url_ = call.get_media_url(); 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_->connecttohost(this->current_url_.value().c_str()); - this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; + this->state = play_state; } else { this->start(); } @@ -35,7 +40,7 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) { case media_player::MEDIA_PLAYER_COMMAND_PLAY: if (!this->audio_->isRunning()) this->audio_->pauseResume(); - this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; + this->state = play_state; break; case media_player::MEDIA_PLAYER_COMMAND_PAUSE: if (this->audio_->isRunning()) @@ -126,7 +131,9 @@ void I2SAudioMediaPlayer::loop() { void I2SAudioMediaPlayer::play_() { 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(); } } @@ -164,6 +171,10 @@ void I2SAudioMediaPlayer::start_() { if (this->current_url_.has_value()) { this->audio_->connecttohost(this->current_url_.value().c_str()); 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(); } } diff --git a/esphome/components/i2s_audio/media_player/i2s_audio_media_player.h b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.h index 092e6de8e8..d7d9b1f74a 100644 --- a/esphome/components/i2s_audio/media_player/i2s_audio_media_player.h +++ b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.h @@ -78,6 +78,7 @@ class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer, HighFrequencyLoopRequester high_freq_; optional current_url_{}; + optional is_announcement_{}; }; } // namespace i2s_audio diff --git a/esphome/components/ltr390/ltr390.cpp b/esphome/components/ltr390/ltr390.cpp index 65c08ab614..4eb1ff2c46 100644 --- a/esphome/components/ltr390/ltr390.cpp +++ b/esphome/components/ltr390/ltr390.cpp @@ -8,6 +8,9 @@ namespace ltr390 { static const char *const TAG = "ltr390"; +static const uint8_t LTR390_WAKEUP_TIME = 10; +static const uint8_t LTR390_SETTLE_TIME = 5; + static const uint8_t LTR390_MAIN_CTRL = 0x00; static const uint8_t LTR390_MEAS_RATE = 0x04; static const uint8_t LTR390_GAIN = 0x05; @@ -101,21 +104,27 @@ void LTR390Component::read_mode_(int mode_index) { std::bitset<8> ctrl = this->reg(LTR390_MAIN_CTRL).get(); ctrl[LTR390_CTRL_MODE] = mode; + ctrl[LTR390_CTRL_EN] = true; this->reg(LTR390_MAIN_CTRL) = ctrl.to_ulong(); // After the sensor integration time do the following - this->set_timeout(((uint32_t) RESOLUTIONVALUE[this->res_]) * 100, [this, mode_index]() { - // Read from the sensor - std::get<1>(this->mode_funcs_[mode_index])(); + this->set_timeout(((uint32_t) RESOLUTIONVALUE[this->res_]) * 100 + LTR390_WAKEUP_TIME + LTR390_SETTLE_TIME, + [this, mode_index]() { + // Read from the sensor + std::get<1>(this->mode_funcs_[mode_index])(); - // If there are more modes to read then begin the next - // otherwise stop - if (mode_index + 1 < (int) this->mode_funcs_.size()) { - this->read_mode_(mode_index + 1); - } else { - this->reading_ = false; - } - }); + // If there are more modes to read then begin the next + // otherwise stop + if (mode_index + 1 < (int) this->mode_funcs_.size()) { + this->read_mode_(mode_index + 1); + } else { + // put sensor in standby + std::bitset<8> ctrl = this->reg(LTR390_MAIN_CTRL).get(); + ctrl[LTR390_CTRL_EN] = false; + this->reg(LTR390_MAIN_CTRL) = ctrl.to_ulong(); + this->reading_ = false; + } + }); } void LTR390Component::setup() { diff --git a/esphome/components/media_player/__init__.py b/esphome/components/media_player/__init__.py index 5db78150bb..320014e355 100644 --- a/esphome/components/media_player/__init__.py +++ b/esphome/components/media_player/__init__.py @@ -51,12 +51,16 @@ VolumeSetAction = media_player_ns.class_( CONF_ON_PLAY = "on_play" CONF_ON_PAUSE = "on_pause" +CONF_ON_ANNOUNCEMENT = "on_announcement" CONF_MEDIA_URL = "media_url" StateTrigger = media_player_ns.class_("StateTrigger", automation.Trigger.template()) IdleTrigger = media_player_ns.class_("IdleTrigger", automation.Trigger.template()) PlayTrigger = media_player_ns.class_("PlayTrigger", 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) 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, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) 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): @@ -106,6 +113,11 @@ MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( 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), + } + ), } ) diff --git a/esphome/components/media_player/automation.h b/esphome/components/media_player/automation.h index 261e93775c..fc3ce7a764 100644 --- a/esphome/components/media_player/automation.h +++ b/esphome/components/media_player/automation.h @@ -52,6 +52,7 @@ class StateTrigger : public Trigger<> { MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(IdleTrigger, IDLE) MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PlayTrigger, PLAYING) MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PauseTrigger, PAUSED) +MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(AnnouncementTrigger, ANNOUNCING) template class IsIdleCondition : public Condition, public Parented { public: diff --git a/esphome/components/media_player/media_player.cpp b/esphome/components/media_player/media_player.cpp index 81cb6ca751..586345ac9f 100644 --- a/esphome/components/media_player/media_player.cpp +++ b/esphome/components/media_player/media_player.cpp @@ -15,6 +15,8 @@ const char *media_player_state_to_string(MediaPlayerState state) { return "PLAYING"; case MEDIA_PLAYER_STATE_PAUSED: return "PAUSED"; + case MEDIA_PLAYER_STATE_ANNOUNCING: + return "ANNOUNCING"; case MEDIA_PLAYER_STATE_NONE: default: return "UNKNOWN"; @@ -68,6 +70,9 @@ void MediaPlayerCall::perform() { if (this->volume_.has_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); } @@ -108,6 +113,11 @@ MediaPlayerCall &MediaPlayerCall::set_volume(float volume) { return *this; } +MediaPlayerCall &MediaPlayerCall::set_announcement(bool announce) { + this->announcement_ = announce; + return *this; +} + void MediaPlayer::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } diff --git a/esphome/components/media_player/media_player.h b/esphome/components/media_player/media_player.h index 88114d5337..77746e1808 100644 --- a/esphome/components/media_player/media_player.h +++ b/esphome/components/media_player/media_player.h @@ -10,7 +10,8 @@ enum MediaPlayerState : uint8_t { MEDIA_PLAYER_STATE_NONE = 0, MEDIA_PLAYER_STATE_IDLE = 1, 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); @@ -51,12 +52,14 @@ class MediaPlayerCall { MediaPlayerCall &set_media_url(const std::string &url); MediaPlayerCall &set_volume(float volume); + MediaPlayerCall &set_announcement(bool announce); void perform(); const optional &get_command() const { return command_; } const optional &get_media_url() const { return media_url_; } const optional &get_volume() const { return volume_; } + const optional &get_announcement() const { return announcement_; } protected: void validate_(); @@ -64,6 +67,7 @@ class MediaPlayerCall { optional command_; optional media_url_; optional volume_; + optional announcement_; }; class MediaPlayer : public EntityBase { diff --git a/esphome/components/time_based/time_based_cover.cpp b/esphome/components/time_based/time_based_cover.cpp index 50376224a9..e1936d5ee1 100644 --- a/esphome/components/time_based/time_based_cover.cpp +++ b/esphome/components/time_based/time_based_cover.cpp @@ -96,6 +96,9 @@ void TimeBasedCover::control(const CoverCall &call) { } } else { auto op = pos < this->position ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING; + if (this->manual_control_ && (pos == COVER_OPEN || pos == COVER_CLOSED)) { + this->position = pos == COVER_CLOSED ? COVER_OPEN : COVER_CLOSED; + } this->target_position_ = pos; this->start_direction_(op); } diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index e68e00948e..109e52f8eb 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -152,7 +152,7 @@ void VoiceAssistant::loop() { } else #endif { - this->set_state_(State::START_PIPELINE, State::START_MICROPHONE); + this->set_state_(State::START_MICROPHONE, State::START_PIPELINE); } } else { this->high_freq_.stop(); @@ -318,7 +318,7 @@ void VoiceAssistant::loop() { #endif #ifdef USE_MEDIA_PLAYER 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 if (playing) { @@ -514,7 +514,7 @@ void VoiceAssistant::request_start(bool continuous, bool silence_detection) { } else #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]() { #ifdef USE_MEDIA_PLAYER 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 this->tts_end_trigger_->trigger(url); diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index d6b1502381..1c0ea12f4f 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -94,10 +94,10 @@ class VoiceAssistant : public Component { uint32_t get_feature_flags() const { uint32_t flags = 0; flags |= VoiceAssistantFeature::FEATURE_VOICE_ASSISTANT; + flags |= VoiceAssistantFeature::FEATURE_API_AUDIO; #ifdef USE_SPEAKER if (this->speaker_ != nullptr) { flags |= VoiceAssistantFeature::FEATURE_SPEAKER; - flags |= VoiceAssistantFeature::FEATURE_API_AUDIO; } #endif return flags; diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 72aae7ef64..232ab40d10 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -40,19 +40,19 @@ WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller) def default_url(config): config = config.copy() 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" - 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" if config[CONF_VERSION] == 2: - if not (CONF_CSS_URL in config): + if CONF_CSS_URL not in config: 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" if config[CONF_VERSION] == 3: - if not (CONF_CSS_URL in config): + if CONF_CSS_URL not in config: 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" return config diff --git a/esphome/config.py b/esphome/config.py index 4f340225fe..2b231fc402 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -34,7 +34,7 @@ from esphome.voluptuous_schema import ExtraKeysInvalid from esphome.log import color, Fore import esphome.final_validate as fv import esphome.config_validation as cv -from esphome.types import ConfigType, ConfigPathType, ConfigFragmentType +from esphome.types import ConfigType, ConfigFragmentType _LOGGER = logging.getLogger(__name__) @@ -213,7 +213,7 @@ class Config(OrderedDict, fv.FinalValidateConfig): return doc_range def get_nested_item( - self, path: ConfigPathType, raise_error: bool = False + self, path: ConfigPath, raise_error: bool = False ) -> ConfigFragmentType: data = self for item_index in path: @@ -244,7 +244,7 @@ class Config(OrderedDict, fv.FinalValidateConfig): return path 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) @property @@ -885,6 +885,9 @@ def _get_parent_name(path, config): # Sub-item break 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] @@ -1106,7 +1109,14 @@ def read_config(command_line_substitutions): if errline: errstr += f" {errline}" 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: safe_print(color(Fore.BOLD_RED, err.msg)) diff --git a/esphome/const.py b/esphome/const.py index c7223fbe54..3621668352 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -884,6 +884,7 @@ CONF_VALUE_FONT = "value_font" CONF_VARIABLES = "variables" CONF_VARIANT = "variant" CONF_VERSION = "version" +CONF_VIBRATIONS = "vibrations" CONF_VISIBLE = "visible" CONF_VISUAL = "visual" CONF_VOLTAGE = "voltage" @@ -985,6 +986,7 @@ ICON_SIGNAL_DISTANCE_VARIANT = "mdi:signal" ICON_THERMOMETER = "mdi:thermometer" ICON_TIMELAPSE = "mdi:timelapse" ICON_TIMER = "mdi:timer-outline" +ICON_VIBRATE = "mdi:vibrate" ICON_WATER = "mdi:water" ICON_WATER_PERCENT = "mdi:water-percent" ICON_WEATHER_SUNSET = "mdi:weather-sunset" @@ -1026,6 +1028,7 @@ UNIT_METER_PER_SECOND_SQUARED = "m/s²" UNIT_MICROGRAMS_PER_CUBIC_METER = "µg/m³" UNIT_MICROMETER = "µm" UNIT_MICROSIEMENS_PER_CENTIMETER = "µS/cm" +UNIT_MICROSILVERTS_PER_HOUR = "µSv/h" UNIT_MICROTESLA = "µT" UNIT_MILLIGRAMS_PER_CUBIC_METER = "mg/m³" UNIT_MILLISECOND = "ms" diff --git a/esphome/core/config.py b/esphome/core/config.py index 2d87796987..80b731b905 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -394,7 +394,7 @@ async def to_code(config): if project_conf := config.get(CONF_PROJECT): 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_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, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) await cg.register_component(trigger, conf) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 04616d97c2..9a4cb2269a 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -2,7 +2,7 @@ import abc import inspect import math import re -from collections.abc import Generator, Sequence +from collections.abc import Sequence from typing import Any, Callable, Optional, Union from esphome.core import ( @@ -477,6 +477,7 @@ def variable( :param rhs: The expression to place on the right hand side of the assignment. :param type_: Manually define a type for the variable, only use this when it's not possible to do so during config validation phase (for example because of template arguments). + :param register: If true register the variable with the core :return: The new variable as a MockObj. """ @@ -492,9 +493,7 @@ def variable( return obj -def with_local_variable( - id_: ID, rhs: SafeExpType, callback: Callable[["MockObj"], None], *args -) -> None: +def with_local_variable(id_: ID, rhs: SafeExpType, callback: Callable, *args) -> None: """Declare a new variable, not pointer type, in the code generation, within a scoped block The variable is only usable within the callback The callback cannot be async. @@ -599,6 +598,7 @@ def add_library(name: str, version: Optional[str], repository: Optional[str] = N :param name: The name of the library (for example 'AsyncTCP') :param version: The version of the library, may be None. + :param repository: The repository for the library """ CORE.add_library(Library(name, version, repository)) @@ -654,7 +654,7 @@ async def process_lambda( parameters: list[tuple[SafeExpType, str]], capture: str = "=", return_type: SafeExpType = None, -) -> Generator[LambdaExpression, None, None]: +) -> Union[LambdaExpression, None]: """Process the given lambda value into a LambdaExpression. This is a coroutine because lambdas can depend on other IDs, @@ -673,7 +673,7 @@ async def process_lambda( ) if value is None: - return + return None parts = value.parts[:] for i, id in enumerate(value.requires_ids): full_id, var = await get_variable_with_full_id(id) @@ -712,7 +712,7 @@ async def templatable( value: Any, args: list[tuple[SafeExpType, str]], output_type: Optional[SafeExpType], - to_exp: Any = None, + to_exp: Union[Callable, dict] = None, ): """Generate code for a templatable config option. diff --git a/requirements.txt b/requirements.txt index df94b8b77c..698ae56447 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ tornado==6.4 tzlocal==5.2 # from time tzdata>=2021.1 # from time 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 click==8.1.7 esphome-dashboard==20240412.0 diff --git a/requirements_test.txt b/requirements_test.txt index 78820765f4..ae833841ca 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ pyupgrade==3.15.2 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests -pytest==8.1.1 +pytest==8.2.0 pytest-cov==4.1.0 pytest-mock==3.14.0 pytest-asyncio==0.23.6 diff --git a/tests/components/gdk101/common.yaml b/tests/components/gdk101/common.yaml new file mode 100644 index 0000000000..f886fc415b --- /dev/null +++ b/tests/components/gdk101/common.yaml @@ -0,0 +1,28 @@ +i2c: + id: i2c_bus + sda: ${i2c_sda} + scl: ${i2c_scl} + +gdk101: + id: my_gdk101 + i2c_id: i2c_bus + +sensor: + - platform: gdk101 + gdk101_id: my_gdk101 + radiation_dose_per_1m: + name: Radiation Dose @ 1 min + radiation_dose_per_10m: + name: Radiation Dose @ 10 min + status: + name: Status + version: + name: FW Version + measurement_duration: + name: Measuring Time + +binary_sensor: + - platform: gdk101 + gdk101_id: my_gdk101 + vibrations: + name: Vibrations diff --git a/tests/components/gdk101/test.esp32-idf.yaml b/tests/components/gdk101/test.esp32-idf.yaml new file mode 100644 index 0000000000..1037d5d35b --- /dev/null +++ b/tests/components/gdk101/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + i2c_scl: GPIO16 + i2c_sda: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/gdk101/test.esp32.yaml b/tests/components/gdk101/test.esp32.yaml new file mode 100644 index 0000000000..1037d5d35b --- /dev/null +++ b/tests/components/gdk101/test.esp32.yaml @@ -0,0 +1,5 @@ +substitutions: + i2c_scl: GPIO16 + i2c_sda: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/gdk101/test.esp8266.yaml b/tests/components/gdk101/test.esp8266.yaml new file mode 100644 index 0000000000..d7ae0d5161 --- /dev/null +++ b/tests/components/gdk101/test.esp8266.yaml @@ -0,0 +1,5 @@ +substitutions: + i2c_scl: GPIO5 + i2c_sda: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/gdk101/test.rp2040.yaml b/tests/components/gdk101/test.rp2040.yaml new file mode 100644 index 0000000000..d7ae0d5161 --- /dev/null +++ b/tests/components/gdk101/test.rp2040.yaml @@ -0,0 +1,5 @@ +substitutions: + i2c_scl: GPIO5 + i2c_sda: GPIO4 + +<<: !include common.yaml