Merge branch 'dev' into add-graphical-layout-system

This commit is contained in:
Michael Davidson 2024-01-20 09:42:45 +11:00 committed by GitHub
commit 6fe40257b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
82 changed files with 2219 additions and 360 deletions

View file

@ -45,7 +45,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v3.3.2
uses: actions/cache@v4.0.0
with:
path: venv
# yamllint disable-line rule:line-length
@ -365,7 +365,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Cache platformio
uses: actions/cache@v3.3.2
uses: actions/cache@v4.0.0
with:
path: ~/.platformio
# yamllint disable-line rule:line-length
@ -392,6 +392,62 @@ jobs:
# yamllint disable-line rule:line-length
if: always()
list-components:
runs-on: ubuntu-latest
needs:
- common
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.1
with:
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
fetch-depth: 500
- name: Fetch dev branch
run: |
git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +refs/heads/dev*:refs/remotes/origin/dev* +refs/tags/dev*:refs/tags/dev*
git merge-base refs/remotes/origin/dev HEAD
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Find changed components
id: set-matrix
run: |
. venv/bin/activate
echo "matrix=$(script/list-components.py --changed | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
test-build-components:
name: Component test ${{ matrix.file }}
runs-on: ubuntu-latest
needs:
- common
- list-components
if: ${{ needs.list-components.outputs.matrix != '[]' && needs.list-components.outputs.matrix != '' }}
strategy:
fail-fast: false
max-parallel: 2
matrix:
file: ${{ fromJson(needs.list-components.outputs.matrix) }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: test_build_components -e config -c ${{ matrix.file }}
run: |
. venv/bin/activate
./script/test_build_components -e config -c ${{ matrix.file }}
- name: test_build_components -e compile -c ${{ matrix.file }}
run: |
. venv/bin/activate
./script/test_build_components -e compile -c ${{ matrix.file }}
ci-status:
name: CI Status
runs-on: ubuntu-latest
@ -406,6 +462,7 @@ jobs:
- pyupgrade
- compile-tests
- clang-tidy
- test-build-components
if: always()
steps:
- name: Success

View file

@ -71,6 +71,7 @@ esphome/components/cd74hc4067/* @asoehlke
esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet
esphome/components/color_temperature/* @jesserockz
esphome/components/combination/* @Cat-Ion @kahrendt
esphome/components/coolix/* @glmnet
esphome/components/copy/* @OttoWinter
esphome/components/cover/* @esphome/core
@ -138,6 +139,7 @@ esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/hm3301/* @freekode
esphome/components/homeassistant/* @OttoWinter
esphome/components/honeywell_hih_i2c/* @Benichou34
esphome/components/honeywellabp/* @RubyBailey
esphome/components/honeywellabp2_i2c/* @jpfaff
esphome/components/host/* @esphome/core
@ -161,7 +163,6 @@ esphome/components/integration/* @OttoWinter
esphome/components/internal_temperature/* @Mat931
esphome/components/interval/* @esphome/core
esphome/components/json/* @OttoWinter
esphome/components/kalman_combinator/* @Cat-Ion
esphome/components/key_collector/* @ssieb
esphome/components/key_provider/* @ssieb
esphome/components/kuntze/* @ssieb
@ -367,6 +368,7 @@ esphome/components/veml3235/* @kbx81
esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz
esphome/components/wake_on_lan/* @willwill2will54
esphome/components/waveshare_epaper/* @clydebarrow
esphome/components/web_server_base/* @OttoWinter
esphome/components/web_server_idf/* @dentra
esphome/components/whirlpool/* @glmnet

View file

@ -81,7 +81,7 @@ RUN \
fi; \
pip3 install \
--break-system-packages --no-cache-dir \
platformio==6.1.11 \
platformio==6.1.13 \
# Change some platformio settings
&& platformio settings set enable_telemetry No \
&& platformio settings set check_platformio_interval 1000000 \

View file

@ -242,7 +242,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
this->set_notify_(true);
#ifdef USE_TIME
if (this->time_id_.has_value()) {
if (this->time_id_ != nullptr) {
this->send_local_time();
}
#endif
@ -441,9 +441,8 @@ uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) {
#ifdef USE_TIME
void BedJetHub::send_local_time() {
if (this->time_id_.has_value()) {
auto *time_id = *this->time_id_;
ESPTime now = time_id->now();
if (this->time_id_ != nullptr) {
ESPTime now = this->time_id_->now();
if (now.is_valid()) {
this->set_clock(now.hour, now.minute);
ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute);
@ -454,10 +453,9 @@ void BedJetHub::send_local_time() {
}
void BedJetHub::setup_time_() {
if (this->time_id_.has_value()) {
if (this->time_id_ != nullptr) {
this->send_local_time();
auto *time_id = *this->time_id_;
time_id->add_on_time_sync_callback([this] { this->send_local_time(); });
this->time_id_->add_on_time_sync_callback([this] { this->send_local_time(); });
} else {
ESP_LOGI(TAG, "`time_id` is not configured: will not sync BedJet clock.");
}

View file

@ -141,7 +141,7 @@ class BedJetHub : public esphome::ble_client::BLEClientNode, public PollingCompo
#ifdef USE_TIME
/** Initializes time sync callbacks to support syncing current time to the BedJet. */
void setup_time_();
optional<time::RealTimeClock *> time_id_{};
time::RealTimeClock *time_id_{nullptr};
#endif
uint32_t timeout_{DEFAULT_STATUS_TIMEOUT};

View file

@ -0,0 +1,262 @@
#include "combination.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include <cmath>
#include <functional>
#include <vector>
namespace esphome {
namespace combination {
static const char *const TAG = "combination";
void CombinationComponent::log_config_(const LogString *combo_type) {
LOG_SENSOR("", "Combination Sensor:", this);
ESP_LOGCONFIG(TAG, " Combination Type: %s", LOG_STR_ARG(combo_type));
this->log_source_sensors();
}
void CombinationNoParameterComponent::add_source(Sensor *sensor) { this->sensors_.emplace_back(sensor); }
void CombinationOneParameterComponent::add_source(Sensor *sensor, std::function<float(float)> const &stddev) {
this->sensor_pairs_.emplace_back(sensor, stddev);
}
void CombinationOneParameterComponent::add_source(Sensor *sensor, float stddev) {
this->add_source(sensor, std::function<float(float)>{[stddev](float x) -> float { return stddev; }});
}
void CombinationNoParameterComponent::log_source_sensors() {
ESP_LOGCONFIG(TAG, " Source Sensors:");
for (const auto &sensor : this->sensors_) {
ESP_LOGCONFIG(TAG, " - %s", sensor->get_name().c_str());
}
}
void CombinationOneParameterComponent::log_source_sensors() {
ESP_LOGCONFIG(TAG, " Source Sensors:");
for (const auto &sensor : this->sensor_pairs_) {
auto &entity = *sensor.first;
ESP_LOGCONFIG(TAG, " - %s", entity.get_name().c_str());
}
}
void CombinationNoParameterComponent::setup() {
for (const auto &sensor : this->sensors_) {
// All sensor updates are deferred until the next loop. This avoids publishing the combined sensor's result
// repeatedly in the same loop if multiple source senors update.
sensor->add_on_state_callback(
[this](float value) -> void { this->defer("update", [this, value]() { this->handle_new_value(value); }); });
}
}
void KalmanCombinationComponent::dump_config() {
this->log_config_(LOG_STR("kalman"));
ESP_LOGCONFIG(TAG, " Update variance: %f per ms", this->update_variance_value_);
if (this->std_dev_sensor_ != nullptr) {
LOG_SENSOR(" ", "Standard Deviation Sensor:", this->std_dev_sensor_);
}
}
void KalmanCombinationComponent::setup() {
for (const auto &sensor : this->sensor_pairs_) {
const auto stddev = sensor.second;
sensor.first->add_on_state_callback([this, stddev](float x) -> void { this->correct_(x, stddev(x)); });
}
}
void KalmanCombinationComponent::update_variance_() {
uint32_t now = millis();
// Variance increases by update_variance_ each millisecond
auto dt = now - this->last_update_;
auto dv = this->update_variance_value_ * dt;
this->variance_ += dv;
this->last_update_ = now;
}
void KalmanCombinationComponent::correct_(float value, float stddev) {
if (std::isnan(value) || std::isinf(stddev)) {
return;
}
if (std::isnan(this->state_) || std::isinf(this->variance_)) {
this->state_ = value;
this->variance_ = stddev * stddev;
if (this->std_dev_sensor_ != nullptr) {
this->std_dev_sensor_->publish_state(stddev);
}
return;
}
this->update_variance_();
// Combine two gaussian distributions mu1+-var1, mu2+-var2 to a new one around mu
// Use the value with the smaller variance as mu1 to prevent precision errors
const bool this_first = this->variance_ < (stddev * stddev);
const float mu1 = this_first ? this->state_ : value;
const float mu2 = this_first ? value : this->state_;
const float var1 = this_first ? this->variance_ : stddev * stddev;
const float var2 = this_first ? stddev * stddev : this->variance_;
const float mu = mu1 + var1 * (mu2 - mu1) / (var1 + var2);
const float var = var1 - (var1 * var1) / (var1 + var2);
// Update and publish state
this->state_ = mu;
this->variance_ = var;
this->publish_state(mu);
if (this->std_dev_sensor_ != nullptr) {
this->std_dev_sensor_->publish_state(std::sqrt(var));
}
}
void LinearCombinationComponent::setup() {
for (const auto &sensor : this->sensor_pairs_) {
// All sensor updates are deferred until the next loop. This avoids publishing the combined sensor's result
// repeatedly in the same loop if multiple source senors update.
sensor.first->add_on_state_callback(
[this](float value) -> void { this->defer("update", [this, value]() { this->handle_new_value(value); }); });
}
}
void LinearCombinationComponent::handle_new_value(float value) {
// Multiplies each sensor state by a configured coeffecient and then sums
if (!std::isfinite(value))
return;
float sum = 0.0;
for (const auto &sensor : this->sensor_pairs_) {
const float sensor_state = sensor.first->state;
if (std::isfinite(sensor_state)) {
sum += sensor_state * sensor.second(sensor_state);
}
}
this->publish_state(sum);
};
void MaximumCombinationComponent::handle_new_value(float value) {
if (!std::isfinite(value))
return;
float max_value = (-1) * std::numeric_limits<float>::infinity(); // note x = max(x, -infinity)
for (const auto &sensor : this->sensors_) {
if (std::isfinite(sensor->state)) {
max_value = std::max(max_value, sensor->state);
}
}
this->publish_state(max_value);
}
void MeanCombinationComponent::handle_new_value(float value) {
if (!std::isfinite(value))
return;
float sum = 0.0;
size_t count = 0.0;
for (const auto &sensor : this->sensors_) {
if (std::isfinite(sensor->state)) {
++count;
sum += sensor->state;
}
}
float mean = sum / count;
this->publish_state(mean);
}
void MedianCombinationComponent::handle_new_value(float value) {
// Sorts sensor states in ascending order and determines the middle value
if (!std::isfinite(value))
return;
std::vector<float> sensor_states;
for (const auto &sensor : this->sensors_) {
if (std::isfinite(sensor->state)) {
sensor_states.push_back(sensor->state);
}
}
sort(sensor_states.begin(), sensor_states.end());
size_t sensor_states_size = sensor_states.size();
float median = NAN;
if (sensor_states_size) {
if (sensor_states_size % 2) {
// Odd number of measurements, use middle measurement
median = sensor_states[sensor_states_size / 2];
} else {
// Even number of measurements, use the average of the two middle measurements
median = (sensor_states[sensor_states_size / 2] + sensor_states[sensor_states_size / 2 - 1]) / 2.0;
}
}
this->publish_state(median);
}
void MinimumCombinationComponent::handle_new_value(float value) {
if (!std::isfinite(value))
return;
float min_value = std::numeric_limits<float>::infinity(); // note x = min(x, infinity)
for (const auto &sensor : this->sensors_) {
if (std::isfinite(sensor->state)) {
min_value = std::min(min_value, sensor->state);
}
}
this->publish_state(min_value);
}
void MostRecentCombinationComponent::handle_new_value(float value) { this->publish_state(value); }
void RangeCombinationComponent::handle_new_value(float value) {
// Sorts sensor states then takes difference between largest and smallest states
if (!std::isfinite(value))
return;
std::vector<float> sensor_states;
for (const auto &sensor : this->sensors_) {
if (std::isfinite(sensor->state)) {
sensor_states.push_back(sensor->state);
}
}
sort(sensor_states.begin(), sensor_states.end());
float range = sensor_states.back() - sensor_states.front();
this->publish_state(range);
}
void SumCombinationComponent::handle_new_value(float value) {
if (!std::isfinite(value))
return;
float sum = 0.0;
for (const auto &sensor : this->sensors_) {
if (std::isfinite(sensor->state)) {
sum += sensor->state;
}
}
this->publish_state(sum);
}
} // namespace combination
} // namespace esphome

View file

@ -0,0 +1,141 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include <vector>
namespace esphome {
namespace combination {
class CombinationComponent : public Component, public sensor::Sensor {
public:
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
/// @brief Logs all source sensor's names
virtual void log_source_sensors() = 0;
protected:
/// @brief Logs the sensor for use in dump_config
/// @param combo_type Name of the combination operation
void log_config_(const LogString *combo_type);
};
/// @brief Base class for operations that do not require an extra parameter to compute the combination
class CombinationNoParameterComponent : public CombinationComponent {
public:
/// @brief Adds a callback to each source sensor
void setup() override;
void add_source(Sensor *sensor);
/// @brief Computes the combination
/// @param value Newest sensor measurement
virtual void handle_new_value(float value) = 0;
/// @brief Logs all source sensor's names in sensors_
void log_source_sensors() override;
protected:
std::vector<Sensor *> sensors_;
};
// Base class for opertions that require one parameter to compute the combination
class CombinationOneParameterComponent : public CombinationComponent {
public:
void add_source(Sensor *sensor, std::function<float(float)> const &stddev);
void add_source(Sensor *sensor, float stddev);
/// @brief Logs all source sensor's names in sensor_pairs_
void log_source_sensors() override;
protected:
std::vector<std::pair<Sensor *, std::function<float(float)>>> sensor_pairs_;
};
class KalmanCombinationComponent : public CombinationOneParameterComponent {
public:
void dump_config() override;
void setup() override;
void set_process_std_dev(float process_std_dev) {
this->update_variance_value_ = process_std_dev * process_std_dev * 0.001f;
}
void set_std_dev_sensor(Sensor *sensor) { this->std_dev_sensor_ = sensor; }
protected:
void update_variance_();
void correct_(float value, float stddev);
// Optional sensor for publishing the current error
sensor::Sensor *std_dev_sensor_{nullptr};
// Tick of the last update
uint32_t last_update_{0};
// Change of the variance, per ms
float update_variance_value_{0.f};
// Best guess for the state and its variance
float state_{NAN};
float variance_{INFINITY};
};
class LinearCombinationComponent : public CombinationOneParameterComponent {
public:
void dump_config() override { this->log_config_(LOG_STR("linear")); }
void setup() override;
void handle_new_value(float value);
};
class MaximumCombinationComponent : public CombinationNoParameterComponent {
public:
void dump_config() override { this->log_config_(LOG_STR("max")); }
void handle_new_value(float value) override;
};
class MeanCombinationComponent : public CombinationNoParameterComponent {
public:
void dump_config() override { this->log_config_(LOG_STR("mean")); }
void handle_new_value(float value) override;
};
class MedianCombinationComponent : public CombinationNoParameterComponent {
public:
void dump_config() override { this->log_config_(LOG_STR("median")); }
void handle_new_value(float value) override;
};
class MinimumCombinationComponent : public CombinationNoParameterComponent {
public:
void dump_config() override { this->log_config_(LOG_STR("min")); }
void handle_new_value(float value) override;
};
class MostRecentCombinationComponent : public CombinationNoParameterComponent {
public:
void dump_config() override { this->log_config_(LOG_STR("most_recently_updated")); }
void handle_new_value(float value) override;
};
class RangeCombinationComponent : public CombinationNoParameterComponent {
public:
void dump_config() override { this->log_config_(LOG_STR("range")); }
void handle_new_value(float value) override;
};
class SumCombinationComponent : public CombinationNoParameterComponent {
public:
void dump_config() override { this->log_config_(LOG_STR("sum")); }
void handle_new_value(float value) override;
};
} // namespace combination
} // namespace esphome

View file

@ -0,0 +1,176 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_ACCURACY_DECIMALS,
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_RANGE,
CONF_SOURCE,
CONF_SUM,
CONF_TYPE,
CONF_UNIT_OF_MEASUREMENT,
)
from esphome.core.entity_helpers import inherit_property_from
CODEOWNERS = ["@Cat-Ion", "@kahrendt"]
combination_ns = cg.esphome_ns.namespace("combination")
KalmanCombinationComponent = combination_ns.class_(
"KalmanCombinationComponent", cg.Component, sensor.Sensor
)
LinearCombinationComponent = combination_ns.class_(
"LinearCombinationComponent", cg.Component, sensor.Sensor
)
MaximumCombinationComponent = combination_ns.class_(
"MaximumCombinationComponent", cg.Component, sensor.Sensor
)
MeanCombinationComponent = combination_ns.class_(
"MeanCombinationComponent", cg.Component, sensor.Sensor
)
MedianCombinationComponent = combination_ns.class_(
"MedianCombinationComponent", cg.Component, sensor.Sensor
)
MinimumCombinationComponent = combination_ns.class_(
"MinimumCombinationComponent", cg.Component, sensor.Sensor
)
MostRecentCombinationComponent = combination_ns.class_(
"MostRecentCombinationComponent", cg.Component, sensor.Sensor
)
RangeCombinationComponent = combination_ns.class_(
"RangeCombinationComponent", cg.Component, sensor.Sensor
)
SumCombinationComponent = combination_ns.class_(
"SumCombinationComponent", cg.Component, sensor.Sensor
)
CONF_COEFFECIENT = "coeffecient"
CONF_ERROR = "error"
CONF_KALMAN = "kalman"
CONF_LINEAR = "linear"
CONF_MAX = "max"
CONF_MEAN = "mean"
CONF_MEDIAN = "median"
CONF_MIN = "min"
CONF_MOST_RECENTLY_UPDATED = "most_recently_updated"
CONF_PROCESS_STD_DEV = "process_std_dev"
CONF_SOURCES = "sources"
CONF_STD_DEV = "std_dev"
KALMAN_SOURCE_SCHEMA = cv.Schema(
{
cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor),
cv.Required(CONF_ERROR): cv.templatable(cv.positive_float),
}
)
LINEAR_SOURCE_SCHEMA = cv.Schema(
{
cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor),
cv.Required(CONF_COEFFECIENT): cv.templatable(cv.float_),
}
)
SENSOR_ONLY_SOURCE_SCHEMA = cv.Schema(
{
cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor),
}
)
CONFIG_SCHEMA = cv.typed_schema(
{
CONF_KALMAN: sensor.sensor_schema(KalmanCombinationComponent)
.extend(cv.COMPONENT_SCHEMA)
.extend(
{
cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float,
cv.Required(CONF_SOURCES): cv.ensure_list(KALMAN_SOURCE_SCHEMA),
cv.Optional(CONF_STD_DEV): sensor.sensor_schema(),
}
),
CONF_LINEAR: sensor.sensor_schema(LinearCombinationComponent)
.extend(cv.COMPONENT_SCHEMA)
.extend({cv.Required(CONF_SOURCES): cv.ensure_list(LINEAR_SOURCE_SCHEMA)}),
CONF_MAX: sensor.sensor_schema(MaximumCombinationComponent)
.extend(cv.COMPONENT_SCHEMA)
.extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}),
CONF_MEAN: sensor.sensor_schema(MeanCombinationComponent)
.extend(cv.COMPONENT_SCHEMA)
.extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}),
CONF_MEDIAN: sensor.sensor_schema(MedianCombinationComponent)
.extend(cv.COMPONENT_SCHEMA)
.extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}),
CONF_MIN: sensor.sensor_schema(MinimumCombinationComponent)
.extend(cv.COMPONENT_SCHEMA)
.extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}),
CONF_MOST_RECENTLY_UPDATED: sensor.sensor_schema(MostRecentCombinationComponent)
.extend(cv.COMPONENT_SCHEMA)
.extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}),
CONF_RANGE: sensor.sensor_schema(RangeCombinationComponent)
.extend(cv.COMPONENT_SCHEMA)
.extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}),
CONF_SUM: sensor.sensor_schema(SumCombinationComponent)
.extend(cv.COMPONENT_SCHEMA)
.extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}),
}
)
# Inherit some sensor values from the first source, for both the state and the error value
# CONF_STATE_CLASS could also be inherited, but might lead to unexpected behaviour with "total_increasing"
properties_to_inherit = [
CONF_ACCURACY_DECIMALS,
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_UNIT_OF_MEASUREMENT,
]
inherit_schema_for_state = [
inherit_property_from(property, [CONF_SOURCES, 0, CONF_SOURCE])
for property in properties_to_inherit
]
inherit_schema_for_std_dev = [
inherit_property_from([CONF_STD_DEV, property], [CONF_SOURCES, 0, CONF_SOURCE])
for property in properties_to_inherit
]
FINAL_VALIDATE_SCHEMA = cv.All(
*inherit_schema_for_state,
*inherit_schema_for_std_dev,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await sensor.register_sensor(var, config)
if proces_std_dev := config.get(CONF_PROCESS_STD_DEV):
cg.add(var.set_process_std_dev(proces_std_dev))
for source_conf in config[CONF_SOURCES]:
source = await cg.get_variable(source_conf[CONF_SOURCE])
if config[CONF_TYPE] == CONF_KALMAN:
error = await cg.templatable(
source_conf[CONF_ERROR],
[(float, "x")],
cg.float_,
)
cg.add(var.add_source(source, error))
elif config[CONF_TYPE] == CONF_LINEAR:
coeffecient = await cg.templatable(
source_conf[CONF_COEFFECIENT],
[(float, "x")],
cg.float_,
)
cg.add(var.add_source(source, coeffecient))
else:
cg.add(var.add_source(source))
if CONF_STD_DEV in config:
sens = await sensor.new_sensor(config[CONF_STD_DEV])
cg.add(var.set_std_dev_sensor(sens))

View file

@ -91,7 +91,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
delayMicroseconds(40);
} else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
delayMicroseconds(2000);
} else if (this->model_ == DHT_MODEL_AM2302) {
} else if (this->model_ == DHT_MODEL_AM2120 || this->model_ == DHT_MODEL_AM2302) {
delayMicroseconds(1000);
} else {
delayMicroseconds(800);

View file

@ -11,6 +11,7 @@ enum DHTModel {
DHT_MODEL_AUTO_DETECT = 0,
DHT_MODEL_DHT11,
DHT_MODEL_DHT22,
DHT_MODEL_AM2120,
DHT_MODEL_AM2302,
DHT_MODEL_RHT03,
DHT_MODEL_SI7021,
@ -27,6 +28,7 @@ class DHT : public PollingComponent {
* - DHT_MODEL_AUTO_DETECT (default)
* - DHT_MODEL_DHT11
* - DHT_MODEL_DHT22
* - DHT_MODEL_AM2120
* - DHT_MODEL_AM2302
* - DHT_MODEL_RHT03
* - DHT_MODEL_SI7021

View file

@ -23,6 +23,7 @@ DHT_MODELS = {
"AUTO_DETECT": DHTModel.DHT_MODEL_AUTO_DETECT,
"DHT11": DHTModel.DHT_MODEL_DHT11,
"DHT22": DHTModel.DHT_MODEL_DHT22,
"AM2120": DHTModel.DHT_MODEL_AM2120,
"AM2302": DHTModel.DHT_MODEL_AM2302,
"RHT03": DHTModel.DHT_MODEL_RHT03,
"SI7021": DHTModel.DHT_MODEL_SI7021,

View file

@ -142,9 +142,9 @@ void Display::filled_circle(int center_x, int center_y, int radius, Color color)
} while (dx <= 0);
}
void HOT Display::triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
this->line(x1, y1, x2, y2);
this->line(x1, y1, x3, y3);
this->line(x2, y2, x3, y3);
this->line(x1, y1, x2, y2, color);
this->line(x1, y1, x3, y3, color);
this->line(x2, y2, x3, y3, color);
}
void Display::sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3) {
if (*y1 > *y2) {

View file

@ -0,0 +1,2 @@
"""Support for Honeywell HumidIcon HIH"""
CODEOWNERS = ["@Benichou34"]

View file

@ -0,0 +1,97 @@
// Honeywell HumidIcon I2C Sensors
// https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/humidity-with-temperature-sensors/common/documents/sps-siot-i2c-comms-humidicon-tn-009061-2-en-ciid-142171.pdf
//
#include "honeywell_hih.h"
#include "esphome/core/log.h"
namespace esphome {
namespace honeywell_hih_i2c {
static const char *const TAG = "honeywell_hih.i2c";
static const uint8_t REQUEST_CMD[1] = {0x00}; // Measurement Request Format
static const uint16_t MAX_COUNT = 0x3FFE; // 2^14 - 2
void HoneywellHIComponent::read_sensor_data_() {
uint8_t data[4];
if (this->read(data, sizeof(data)) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Communication with Honeywell HIH failed!");
this->mark_failed();
return;
}
const uint16_t raw_humidity = (static_cast<uint16_t>(data[0] & 0x3F) << 8) | data[1];
float humidity = (static_cast<float>(raw_humidity) / MAX_COUNT) * 100;
const uint16_t raw_temperature = (static_cast<uint16_t>(data[2]) << 6) | (data[3] >> 2);
float temperature = (static_cast<float>(raw_temperature) / MAX_COUNT) * 165 - 40;
ESP_LOGD(TAG, "Got temperature=%.2f°C humidity=%.2f%%", temperature, humidity);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->humidity_sensor_ != nullptr)
this->humidity_sensor_->publish_state(humidity);
}
void HoneywellHIComponent::start_measurement_() {
if (this->write(REQUEST_CMD, sizeof(REQUEST_CMD)) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Communication with Honeywell HIH failed!");
this->mark_failed();
return;
}
this->measurement_running_ = true;
}
bool HoneywellHIComponent::is_measurement_ready_() {
uint8_t data[1];
if (this->read(data, sizeof(data)) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Communication with Honeywell HIH failed!");
this->mark_failed();
return false;
}
// Check status bits
return ((data[0] & 0xC0) == 0x00);
}
void HoneywellHIComponent::measurement_timeout_() {
ESP_LOGE(TAG, "Honeywell HIH Timeout!");
this->measurement_running_ = false;
this->mark_failed();
}
void HoneywellHIComponent::update() {
ESP_LOGV(TAG, "Update Honeywell HIH Sensor");
this->start_measurement_();
// The measurement cycle duration is typically 36.65 ms for temperature and humidity readings.
this->set_timeout("meas_timeout", 100, [this] { this->measurement_timeout_(); });
}
void HoneywellHIComponent::loop() {
if (this->measurement_running_ && this->is_measurement_ready_()) {
this->measurement_running_ = false;
this->cancel_timeout("meas_timeout");
this->read_sensor_data_();
}
}
void HoneywellHIComponent::dump_config() {
ESP_LOGD(TAG, "Honeywell HIH:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with Honeywell HIH failed!");
}
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
LOG_UPDATE_INTERVAL(this);
}
float HoneywellHIComponent::get_setup_priority() const { return setup_priority::DATA; }
} // namespace honeywell_hih_i2c
} // namespace esphome

