mirror of
https://github.com/esphome/esphome.git
synced 2026-02-18 15:35:59 -07:00
Merge branch 'integration' into memory_api
This commit is contained in:
2
.github/workflows/auto-label-pr.yml
vendored
2
.github/workflows/auto-label-pr.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
|
||||
2
.github/workflows/ci-api-proto.yml
vendored
2
.github/workflows/ci-api-proto.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
|
||||
2
.github/workflows/ci-clang-tidy-hash.yml
vendored
2
.github/workflows/ci-clang-tidy-hash.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
|
||||
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
- "docker"
|
||||
# - "lint"
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Check out code from base repository
|
||||
if: steps.pr.outputs.skip != 'true'
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
# Always check out from the base repository (esphome/esphome), never from forks
|
||||
# Use the PR's target branch to ensure we run trusted code from the main repo
|
||||
|
||||
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Generate cache-key
|
||||
id: cache-key
|
||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.python-linters == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Restore Python
|
||||
id: restore-python
|
||||
uses: ./.github/actions/restore-python
|
||||
@@ -183,7 +183,7 @@ jobs:
|
||||
component-test-batches: ${{ steps.determine.outputs.component-test-batches }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
# Fetch enough history to find the merge base
|
||||
fetch-depth: 2
|
||||
@@ -237,7 +237,7 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.integration-tests == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Set up Python 3.13
|
||||
id: python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
@@ -273,7 +273,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request' && (needs.determine-jobs.outputs.cpp-unit-tests-run-all == 'true' || needs.determine-jobs.outputs.cpp-unit-tests-components != '[]')
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
@@ -321,7 +321,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
@@ -400,7 +400,7 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
@@ -489,7 +489,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
@@ -577,7 +577,7 @@ jobs:
|
||||
version: 1.0
|
||||
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -662,7 +662,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request' && !startsWith(github.base_ref, 'beta') && !startsWith(github.base_ref, 'release')
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -688,7 +688,7 @@ jobs:
|
||||
skip: ${{ steps.check-script.outputs.skip }}
|
||||
steps:
|
||||
- name: Check out target branch
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
@@ -840,7 +840,7 @@ jobs:
|
||||
flash_usage: ${{ steps.extract.outputs.flash_usage }}
|
||||
steps:
|
||||
- name: Check out PR branch
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -908,7 +908,7 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
||||
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
branch_build: ${{ steps.tag.outputs.branch_build }}
|
||||
deploy_env: ${{ steps.tag.outputs.deploy_env }}
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Get tag
|
||||
id: tag
|
||||
# yamllint disable rule:line-length
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
os: "ubuntu-24.04-arm"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
- ghcr
|
||||
- dockerhub
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
|
||||
4
.github/workflows/sync-device-classes.yml
vendored
4
.github/workflows/sync-device-classes.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Checkout Home Assistant
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
repository: home-assistant/core
|
||||
path: lib/home-assistant
|
||||
|
||||
@@ -202,6 +202,7 @@ esphome/components/havells_solar/* @sourabhjaiswal
|
||||
esphome/components/hbridge/fan/* @WeekendWarrior
|
||||
esphome/components/hbridge/light/* @DotNetDann
|
||||
esphome/components/hbridge/switch/* @dwmw2
|
||||
esphome/components/hc8/* @omartijn
|
||||
esphome/components/hdc2010/* @optimusprimespace @ssieb
|
||||
esphome/components/he60r/* @clydebarrow
|
||||
esphome/components/heatpumpir/* @rob-deutsch
|
||||
|
||||
@@ -3,7 +3,12 @@ import logging
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import climate, remote_base, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY_SENSOR,
|
||||
CONF_SENSOR,
|
||||
CONF_SUPPORTS_COOL,
|
||||
CONF_SUPPORTS_HEAT,
|
||||
)
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -32,6 +37,7 @@ def climate_ir_schema(
|
||||
cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean,
|
||||
cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean,
|
||||
cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||
cv.Optional(CONF_HUMIDITY_SENSOR): cv.use_id(sensor.Sensor),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
@@ -61,6 +67,9 @@ async def register_climate_ir(var, config):
|
||||
if sensor_id := config.get(CONF_SENSOR):
|
||||
sens = await cg.get_variable(sensor_id)
|
||||
cg.add(var.set_sensor(sens))
|
||||
if sensor_id := config.get(CONF_HUMIDITY_SENSOR):
|
||||
sens = await cg.get_variable(sensor_id)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
|
||||
|
||||
async def new_climate_ir(config, *args):
|
||||
|
||||
@@ -11,7 +11,9 @@ climate::ClimateTraits ClimateIR::traits() {
|
||||
if (this->sensor_ != nullptr) {
|
||||
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE);
|
||||
}
|
||||
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY);
|
||||
}
|
||||
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
|
||||
if (this->supports_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||
@@ -39,9 +41,16 @@ void ClimateIR::setup() {
|
||||
this->publish_state();
|
||||
});
|
||||
this->current_temperature = this->sensor_->state;
|
||||
} else {
|
||||
this->current_temperature = NAN;
|
||||
}
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
this->humidity_sensor_->add_on_state_callback([this](float state) {
|
||||
this->current_humidity = state;
|
||||
// current humidity changed, publish state
|
||||
this->publish_state();
|
||||
});
|
||||
this->current_humidity = this->humidity_sensor_->state;
|
||||
}
|
||||
|
||||
// restore set points
|
||||
auto restore = this->restore_state_();
|
||||
if (restore.has_value()) {
|
||||
|
||||
@@ -43,6 +43,7 @@ class ClimateIR : public Component,
|
||||
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
||||
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
|
||||
void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; }
|
||||
|
||||
protected:
|
||||
float minimum_temperature_, maximum_temperature_, temperature_step_;
|
||||
@@ -67,6 +68,7 @@ class ClimateIR : public Component,
|
||||
climate::ClimatePresetMask presets_{};
|
||||
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace climate_ir
|
||||
|
||||
@@ -19,8 +19,8 @@ void CST816Touchscreen::continue_setup_() {
|
||||
case CST816T_CHIP_ID:
|
||||
break;
|
||||
default:
|
||||
this->mark_failed();
|
||||
this->status_set_error(str_sprintf("Unknown chip ID 0x%02X", this->chip_id_).c_str());
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);
|
||||
|
||||
@@ -85,7 +85,6 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
|
||||
break;
|
||||
}
|
||||
gpio_set_intr_type(this->get_pin_num(), idf_type);
|
||||
gpio_intr_enable(this->get_pin_num());
|
||||
if (!isr_service_installed) {
|
||||
auto res = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);
|
||||
if (res != ESP_OK) {
|
||||
|
||||
1
esphome/components/hc8/__init__.py
Normal file
1
esphome/components/hc8/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@omartijn"]
|
||||
99
esphome/components/hc8/hc8.cpp
Normal file
99
esphome/components/hc8/hc8.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "hc8.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace esphome::hc8 {
|
||||
|
||||
static const char *const TAG = "hc8";
|
||||
static const std::array<uint8_t, 5> HC8_COMMAND_GET_PPM{0x64, 0x69, 0x03, 0x5E, 0x4E};
|
||||
static const std::array<uint8_t, 3> HC8_COMMAND_CALIBRATE_PREAMBLE{0x11, 0x03, 0x03};
|
||||
|
||||
void HC8Component::setup() {
|
||||
// send an initial query to the device, this will
|
||||
// get it out of "active output mode", where it
|
||||
// generates data every second
|
||||
this->write_array(HC8_COMMAND_GET_PPM);
|
||||
this->flush();
|
||||
|
||||
// ensure the buffer is empty
|
||||
while (this->available())
|
||||
this->read();
|
||||
}
|
||||
|
||||
void HC8Component::update() {
|
||||
uint32_t now_ms = App.get_loop_component_start_time();
|
||||
uint32_t warmup_ms = this->warmup_seconds_ * 1000;
|
||||
if (now_ms < warmup_ms) {
|
||||
ESP_LOGW(TAG, "HC8 warming up, %" PRIu32 " s left", (warmup_ms - now_ms) / 1000);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
while (this->available())
|
||||
this->read();
|
||||
|
||||
this->write_array(HC8_COMMAND_GET_PPM);
|
||||
this->flush();
|
||||
|
||||
// the sensor is a bit slow in responding, so trying to
|
||||
// read immediately after sending a query will timeout
|
||||
this->set_timeout(50, [this]() {
|
||||
std::array<uint8_t, 14> response;
|
||||
if (!this->read_array(response.data(), response.size())) {
|
||||
ESP_LOGW(TAG, "Reading data from HC8 failed!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
if (response[0] != 0x64 || response[1] != 0x69) {
|
||||
ESP_LOGW(TAG, "Invalid preamble from HC8!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
if (crc16(response.data(), 12) != encode_uint16(response[13], response[12])) {
|
||||
ESP_LOGW(TAG, "HC8 Checksum mismatch");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
|
||||
const uint16_t ppm = encode_uint16(response[5], response[4]);
|
||||
ESP_LOGD(TAG, "HC8 Received CO₂=%uppm", ppm);
|
||||
if (this->co2_sensor_ != nullptr)
|
||||
this->co2_sensor_->publish_state(ppm);
|
||||
});
|
||||
}
|
||||
|
||||
void HC8Component::calibrate(uint16_t baseline) {
|
||||
ESP_LOGD(TAG, "HC8 Calibrating baseline to %uppm", baseline);
|
||||
|
||||
std::array<uint8_t, 6> command{};
|
||||
std::copy(begin(HC8_COMMAND_CALIBRATE_PREAMBLE), end(HC8_COMMAND_CALIBRATE_PREAMBLE), begin(command));
|
||||
command[3] = baseline >> 8;
|
||||
command[4] = baseline;
|
||||
command[5] = 0;
|
||||
|
||||
// the last byte is a checksum over the data
|
||||
for (uint8_t i = 0; i < 5; ++i)
|
||||
command[5] -= command[i];
|
||||
|
||||
this->write_array(command);
|
||||
this->flush();
|
||||
}
|
||||
|
||||
float HC8Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void HC8Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "HC8:");
|
||||
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
|
||||
this->check_uart_settings(9600);
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Warmup time: %" PRIu32 " s", this->warmup_seconds_);
|
||||
}
|
||||
|
||||
} // namespace esphome::hc8
|
||||
37
esphome/components/hc8/hc8.h
Normal file
37
esphome/components/hc8/hc8.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome::hc8 {
|
||||
|
||||
class HC8Component : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
|
||||
void calibrate(uint16_t baseline);
|
||||
|
||||
void set_co2_sensor(sensor::Sensor *co2_sensor) { co2_sensor_ = co2_sensor; }
|
||||
void set_warmup_seconds(uint32_t seconds) { warmup_seconds_ = seconds; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *co2_sensor_{nullptr};
|
||||
uint32_t warmup_seconds_{0};
|
||||
};
|
||||
|
||||
template<typename... Ts> class HC8CalibrateAction : public Action<Ts...>, public Parented<HC8Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, baseline)
|
||||
|
||||
void play(const Ts &...x) override { this->parent_->calibrate(this->baseline_.value(x...)); }
|
||||
};
|
||||
|
||||
} // namespace esphome::hc8
|
||||
79
esphome/components/hc8/sensor.py
Normal file
79
esphome/components/hc8/sensor.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor, uart
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BASELINE,
|
||||
CONF_CO2,
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_CARBON_DIOXIDE,
|
||||
ICON_MOLECULE_CO2,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
CONF_WARMUP_TIME = "warmup_time"
|
||||
|
||||
hc8_ns = cg.esphome_ns.namespace("hc8")
|
||||
HC8Component = hc8_ns.class_("HC8Component", cg.PollingComponent, uart.UARTDevice)
|
||||
HC8CalibrateAction = hc8_ns.class_("HC8CalibrateAction", automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HC8Component),
|
||||
cv.Optional(CONF_CO2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||
icon=ICON_MOLECULE_CO2,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_WARMUP_TIME, default="75s"
|
||||
): cv.positive_time_period_seconds,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"hc8",
|
||||
baud_rate=9600,
|
||||
require_rx=True,
|
||||
require_tx=True,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
if co2 := config.get(CONF_CO2):
|
||||
sens = await sensor.new_sensor(co2)
|
||||
cg.add(var.set_co2_sensor(sens))
|
||||
|
||||
cg.add(var.set_warmup_seconds(config[CONF_WARMUP_TIME]))
|
||||
|
||||
|
||||
CALIBRATION_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(HC8Component),
|
||||
cv.Required(CONF_BASELINE): cv.templatable(cv.uint16_t),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"hc8.calibrate", HC8CalibrateAction, CALIBRATION_ACTION_SCHEMA
|
||||
)
|
||||
async def hc8_calibration_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_BASELINE], args, cg.uint16)
|
||||
cg.add(var.set_baseline(template_))
|
||||
return var
|
||||
@@ -8,6 +8,22 @@ namespace esphome::i2c {
|
||||
|
||||
static const char *const TAG = "i2c.zephyr";
|
||||
|
||||
static const char *get_speed(uint32_t dev_config) {
|
||||
switch (I2C_SPEED_GET(dev_config)) {
|
||||
case I2C_SPEED_STANDARD:
|
||||
return "100 kHz";
|
||||
case I2C_SPEED_FAST:
|
||||
return "400 kHz";
|
||||
case I2C_SPEED_FAST_PLUS:
|
||||
return "1 MHz";
|
||||
case I2C_SPEED_HIGH:
|
||||
return "3.4 MHz";
|
||||
case I2C_SPEED_ULTRA:
|
||||
return "5 MHz";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
void ZephyrI2CBus::setup() {
|
||||
if (!device_is_ready(this->i2c_dev_)) {
|
||||
ESP_LOGE(TAG, "I2C dev is not ready.");
|
||||
@@ -31,21 +47,6 @@ void ZephyrI2CBus::setup() {
|
||||
}
|
||||
|
||||
void ZephyrI2CBus::dump_config() {
|
||||
auto get_speed = [](uint32_t dev_config) {
|
||||
switch (I2C_SPEED_GET(dev_config)) {
|
||||
case I2C_SPEED_STANDARD:
|
||||
return "100 kHz";
|
||||
case I2C_SPEED_FAST:
|
||||
return "400 kHz";
|
||||
case I2C_SPEED_FAST_PLUS:
|
||||
return "1 MHz";
|
||||
case I2C_SPEED_HIGH:
|
||||
return "3.4 MHz";
|
||||
case I2C_SPEED_ULTRA:
|
||||
return "5 MHz";
|
||||
}
|
||||
return "unknown";
|
||||
};
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"I2C Bus:\n"
|
||||
" SDA Pin: GPIO%u\n"
|
||||
|
||||
@@ -261,6 +261,10 @@ async def component_to_code(config):
|
||||
cg.add_build_flag(f"-DUSE_LIBRETINY_VARIANT_{config[CONF_FAMILY]}")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_define("ESPHOME_VARIANT", FAMILY_FRIENDLY[config[CONF_FAMILY]])
|
||||
# LibreTiny uses MULTI_NO_ATOMICS because platforms like BK7231N (ARM968E-S) lack
|
||||
# exclusive load/store (no LDREX/STREX). std::atomic RMW operations require libatomic,
|
||||
# which is not linked to save flash (4-8KB). Even if linked, libatomic would use locks
|
||||
# (ATOMIC_INT_LOCK_FREE=1), so explicit FreeRTOS mutexes are simpler and equivalent.
|
||||
cg.add_define(ThreadModel.MULTI_NO_ATOMICS)
|
||||
|
||||
# force using arduino framework
|
||||
|
||||
@@ -174,7 +174,7 @@ void LTRAlsPs501Component::loop() {
|
||||
break;
|
||||
|
||||
case State::WAITING_FOR_DATA:
|
||||
if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) {
|
||||
if (this->is_als_data_ready_(this->als_readings_) == LtrDataAvail::LTR_DATA_OK) {
|
||||
tries = 0;
|
||||
ESP_LOGV(TAG, "Reading sensor data assuming gain = %.0fx, time = %d ms",
|
||||
get_gain_coeff(this->als_readings_.gain), get_itime_ms(this->als_readings_.integration_time));
|
||||
@@ -379,18 +379,18 @@ void LTRAlsPs501Component::configure_integration_time_(IntegrationTime501 time)
|
||||
}
|
||||
}
|
||||
|
||||
DataAvail LTRAlsPs501Component::is_als_data_ready_(AlsReadings &data) {
|
||||
LtrDataAvail LTRAlsPs501Component::is_als_data_ready_(AlsReadings &data) {
|
||||
AlsPsStatusRegister als_status{0};
|
||||
als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get();
|
||||
if (!als_status.als_new_data)
|
||||
return DataAvail::NO_DATA;
|
||||
return LtrDataAvail::LTR_NO_DATA;
|
||||
ESP_LOGV(TAG, "Data ready, reported gain is %.0fx", get_gain_coeff(als_status.gain));
|
||||
if (data.gain != als_status.gain) {
|
||||
ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.gain));
|
||||
return DataAvail::BAD_DATA;
|
||||
return LtrDataAvail::LTR_BAD_DATA;
|
||||
}
|
||||
data.gain = als_status.gain;
|
||||
return DataAvail::DATA_OK;
|
||||
return LtrDataAvail::LTR_DATA_OK;
|
||||
}
|
||||
|
||||
void LTRAlsPs501Component::read_sensor_data_(AlsReadings &data) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace esphome {
|
||||
namespace ltr501 {
|
||||
|
||||
enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK };
|
||||
enum LtrDataAvail : uint8_t { LTR_NO_DATA, LTR_BAD_DATA, LTR_DATA_OK };
|
||||
|
||||
enum LtrType : uint8_t {
|
||||
LTR_TYPE_UNKNOWN = 0,
|
||||
@@ -106,7 +106,7 @@ class LTRAlsPs501Component : public PollingComponent, public i2c::I2CDevice {
|
||||
void configure_als_();
|
||||
void configure_integration_time_(IntegrationTime501 time);
|
||||
void configure_gain_(AlsGain501 gain);
|
||||
DataAvail is_als_data_ready_(AlsReadings &data);
|
||||
LtrDataAvail is_als_data_ready_(AlsReadings &data);
|
||||
void read_sensor_data_(AlsReadings &data);
|
||||
bool are_adjustments_required_(AlsReadings &data);
|
||||
void apply_lux_calculation_(AlsReadings &data);
|
||||
|
||||
@@ -165,7 +165,7 @@ void LTRAlsPsComponent::loop() {
|
||||
break;
|
||||
|
||||
case State::WAITING_FOR_DATA:
|
||||
if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) {
|
||||
if (this->is_als_data_ready_(this->als_readings_) == LtrDataAvail::LTR_DATA_OK) {
|
||||
tries = 0;
|
||||
ESP_LOGV(TAG, "Reading sensor data having gain = %.0fx, time = %d ms", get_gain_coeff(this->als_readings_.gain),
|
||||
get_itime_ms(this->als_readings_.integration_time));
|
||||
@@ -376,23 +376,23 @@ void LTRAlsPsComponent::configure_integration_time_(IntegrationTime time) {
|
||||
}
|
||||
}
|
||||
|
||||
DataAvail LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) {
|
||||
LtrDataAvail LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) {
|
||||
AlsPsStatusRegister als_status{0};
|
||||
|
||||
als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get();
|
||||
if (!als_status.als_new_data)
|
||||
return DataAvail::NO_DATA;
|
||||
return LtrDataAvail::LTR_NO_DATA;
|
||||
|
||||
if (als_status.data_invalid) {
|
||||
ESP_LOGW(TAG, "Data available but not valid");
|
||||
return DataAvail::BAD_DATA;
|
||||
return LtrDataAvail::LTR_BAD_DATA;
|
||||
}
|
||||
ESP_LOGV(TAG, "Data ready, reported gain is %.0f", get_gain_coeff(als_status.gain));
|
||||
if (data.gain != als_status.gain) {
|
||||
ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.gain));
|
||||
return DataAvail::BAD_DATA;
|
||||
return LtrDataAvail::LTR_BAD_DATA;
|
||||
}
|
||||
return DataAvail::DATA_OK;
|
||||
return LtrDataAvail::LTR_DATA_OK;
|
||||
}
|
||||
|
||||
void LTRAlsPsComponent::read_sensor_data_(AlsReadings &data) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace esphome {
|
||||
namespace ltr_als_ps {
|
||||
|
||||
enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK };
|
||||
enum LtrDataAvail : uint8_t { LTR_NO_DATA, LTR_BAD_DATA, LTR_DATA_OK };
|
||||
|
||||
enum LtrType : uint8_t {
|
||||
LTR_TYPE_UNKNOWN = 0,
|
||||
@@ -106,7 +106,7 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
void configure_als_();
|
||||
void configure_integration_time_(IntegrationTime time);
|
||||
void configure_gain_(AlsGain gain);
|
||||
DataAvail is_als_data_ready_(AlsReadings &data);
|
||||
LtrDataAvail is_als_data_ready_(AlsReadings &data);
|
||||
void read_sensor_data_(AlsReadings &data);
|
||||
bool are_adjustments_required_(AlsReadings &data);
|
||||
void apply_lux_calculation_(AlsReadings &data);
|
||||
|
||||
@@ -24,6 +24,8 @@ class ST7701S(DriverChip):
|
||||
sdir = 0
|
||||
if transform.get(CONF_MIRROR_X):
|
||||
sdir |= 0x04
|
||||
# XFLIP doesn't do anything in the ST7701S,
|
||||
# it's set in the madctl byte just so it can be reported at runtime by logconfig
|
||||
madctl |= MADCTL_XFLIP
|
||||
sequence.append((SDIR_CMD, sdir))
|
||||
return madctl
|
||||
|
||||
@@ -195,8 +195,8 @@ static void add(std::vector<uint8_t> &vec, const char *str) {
|
||||
void PacketTransport::setup() {
|
||||
this->name_ = App.get_name().c_str();
|
||||
if (strlen(this->name_) > 255) {
|
||||
this->mark_failed();
|
||||
this->status_set_error("Device name exceeds 255 chars");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->resend_ping_key_ = this->ping_pong_enable_;
|
||||
|
||||
@@ -21,8 +21,8 @@ void UDPComponent::setup() {
|
||||
if (this->should_broadcast_) {
|
||||
this->broadcast_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (this->broadcast_socket_ == nullptr) {
|
||||
this->mark_failed();
|
||||
this->status_set_error("Could not create socket");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
int enable = 1;
|
||||
@@ -41,15 +41,15 @@ void UDPComponent::setup() {
|
||||
if (this->should_listen_) {
|
||||
this->listen_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (this->listen_socket_ == nullptr) {
|
||||
this->mark_failed();
|
||||
this->status_set_error("Could not create socket");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
auto err = this->listen_socket_->setblocking(false);
|
||||
if (err < 0) {
|
||||
ESP_LOGE(TAG, "Unable to set nonblocking: errno %d", errno);
|
||||
this->mark_failed();
|
||||
this->status_set_error("Unable to set nonblocking");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
int enable = 1;
|
||||
@@ -73,8 +73,8 @@ void UDPComponent::setup() {
|
||||
err = this->listen_socket_->setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(imreq));
|
||||
if (err < 0) {
|
||||
ESP_LOGE(TAG, "Failed to set IP_ADD_MEMBERSHIP. Error %d", errno);
|
||||
this->mark_failed();
|
||||
this->status_set_error("Failed to set IP_ADD_MEMBERSHIP");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -82,8 +82,8 @@ void UDPComponent::setup() {
|
||||
err = this->listen_socket_->bind((struct sockaddr *) &server, sizeof(server));
|
||||
if (err != 0) {
|
||||
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
|
||||
this->mark_failed();
|
||||
this->status_set_error("Unable to bind socket");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ void WakeOnLanButton::setup() {
|
||||
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
this->broadcast_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (this->broadcast_socket_ == nullptr) {
|
||||
this->mark_failed();
|
||||
this->status_set_error("Could not create socket");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
int enable = 1;
|
||||
|
||||
@@ -36,7 +36,30 @@ class Framework(StrEnum):
|
||||
|
||||
|
||||
class ThreadModel(StrEnum):
|
||||
"""Threading model identifiers for ESPHome scheduler."""
|
||||
"""Threading model identifiers for ESPHome scheduler.
|
||||
|
||||
ESPHome currently uses three threading models based on platform capabilities:
|
||||
|
||||
SINGLE:
|
||||
- Single-threaded platforms (ESP8266, RP2040)
|
||||
- No RTOS task switching
|
||||
- No concurrent access to scheduler data structures
|
||||
- No atomics or locks required
|
||||
- Minimal overhead
|
||||
|
||||
MULTI_NO_ATOMICS:
|
||||
- Multi-threaded platforms without hardware atomic RMW support (e.g. LibreTiny BK7231N)
|
||||
- Uses FreeRTOS or another RTOS with multiple tasks
|
||||
- CPU lacks exclusive load/store instructions (ARM968E-S has no LDREX/STREX)
|
||||
- std::atomic cannot provide lock-free RMW; libatomic is avoided to save flash (4–8 KB)
|
||||
- Scheduler uses explicit FreeRTOS mutexes for synchronization
|
||||
|
||||
MULTI_ATOMICS:
|
||||
- Multi-threaded platforms with hardware atomic RMW support (ESP32, Cortex-M, Host)
|
||||
- CPU provides native atomic instructions (ESP32 S32C1I, ARM LDREX/STREX)
|
||||
- std::atomic is used for lock-free synchronization
|
||||
- Reduced contention and better performance
|
||||
"""
|
||||
|
||||
SINGLE = "ESPHOME_THREAD_SINGLE"
|
||||
MULTI_NO_ATOMICS = "ESPHOME_THREAD_MULTI_NO_ATOMICS"
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
sensor:
|
||||
- platform: template
|
||||
id: temp_sensor
|
||||
lambda: return 22.0;
|
||||
update_interval: 60s
|
||||
- platform: template
|
||||
id: humidity_sensor
|
||||
lambda: return 50.0;
|
||||
update_interval: 60s
|
||||
|
||||
climate:
|
||||
- platform: climate_ir_lg
|
||||
name: LG Climate
|
||||
transmitter_id: xmitr
|
||||
sensor: temp_sensor
|
||||
humidity_sensor: humidity_sensor
|
||||
|
||||
13
tests/components/hc8/common.yaml
Normal file
13
tests/components/hc8/common.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- hc8.calibrate:
|
||||
id: hc8_sensor
|
||||
baseline: 420
|
||||
|
||||
sensor:
|
||||
- platform: hc8
|
||||
id: hc8_sensor
|
||||
co2:
|
||||
name: HC8 CO2 Value
|
||||
update_interval: 15s
|
||||
4
tests/components/hc8/test.esp32-idf.yaml
Normal file
4
tests/components/hc8/test.esp32-idf.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/hc8/test.esp8266-ard.yaml
Normal file
4
tests/components/hc8/test.esp8266-ard.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/hc8/test.rp2040-ard.yaml
Normal file
4
tests/components/hc8/test.rp2040-ard.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
Reference in New Issue
Block a user