View file

@ -0,0 +1,34 @@
// Honeywell HumidIcon I2C Sensors
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace honeywell_hih_i2c {
class HoneywellHIComponent : public PollingComponent, public i2c::I2CDevice {
public:
void dump_config() override;
float get_setup_priority() const override;
void loop() override;
void update() override;
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
protected:
bool measurement_running_{false};
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
private:
void read_sensor_data_();
void start_measurement_();
bool is_measurement_ready_();
void measurement_timeout_();
};
} // namespace honeywell_hih_i2c
} // namespace esphome

View file

@ -0,0 +1,56 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_HUMIDITY,
CONF_ID,
CONF_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
)
DEPENDENCIES = ["i2c"]
honeywell_hih_ns = cg.esphome_ns.namespace("honeywell_hih_i2c")
HONEYWELLHIComponent = honeywell_hih_ns.class_(
"HoneywellHIComponent", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(HONEYWELLHIComponent),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x27))
)
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)
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens))
if humidity_config := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(humidity_config)
cg.add(var.set_humidity_sensor(sens))

View file

@ -6,6 +6,7 @@ from esphome.const import (
PLATFORM_HOST,
)
from esphome.core import CORE
from esphome.helpers import IS_MACOS
import esphome.config_validation as cv
import esphome.codegen as cg
@ -14,7 +15,6 @@ from .const import KEY_HOST
# force import gpio to register pin schema
from .gpio import host_pin_to_code # noqa
CODEOWNERS = ["@esphome/core"]
AUTO_LOAD = ["network"]
@ -35,5 +35,9 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
cg.add_build_flag("-DUSE_HOST")
cg.add_build_flag("-std=c++17")
cg.add_build_flag("-lsodium")
if IS_MACOS:
cg.add_build_flag("-L/opt/homebrew/lib")
cg.add_define("ESPHOME_BOARD", "host")
cg.add_platformio_option("platform", "platformio/native")

View file

@ -1 +0,0 @@
CODEOWNERS = ["@Cat-Ion"]

View file

@ -1,82 +0,0 @@
#include "kalman_combinator.h"
#include "esphome/core/hal.h"
#include <cmath>
#include <functional>
namespace esphome {
namespace kalman_combinator {
void KalmanCombinatorComponent::dump_config() {
ESP_LOGCONFIG("kalman_combinator", "Kalman Combinator:");
ESP_LOGCONFIG("kalman_combinator", " Update variance: %f per ms", this->update_variance_value_);
ESP_LOGCONFIG("kalman_combinator", " Sensors:");
for (const auto &sensor : this->sensors_) {
auto &entity = *sensor.first;
ESP_LOGCONFIG("kalman_combinator", " - %s", entity.get_name().c_str());
}
}
void KalmanCombinatorComponent::setup() {
for (const auto &sensor : this->sensors_) {
const auto stddev = sensor.second;
sensor.first->add_on_state_callback([this, stddev](float x) -> void { this->correct_(x, stddev(x)); });
}
}
void KalmanCombinatorComponent::add_source(Sensor *sensor, std::function<float(float)> const &stddev) {
this->sensors_.emplace_back(sensor, stddev);
}
void KalmanCombinatorComponent::add_source(Sensor *sensor, float stddev) {
this->add_source(sensor, std::function<float(float)>{[stddev](float x) -> float { return stddev; }});
}
void KalmanCombinatorComponent::update_variance_() {
uint32_t now = millis();
// Variance increases by update_variance_ each millisecond
auto dt = now - this->last_update_;
auto dv = this->update_variance_value_ * dt;
this->variance_ += dv;
this->last_update_ = now;
}
void KalmanCombinatorComponent::correct_(float value, float stddev) {
if (std::isnan(value) || std::isinf(stddev)) {
return;
}
if (std::isnan(this->state_) || std::isinf(this->variance_)) {
this->state_ = value;
this->variance_ = stddev * stddev;
if (this->std_dev_sensor_ != nullptr) {
this->std_dev_sensor_->publish_state(stddev);
}
return;
}
this->update_variance_();
// Combine two gaussian distributions mu1+-var1, mu2+-var2 to a new one around mu
// Use the value with the smaller variance as mu1 to prevent precision errors
const bool this_first = this->variance_ < (stddev * stddev);
const float mu1 = this_first ? this->state_ : value;
const float mu2 = this_first ? value : this->state_;
const float var1 = this_first ? this->variance_ : stddev * stddev;
const float var2 = this_first ? stddev * stddev : this->variance_;
const float mu = mu1 + var1 * (mu2 - mu1) / (var1 + var2);
const float var = var1 - (var1 * var1) / (var1 + var2);
// Update and publish state
this->state_ = mu;
this->variance_ = var;
this->publish_state(mu);
if (this->std_dev_sensor_ != nullptr) {
this->std_dev_sensor_->publish_state(std::sqrt(var));
}
}
} // namespace kalman_combinator
} // namespace esphome

View file

@ -1,46 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include <cmath>
#include <vector>
namespace esphome {
namespace kalman_combinator {
class KalmanCombinatorComponent : public Component, public sensor::Sensor {
public:
KalmanCombinatorComponent() = default;
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
void dump_config() override;
void setup() override;
void add_source(Sensor *sensor, std::function<float(float)> const &stddev);
void add_source(Sensor *sensor, float stddev);
void set_process_std_dev(float process_std_dev) {
this->update_variance_value_ = process_std_dev * process_std_dev * 0.001f;
}
void set_std_dev_sensor(Sensor *sensor) { this->std_dev_sensor_ = sensor; }
private:
void update_variance_();
void correct_(float value, float stddev);
// Source sensors and their error functions
std::vector<std::pair<Sensor *, std::function<float(float)>>> sensors_;
// Optional sensor for publishing the current error
sensor::Sensor *std_dev_sensor_{nullptr};
// Tick of the last update
uint32_t last_update_{0};
// Change of the variance, per ms
float update_variance_value_{0.f};
// Best guess for the state and its variance
float state_{NAN};
float variance_{INFINITY};
};
} // namespace kalman_combinator
} // namespace esphome

View file

@ -1,90 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_ID,
CONF_SOURCE,
CONF_ACCURACY_DECIMALS,
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_UNIT_OF_MEASUREMENT,
CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid(
"The kalman_combinator sensor has moved.\nPlease use the combination platform instead with type: kalman.\n"
"See https://esphome.io/components/sensor/combination.html"
)
from esphome.core.entity_helpers import inherit_property_from
kalman_combinator_ns = cg.esphome_ns.namespace("kalman_combinator")
KalmanCombinatorComponent = kalman_combinator_ns.class_(
"KalmanCombinatorComponent", cg.Component, sensor.Sensor
)
CONF_ERROR = "error"
CONF_SOURCES = "sources"
CONF_PROCESS_STD_DEV = "process_std_dev"
CONF_STD_DEV = "std_dev"
CONFIG_SCHEMA = (
sensor.sensor_schema(KalmanCombinatorComponent)
.extend(cv.COMPONENT_SCHEMA)
.extend(
{
cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float,
cv.Required(CONF_SOURCES): cv.ensure_list(
cv.Schema(
{
cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor),
cv.Required(CONF_ERROR): cv.templatable(cv.positive_float),
}
),
),
cv.Optional(CONF_STD_DEV): sensor.sensor_schema(),
}
)
)
# Inherit some sensor values from the first source, for both the state and the error value
properties_to_inherit = [
CONF_ACCURACY_DECIMALS,
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_UNIT_OF_MEASUREMENT,
# CONF_STATE_CLASS could also be inherited, but might lead to unexpected behaviour with "total_increasing"
]
inherit_schema_for_state = [
inherit_property_from(property, [CONF_SOURCES, 0, CONF_SOURCE])
for property in properties_to_inherit
]
inherit_schema_for_std_dev = [
inherit_property_from([CONF_STD_DEV, property], [CONF_SOURCES, 0, CONF_SOURCE])
for property in properties_to_inherit
]
FINAL_VALIDATE_SCHEMA = cv.All(
CONFIG_SCHEMA.extend(
{cv.Required(CONF_ID): cv.use_id(KalmanCombinatorComponent)},
extra=cv.ALLOW_EXTRA,
),
*inherit_schema_for_state,
*inherit_schema_for_std_dev,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await sensor.register_sensor(var, config)
cg.add(var.set_process_std_dev(config[CONF_PROCESS_STD_DEV]))
for source_conf in config[CONF_SOURCES]:
source = await cg.get_variable(source_conf[CONF_SOURCE])
error = await cg.templatable(
source_conf[CONF_ERROR],
[(float, "x")],
cg.float_,
)
cg.add(var.add_source(source, error))
if CONF_STD_DEV in config:
sens = await sensor.new_sensor(config[CONF_STD_DEV])
cg.add(var.set_std_dev_sensor(sens))

View file

@ -10,6 +10,8 @@ from esphome.const import (
CONF_BIRTH_MESSAGE,
CONF_BROKER,
CONF_CERTIFICATE_AUTHORITY,
CONF_CLIENT_CERTIFICATE,
CONF_CLIENT_CERTIFICATE_KEY,
CONF_CLIENT_ID,
CONF_COMMAND_TOPIC,
CONF_COMMAND_RETAIN,
@ -199,6 +201,12 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_CERTIFICATE_AUTHORITY): cv.All(
cv.string, cv.only_with_esp_idf
),
cv.Inclusive(CONF_CLIENT_CERTIFICATE, "cert-key-pair"): cv.All(
cv.string, cv.only_on_esp32
),
cv.Inclusive(CONF_CLIENT_CERTIFICATE_KEY, "cert-key-pair"): cv.All(
cv.string, cv.only_on_esp32
),
cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32_idf=False): cv.All(
cv.boolean, cv.only_with_esp_idf
),
@ -378,6 +386,9 @@ async def to_code(config):
if CONF_CERTIFICATE_AUTHORITY in config:
cg.add(var.set_ca_certificate(config[CONF_CERTIFICATE_AUTHORITY]))
cg.add(var.set_skip_cert_cn_check(config[CONF_SKIP_CERT_CN_CHECK]))
if CONF_CLIENT_CERTIFICATE in config:
cg.add(var.set_cl_certificate(config[CONF_CLIENT_CERTIFICATE]))
cg.add(var.set_cl_key(config[CONF_CLIENT_CERTIFICATE_KEY]))
# prevent error -0x428e
# See https://github.com/espressif/esp-idf/issues/139

View file

@ -45,6 +45,11 @@ bool MQTTBackendESP32::initialize_() {
mqtt_cfg_.cert_pem = ca_certificate_.value().c_str();
mqtt_cfg_.skip_cert_common_name_check = skip_cert_cn_check_;
mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_SSL;
if (this->cl_certificate_.has_value() && this->cl_key_.has_value()) {
mqtt_cfg_.client_cert_pem = this->cl_certificate_.value().c_str();
mqtt_cfg_.client_key_pem = this->cl_key_.value().c_str();
}
} else {
mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_TCP;
}
@ -79,6 +84,11 @@ bool MQTTBackendESP32::initialize_() {
mqtt_cfg_.broker.verification.certificate = ca_certificate_.value().c_str();
mqtt_cfg_.broker.verification.skip_cert_common_name_check = skip_cert_cn_check_;
mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_SSL;
if (this->cl_certificate_.has_value() && this->cl_key_.has_value()) {
mqtt_cfg_.credentials.authentication.certificate = this->cl_certificate_.value().c_str();
mqtt_cfg_.credentials.authentication.key = this->cl_key_.value().c_str();
}
} else {
mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_TCP;
}

View file

@ -124,6 +124,8 @@ class MQTTBackendESP32 final : public MQTTBackend {
void loop() final;
void set_ca_certificate(const std::string &cert) { ca_certificate_ = cert; }
void set_cl_certificate(const std::string &cert) { cl_certificate_ = cert; }
void set_cl_key(const std::string &key) { cl_key_ = key; }
void set_skip_cert_cn_check(bool skip_check) { skip_cert_cn_check_ = skip_check; }
protected:
@ -154,6 +156,8 @@ class MQTTBackendESP32 final : public MQTTBackend {
uint16_t keep_alive_;
bool clean_session_;
optional<std::string> ca_certificate_;
optional<std::string> cl_certificate_;
optional<std::string> cl_key_;
bool skip_cert_cn_check_{false};
// callbacks

View file

@ -146,6 +146,8 @@ class MQTTClientComponent : public Component {
#endif
#ifdef USE_ESP32
void set_ca_certificate(const char *cert) { this->mqtt_backend_.set_ca_certificate(cert); }
void set_cl_certificate(const char *cert) { this->mqtt_backend_.set_cl_certificate(cert); }
void set_cl_key(const char *key) { this->mqtt_backend_.set_cl_key(key); }
void set_skip_cert_cn_check(bool skip_check) { this->mqtt_backend_.set_skip_cert_cn_check(skip_check); }
#endif
const Availability &get_availability();

View file

@ -14,6 +14,13 @@
#include <IPAddress.h>
#endif /* USE_ADRDUINO */
#ifdef USE_HOST
#include <arpa/inet.h>
using ip_addr_t = in_addr;
using ip4_addr_t = in_addr;
#define ipaddr_aton(x, y) inet_aton((x), (y))
#endif
#if USE_ESP32_FRAMEWORK_ARDUINO
#define arduino_ns Arduino_h
#elif USE_LIBRETINY
@ -32,6 +39,14 @@ namespace network {
struct IPAddress {
public:
#ifdef USE_HOST
IPAddress() { ip_addr_.s_addr = 0; }
IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) {
this->ip_addr_.s_addr = htonl((first << 24) | (second << 16) | (third << 8) | fourth);
}
IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); }
IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; }
#else
IPAddress() { ip_addr_set_zero(&ip_addr_); }
IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) {
IP_ADDR4(&ip_addr_, first, second, third, fourth);
@ -107,6 +122,7 @@ struct IPAddress {
}
return *this;
}
#endif
protected:
ip_addr_t ip_addr_;

View file

@ -12,6 +12,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_OTA,
KEY_PAST_SAFE_MODE,
CONF_VERSION,
)
from esphome.core import CORE, coroutine_with_priority
@ -41,6 +42,7 @@ CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(OTAComponent),
cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, int=True),
cv.SplitDefault(
CONF_PORT,
esp8266=8266,
@ -93,6 +95,7 @@ async def to_code(config):
if CONF_PASSWORD in config:
cg.add(var.set_auth_password(config[CONF_PASSWORD]))
cg.add_define("USE_OTA_PASSWORD")
cg.add_define("USE_OTA_VERSION", config[CONF_VERSION])
await cg.register_component(var, config)

View file

@ -20,8 +20,7 @@ namespace esphome {
namespace ota {
static const char *const TAG = "ota";
static const uint8_t OTA_VERSION_1_0 = 1;
static constexpr u_int16_t OTA_BLOCK_SIZE = 8192;
OTAComponent *global_ota_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
@ -101,6 +100,7 @@ void OTAComponent::dump_config() {
ESP_LOGCONFIG(TAG, " Using Password.");
}
#endif
ESP_LOGCONFIG(TAG, " OTA version: %d.", USE_OTA_VERSION);
if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1 &&
this->safe_mode_rtc_value_ != esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) {
ESP_LOGW(TAG, "Last Boot was an unhandled reset, will proceed to safe mode in %" PRIu32 " restarts",
@ -132,6 +132,9 @@ void OTAComponent::handle_() {
uint8_t ota_features;
std::unique_ptr<OTABackend> backend;
(void) ota_features;
#if USE_OTA_VERSION == 2
size_t size_acknowledged = 0;
#endif
if (client_ == nullptr) {
struct sockaddr_storage source_addr;
@ -168,7 +171,7 @@ void OTAComponent::handle_() {
// Send OK and version - 2 bytes
buf[0] = OTA_RESPONSE_OK;
buf[1] = OTA_VERSION_1_0;
buf[1] = USE_OTA_VERSION;
this->writeall_(buf, 2);
backend = make_ota_backend();
@ -312,6 +315,13 @@ void OTAComponent::handle_() {
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
}
total += read;
#if USE_OTA_VERSION == 2
while (size_acknowledged + OTA_BLOCK_SIZE <= total || (total == ota_size && size_acknowledged < ota_size)) {
buf[0] = OTA_RESPONSE_CHUNK_OK;
this->writeall_(buf, 1);
size_acknowledged += OTA_BLOCK_SIZE;
}
#endif
uint32_t now = millis();
if (now - last_progress > 1000) {

View file

@ -10,31 +10,32 @@ namespace esphome {
namespace ota {
enum OTAResponseTypes {
OTA_RESPONSE_OK = 0,
OTA_RESPONSE_REQUEST_AUTH = 1,
OTA_RESPONSE_OK = 0x00,
OTA_RESPONSE_REQUEST_AUTH = 0x01,
OTA_RESPONSE_HEADER_OK = 64,
OTA_RESPONSE_AUTH_OK = 65,
OTA_RESPONSE_UPDATE_PREPARE_OK = 66,
OTA_RESPONSE_BIN_MD5_OK = 67,
OTA_RESPONSE_RECEIVE_OK = 68,
OTA_RESPONSE_UPDATE_END_OK = 69,
OTA_RESPONSE_SUPPORTS_COMPRESSION = 70,
OTA_RESPONSE_HEADER_OK = 0x40,
OTA_RESPONSE_AUTH_OK = 0x41,
OTA_RESPONSE_UPDATE_PREPARE_OK = 0x42,
OTA_RESPONSE_BIN_MD5_OK = 0x43,
OTA_RESPONSE_RECEIVE_OK = 0x44,
OTA_RESPONSE_UPDATE_END_OK = 0x45,
OTA_RESPONSE_SUPPORTS_COMPRESSION = 0x46,
OTA_RESPONSE_CHUNK_OK = 0x47,
OTA_RESPONSE_ERROR_MAGIC = 128,
OTA_RESPONSE_ERROR_UPDATE_PREPARE = 129,
OTA_RESPONSE_ERROR_AUTH_INVALID = 130,
OTA_RESPONSE_ERROR_WRITING_FLASH = 131,
OTA_RESPONSE_ERROR_UPDATE_END = 132,
OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 133,
OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 134,
OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 135,
OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 136,
OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137,
OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 138,
OTA_RESPONSE_ERROR_MD5_MISMATCH = 139,
OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 140,
OTA_RESPONSE_ERROR_UNKNOWN = 255,
OTA_RESPONSE_ERROR_MAGIC = 0x80,
OTA_RESPONSE_ERROR_UPDATE_PREPARE = 0x81,
OTA_RESPONSE_ERROR_AUTH_INVALID = 0x82,
OTA_RESPONSE_ERROR_WRITING_FLASH = 0x83,
OTA_RESPONSE_ERROR_UPDATE_END = 0x84,
OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 0x85,
OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 0x86,
OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 0x87,
OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 0x88,
OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 0x89,
OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 0x8A,
OTA_RESPONSE_ERROR_MD5_MISMATCH = 0x8B,
OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 0x8C,
OTA_RESPONSE_ERROR_UNKNOWN = 0xFF,
};
enum OTAState { OTA_COMPLETED = 0, OTA_STARTED, OTA_IN_PROGRESS, OTA_ERROR };

View file

@ -87,7 +87,7 @@ class BSDSocketImpl : public Socket {
int listen(int backlog) override { return ::listen(fd_, backlog); }
ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); }
ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override {
#if defined(USE_ESP32)
#if defined(USE_ESP32) || defined(USE_HOST)
return ::recvfrom(this->fd_, buf, len, 0, addr, addr_len);
#else
return ::lwip_recvfrom(this->fd_, buf, len, 0, addr, addr_len);

View file

@ -29,12 +29,15 @@ from esphome.const import (
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
CONF_ALLOW_OTHER_USES,
CONF_DATA_PINS,
)
from esphome.core import coroutine_with_priority, CORE
CODEOWNERS = ["@esphome/core", "@clydebarrow"]
spi_ns = cg.esphome_ns.namespace("spi")
SPIComponent = spi_ns.class_("SPIComponent", cg.Component)
QuadSPIComponent = spi_ns.class_("QuadSPIComponent", cg.Component)
SPIDevice = spi_ns.class_("SPIDevice")
SPIDataRate = spi_ns.enum("SPIDataRate")
SPIMode = spi_ns.enum("SPIMode")
@ -190,12 +193,9 @@ def get_hw_spi(config, available):
def validate_spi_config(config):
available = list(range(len(get_hw_interface_list())))
for spi in config:
# map pin number to schema
spi[CONF_CLK_PIN] = pins.gpio_output_pin_schema(spi[CONF_CLK_PIN])
interface = spi[CONF_INTERFACE]
if spi[CONF_FORCE_SW]:
if interface == "any":
spi[CONF_INTERFACE] = interface = "software"
elif interface != "software":
raise cv.Invalid("force_sw is deprecated - use interface: software")
if interface == "software":
pass
elif interface == "any":
@ -229,6 +229,8 @@ def validate_spi_config(config):
spi, spi[CONF_INTERFACE_INDEX]
):
raise cv.Invalid("Invalid pin selections for hardware SPI interface")
if CONF_DATA_PINS in spi and CONF_INTERFACE_INDEX not in spi:
raise cv.Invalid("Quad mode requires a hardware interface")
return config
@ -249,14 +251,26 @@ def get_spi_interface(index):
return "new SPIClass(HSPI)"
# Do not use a pin schema for the number, as that will trigger a pin reuse error due to duplication of the
# clock pin in the standard and quad schemas.
clk_pin_validator = cv.maybe_simple_value(
{
cv.Required(CONF_NUMBER): cv.Any(cv.int_, cv.string),
cv.Optional(CONF_ALLOW_OTHER_USES): cv.boolean,
},
key=CONF_NUMBER,
)
SPI_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(SPIComponent),
cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_CLK_PIN): clk_pin_validator,
cv.Optional(CONF_MISO_PIN): pins.gpio_input_pin_schema,
cv.Optional(CONF_MOSI_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_FORCE_SW, default=False): cv.boolean,
cv.Optional(CONF_FORCE_SW): cv.invalid(
"force_sw is deprecated - use interface: software"
),
cv.Optional(CONF_INTERFACE, default="any"): cv.one_of(
*sum(get_hw_interface_list(), ["software", "hardware", "any"]),
lower=True,
@ -267,8 +281,34 @@ SPI_SCHEMA = cv.All(
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]),
)
SPI_QUAD_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(QuadSPIComponent),
cv.Required(CONF_CLK_PIN): clk_pin_validator,
cv.Required(CONF_DATA_PINS): cv.All(
cv.ensure_list(pins.internal_gpio_output_pin_number),
cv.Length(min=4, max=4),
),
cv.Optional(CONF_INTERFACE, default="hardware"): cv.one_of(
*sum(get_hw_interface_list(), ["hardware"]),
lower=True,
),
}
),
cv.only_with_esp_idf,
)
CONFIG_SCHEMA = cv.All(
cv.ensure_list(SPI_SCHEMA),
# Order is important. SPI_SCHEMA is the default.
cv.ensure_list(
cv.Any(
SPI_SCHEMA,
SPI_QUAD_SCHEMA,
msg="Standard SPI requires mosi_pin and/or miso_pin; quad SPI requires data_pins only."
+ " A clock pin is always required",
),
),
validate_spi_config,
)
@ -277,43 +317,46 @@ CONFIG_SCHEMA = cv.All(
async def to_code(configs):
cg.add_define("USE_SPI")
cg.add_global(spi_ns.using)
if CORE.using_arduino:
cg.add_library("SPI", None)
for spi in configs:
var = cg.new_Pvariable(spi[CONF_ID])
await cg.register_component(var, spi)
clk = await cg.gpio_pin_expression(spi[CONF_CLK_PIN])
cg.add(var.set_clk(clk))
if CONF_MISO_PIN in spi:
miso = await cg.gpio_pin_expression(spi[CONF_MISO_PIN])
cg.add(var.set_miso(miso))
if CONF_MOSI_PIN in spi:
mosi = await cg.gpio_pin_expression(spi[CONF_MOSI_PIN])
cg.add(var.set_mosi(mosi))
if CONF_INTERFACE_INDEX in spi:
index = spi[CONF_INTERFACE_INDEX]
cg.add(var.set_interface(cg.RawExpression(get_spi_interface(index))))
if miso := spi.get(CONF_MISO_PIN):
cg.add(var.set_miso(await cg.gpio_pin_expression(miso)))
if mosi := spi.get(CONF_MOSI_PIN):
cg.add(var.set_mosi(await cg.gpio_pin_expression(mosi)))
if data_pins := spi.get(CONF_DATA_PINS):
cg.add(var.set_data_pins(data_pins))
if (index := spi.get(CONF_INTERFACE_INDEX)) is not None:
interface = get_spi_interface(index)
cg.add(var.set_interface(cg.RawExpression(interface)))
cg.add(
var.set_interface_name(
re.sub(
r"\W", "", get_spi_interface(index).replace("new SPIClass", "")
)
re.sub(r"\W", "", interface.replace("new SPIClass", ""))
)
)
if CORE.using_arduino:
cg.add_library("SPI", None)
def spi_device_schema(
cs_pin_required=True, default_data_rate=cv.UNDEFINED, default_mode=cv.UNDEFINED
cs_pin_required=True,
default_data_rate=cv.UNDEFINED,
default_mode=cv.UNDEFINED,
quad=False,
):
"""Create a schema for an SPI device.
:param cs_pin_required: If true, make the CS_PIN required in the config.
:param default_data_rate: Optional data_rate to use as default
:param default_mode Optional. The default SPI mode to use.
:param quad If set, will require an SPI component configured as quad data bits.
:return: The SPI device schema, `extend` this in your config schema.
"""
schema = {
cv.GenerateID(CONF_SPI_ID): cv.use_id(SPIComponent),
cv.GenerateID(CONF_SPI_ID): cv.use_id(
QuadSPIComponent if quad else SPIComponent
),
cv.Optional(CONF_DATA_RATE, default=default_data_rate): SPI_DATA_RATE_SCHEMA,
cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum(
SPI_MODE_OPTIONS, upper=True

View file

@ -49,7 +49,8 @@ void SPIComponent::setup() {
}
if (this->using_hw_) {
this->spi_bus_ = SPIComponent::get_bus(this->interface_, this->clk_pin_, this->sdo_pin_, this->sdi_pin_);
this->spi_bus_ =
SPIComponent::get_bus(this->interface_, this->clk_pin_, this->sdo_pin_, this->sdi_pin_, this->data_pins_);
if (this->spi_bus_ == nullptr) {
ESP_LOGE(TAG, "Unable to allocate SPI interface");
this->mark_failed();
@ -68,6 +69,9 @@ void SPIComponent::dump_config() {
LOG_PIN(" CLK Pin: ", this->clk_pin_)
LOG_PIN(" SDI Pin: ", this->sdi_pin_)
LOG_PIN(" SDO Pin: ", this->sdo_pin_)
for (size_t i = 0; i != this->data_pins_.size(); i++) {
ESP_LOGCONFIG(TAG, " Data pin %u: GPIO%d", i, this->data_pins_[i]);
}
if (this->spi_bus_->is_hw()) {
ESP_LOGCONFIG(TAG, " Using HW SPI: %s", this->interface_name_);
} else {

View file

@ -1,11 +1,12 @@
#pragma once
#include "esphome/core/application.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include <vector>
#include <map>
#include <utility>
#include <vector>
#ifdef USE_ARDUINO
@ -208,6 +209,10 @@ class SPIDelegate {
esph_log_e("spi_device", "variable length write not implemented");
}
virtual void write_cmd_addr_data(size_t cmd_bits, uint32_t cmd, size_t addr_bits, uint32_t address,
const uint8_t *data, size_t length, uint8_t bus_width) {
esph_log_e("spi_device", "write_cmd_addr_data not implemented");
}
// write 16 bits
virtual void write16(uint16_t data) {
if (this->bit_order_ == BIT_ORDER_MSB_FIRST) {
@ -331,6 +336,7 @@ class SPIComponent : public Component {
void set_miso(GPIOPin *sdi) { this->sdi_pin_ = sdi; }
void set_mosi(GPIOPin *sdo) { this->sdo_pin_ = sdo; }
void set_data_pins(std::vector<uint8_t> pins) { this->data_pins_ = std::move(pins); }
void set_interface(SPIInterface interface) {
this->interface_ = interface;
@ -348,15 +354,19 @@ class SPIComponent : public Component {
GPIOPin *clk_pin_{nullptr};
GPIOPin *sdi_pin_{nullptr};
GPIOPin *sdo_pin_{nullptr};
std::vector<uint8_t> data_pins_{};
SPIInterface interface_{};
bool using_hw_{false};
const char *interface_name_{nullptr};
SPIBus *spi_bus_{};
std::map<SPIClient *, SPIDelegate *> devices_;
static SPIBus *get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi);
static SPIBus *get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi,
const std::vector<uint8_t> &data_pins);
};
using QuadSPIComponent = SPIComponent;
/**
* Base class for SPIDevice, un-templated.
*/
@ -422,18 +432,49 @@ class SPIDevice : public SPIClient {
void read_array(uint8_t *data, size_t length) { return this->delegate_->read_array(data, length); }
/**
* Write a single data item, up to 32 bits.
* @param data The data
* @param num_bits The number of bits to write. The lower num_bits of data will be sent.
*/
void write(uint16_t data, size_t num_bits) { this->delegate_->write(data, num_bits); };
/* Write command, address and data. Command and address will be written as single-bit SPI,
* data phase can be multiple bit (currently only 1 or 4)
* @param cmd_bits Number of bits to write in the command phase
* @param cmd The command value to write
* @param addr_bits Number of bits to write in addr phase
* @param address Address data
* @param data Plain data bytes
* @param length Number of data bytes
* @param bus_width The number of data lines to use for the data phase.
*/
void write_cmd_addr_data(size_t cmd_bits, uint32_t cmd, size_t addr_bits, uint32_t address, const uint8_t *data,
size_t length, uint8_t bus_width = 1) {
this->delegate_->write_cmd_addr_data(cmd_bits, cmd, addr_bits, address, data, length, bus_width);
}
void write_byte(uint8_t data) { this->delegate_->write_array(&data, 1); }
/**
* Write the array data, replace with received data.
* @param data
* @param length
*/
void transfer_array(uint8_t *data, size_t length) { this->delegate_->transfer(data, length); }
uint8_t transfer_byte(uint8_t data) { return this->delegate_->transfer(data); }
// the driver will byte-swap if required.
/** Write 16 bit data. The driver will byte-swap if required.
*/
void write_byte16(uint16_t data) { this->delegate_->write16(data); }
// avoid use of this if possible. It's inefficient and ugly.
/**
* Write an array of data as 16 bit values, byte-swapping if required. Use of this should be avoided as
* it is horribly slow.
* @param data
* @param length
*/
void write_array16(const uint16_t *data, size_t length) { this->delegate_->write_array16(data, length); }
void enable() { this->delegate_->begin_transaction(); }

View file

@ -85,7 +85,8 @@ class SPIBusHw : public SPIBus {
bool is_hw() override { return true; }
};
SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) {
SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi,
const std::vector<uint8_t> &data_pins) {
return new SPIBusHw(clk, sdo, sdi, interface);
}

View file

@ -104,6 +104,60 @@ class SPIDelegateHw : public SPIDelegate {
}
}
/**
* Write command, address and data
* @param cmd_bits Number of bits to write in the command phase
* @param cmd The command value to write
* @param addr_bits Number of bits to write in addr phase
* @param address Address data
* @param data Remaining data bytes
* @param length Number of data bytes
* @param bus_width The number of data lines to use
*/
void write_cmd_addr_data(size_t cmd_bits, uint32_t cmd, size_t addr_bits, uint32_t address, const uint8_t *data,
size_t length, uint8_t bus_width) override {
spi_transaction_ext_t desc = {};
if (length == 0 && cmd_bits == 0 && addr_bits == 0) {
esph_log_w(TAG, "Nothing to transfer");
return;
}
desc.base.flags = SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_DUMMY;
if (bus_width == 4) {
desc.base.flags |= SPI_TRANS_MODE_QIO;
} else if (bus_width == 8) {
desc.base.flags |= SPI_TRANS_MODE_OCT;
}
desc.command_bits = cmd_bits;
desc.address_bits = addr_bits;
desc.dummy_bits = 0;
desc.base.rxlength = 0;
desc.base.cmd = cmd;
desc.base.addr = address;
do {
size_t chunk_size = std::min(length, MAX_TRANSFER_SIZE);
if (data != nullptr && chunk_size != 0) {
desc.base.length = chunk_size * 8;
desc.base.tx_buffer = data;
length -= chunk_size;
data += chunk_size;
} else {
length = 0;
desc.base.length = 0;
}
esp_err_t err = spi_device_polling_start(this->handle_, (spi_transaction_t *) &desc, portMAX_DELAY);
if (err == ESP_OK) {
err = spi_device_polling_end(this->handle_, portMAX_DELAY);
}
if (err != ESP_OK) {
ESP_LOGE(TAG, "Transmit failed - err %X", err);
return;
}
// if more data is to be sent, skip the command and address phases.
desc.command_bits = 0;
desc.address_bits = 0;
} while (length != 0);
}
void transfer(uint8_t *ptr, size_t length) override { this->transfer(ptr, ptr, length); }
uint8_t transfer(uint8_t data) override {
@ -142,13 +196,27 @@ class SPIDelegateHw : public SPIDelegate {
class SPIBusHw : public SPIBus {
public:
SPIBusHw(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, SPIInterface channel) : SPIBus(clk, sdo, sdi), channel_(channel) {
SPIBusHw(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, SPIInterface channel, std::vector<uint8_t> data_pins)
: SPIBus(clk, sdo, sdi), channel_(channel) {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = Utility::get_pin_no(sdo);
buscfg.miso_io_num = Utility::get_pin_no(sdi);
buscfg.sclk_io_num = Utility::get_pin_no(clk);
buscfg.quadwp_io_num = -1;
buscfg.quadhd_io_num = -1;
buscfg.flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_SCLK;
if (data_pins.empty()) {
buscfg.mosi_io_num = Utility::get_pin_no(sdo);
buscfg.miso_io_num = Utility::get_pin_no(sdi);
buscfg.quadwp_io_num = -1;
buscfg.quadhd_io_num = -1;
} else {
buscfg.data0_io_num = data_pins[0];
buscfg.data1_io_num = data_pins[1];
buscfg.data2_io_num = data_pins[2];
buscfg.data3_io_num = data_pins[3];
buscfg.data4_io_num = -1;
buscfg.data5_io_num = -1;
buscfg.data6_io_num = -1;
buscfg.data7_io_num = -1;
buscfg.flags |= SPICOMMON_BUSFLAG_QUAD;
}
buscfg.max_transfer_sz = MAX_TRANSFER_SIZE;
auto err = spi_bus_initialize(channel, &buscfg, SPI_DMA_CH_AUTO);
if (err != ESP_OK)
@ -166,8 +234,9 @@ class SPIBusHw : public SPIBus {
bool is_hw() override { return true; }
};
SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) {
return new SPIBusHw(clk, sdo, sdi, interface);
SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi,
const std::vector<uint8_t> &data_pins) {
return new SPIBusHw(clk, sdo, sdi, interface, data_pins);
}
#endif

View file

@ -1,6 +1,10 @@
#include "real_time_clock.h"
#include "esphome/core/log.h"
#ifdef USE_HOST
#include <sys/time.h>
#else
#include "lwip/opt.h"
#endif
#ifdef USE_ESP8266
#include "sys/time.h"
#endif
@ -25,7 +29,7 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
.tv_sec = static_cast<time_t>(epoch), .tv_usec = 0,
};
ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch);
timezone tz = {0, 0};
struct timezone tz = {0, 0};
int ret = settimeofday(&timev, &tz);
if (ret == EINVAL) {
// Some ESP8266 frameworks abort when timezone parameter is not NULL

View file

@ -23,8 +23,8 @@ static const int MAX_RETRIES = 5;
void Tuya::setup() {
this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
if (this->status_pin_.has_value()) {
this->status_pin_.value()->digital_write(false);
if (this->status_pin_ != nullptr) {
this->status_pin_->digital_write(false);
}
}
@ -70,9 +70,7 @@ void Tuya::dump_config() {
ESP_LOGCONFIG(TAG, " GPIO Configuration: status: pin %d, reset: pin %d", this->status_pin_reported_,
this->reset_pin_reported_);
}
if (this->status_pin_.has_value()) {
LOG_PIN(" Status Pin: ", this->status_pin_.value());
}
LOG_PIN(" Status Pin: ", this->status_pin_);
ESP_LOGCONFIG(TAG, " Product: '%s'", this->product_.c_str());
}
@ -194,7 +192,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
this->init_state_ = TuyaInitState::INIT_DATAPOINT;
this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
bool is_pin_equals =
this->status_pin_.has_value() && this->status_pin_.value()->get_pin() == this->status_pin_reported_;
this->status_pin_ != nullptr && this->status_pin_->get_pin() == this->status_pin_reported_;
// Configure status pin toggling (if reported and configured) or WIFI_STATE periodic send
if (is_pin_equals) {
ESP_LOGV(TAG, "Configured status pin %i", this->status_pin_reported_);
@ -244,13 +242,12 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
break;
case TuyaCommandType::LOCAL_TIME_QUERY:
#ifdef USE_TIME
if (this->time_id_.has_value()) {
if (this->time_id_ != nullptr) {
this->send_local_time_();
if (!this->time_sync_callback_registered_) {
// tuya mcu supports time, so we let them know when our time changed
auto *time_id = *this->time_id_;
time_id->add_on_time_sync_callback([this] { this->send_local_time_(); });
this->time_id_->add_on_time_sync_callback([this] { this->send_local_time_(); });
this->time_sync_callback_registered_ = true;
}
} else
@ -463,7 +460,7 @@ void Tuya::send_empty_command_(TuyaCommandType command) {
void Tuya::set_status_pin_() {
bool is_network_ready = network::is_connected() && remote_is_connected();
this->status_pin_.value()->digital_write(is_network_ready);
this->status_pin_->digital_write(is_network_ready);
}
uint8_t Tuya::get_wifi_status_code_() {
@ -511,8 +508,7 @@ void Tuya::send_wifi_status_() {
#ifdef USE_TIME
void Tuya::send_local_time_() {
std::vector<uint8_t> payload;
auto *time_id = *this->time_id_;
ESPTime now = time_id->now();
ESPTime now = this->time_id_->now();
if (now.is_valid()) {
uint8_t year = now.year - 2000;
uint8_t month = now.month;

View file

@ -130,14 +130,14 @@ class Tuya : public Component, public uart::UARTDevice {
#ifdef USE_TIME
void send_local_time_();
optional<time::RealTimeClock *> time_id_{};
time::RealTimeClock *time_id_{nullptr};
bool time_sync_callback_registered_{false};
#endif
TuyaInitState init_state_ = TuyaInitState::INIT_HEARTBEAT;
bool init_failed_{false};
int init_retries_{0};
uint8_t protocol_version_ = -1;
optional<InternalGPIOPin *> status_pin_{};
InternalGPIOPin *status_pin_{nullptr};
int status_pin_reported_ = -1;
int reset_pin_reported_ = -1;
uint32_t last_command_timestamp_ = 0;

View file

@ -17,7 +17,7 @@ static const char *const TAG = "voice_assistant";
static const size_t SAMPLE_RATE_HZ = 16000;
static const size_t INPUT_BUFFER_SIZE = 32 * SAMPLE_RATE_HZ / 1000; // 32ms * 16kHz / 1000ms
static const size_t BUFFER_SIZE = 1000 * SAMPLE_RATE_HZ / 1000; // 1s
static const size_t BUFFER_SIZE = 1024 * SAMPLE_RATE_HZ / 1000;
static const size_t SEND_BUFFER_SIZE = INPUT_BUFFER_SIZE * sizeof(int16_t);
static const size_t RECEIVE_SIZE = 1024;
static const size_t SPEAKER_BUFFER_SIZE = 16 * RECEIVE_SIZE;
@ -231,10 +231,12 @@ void VoiceAssistant::loop() {
}
case State::STREAMING_MICROPHONE: {
this->read_microphone_();
if (this->ring_buffer_->available() >= SEND_BUFFER_SIZE) {
this->ring_buffer_->read((void *) this->send_buffer_, SEND_BUFFER_SIZE, 0);
this->socket_->sendto(this->send_buffer_, SEND_BUFFER_SIZE, 0, (struct sockaddr *) &this->dest_addr_,
size_t available = this->ring_buffer_->available();
while (available >= SEND_BUFFER_SIZE) {
size_t read_bytes = this->ring_buffer_->read((void *) this->send_buffer_, SEND_BUFFER_SIZE, 0);
this->socket_->sendto(this->send_buffer_, read_bytes, 0, (struct sockaddr *) &this->dest_addr_,
sizeof(this->dest_addr_));
available = this->ring_buffer_->available();
}
break;

View file

@ -0,0 +1 @@
CODEOWNERS = ["@clydebarrow"]

View file

@ -72,6 +72,9 @@ WaveshareEPaper7P5InHDB = waveshare_epaper_ns.class_(
WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_(
"WaveshareEPaper2P13InDKE", WaveshareEPaper
)
WaveshareEPaper2P13InV3 = waveshare_epaper_ns.class_(
"WaveshareEPaper2P13InV3", WaveshareEPaper
)
GDEW0154M09 = waveshare_epaper_ns.class_("GDEW0154M09", WaveshareEPaper)
WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel")
@ -104,6 +107,7 @@ MODELS = {
"7.50inv2alt": ("b", WaveshareEPaper7P5InV2alt),
"7.50in-hd-b": ("b", WaveshareEPaper7P5InHDB),
"2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE),
"2.13inv3": ("c", WaveshareEPaper2P13InV3),
"1.54in-m5coreink-m09": ("c", GDEW0154M09),
}

View file

@ -0,0 +1,186 @@
#include "waveshare_epaper.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace waveshare_epaper {
static const char *const TAG = "waveshare_2.13v3";
static const uint8_t PARTIAL_LUT[] = {
0x32, // cmd
0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
};
static const uint8_t FULL_LUT[] = {
0x32, // CMD
0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x4A, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x4A, 0x80, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0x0, 0xF, 0x0, 0x0, 0x2, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
};
static const uint8_t SW_RESET = 0x12;
static const uint8_t ACTIVATE = 0x20;
static const uint8_t WRITE_BUFFER = 0x24;
static const uint8_t WRITE_BASE = 0x26;
static const uint8_t DRV_OUT_CTL[] = {0x01, 0x27, 0x01, 0x00}; // driver output control
static const uint8_t GATEV[] = {0x03, 0x17};
static const uint8_t SRCV[] = {0x04, 0x41, 0x0C, 0x32};
static const uint8_t SLEEP[] = {0x10, 0x01};
static const uint8_t DATA_ENTRY[] = {0x11, 0x03}; // data entry mode
static const uint8_t TEMP_SENS[] = {0x18, 0x80}; // Temp sensor
static const uint8_t DISPLAY_UPDATE[] = {0x21, 0x00, 0x80}; // Display update control
static const uint8_t UPSEQ[] = {0x22, 0xC0};
static const uint8_t ON_FULL[] = {0x22, 0xC7};
static const uint8_t ON_PARTIAL[] = {0x22, 0x0F};
static const uint8_t VCOM[] = {0x2C, 0x36};
static const uint8_t CMD5[] = {0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00};
static const uint8_t BORDER_PART[] = {0x3C, 0x80}; // border waveform
static const uint8_t BORDER_FULL[] = {0x3C, 0x05}; // border waveform
static const uint8_t CMD1[] = {0x3F, 0x22};
static const uint8_t RAM_X_START[] = {0x44, 0x00, 121 / 8}; // set ram_x_address_start_end
static const uint8_t RAM_Y_START[] = {0x45, 0x00, 0x00, 250 - 1, 0}; // set ram_y_address_start_end
static const uint8_t RAM_X_POS[] = {0x4E, 0x00}; // set ram_x_address_counter
// static const uint8_t RAM_Y_POS[] = {0x4F, 0x00, 0x00}; // set ram_y_address_counter
#define SEND(x) this->cmd_data(x, sizeof(x))
void WaveshareEPaper2P13InV3::write_lut_(const uint8_t *lut) {
this->wait_until_idle_();
this->cmd_data(lut, sizeof(PARTIAL_LUT));
SEND(CMD1);
SEND(GATEV);
SEND(SRCV);
SEND(VCOM);
}
// write the buffer starting on line top, up to line bottom.
void WaveshareEPaper2P13InV3::write_buffer_(uint8_t cmd, int top, int bottom) {
this->wait_until_idle_();
this->set_window_(top, bottom);
this->command(cmd);
this->start_data_();
auto width_bytes = this->get_width_internal() / 8;
this->write_array(this->buffer_ + top * width_bytes, (bottom - top) * width_bytes);
this->end_data_();
}
void WaveshareEPaper2P13InV3::send_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(false);
delay(2);
this->reset_pin_->digital_write(true);
}
}
void WaveshareEPaper2P13InV3::setup() {
setup_pins_();
delay(20);
this->send_reset_();
// as a one-off delay this is not worth working around.
delay(100); // NOLINT
this->wait_until_idle_();
this->command(SW_RESET);
this->wait_until_idle_();
SEND(DRV_OUT_CTL);
SEND(DATA_ENTRY);
SEND(CMD5);
this->set_window_(0, this->get_height_internal());
SEND(BORDER_FULL);
SEND(DISPLAY_UPDATE);
SEND(TEMP_SENS);
this->wait_until_idle_();
this->write_lut_(FULL_LUT);
}
// t and b are y positions, i.e. line numbers.
void WaveshareEPaper2P13InV3::set_window_(int t, int b) {
uint8_t buffer[3];
SEND(RAM_X_START);
SEND(RAM_Y_START);
SEND(RAM_X_POS);
buffer[0] = 0x4F;
buffer[1] = (uint8_t) t;
buffer[2] = (uint8_t) (t >> 8);
SEND(buffer);
}
// must implement, but we override setup to have more control
void WaveshareEPaper2P13InV3::initialize() {}
void WaveshareEPaper2P13InV3::partial_update_() {
this->send_reset_();
this->set_timeout(100, [this] {
this->write_lut_(PARTIAL_LUT);
SEND(BORDER_PART);
SEND(UPSEQ);
this->command(ACTIVATE);
this->set_timeout(100, [this] {
this->wait_until_idle_();
this->write_buffer_(WRITE_BUFFER, 0, this->get_height_internal());
SEND(ON_PARTIAL);
this->command(ACTIVATE); // Activate Display Update Sequence
this->is_busy_ = false;
});
});
}
void WaveshareEPaper2P13InV3::full_update_() {
ESP_LOGI(TAG, "Performing full e-paper update.");
this->write_lut_(FULL_LUT);
this->write_buffer_(WRITE_BUFFER, 0, this->get_height_internal());
this->write_buffer_(WRITE_BASE, 0, this->get_height_internal());
SEND(ON_FULL);
this->command(ACTIVATE); // don't wait here
this->is_busy_ = false;
}
void WaveshareEPaper2P13InV3::display() {
if (this->is_busy_ || (this->busy_pin_ != nullptr && this->busy_pin_->digital_read()))
return;
this->is_busy_ = true;
const bool partial = this->at_update_ != 0;
this->at_update_ = (this->at_update_ + 1) % this->full_update_every_;
if (partial) {
this->partial_update_();
} else {
this->full_update_();
}
}
int WaveshareEPaper2P13InV3::get_width_internal() { return 128; }
int WaveshareEPaper2P13InV3::get_height_internal() { return 250; }
uint32_t WaveshareEPaper2P13InV3::idle_timeout_() { return 5000; }
void WaveshareEPaper2P13InV3::dump_config() {
LOG_DISPLAY("", "Waveshare E-Paper", this)
ESP_LOGCONFIG(TAG, " Model: 2.13inV3");
LOG_PIN(" CS Pin: ", this->cs_)
LOG_PIN(" Reset Pin: ", this->reset_pin_)
LOG_PIN(" DC Pin: ", this->dc_pin_)
LOG_PIN(" Busy Pin: ", this->busy_pin_)
LOG_UPDATE_INTERVAL(this)
}
void WaveshareEPaper2P13InV3::set_full_update_every(uint32_t full_update_every) {
this->full_update_every_ = full_update_every;
}
} // namespace waveshare_epaper
} // namespace esphome

View file

@ -109,8 +109,20 @@ void WaveshareEPaper::data(uint8_t value) {
this->write_byte(value);
this->end_data_();
}
// write a command followed by one or more bytes of data.
// The command is the first byte, length is the total including cmd.
void WaveshareEPaper::cmd_data(const uint8_t *c_data, size_t length) {
this->dc_pin_->digital_write(false);
this->enable();
this->write_byte(c_data[0]);
this->dc_pin_->digital_write(true);
this->write_array(c_data + 1, length - 1);
this->disable();
}
bool WaveshareEPaper::wait_until_idle_() {
if (this->busy_pin_ == nullptr) {
if (this->busy_pin_ == nullptr || !this->busy_pin_->digital_read()) {
return true;
}
@ -120,7 +132,7 @@ bool WaveshareEPaper::wait_until_idle_() {
ESP_LOGE(TAG, "Timeout while displaying image!");
return false;
}
delay(10);
delay(1);
}
return true;
}
@ -1443,6 +1455,12 @@ void WaveshareEPaper7P5InBV2::initialize() {
// COMMAND TCON SETTING
this->command(0x60);
this->data(0x22);
this->command(0x82);
this->data(0x08);
this->command(0x30);
this->data(0x06);
// COMMAND RESOLUTION SETTING
this->command(0x65);
this->data(0x00);
@ -1472,6 +1490,7 @@ void HOT WaveshareEPaper7P5InBV2::display() {
this->command(0x12);
delay(100); // NOLINT
this->wait_until_idle_();
this->deep_sleep();
}
int WaveshareEPaper7P5InBV2::get_width_internal() { return 800; }
int WaveshareEPaper7P5InBV2::get_height_internal() { return 480; }
@ -1617,7 +1636,7 @@ void HOT WaveshareEPaper7P5InBV3::display() {
this->command(0x13); // Start Transmission
delay(2);
for (uint32_t i = 0; i < buf_len; i++) {
this->data(this->buffer_[i]);
this->data(~this->buffer_[i]);
}
this->command(0x12); // Display Refresh
@ -2211,8 +2230,9 @@ void HOT WaveshareEPaper2P13InDKE::display() {
} else {
// set up partial update
this->command(0x32);
for (uint8_t v : PART_UPDATE_LUT_TTGO_DKE)
this->data(v);
this->start_data_();
this->write_array(PART_UPDATE_LUT_TTGO_DKE, sizeof(PART_UPDATE_LUT_TTGO_DKE));
this->end_data_();
this->command(0x3F);
this->data(0x22);
@ -2257,12 +2277,10 @@ void HOT WaveshareEPaper2P13InDKE::display() {
this->wait_until_idle_();
// data must be sent again on partial update
delay(300); // NOLINT
this->command(0x24);
this->start_data_();
this->write_array(this->buffer_, this->get_buffer_length_());
this->end_data_();
delay(300); // NOLINT
}
ESP_LOGI(TAG, "Completed e-paper update.");
@ -2274,6 +2292,7 @@ uint32_t WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; }
void WaveshareEPaper2P13InDKE::dump_config() {
LOG_DISPLAY("", "Waveshare E-Paper", this);
ESP_LOGCONFIG(TAG, " Model: 2.13inDKE");
LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Busy Pin: ", this->busy_pin_);

View file

@ -19,6 +19,7 @@ class WaveshareEPaper : public display::DisplayBuffer,
void command(uint8_t value);
void data(uint8_t value);
void cmd_data(const uint8_t *data, size_t length);
virtual void display() = 0;
virtual void initialize() = 0;
@ -49,7 +50,7 @@ class WaveshareEPaper : public display::DisplayBuffer,
this->reset_pin_->digital_write(false);
delay(reset_duration_); // NOLINT
this->reset_pin_->digital_write(true);
delay(200); // NOLINT
delay(20);
}
}
@ -614,5 +615,39 @@ class WaveshareEPaper2P13InDKE : public WaveshareEPaper {
uint32_t at_update_{0};
};
class WaveshareEPaper2P13InV3 : public WaveshareEPaper {
public:
void display() override;
void dump_config() override;
void deep_sleep() override {
// COMMAND POWER DOWN
this->command(0x10);
this->data(0x01);
// cannot wait until idle here, the device no longer responds
}
void set_full_update_every(uint32_t full_update_every);
void setup() override;
void initialize() override;
protected:
int get_width_internal() override;
int get_height_internal() override;
uint32_t idle_timeout_() override;
void write_buffer_(uint8_t cmd, int top, int bottom);
void set_window_(int t, int b);
void send_reset_();
void partial_update_();
void full_update_();
uint32_t full_update_every_{30};
uint32_t at_update_{0};
bool is_busy_{false};
void write_lut_(const uint8_t *lut);
};
} // namespace waveshare_epaper
} // namespace esphome

View file

@ -55,6 +55,9 @@ void WiFiComponent::start() {
uint32_t hash = this->has_sta() ? fnv1_hash(App.get_compilation_time()) : 88491487UL;
this->pref_ = global_preferences->make_preference<wifi::SavedWifiSettings>(hash, true);
if (this->fast_connect_) {
this->fast_connect_pref_ = global_preferences->make_preference<wifi::SavedWifiFastConnectSettings>(hash, false);
}
SavedWifiSettings save{};
if (this->pref_.load(&save)) {
@ -78,6 +81,7 @@ void WiFiComponent::start() {
if (this->fast_connect_) {
this->selected_ap_ = this->sta_[0];
this->load_fast_connect_settings_();
this->start_connecting(this->selected_ap_, false);
} else {
this->start_scanning();
@ -604,6 +608,11 @@ void WiFiComponent::check_connecting_finished() {
this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTED;
this->num_retried_ = 0;
if (this->fast_connect_) {
this->save_fast_connect_settings_();
}
return;
}
@ -705,6 +714,35 @@ bool WiFiComponent::is_esp32_improv_active_() {
#endif
}
void WiFiComponent::load_fast_connect_settings_() {
SavedWifiFastConnectSettings fast_connect_save{};
if (this->fast_connect_pref_.load(&fast_connect_save)) {
bssid_t bssid{};
std::copy(fast_connect_save.bssid, fast_connect_save.bssid + 6, bssid.begin());
this->selected_ap_.set_bssid(bssid);
this->selected_ap_.set_channel(fast_connect_save.channel);
ESP_LOGD(TAG, "Loaded saved fast_connect wifi settings");
}
}
void WiFiComponent::save_fast_connect_settings_() {
bssid_t bssid = wifi_bssid();
uint8_t channel = wifi_channel_();
if (bssid != this->selected_ap_.get_bssid() || channel != this->selected_ap_.get_channel()) {
SavedWifiFastConnectSettings fast_connect_save{};
memcpy(fast_connect_save.bssid, bssid.data(), 6);
fast_connect_save.channel = channel;
this->fast_connect_pref_.save(&fast_connect_save);
ESP_LOGD(TAG, "Saved fast_connect wifi settings");
}
}
void WiFiAP::set_ssid(const std::string &ssid) { this->ssid_ = ssid; }
void WiFiAP::set_bssid(bssid_t bssid) { this->bssid_ = bssid; }
void WiFiAP::set_bssid(optional<bssid_t> bssid) { this->bssid_ = bssid; }

View file

@ -48,6 +48,11 @@ struct SavedWifiSettings {
char password[65];
} PACKED; // NOLINT
struct SavedWifiFastConnectSettings {
uint8_t bssid[6];
uint8_t channel;
} PACKED; // NOLINT
enum WiFiComponentState {
/** Nothing has been initialized yet. Internal AP, if configured, is disabled at this point. */
WIFI_COMPONENT_STATE_OFF = 0,
@ -334,6 +339,9 @@ class WiFiComponent : public Component {
bool is_captive_portal_active_();
bool is_esp32_improv_active_();
void load_fast_connect_settings_();
void save_fast_connect_settings_();
#ifdef USE_ESP8266
static void wifi_event_callback(System_Event_t *event);
void wifi_scan_done_callback_(void *arg, STATUS status);
@ -381,6 +389,7 @@ class WiFiComponent : public Component {
optional<float> output_power_;
bool passive_scan_{false};
ESPPreferenceObject pref_;
ESPPreferenceObject fast_connect_pref_;
bool has_saved_wifi_settings_{false};
#ifdef USE_WIFI_11KV_SUPPORT
bool btm_{false};

View file

@ -113,6 +113,8 @@ CONF_CHANNELS = "channels"
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
CONF_CHIPSET = "chipset"
CONF_CLEAR_IMPEDANCE = "clear_impedance"
CONF_CLIENT_CERTIFICATE = "client_certificate"
CONF_CLIENT_CERTIFICATE_KEY = "client_certificate_key"
CONF_CLIENT_ID = "client_id"
CONF_CLK_PIN = "clk_pin"
CONF_CLOCK_PIN = "clock_pin"

View file

@ -169,7 +169,7 @@ float Component::get_actual_setup_priority() const {
void Component::set_setup_priority(float priority) { this->setup_priority_override_ = priority; }
bool Component::has_overridden_loop() const {
#ifdef CLANG_TIDY
#if defined(USE_HOST) || defined(CLANG_TIDY)
bool loop_overridden = true;
bool call_loop_overridden = true;
#else

View file

@ -37,6 +37,7 @@
#define USE_OTA
#define USE_OTA_PASSWORD
#define USE_OTA_STATE_CALLBACK
#define USE_OTA_VERSION 1
#define USE_OUTPUT
#define USE_POWER_SUPPLY
#define USE_QR_CODE

View file

@ -15,17 +15,18 @@ std::unique_ptr<RingBuffer> RingBuffer::create(size_t len) {
std::unique_ptr<RingBuffer> rb = make_unique<RingBuffer>();
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
rb->storage_ = allocator.allocate(len);
rb->storage_ = allocator.allocate(len + 1);
if (rb->storage_ == nullptr) {
return nullptr;
}
rb->handle_ = xStreamBufferCreateStatic(len, 0, rb->storage_, &rb->structure_);
rb->handle_ = xStreamBufferCreateStatic(len + 1, 0, rb->storage_, &rb->structure_);
ESP_LOGD(TAG, "Created ring buffer with size %u", len);
return rb;
}
size_t RingBuffer::read(void *data, size_t size, TickType_t ticks_to_wait) {
return xStreamBufferReceive(this->handle_, data, size, ticks_to_wait);
size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) {
return xStreamBufferReceive(this->handle_, data, len, ticks_to_wait);
}
size_t RingBuffer::write(void *data, size_t len) {

View file

@ -12,7 +12,7 @@ namespace esphome {
class RingBuffer {
public:
size_t read(void *data, size_t size, TickType_t ticks_to_wait = 0);
size_t read(void *data, size_t len, TickType_t ticks_to_wait = 0);
size_t write(void *data, size_t len);

View file

@ -12,32 +12,34 @@ import time
from esphome.core import EsphomeError
from esphome.helpers import is_ip_address, resolve_ip_address
RESPONSE_OK = 0
RESPONSE_REQUEST_AUTH = 1
RESPONSE_OK = 0x00
RESPONSE_REQUEST_AUTH = 0x01
RESPONSE_HEADER_OK = 64
RESPONSE_AUTH_OK = 65
RESPONSE_UPDATE_PREPARE_OK = 66
RESPONSE_BIN_MD5_OK = 67
RESPONSE_RECEIVE_OK = 68
RESPONSE_UPDATE_END_OK = 69
RESPONSE_SUPPORTS_COMPRESSION = 70
RESPONSE_HEADER_OK = 0x40
RESPONSE_AUTH_OK = 0x41
RESPONSE_UPDATE_PREPARE_OK = 0x42
RESPONSE_BIN_MD5_OK = 0x43
RESPONSE_RECEIVE_OK = 0x44
RESPONSE_UPDATE_END_OK = 0x45
RESPONSE_SUPPORTS_COMPRESSION = 0x46
RESPONSE_CHUNK_OK = 0x47
RESPONSE_ERROR_MAGIC = 128
RESPONSE_ERROR_UPDATE_PREPARE = 129
RESPONSE_ERROR_AUTH_INVALID = 130
RESPONSE_ERROR_WRITING_FLASH = 131
RESPONSE_ERROR_UPDATE_END = 132
RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 133
RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 134
RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 135
RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 136
RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137
RESPONSE_ERROR_NO_UPDATE_PARTITION = 138
RESPONSE_ERROR_MD5_MISMATCH = 139
RESPONSE_ERROR_UNKNOWN = 255
RESPONSE_ERROR_MAGIC = 0x80
RESPONSE_ERROR_UPDATE_PREPARE = 0x81
RESPONSE_ERROR_AUTH_INVALID = 0x82
RESPONSE_ERROR_WRITING_FLASH = 0x83
RESPONSE_ERROR_UPDATE_END = 0x84
RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 0x85
RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 0x86
RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 0x87
RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 0x88
RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 0x89
RESPONSE_ERROR_NO_UPDATE_PARTITION = 0x8A
RESPONSE_ERROR_MD5_MISMATCH = 0x8B
RESPONSE_ERROR_UNKNOWN = 0xFF
OTA_VERSION_1_0 = 1
OTA_VERSION_2_0 = 2
MAGIC_BYTES = [0x6C, 0x26, 0xF7, 0x5C, 0x45]
@ -203,7 +205,8 @@ def perform_ota(
send_check(sock, MAGIC_BYTES, "magic bytes")
_, version = receive_exactly(sock, 2, "version", RESPONSE_OK)
if version != OTA_VERSION_1_0:
_LOGGER.debug("Device support OTA version: %s", version)
if version not in (OTA_VERSION_1_0, OTA_VERSION_2_0):
raise OTAError(f"Unsupported OTA version {version}")
# Features
@ -279,6 +282,8 @@ def perform_ota(
try:
sock.sendall(chunk)
if version >= OTA_VERSION_2_0:
receive_exactly(sock, 1, "chunk OK", RESPONSE_CHUNK_OK)
except OSError as err:
sys.stderr.write("\n")
raise OTAError(f"Error sending data: {err}") from err

View file

@ -3,6 +3,7 @@ from contextlib import suppress
import logging
import os
import platform
from pathlib import Path
from typing import Union
import tempfile
@ -11,6 +12,10 @@ import re
_LOGGER = logging.getLogger(__name__)
IS_MACOS = platform.system() == "Darwin"
IS_WINDOWS = platform.system() == "Windows"
IS_LINUX = platform.system() == "Linux"
def ensure_unique_string(preferred_string, current_strings):
test_string = preferred_string

View file

@ -388,3 +388,4 @@ lib_deps =
build_flags =
${common.build_flags}
-DUSE_HOST
-std=c++17

View file

@ -8,7 +8,7 @@ tornado==6.4
tzlocal==5.2 # from time
tzdata>=2021.1 # from time
pyserial==3.5
platformio==6.1.11 # When updating platformio, also update Dockerfile
platformio==6.1.13 # When updating platformio, also update Dockerfile
esptool==4.7.0
click==8.1.7
esphome-dashboard==20231107.0

View file

@ -12,3 +12,4 @@ script/lint-cpp
script/unit_test
script/component_test
script/test
script/test_build_components

153
script/list-components.py Executable file
View file

@ -0,0 +1,153 @@
#!/usr/bin/env python3
from pathlib import Path
import sys
import argparse
from helpers import git_ls_files, changed_files
from esphome.loader import get_component, get_platform
from esphome.core import CORE
from esphome.const import (
KEY_CORE,
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
PLATFORM_ESP32,
PLATFORM_ESP8266,
)
def filter_component_files(str):
return str.startswith("esphome/components/") | str.startswith("tests/components/")
def extract_component_names_array_from_files_array(files):
components = []
for file in files:
file_parts = file.split("/")
if len(file_parts) >= 4:
component_name = file_parts[2]
if component_name not in components:
components.append(component_name)
return components
def add_item_to_components_graph(components_graph, parent, child):
if not parent.startswith("__") and parent != child:
if parent not in components_graph:
components_graph[parent] = []
if child not in components_graph[parent]:
components_graph[parent].append(child)
def create_components_graph():
# The root directory of the repo
root = Path(__file__).parent.parent
components_dir = root / "esphome" / "components"
# Fake some directory so that get_component works
CORE.config_path = str(root)
# Various configuration to capture different outcomes used by `AUTO_LOAD` function.
TARGET_CONFIGURATIONS = [
{KEY_TARGET_FRAMEWORK: None, KEY_TARGET_PLATFORM: None},
{KEY_TARGET_FRAMEWORK: "arduino", KEY_TARGET_PLATFORM: None},
{KEY_TARGET_FRAMEWORK: "esp-idf", KEY_TARGET_PLATFORM: None},
{KEY_TARGET_FRAMEWORK: None, KEY_TARGET_PLATFORM: PLATFORM_ESP32},
]
CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0]
components_graph = {}
for path in components_dir.iterdir():
if not path.is_dir():
continue
if not (path / "__init__.py").is_file():
continue
name = path.name
comp = get_component(name)
if comp is None:
print(
f"Cannot find component {name}. Make sure current path is pip installed ESPHome"
)
sys.exit(1)
for dependency in comp.dependencies:
add_item_to_components_graph(components_graph, dependency, name)
for target_config in TARGET_CONFIGURATIONS:
CORE.data[KEY_CORE] = target_config
for auto_load in comp.auto_load:
add_item_to_components_graph(components_graph, auto_load, name)
# restore config
CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0]
for platform_path in path.iterdir():
platform_name = platform_path.stem
platform = get_platform(platform_name, name)
if platform is None:
continue
add_item_to_components_graph(components_graph, platform_name, name)
for dependency in platform.dependencies:
add_item_to_components_graph(components_graph, dependency, name)
for target_config in TARGET_CONFIGURATIONS:
CORE.data[KEY_CORE] = target_config
for auto_load in platform.auto_load:
add_item_to_components_graph(components_graph, auto_load, name)
# restore config
CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0]
return components_graph
def find_children_of_component(components_graph, component_name, depth=0):
if component_name not in components_graph:
return []
children = []
for child in components_graph[component_name]:
children.append(child)
if depth < 10:
children.extend(
find_children_of_component(components_graph, child, depth + 1)
)
# Remove duplicate values
return list(set(children))
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"-c", "--changed", action="store_true", help="Only run on changed files"
)
args = parser.parse_args()
files = git_ls_files()
files = filter(filter_component_files, files)
if args.changed:
changed = changed_files()
files = [f for f in files if f in changed]
components = extract_component_names_array_from_files_array(files)
if args.changed:
components_graph = create_components_graph()
all_changed_components = components.copy()
for c in components:
all_changed_components.extend(
find_children_of_component(components_graph, c)
)
# Remove duplicate values
all_changed_components = list(set(all_changed_components))
for c in sorted(all_changed_components):
print(c)
else:
for c in sorted(components):
print(c)
if __name__ == "__main__":
main()

85
script/test_build_components Executable file
View file

@ -0,0 +1,85 @@
#!/usr/bin/env bash
set -e
# Parse parameter:
# - `e` - Parameter for `esphome` command. Default `compile`. Common alternative is `config`.
# - `c` - Component folder name to test. Default `*`.
esphome_command="compile"
target_component="*"
while getopts e:c: flag
do
case $flag in
e) esphome_command=${OPTARG};;
c) target_component=${OPTARG};;
\?) echo "Usage: $0 [-e <config|compile|clean>] [-c <string>]" 1>&2; exit 1;;
esac
done
cd "$(dirname "$0")/.."
if ! [ -d "./tests/test_build_components/build" ]; then
mkdir ./tests/test_build_components/build
fi
start_esphome() {
# create dynamic yaml file in `build` folder.
# `./tests/test_build_components/build/[target_component].[test_name].[target_platform].yaml`
component_test_file="./tests/test_build_components/build/$target_component.$test_name.$target_platform.yaml"
cp $target_platform_file $component_test_file
sed -i "s!\$component_test_file!../../.$f!g" $component_test_file
# Start esphome process
echo "> [$target_component] [$test_name] [$target_platform]"
echo "esphome -s component_name $target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file"
# TODO: Validate escape of Command line substitution value
esphome -s component_name $target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file
}
# Find all test yaml files.
# - `./tests/components/[target_component]/[test_name].[target_platform].yaml`
# - `./tests/components/[target_component]/[test_name].all.yaml`
for f in ./tests/components/$target_component/*.*.yaml; do
[ -f "$f" ] || continue
IFS='/' read -r -a folder_name <<< "$f"
target_component="${folder_name[3]}"
IFS='.' read -r -a file_name <<< "${folder_name[4]}"
test_name="${file_name[0]}"
target_platform="${file_name[1]}"
file_name_parts=${#file_name[@]}
if [ "$target_platform" = "all" ] || [ $file_name_parts = 2 ]; then
# Test has *not* defined a specific target platform. Need to run tests for all possible target platforms.
for target_platform_file in ./tests/test_build_components/build_components_base.*.yaml; do
IFS='/' read -r -a folder_name <<< "$target_platform_file"
IFS='.' read -r -a file_name <<< "${folder_name[3]}"
target_platform="${file_name[1]}"
start_esphome
done
else
# Test has defined a specific target platform.
# Validate we have a base test yaml for selected platform.
# The target_platform is sourced from the following location.
# 1. `./tests/test_build_components/build_components_base.[target_platform].yaml`
# 2. `./tests/test_build_components/build_components_base.[target_platform]-ard.yaml`
target_platform_file="./tests/test_build_components/build_components_base.$target_platform.yaml"
if ! [ -f "$target_platform_file" ]; then
# Try find arduino test framework as platform.
target_platform_ard="$target_platform-ard"
target_platform_file="./tests/test_build_components/build_components_base.$target_platform_ard.yaml"
if ! [ -f "$target_platform_file" ]; then
echo "No base test file [./tests/test_build_components/build_components_base.$target_platform.yaml, ./tests/build_components_base.$target_platform_ard.yaml] for component test [$f] found."
exit 1
fi
target_platform=$target_platform_ard
fi
start_esphome
fi
done

View file

@ -0,0 +1,5 @@
sensor:
- platform: adc
id: my_sensor
pin: 4
attenuation: 11db

View file

@ -0,0 +1,11 @@
sensor:
- platform: adc
pin: A0
name: Living Room Brightness
update_interval: "1:01"
attenuation: 2.5db
unit_of_measurement: "°C"
icon: "mdi:water-percent"
accuracy_decimals: 5
setup_priority: -100
force_update: true

View file

@ -0,0 +1,5 @@
sensor:
- platform: adc
id: my_sensor
pin: 1
attenuation: 11db

View file

@ -0,0 +1,5 @@
sensor:
- platform: adc
id: my_sensor
pin: 1
attenuation: 11db

View file

@ -0,0 +1,11 @@
sensor:
- platform: adc
pin: A0
name: Living Room Brightness
update_interval: "1:01"
attenuation: 2.5db
unit_of_measurement: "°C"
icon: "mdi:water-percent"
accuracy_decimals: 5
setup_priority: -100
force_update: true

View file

@ -0,0 +1,4 @@
sensor:
- platform: adc
id: my_sensor
pin: VCC

View file

@ -0,0 +1,4 @@
sensor:
- platform: adc
pin: VCC
name: VSYS

View file

@ -0,0 +1,16 @@
esp32_ble_tracker:
sensor:
# Example using 11kg 100% propane tank.
- platform: mopeka_std_check
mac_address: D3:75:F2:DC:16:91
tank_type: Europe_11kg
temperature:
name: "Propane test temp"
level:
name: "Propane test level"
distance:
name: "Propane test distance"
battery_level:
name: "Propane test battery level"

View file

@ -0,0 +1,127 @@
sensor:
- platform: template
name: "Template Sensor"
id: template_sens
lambda: |-
if (id(some_binary_sensor).state) {
return 42.0;
} else {
return 0.0;
}
update_interval: 60s
esphome:
on_boot:
- sensor.template.publish:
id: template_sens
state: 42.0
# Templated
- sensor.template.publish:
id: template_sens
state: !lambda 'return 42.0;'
binary_sensor:
- platform: template
id: some_binary_sensor
name: "Garage Door Open"
lambda: |-
if (id(template_sens).state > 30) {
// Garage Door is open.
return true;
} else {
// Garage Door is closed.
return false;
}
output:
- platform: template
id: outputsplit
type: float
write_action:
- logger.log: "write_action"
switch:
- platform: template
name: "Template Switch"
lambda: |-
if (id(some_binary_sensor).state) {
return true;
} else {
return false;
}
turn_on_action:
- logger.log: "turn_on_action"
turn_off_action:
- logger.log: "turn_off_action"
button:
- platform: template
name: "Template Button"
on_press:
- logger.log: Button Pressed
cover:
- platform: template
name: "Template Cover"
lambda: |-
if (id(some_binary_sensor).state) {
return COVER_OPEN;
} else {
return COVER_CLOSED;
}
open_action:
- logger.log: open_action
close_action:
- logger.log: close_action
stop_action:
- logger.log: stop_action
optimistic: true
number:
- platform: template
name: "Template number"
optimistic: true
min_value: 0
max_value: 100
step: 1
select:
- platform: template
name: "Template select"
optimistic: true
options:
- one
- two
- three
initial_option: two
lock:
- platform: template
name: "Template Lock"
lambda: |-
if (id(some_binary_sensor).state) {
return LOCK_STATE_LOCKED;
} else {
return LOCK_STATE_UNLOCKED;
}
lock_action:
- logger.log: lock_action
unlock_action:
- logger.log: unlock_action
open_action:
- logger.log: open_action
text:
- platform: template
name: "Template text"
optimistic: true
min_length: 0
max_length: 100
mode: text
alarm_control_panel:
- platform: template
name: Alarm Panel
codes:
- "1234"

View file

@ -863,6 +863,13 @@ sensor:
oversampling: 8x
update_interval: 15s
i2c_id: i2c_bus
- platform: honeywell_hih_i2c
temperature:
name: Living Room Temperature 7
humidity:
name: Living Room Humidity 7
update_interval: 15s
i2c_id: i2c_bus
- platform: honeywellabp
pressure:
name: Honeywell pressure
@ -964,7 +971,8 @@ sensor:
name: Internal Ttemperature
update_interval: 15s
i2c_id: i2c_bus
- platform: kalman_combinator
- platform: combination
type: kalman
name: Kalman-filtered temperature
process_std_dev: 0.00139
sources:
@ -973,6 +981,57 @@ sensor:
return 0.4 + std::abs(x - 25) * 0.023;
- source: scd4x_temperature
error: 1.5
- platform: combination
type: linear
name: Linearly combined temperatures
sources:
- source: scd30_temperature
coeffecient: !lambda |-
return 0.4 + std::abs(x - 25) * 0.023;
- source: scd4x_temperature
coeffecient: 1.5
- platform: combination
type: max
name: Max of combined temperatures
sources:
- source: scd30_temperature
- source: scd4x_temperature
- platform: combination
type: mean
name: Mean of combined temperatures
sources:
- source: scd30_temperature
- source: scd4x_temperature
- platform: combination
type: median
name: Median of combined temperatures
sources:
- source: scd30_temperature
- source: scd4x_temperature
- platform: combination
type: min
name: Min of combined temperatures
sources:
- source: scd30_temperature
- source: scd4x_temperature
- platform: combination
type: most_recently_updated
name: Most recently updated of combined temperatures
sources:
- source: scd30_temperature
- source: scd4x_temperature
- platform: combination
type: range
name: Range of combined temperatures
sources:
- source: scd30_temperature
- source: scd4x_temperature
- platform: combination
type: sum
name: Sum of combined temperatures
sources:
- source: scd30_temperature
- source: scd4x_temperature
- platform: htu21d
temperature:
name: Living Room Temperature 6

View file

@ -49,6 +49,7 @@ spi:
number: GPIO14
ota:
version: 2
logger:

View file

@ -693,7 +693,6 @@ display:
greyscale: false
partial_updating: false
update_interval: 60s
display_data_1_pin:
number: GPIO5
allow_other_uses: true
@ -742,6 +741,24 @@ display:
vcom_pin:
number: GPIO1
allow_other_uses: true
- platform: waveshare_epaper
spi_id: spi_id_1
cs_pin:
number: GPIO23
allow_other_uses: true
dc_pin:
number: GPIO23
allow_other_uses: true
busy_pin:
number: GPIO23
allow_other_uses: true
reset_pin:
number: GPIO23
allow_other_uses: true
model: 2.13inv3
full_update_every: 30
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
number:
- platform: tuya

View file

@ -28,6 +28,15 @@ spi:
allow_other_uses: false
mosi_pin: GPIO6
interface: any
- id: quad_spi
clk_pin: 47
data_pins:
-
number: 40
allow_other_uses: false
- 41
- 42
- 43
spi_device:
id: spidev

View file

@ -0,0 +1,20 @@
esphome:
name: componenttestesp32ard
friendly_name: $component_name
esp32:
board: nodemcu-32s
framework:
type: arduino
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_name: $component_name
test_name: $test_name
target_platform: $target_platform
component_test_file: $component_test_file

View file

@ -0,0 +1,20 @@
esphome:
name: componenttestesp32c3ard
friendly_name: $component_name
esp32:
board: lolin_c3_mini
framework:
type: arduino
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_name: $component_name
test_name: $test_name
target_platform: $target_platform
component_test_file: $component_test_file

View file

@ -0,0 +1,20 @@
esphome:
name: componenttestesp32c3idf
friendly_name: $component_name
esp32:
board: lolin_c3_mini
framework:
type: esp-idf
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_name: $component_name
test_name: $test_name
target_platform: $target_platform
component_test_file: $component_test_file

View file

@ -0,0 +1,20 @@
esphome:
name: componenttestesp32idf
friendly_name: $component_name
esp32:
board: nodemcu-32s
framework:
type: esp-idf
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_name: $component_name
test_name: $test_name
target_platform: $target_platform
component_test_file: $component_test_file

View file

@ -0,0 +1,21 @@
esphome:
name: componenttestesp32s2ard
friendly_name: $component_name
esp32:
board: esp32-s2-saola-1
variant: ESP32S2
framework:
type: arduino
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_name: $component_name
test_name: $test_name
target_platform: $target_platform
component_test_file: $component_test_file

View file

@ -0,0 +1,21 @@
esphome:
name: componenttestesp32s2ard
friendly_name: $component_name
esp32:
board: esp32-s2-saola-1
variant: ESP32S2
framework:
type: esp-idf
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_name: $component_name
test_name: $test_name
target_platform: $target_platform
component_test_file: $component_test_file

View file

@ -0,0 +1,21 @@
esphome:
name: componenttestesp32s3ard
friendly_name: $component_name
esp32:
board: esp32s3box
variant: ESP32S3
framework:
type: arduino
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_name: $component_name
test_name: $test_name
target_platform: $target_platform
component_test_file: $component_test_file

View file

@ -0,0 +1,21 @@
esphome:
name: componenttestesp32s3ard
friendly_name: $component_name
esp32:
board: esp32s3box
variant: ESP32S3
framework:
type: esp-idf
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_name: $component_name
test_name: $test_name
target_platform: $target_platform
component_test_file: $component_test_file

View file

@ -0,0 +1,18 @@
esphome:
name: componenttestesp8266
friendly_name: $component_name
esp8266:
board: d1_mini
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_name: $component_name
test_name: $test_name
target_platform: $target_platform
component_test_file: $component_test_file

View file

@ -0,0 +1,21 @@
esphome:
name: componenttestrp2040
friendly_name: $component_name
rp2040:
board: rpipicow
framework:
# Waiting for https://github.com/platformio/platform-raspberrypi/pull/36
platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_name: $component_name
test_name: $test_name
target_platform: $target_platform
component_test_file: $component_test_file