Merge branch 'dev' into patch-1

This commit is contained in:
Cameron Steel 2024-02-12 09:43:06 +11:00 committed by GitHub
commit d178b3c229
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
501 changed files with 11924 additions and 596 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
@ -137,6 +138,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
@ -160,7 +162,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
@ -366,6 +367,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

@ -265,8 +265,8 @@ float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) {
int32_t const var2 = (((((adc >> 4) - t1) * ((adc >> 4) - t1)) >> 12) * t3) >> 14;
*t_fine = var1 + var2;
float const temperature = (*t_fine * 5 + 128) >> 8;
return temperature / 100.0f;
float const temperature = (*t_fine * 5 + 128);
return temperature / 25600.0f;
}
float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) {

View file

@ -200,8 +200,8 @@ float BMP280Component::read_temperature_(int32_t *t_fine) {
int32_t var2 = (((((adc >> 4) - t1) * ((adc >> 4) - t1)) >> 12) * t3) >> 14;
*t_fine = var1 + var2;
float temperature = (*t_fine * 5 + 128) >> 8;
return temperature / 100.0f;
float temperature = (*t_fine * 5 + 128);
return temperature / 25600.0f;
}
float BMP280Component::read_pressure_(int32_t t_fine) {

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

@ -1,6 +1,8 @@
#include "cse7766.h"
#include "esphome/core/log.h"
#include <cinttypes>
#include <iomanip>
#include <sstream>
namespace esphome {
namespace cse7766 {
@ -68,20 +70,26 @@ bool CSE7766Component::check_byte_() {
return true;
}
void CSE7766Component::parse_data_() {
ESP_LOGVV(TAG, "CSE7766 Data: ");
for (uint8_t i = 0; i < 23; i++) {
ESP_LOGVV(TAG, " %u: 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", i + 1, BYTE_TO_BINARY(this->raw_data_[i]),
this->raw_data_[i]);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
{
std::stringstream ss;
ss << "Raw data:" << std::hex << std::uppercase << std::setfill('0');
for (uint8_t i = 0; i < 23; i++) {
ss << ' ' << std::setw(2) << static_cast<unsigned>(this->raw_data_[i]);
}
ESP_LOGVV(TAG, "%s", ss.str().c_str());
}
#endif
// Parse header
uint8_t header1 = this->raw_data_[0];
if (header1 == 0xAA) {
ESP_LOGE(TAG, "CSE7766 not calibrated!");
return;
}
bool power_cycle_exceeds_range = false;
if ((header1 & 0xF0) == 0xF0) {
if (header1 & 0xD) {
ESP_LOGE(TAG, "CSE7766 reports abnormal external circuit or chip damage: (0x%02X)", header1);
@ -94,74 +102,106 @@ void CSE7766Component::parse_data_() {
if (header1 & (1 << 0)) {
ESP_LOGE(TAG, " Coefficient storage area is abnormal.");
}
// Datasheet: voltage or current cycle exceeding range means invalid values
return;
}
power_cycle_exceeds_range = header1 & (1 << 1);
}
uint32_t voltage_calib = this->get_24_bit_uint_(2);
// Parse data frame
uint32_t voltage_coeff = this->get_24_bit_uint_(2);
uint32_t voltage_cycle = this->get_24_bit_uint_(5);
uint32_t current_calib = this->get_24_bit_uint_(8);
uint32_t current_coeff = this->get_24_bit_uint_(8);
uint32_t current_cycle = this->get_24_bit_uint_(11);
uint32_t power_calib = this->get_24_bit_uint_(14);
uint32_t power_coeff = this->get_24_bit_uint_(14);
uint32_t power_cycle = this->get_24_bit_uint_(17);
uint8_t adj = this->raw_data_[20];
uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22];
bool have_power = adj & 0x10;
bool have_current = adj & 0x20;
bool have_voltage = adj & 0x40;
float voltage = 0.0f;
if (have_voltage) {
// voltage cycle of serial port outputted is a complete cycle;
float voltage = voltage_calib / float(voltage_cycle);
if (this->voltage_sensor_ != nullptr)
voltage = voltage_coeff / float(voltage_cycle);
if (this->voltage_sensor_ != nullptr) {
this->voltage_sensor_->publish_state(voltage);
}
}
bool have_power = adj & 0x10;
float power = 0.0f;
if (have_power) {
// power cycle of serial port outputted is a complete cycle;
// According to the user manual, power cycle exceeding range means the measured power is 0
if (!power_cycle_exceeds_range) {
power = power_calib / float(power_cycle);
float energy = 0.0f;
if (power_cycle_exceeds_range) {
// Datasheet: power cycle exceeding range means active power is 0
if (this->power_sensor_ != nullptr) {
this->power_sensor_->publish_state(0.0f);
}
if (this->power_sensor_ != nullptr)
} else if (have_power) {
power = power_coeff / float(power_cycle);
if (this->power_sensor_ != nullptr) {
this->power_sensor_->publish_state(power);
}
// Add CF pulses to the total energy only if we have Power coefficient to multiply by
uint32_t difference;
if (this->cf_pulses_last_ == 0) {
this->cf_pulses_last_ = cf_pulses;
}
uint32_t cf_diff;
if (cf_pulses < this->cf_pulses_last_) {
difference = cf_pulses + (0x10000 - this->cf_pulses_last_);
cf_diff = cf_pulses + (0x10000 - this->cf_pulses_last_);
} else {
difference = cf_pulses - this->cf_pulses_last_;
cf_diff = cf_pulses - this->cf_pulses_last_;
}
this->cf_pulses_last_ = cf_pulses;
this->energy_total_ += difference * float(power_calib) / 1000000.0f / 3600.0f;
energy = cf_diff * float(power_coeff) / 1000000.0f / 3600.0f;
this->energy_total_ += energy;
if (this->energy_sensor_ != nullptr)
this->energy_sensor_->publish_state(this->energy_total_);
} else if ((this->energy_sensor_ != nullptr) && !this->energy_sensor_->has_state()) {
this->energy_sensor_->publish_state(0);
}
if (adj & 0x20) {
// indicates current cycle of serial port outputted is a complete cycle;
float current = 0.0f;
if (have_voltage && !have_power) {
// Testing has shown that when we have voltage and current but not power, that means the power is 0.
// We report a power of 0, which in turn means we should report a current of 0.
if (this->power_sensor_ != nullptr)
this->power_sensor_->publish_state(0);
} else if (power != 0.0f) {
current = current_calib / float(current_cycle);
float current = 0.0f;
float calculated_current = 0.0f;
if (have_current) {
// Assumption: if we don't have power measurement, then current is likely below 50mA
if (have_power && voltage > 1.0f) {
calculated_current = power / voltage;
}
if (this->current_sensor_ != nullptr)
// Datasheet: minimum measured current is 50mA
if (calculated_current > 0.05f) {
current = current_coeff / float(current_cycle);
}
if (this->current_sensor_ != nullptr) {
this->current_sensor_->publish_state(current);
}
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
{
std::stringstream ss;
ss << "Parsed:";
if (have_voltage) {
ss << " V=" << voltage << "V";
}
if (have_current) {
ss << " I=" << current * 1000.0f << "mA (~" << calculated_current * 1000.0f << "mA)";
}
if (have_power) {
ss << " P=" << power << "W";
}
if (energy != 0.0f) {
ss << " E=" << energy << "kWh (" << cf_pulses << ")";
}
ESP_LOGVV(TAG, "%s", ss.str().c_str());
}
#endif
}
uint32_t CSE7766Component::get_24_bit_uint_(uint8_t start_index) {

View file

@ -1,7 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome import core
from esphome.automation import maybe_simple_id
from esphome.const import CONF_ID
from esphome.components import uart
@ -101,7 +100,7 @@ def range_segment_list(input):
largest_distance = -1
for distance in input:
if isinstance(distance, core.Lambda):
if isinstance(distance, cv.Lambda):
continue
m = cv.distance(distance)
if m > 9:
@ -128,14 +127,14 @@ MMWAVE_SETTINGS_SCHEMA = cv.Schema(
cv.Optional(CONF_OUTPUT_LATENCY): {
cv.Required(CONF_DELAY_AFTER_DETECT): cv.templatable(
cv.All(
cv.positive_time_period,
cv.Range(max=core.TimePeriod(seconds=1638.375)),
cv.positive_time_period_milliseconds,
cv.Range(max=cv.TimePeriod(seconds=1638.375)),
)
),
cv.Required(CONF_DELAY_AFTER_DISAPPEAR): cv.templatable(
cv.All(
cv.positive_time_period,
cv.Range(max=core.TimePeriod(seconds=1638.375)),
cv.positive_time_period_milliseconds,
cv.Range(max=cv.TimePeriod(seconds=1638.375)),
)
),
},

View file

@ -50,7 +50,7 @@ class DfrobotSen0395SettingsAction : public Action<Ts...>, public Parented<Dfrob
float detect = this->delay_after_detect_.value(x...);
float disappear = this->delay_after_disappear_.value(x...);
if (detect >= 0 && disappear >= 0) {
this->parent_->enqueue(make_unique<OutputLatencyCommand>(detect, disappear));
this->parent_->enqueue(make_unique<SetLatencyCommand>(detect, disappear));
}
}
if (this->start_after_power_on_.has_value()) {

View file

@ -1,5 +1,7 @@
#include "commands.h"
#include <cmath>
#include "esphome/core/log.h"
#include "dfrobot_sen0395.h"
@ -194,32 +196,22 @@ uint8_t DetRangeCfgCommand::on_message(std::string &message) {
return 0; // Command not done yet.
}
OutputLatencyCommand::OutputLatencyCommand(float delay_after_detection, float delay_after_disappear) {
delay_after_detection = round(delay_after_detection / 0.025) * 0.025;
delay_after_disappear = round(delay_after_disappear / 0.025) * 0.025;
if (delay_after_detection < 0)
delay_after_detection = 0;
if (delay_after_detection > 1638.375)
delay_after_detection = 1638.375;
if (delay_after_disappear < 0)
delay_after_disappear = 0;
if (delay_after_disappear > 1638.375)
delay_after_disappear = 1638.375;
this->delay_after_detection_ = delay_after_detection;
this->delay_after_disappear_ = delay_after_disappear;
this->cmd_ = str_sprintf("outputLatency -1 %.0f %.0f", delay_after_detection / 0.025, delay_after_disappear / 0.025);
SetLatencyCommand::SetLatencyCommand(float delay_after_detection, float delay_after_disappear) {
delay_after_detection = std::round(delay_after_detection / 0.025f) * 0.025f;
delay_after_disappear = std::round(delay_after_disappear / 0.025f) * 0.025f;
this->delay_after_detection_ = clamp(delay_after_detection, 0.0f, 1638.375f);
this->delay_after_disappear_ = clamp(delay_after_disappear, 0.0f, 1638.375f);
this->cmd_ = str_sprintf("setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_);
};
uint8_t OutputLatencyCommand::on_message(std::string &message) {
uint8_t SetLatencyCommand::on_message(std::string &message) {
if (message == "sensor is not stopped") {
ESP_LOGE(TAG, "Cannot configure output latency. Sensor is not stopped!");
return 1; // Command done
} else if (message == "Done") {
ESP_LOGI(TAG, "Updated output latency config:");
ESP_LOGI(TAG, "Signal that someone was detected is delayed by %.02fs.", this->delay_after_detection_);
ESP_LOGI(TAG, "Signal that nobody is detected anymore is delayed by %.02fs.", this->delay_after_disappear_);
ESP_LOGI(TAG, "Signal that someone was detected is delayed by %.03f s.", this->delay_after_detection_);
ESP_LOGI(TAG, "Signal that nobody is detected anymore is delayed by %.03f s.", this->delay_after_disappear_);
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
return 1; // Command done
}

View file

@ -62,9 +62,9 @@ class DetRangeCfgCommand : public Command {
// TODO: Set min max values in component, so they can be published as sensor.
};
class OutputLatencyCommand : public Command {
class SetLatencyCommand : public Command {
public:
OutputLatencyCommand(float delay_after_detection, float delay_after_disappear);
SetLatencyCommand(float delay_after_detection, float delay_after_disappear);
uint8_t on_message(std::string &message) override;
protected:

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

@ -161,10 +161,12 @@ light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index
break;
}
uint8_t multiplier = this->is_rgbw_ ? 4 : 3;
return {this->buf_ + (index * multiplier) + r,
this->buf_ + (index * multiplier) + g,
this->buf_ + (index * multiplier) + b,
this->is_rgbw_ ? this->buf_ + (index * multiplier) + 3 : nullptr,
uint8_t white = this->is_wrgb_ ? 0 : 3;
return {this->buf_ + (index * multiplier) + r + this->is_wrgb_,
this->buf_ + (index * multiplier) + g + this->is_wrgb_,
this->buf_ + (index * multiplier) + b + this->is_wrgb_,
this->is_rgbw_ || this->is_wrgb_ ? this->buf_ + (index * multiplier) + white : nullptr,
&this->effect_data_[index],
&this->correction_};
}

View file

@ -33,7 +33,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
int32_t size() const override { return this->num_leds_; }
light::LightTraits get_traits() override {
auto traits = light::LightTraits();
if (this->is_rgbw_) {
if (this->is_rgbw_ || this->is_wrgb_) {
traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE});
} else {
traits.set_supported_color_modes({light::ColorMode::RGB});
@ -44,6 +44,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
void set_pin(uint8_t pin) { this->pin_ = pin; }
void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; }
void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; }
/// Set a maximum refresh rate in µs as some lights do not like being updated too often.
void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; }
@ -72,6 +73,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
uint8_t pin_;
uint16_t num_leds_;
bool is_rgbw_;
bool is_wrgb_;
rmt_item32_t bit0_, bit1_;
RGBOrder rgb_order_;

View file

@ -52,6 +52,7 @@ CHIPSETS = {
CONF_IS_RGBW = "is_rgbw"
CONF_IS_WRGB = "is_wrgb"
CONF_BIT0_HIGH = "bit0_high"
CONF_BIT0_LOW = "bit0_low"
CONF_BIT1_HIGH = "bit1_high"
@ -90,6 +91,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
cv.Optional(CONF_IS_WRGB, default=False): cv.boolean,
cv.Inclusive(
CONF_BIT0_HIGH,
"custom",
@ -145,6 +147,7 @@ async def to_code(config):
cg.add(var.set_rgb_order(config[CONF_RGB_ORDER]))
cg.add(var.set_is_rgbw(config[CONF_IS_RGBW]))
cg.add(var.set_is_wrgb(config[CONF_IS_WRGB]))
cg.add(
var.set_rmt_channel(

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

@ -4,8 +4,10 @@ from esphome.const import (
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
PLATFORM_HOST,
CONF_MAC_ADDRESS,
)
from esphome.core import CORE
from esphome.helpers import IS_MACOS
import esphome.config_validation as cv
import esphome.codegen as cg
@ -14,7 +16,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"]
@ -28,12 +29,21 @@ def set_core_data(config):
CONFIG_SCHEMA = cv.All(
cv.Schema({}),
cv.Schema(
{
cv.Optional(CONF_MAC_ADDRESS, default="98:35:69:ab:f6:79"): cv.mac_address,
}
),
set_core_data,
)
async def to_code(config):
cg.add_build_flag("-DUSE_HOST")
cg.add_define("USE_ESPHOME_HOST_MAC_ADDRESS", config[CONF_MAC_ADDRESS].parts)
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

@ -66,6 +66,7 @@ MODELS = {
"ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay),
"S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay),
"S3BOX_LITE": ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay),
"WAVESHARE_RES_3_5": ili9xxx_ns.class_("WAVESHARERES35", ILI9XXXDisplay),
}
COLOR_ORDERS = {

View file

@ -7,7 +7,6 @@
namespace esphome {
namespace ili9xxx {
static const char *const TAG = "ili9xxx";
static const uint16_t SPI_SETUP_US = 100; // estimated fixed overhead in microseconds for an SPI write
static const uint16_t SPI_MAX_BLOCK_SIZE = 4092; // Max size of continuous SPI transfer
@ -17,13 +16,7 @@ static inline void put16_be(uint8_t *buf, uint16_t value) {
buf[1] = value;
}
void ILI9XXXDisplay::setup() {
ESP_LOGD(TAG, "Setting up ILI9xxx");
this->setup_pins_();
this->init_lcd_();
this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF);
void ILI9XXXDisplay::set_madctl() {
// custom x/y transform and color order
uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB;
if (this->swap_xy_)
@ -32,8 +25,19 @@ void ILI9XXXDisplay::setup() {
mad |= MADCTL_MX;
if (this->mirror_y_)
mad |= MADCTL_MY;
this->send_command(ILI9XXX_MADCTL, &mad, 1);
this->command(ILI9XXX_MADCTL);
this->data(mad);
esph_log_d(TAG, "Wrote MADCTL 0x%02X", mad);
}
void ILI9XXXDisplay::setup() {
ESP_LOGD(TAG, "Setting up ILI9xxx");
this->setup_pins_();
this->init_lcd_();
this->set_madctl();
this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF);
this->x_low_ = this->width_;
this->y_low_ = this->height_;
this->x_high_ = 0;
@ -89,6 +93,7 @@ void ILI9XXXDisplay::dump_config() {
LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Busy Pin: ", this->busy_pin_);
ESP_LOGCONFIG(TAG, " Color order: %s", this->color_order_ == display::COLOR_ORDER_BGR ? "BGR" : "RGB");
ESP_LOGCONFIG(TAG, " Swap_xy: %s", YESNO(this->swap_xy_));
ESP_LOGCONFIG(TAG, " Mirror_x: %s", YESNO(this->mirror_x_));
ESP_LOGCONFIG(TAG, " Mirror_y: %s", YESNO(this->mirror_y_));
@ -196,7 +201,6 @@ void ILI9XXXDisplay::display_() {
uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE];
// check if something was displayed
if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) {
ESP_LOGV(TAG, "Nothing to display");
return;
}
@ -211,14 +215,13 @@ void ILI9XXXDisplay::display_() {
size_t mw_time = (w * h * 16) / mhz + w * h * 2 / ILI9XXX_TRANSFER_BUFFER_SIZE * SPI_SETUP_US;
ESP_LOGV(TAG,
"Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, "
"height:%d, mode=%d, 18bit=%d, sw_time=%dus, mw_time=%dus)",
"height:%zu, mode=%d, 18bit=%d, sw_time=%zuus, mw_time=%zuus)",
this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, this->buffer_color_mode_,
this->is_18bitdisplay_, sw_time, mw_time);
auto now = millis();
this->enable();
if (this->buffer_color_mode_ == BITS_16 && !this->is_18bitdisplay_ && sw_time < mw_time) {
// 16 bit mode maps directly to display format
ESP_LOGV(TAG, "Doing single write of %d bytes", this->width_ * h * 2);
ESP_LOGV(TAG, "Doing single write of %zu bytes", this->width_ * h * 2);
set_addr_window_(0, this->y_low_, this->width_ - 1, this->y_high_);
this->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2);
} else {
@ -267,7 +270,7 @@ void ILI9XXXDisplay::display_() {
this->write_array(transfer_buffer, idx);
}
}
this->disable();
this->end_data_();
ESP_LOGV(TAG, "Data write took %dms", (unsigned) (millis() - now));
// invalidate watermarks
this->x_low_ = this->width_;
@ -290,7 +293,6 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons
return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
x_pad);
}
this->enable();
this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1);
// x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
@ -302,7 +304,7 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons
this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2);
}
}
this->disable();
this->end_data_();
}
// should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color
@ -328,20 +330,6 @@ void ILI9XXXDisplay::send_command(uint8_t command_byte, const uint8_t *data_byte
this->end_data_();
}
uint8_t ILI9XXXDisplay::read_command(uint8_t command_byte, uint8_t index) {
uint8_t data = 0x10 + index;
this->send_command(0xD9, &data, 1); // Set Index Register
uint8_t result;
this->start_command_();
this->write_byte(command_byte);
this->start_data_();
do {
result = this->read_byte();
} while (index--);
this->end_data_();
return result;
}
void ILI9XXXDisplay::start_command_() {
this->dc_pin_->digital_write(false);
this->enable();
@ -357,9 +345,9 @@ void ILI9XXXDisplay::end_data_() { this->disable(); }
void ILI9XXXDisplay::reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(false);
delay(10);
delay(20);
this->reset_pin_->digital_write(true);
delay(10);
delay(20);
}
}
@ -369,7 +357,7 @@ void ILI9XXXDisplay::init_lcd_() {
while ((cmd = *addr++) > 0) {
x = *addr++;
num_args = x & 0x7F;
send_command(cmd, addr, num_args);
this->send_command(cmd, addr, num_args);
addr += num_args;
if (x & 0x80)
delay(150); // NOLINT
@ -377,24 +365,23 @@ void ILI9XXXDisplay::init_lcd_() {
}
// Tell the display controller where we want to draw pixels.
// when called, the SPI should have already been enabled, only the D/C pin will be toggled here.
void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
uint8_t buf[4];
this->dc_pin_->digital_write(false);
this->write_byte(ILI9XXX_CASET); // Column address set
put16_be(buf, x1 + this->offset_x_);
put16_be(buf + 2, x2 + this->offset_x_);
this->dc_pin_->digital_write(true);
this->write_array(buf, sizeof buf);
this->dc_pin_->digital_write(false);
this->write_byte(ILI9XXX_PASET); // Row address set
put16_be(buf, y1 + this->offset_y_);
put16_be(buf + 2, y2 + this->offset_y_);
this->dc_pin_->digital_write(true);
this->write_array(buf, sizeof buf);
this->dc_pin_->digital_write(false);
this->write_byte(ILI9XXX_RAMWR); // Write to RAM
this->dc_pin_->digital_write(true);
x1 += this->offset_x_;
x2 += this->offset_x_;
y1 += this->offset_y_;
y2 += this->offset_y_;
this->command(ILI9XXX_CASET);
this->data(x1 >> 8);
this->data(x1 & 0xFF);
this->data(x2 >> 8);
this->data(x2 & 0xFF);
this->command(ILI9XXX_PASET); // Page address set
this->data(y1 >> 8);
this->data(y1 & 0xFF);
this->data(y2 >> 8);
this->data(y2 & 0xFF);
this->command(ILI9XXX_RAMWR); // Write to RAM
this->start_data_();
}
void ILI9XXXDisplay::invert_colors(bool invert) {

View file

@ -8,6 +8,7 @@
namespace esphome {
namespace ili9xxx {
static const char *const TAG = "ili9xxx";
const size_t ILI9XXX_TRANSFER_BUFFER_SIZE = 126; // ensure this is divisible by 6
enum ILI9XXXColorMode {
@ -32,6 +33,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
while ((cmd = *addr++) != 0) {
num_args = *addr++ & 0x7F;
bits = *addr;
esph_log_d(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, bits);
switch (cmd) {
case ILI9XXX_MADCTL: {
this->swap_xy_ = (bits & MADCTL_MV) != 0;
@ -68,10 +70,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
this->offset_y_ = offset_y;
}
void invert_colors(bool invert);
void command(uint8_t value);
void data(uint8_t value);
virtual void command(uint8_t value);
virtual void data(uint8_t value);
void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes);
uint8_t read_command(uint8_t command_byte, uint8_t index);
void set_color_order(display::ColorOrder color_order) { this->color_order_ = color_order; }
void set_swap_xy(bool swap_xy) { this->swap_xy_ = swap_xy; }
void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; }
@ -92,6 +93,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
void draw_absolute_pixel_internal(int x, int y, Color color) override;
void setup_pins_();
virtual void set_madctl();
void display_();
void init_lcd_();
void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2);
@ -127,7 +129,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
bool need_update_ = false;
bool is_18bitdisplay_ = false;
bool pre_invertcolors_ = false;
display::ColorOrder color_order_{};
display::ColorOrder color_order_{display::COLOR_ORDER_BGR};
bool swap_xy_{};
bool mirror_x_{};
bool mirror_y_{};
@ -181,10 +183,48 @@ class ILI9XXXILI9486 : public ILI9XXXDisplay {
ILI9XXXILI9486() : ILI9XXXDisplay(INITCMD_ILI9486, 480, 320, false) {}
};
//----------- ILI9XXX_35_TFT rotated display --------------
class ILI9XXXILI9488 : public ILI9XXXDisplay {
public:
ILI9XXXILI9488() : ILI9XXXDisplay(INITCMD_ILI9488, 480, 320, true) {}
ILI9XXXILI9488(const uint8_t *seq = INITCMD_ILI9488) : ILI9XXXDisplay(seq, 480, 320, true) {}
protected:
void set_madctl() override {
uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB;
uint8_t dfun = 0x22;
this->width_ = 320;
this->height_ = 480;
if (!(this->swap_xy_ || this->mirror_x_ || this->mirror_y_)) {
// no transforms
} else if (this->mirror_y_ && this->mirror_x_) {
// rotate 180
dfun = 0x42;
} else if (this->swap_xy_) {
this->width_ = 480;
this->height_ = 320;
mad |= 0x20;
if (this->mirror_x_) {
dfun = 0x02;
} else {
dfun = 0x62;
}
}
this->command(ILI9XXX_DFUNCTR);
this->data(0);
this->data(dfun);
this->command(ILI9XXX_MADCTL);
this->data(mad);
}
};
//----------- Waveshare 3.5 Res Touch - ILI9488 interfaced via 16 bit shift register to parallel */
class WAVESHARERES35 : public ILI9XXXILI9488 {
public:
WAVESHARERES35() : ILI9XXXILI9488(INITCMD_WAVESHARE_RES_3_5) {}
void data(uint8_t value) override {
this->start_data_();
this->write_byte(0);
this->write_byte(value);
this->end_data_();
}
};
//----------- ILI9XXX_35_TFT origin colors rotated display --------------

View file

@ -141,7 +141,8 @@ static const uint8_t PROGMEM INITCMD_ILI9486[] = {
0x00 // End of list
};
static const uint8_t PROGMEM INITCMD_ILI9488[] = {
static const uint8_t INITCMD_ILI9488[] = {
ILI9XXX_GMCTRP1,15, 0x0f, 0x24, 0x1c, 0x0a, 0x0f, 0x08, 0x43, 0x88, 0x32, 0x0f, 0x10, 0x06, 0x0f, 0x07, 0x00,
ILI9XXX_GMCTRN1,15, 0x0F, 0x38, 0x30, 0x09, 0x0f, 0x0f, 0x4e, 0x77, 0x3c, 0x07, 0x10, 0x05, 0x23, 0x1b, 0x00,
@ -153,28 +154,27 @@ static const uint8_t PROGMEM INITCMD_ILI9488[] = {
ILI9XXX_FRMCTR1, 1, 0xA0, // Frame rate = 60Hz
ILI9XXX_INVCTR, 1, 0x02, // Display Inversion Control = 2dot
ILI9XXX_DFUNCTR, 2, 0x02, 0x02, // Nomal scan
0xE9, 1, 0x00, // Set Image Functio. Disable 24 bit data
ILI9XXX_ADJCTL3, 4, 0xA9, 0x51, 0x2C, 0x82, // Adjust Control 3
ILI9XXX_MADCTL, 1, 0x28,
//ILI9XXX_PIXFMT, 1, 0x55, // Interface Pixel Format = 16bit
ILI9XXX_PIXFMT, 1, 0x66, //ILI9488 only supports 18-bit pixel format in 4/3 wire SPI mode
// 5 frames
//ILI9XXX_ETMOD, 1, 0xC6, //
ILI9XXX_SLPOUT, 0x80, // Exit sleep mode
//ILI9XXX_INVON , 0,
ILI9XXX_DISPON, 0x80, // Set display on
0x00 // end
};
static const uint8_t INITCMD_WAVESHARE_RES_3_5[] = {
ILI9XXX_PWCTR3, 1, 0x33,
ILI9XXX_VMCTR1, 3, 0x00, 0x1e, 0x80,
ILI9XXX_FRMCTR1, 1, 0xA0,
ILI9XXX_GMCTRP1, 15, 0x0, 0x13, 0x18, 0x04, 0x0F, 0x06, 0x3a, 0x56, 0x4d, 0x03, 0x0a, 0x06, 0x30, 0x3e, 0x0f,
ILI9XXX_GMCTRN1, 15, 0x0, 0x13, 0x18, 0x01, 0x11, 0x06, 0x38, 0x34, 0x4d, 0x06, 0x0d, 0x0b, 0x31, 0x37, 0x0f,
ILI9XXX_PIXFMT, 1, 0x55,
ILI9XXX_SLPOUT, 0x80, // slpout, delay
ILI9XXX_DISPON, 0,
0x00 // End of list
};
static const uint8_t PROGMEM INITCMD_ILI9488_A[] = {
ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F,
ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F,

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

@ -120,6 +120,7 @@ void LightState::loop() {
// Apply transformer (if any)
if (this->transformer_ != nullptr) {
auto values = this->transformer_->apply();
this->is_transformer_active_ = true;
if (values.has_value()) {
this->current_values = *values;
this->output_->update_state(this);
@ -131,6 +132,7 @@ void LightState::loop() {
this->current_values = this->transformer_->get_target_values();
this->transformer_->stop();
this->is_transformer_active_ = false;
this->transformer_ = nullptr;
this->target_state_reached_callback_.call();
}
@ -214,6 +216,8 @@ void LightState::current_values_as_ct(float *color_temperature, float *white_bri
this->gamma_correct_);
}
bool LightState::is_transformer_active() { return this->is_transformer_active_; }
void LightState::start_effect_(uint32_t effect_index) {
this->stop_effect_();
if (effect_index == 0)
@ -263,6 +267,7 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b
}
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) {
this->is_transformer_active_ = false;
this->transformer_ = nullptr;
this->current_values = target;
if (set_remote_values) {

View file

@ -144,6 +144,17 @@ class LightState : public EntityBase, public Component {
void current_values_as_ct(float *color_temperature, float *white_brightness);
/**
* Indicator if a transformer (e.g. transition) is active. This is useful
* for effects e.g. at the start of the apply() method, add a check like:
*
* if (this->state_->is_transformer_active()) {
* // Something is already running.
* return;
* }
*/
bool is_transformer_active();
protected:
friend LightOutput;
friend LightCall;
@ -203,6 +214,9 @@ class LightState : public EntityBase, public Component {
LightRestoreMode restore_mode_;
/// List of effects for this light.
std::vector<LightEffect *> effects_;
// for effects, true if a transformer (transition) is active.
bool is_transformer_active_ = false;
};
} // namespace light

View file

@ -3,6 +3,8 @@
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include <vector>
namespace esphome {
namespace lightwaverf {

View file

@ -212,6 +212,14 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
return;
#endif
#ifdef USE_HOST
time_t rawtime;
struct tm *timeinfo;
char buffer[80];
time(&rawtime);
timeinfo = localtime(&rawtime);
strftime(buffer, sizeof buffer, "[%H:%M:%S]", timeinfo);
fputs(buffer, stdout);
puts(msg);
#endif

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

@ -24,7 +24,7 @@ int Nextion::upload_range(const std::string &url, int range_start) {
ESP_LOGVV(TAG, "url: %s", url.c_str());
uint range_size = this->tft_size_ - range_start;
ESP_LOGVV(TAG, "tft_size_: %i", this->tft_size_);
ESP_LOGV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
int range_end = (range_start == 0) ? std::min(this->tft_size_, 16383) : this->tft_size_;
if (range_size <= 0 or range_end <= range_start) {
ESP_LOGE(TAG, "Invalid range");
@ -67,12 +67,13 @@ int Nextion::upload_range(const std::string &url, int range_start) {
int total_read_len = 0, read_len;
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
ESP_LOGV(TAG, "Allocate buffer");
uint8_t *buffer = new uint8_t[4096];
std::string recv_string;
if (buffer == nullptr) {
ESP_LOGE(TAG, "Failed to allocate memory for buffer");
ESP_LOGV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
} else {
ESP_LOGV(TAG, "Memory for buffer allocated successfully");
@ -86,15 +87,14 @@ int Nextion::upload_range(const std::string &url, int range_start) {
ESP_LOGVV(TAG, "Write to UART successful");
this->recv_ret_string_(recv_string, 5000, true);
this->content_length_ -= read_len;
ESP_LOGD(TAG, "Uploaded %0.2f %%, remaining %d bytes",
100.0 * (this->tft_size_ - this->content_length_) / this->tft_size_, this->content_length_);
if (recv_string[0] != 0x05) { // 0x05 == "ok"
ESP_LOGD(TAG, "Uploaded %0.2f %%, remaining %d bytes, heap is %" PRIu32 " bytes",
100.0 * (this->tft_size_ - this->content_length_) / this->tft_size_, this->content_length_,
esp_get_free_heap_size());
if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request
ESP_LOGD(
TAG, "recv_string [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
}
// handle partial upload request
if (recv_string[0] == 0x08 && recv_string.size() == 5) {
uint32_t result = 0;
for (int j = 0; j < 4; ++j) {
result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j);
@ -103,13 +103,37 @@ int Nextion::upload_range(const std::string &url, int range_start) {
ESP_LOGI(TAG, "Nextion reported new range %" PRIu32, result);
this->content_length_ = this->tft_size_ - result;
// Deallocate the buffer when done
ESP_LOGV(TAG, "Deallocate buffer");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
delete[] buffer;
ESP_LOGVV(TAG, "Memory for buffer deallocated");
esp_http_client_cleanup(client);
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
ESP_LOGV(TAG, "Close http client");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
esp_http_client_close(client);
esp_http_client_cleanup(client);
ESP_LOGVV(TAG, "Client closed");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
return result;
}
} else if (recv_string[0] != 0x05) { // 0x05 == "ok"
ESP_LOGE(
TAG, "Invalid response from Nextion: [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
ESP_LOGV(TAG, "Deallocate buffer");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
delete[] buffer;
ESP_LOGVV(TAG, "Memory for buffer deallocated");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
ESP_LOGV(TAG, "Close http client");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
esp_http_client_close(client);
esp_http_client_cleanup(client);
ESP_LOGVV(TAG, "Client closed");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
return -1;
}
recv_string.clear();
} else if (read_len == 0) {
ESP_LOGV(TAG, "End of HTTP response reached");
@ -121,11 +145,18 @@ int Nextion::upload_range(const std::string &url, int range_start) {
}
// Deallocate the buffer when done
ESP_LOGV(TAG, "Deallocate buffer");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
delete[] buffer;
ESP_LOGVV(TAG, "Memory for buffer deallocated");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
}
esp_http_client_cleanup(client);
ESP_LOGV(TAG, "Close http client");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
esp_http_client_close(client);
esp_http_client_cleanup(client);
ESP_LOGVV(TAG, "Client closed");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
return range_end + 1;
}
@ -159,7 +190,7 @@ bool Nextion::upload_tft() {
// Initialize the HTTP client with the configuration
ESP_LOGV(TAG, "Initializing HTTP client");
ESP_LOGV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
esp_http_client_handle_t http = esp_http_client_init(&config);
if (!http) {
ESP_LOGE(TAG, "Failed to initialize HTTP client.");
@ -168,7 +199,7 @@ bool Nextion::upload_tft() {
// Perform the HTTP request
ESP_LOGV(TAG, "Check if the client could connect");
ESP_LOGV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
esp_err_t err = esp_http_client_perform(http);
if (err != ESP_OK) {
ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err));
@ -177,14 +208,22 @@ bool Nextion::upload_tft() {
}
// Check the HTTP Status Code
ESP_LOGV(TAG, "Check the HTTP Status Code");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
int status_code = esp_http_client_get_status_code(http);
ESP_LOGV(TAG, "HTTP Status Code: %d", status_code);
size_t tft_file_size = esp_http_client_get_content_length(http);
ESP_LOGD(TAG, "TFT file size: %zu", tft_file_size);
ESP_LOGD(TAG, "Close HTTP connection");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
esp_http_client_close(http);
esp_http_client_cleanup(http);
ESP_LOGVV(TAG, "Connection closed");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
if (tft_file_size < 4096) {
ESP_LOGE(TAG, "File size check failed. Size: %zu", tft_file_size);
esp_http_client_cleanup(http);
return this->upload_end(false);
} else {
ESP_LOGV(TAG, "File size check passed. Proceeding...");
@ -193,8 +232,10 @@ bool Nextion::upload_tft() {
this->tft_size_ = tft_file_size;
ESP_LOGD(TAG, "Updating Nextion");
// The Nextion will ignore the update command if it is sleeping
// The Nextion will ignore the update command if it is sleeping
ESP_LOGV(TAG, "Wake-up Nextion");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
this->send_command_("sleep=0");
this->set_backlight_brightness(1.0);
vTaskDelay(pdMS_TO_TICKS(250)); // NOLINT
@ -207,26 +248,31 @@ bool Nextion::upload_tft() {
sprintf(command, "whmi-wris %d,%" PRIu32 ",1", this->content_length_, this->parent_->get_baud_rate());
// Clear serial receive buffer
ESP_LOGV(TAG, "Clear serial receive buffer");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
uint8_t d;
while (this->available()) {
this->read_byte(&d);
};
ESP_LOGV(TAG, "Send update instruction: %s", command);
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
this->send_command_(command);
std::string response;
ESP_LOGV(TAG, "Waiting for upgrade response");
this->recv_ret_string_(response, 2048, true); // This can take some time to return
this->recv_ret_string_(response, 5000, true); // This can take some time to return
// The Nextion display will, if it's ready to accept data, send a 0x05 byte.
ESP_LOGD(TAG, "Upgrade response is [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str());
ESP_LOGD(TAG, "Upgrade response is [%s] - %zu bytes",
format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str(),
response.length());
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
if (response.find(0x05) != std::string::npos) {
ESP_LOGV(TAG, "Preparation for tft update done");
} else {
ESP_LOGE(TAG, "Preparation for tft update failed %d \"%s\"", response[0], response.c_str());
esp_http_client_cleanup(http);
return this->upload_end(false);
}
@ -234,12 +280,12 @@ bool Nextion::upload_tft() {
content_length_, esp_get_free_heap_size());
ESP_LOGV(TAG, "Starting transfer by chunks loop");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
int result = 0;
while (content_length_ > 0) {
result = upload_range(this->tft_url_.c_str(), result);
if (result < 0) {
ESP_LOGE(TAG, "Error updating Nextion!");
esp_http_client_cleanup(http);
return this->upload_end(false);
}
App.feed_wdt();
@ -248,9 +294,6 @@ bool Nextion::upload_tft() {
ESP_LOGD(TAG, "Successfully updated Nextion!");
ESP_LOGD(TAG, "Close HTTP connection");
esp_http_client_close(http);
esp_http_client_cleanup(http);
return upload_end(true);
}

View file

@ -62,6 +62,7 @@ from esphome.const import (
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_VOLUME,
DEVICE_CLASS_VOLUME_FLOW_RATE,
DEVICE_CLASS_VOLUME_STORAGE,
DEVICE_CLASS_WATER,
DEVICE_CLASS_WEIGHT,
@ -117,6 +118,7 @@ DEVICE_CLASSES = [
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_VOLUME,
DEVICE_CLASS_VOLUME_FLOW_RATE,
DEVICE_CLASS_VOLUME_STORAGE,
DEVICE_CLASS_WATER,
DEVICE_CLASS_WEIGHT,

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

@ -82,6 +82,7 @@ from esphome.const import (
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_VOLUME,
DEVICE_CLASS_VOLUME_FLOW_RATE,
DEVICE_CLASS_VOLUME_STORAGE,
DEVICE_CLASS_WATER,
DEVICE_CLASS_WEIGHT,
@ -141,6 +142,7 @@ DEVICE_CLASSES = [
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_VOLUME,
DEVICE_CLASS_VOLUME_FLOW_RATE,
DEVICE_CLASS_VOLUME_STORAGE,
DEVICE_CLASS_WATER,
DEVICE_CLASS_WEIGHT,

View file

@ -25,6 +25,7 @@ namespace sntp {
static const char *const TAG = "sntp";
void SNTPComponent::setup() {
#ifndef USE_HOST
ESP_LOGCONFIG(TAG, "Setting up SNTP...");
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
if (sntp_enabled()) {
@ -48,6 +49,7 @@ void SNTPComponent::setup() {
#endif
sntp_init();
#endif
}
void SNTPComponent::dump_config() {
ESP_LOGCONFIG(TAG, "SNTP Time:");
@ -57,7 +59,7 @@ void SNTPComponent::dump_config() {
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
}
void SNTPComponent::update() {
#ifndef USE_ESP_IDF
#if !defined(USE_ESP_IDF) && !defined(USE_HOST)
// force resync
if (sntp_enabled()) {
sntp_stop();

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

@ -28,7 +28,7 @@ class Filter {
* @param value The new value.
* @return An optional string, the new value that should be pushed out.
*/
virtual optional<std::string> new_value(std::string value);
virtual optional<std::string> new_value(std::string value) = 0;
/// Initialize this filter, please note this can be called more than once.
virtual void initialize(TextSensor *parent, Filter *next);

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

@ -17,8 +17,12 @@ from esphome.const import (
DEPENDENCIES = ["spi"]
waveshare_epaper_ns = cg.esphome_ns.namespace("waveshare_epaper")
WaveshareEPaper = waveshare_epaper_ns.class_(
"WaveshareEPaper", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer
WaveshareEPaperBase = waveshare_epaper_ns.class_(
"WaveshareEPaperBase", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer
)
WaveshareEPaper = waveshare_epaper_ns.class_("WaveshareEPaper", WaveshareEPaperBase)
WaveshareEPaperBWR = waveshare_epaper_ns.class_(
"WaveshareEPaperBWR", WaveshareEPaperBase
)
WaveshareEPaperTypeA = waveshare_epaper_ns.class_(
"WaveshareEPaperTypeA", WaveshareEPaper
@ -26,6 +30,12 @@ WaveshareEPaperTypeA = waveshare_epaper_ns.class_(
WaveshareEPaper2P7In = waveshare_epaper_ns.class_(
"WaveshareEPaper2P7In", WaveshareEPaper
)
WaveshareEPaper2P7InB = waveshare_epaper_ns.class_(
"WaveshareEPaper2P7InB", WaveshareEPaperBWR
)
WaveshareEPaper2P7InBV2 = waveshare_epaper_ns.class_(
"WaveshareEPaper2P7InBV2", WaveshareEPaperBWR
)
WaveshareEPaper2P7InV2 = waveshare_epaper_ns.class_(
"WaveshareEPaper2P7InV2", WaveshareEPaper
)
@ -72,6 +82,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")
@ -89,6 +102,8 @@ MODELS = {
"2.90inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN_V2),
"gdey029t94": ("c", GDEY029T94),
"2.70in": ("b", WaveshareEPaper2P7In),
"2.70in-b": ("b", WaveshareEPaper2P7InB),
"2.70in-bv2": ("b", WaveshareEPaper2P7InBV2),
"2.70inv2": ("b", WaveshareEPaper2P7InV2),
"2.90in-b": ("b", WaveshareEPaper2P9InB),
"2.90in-bv3": ("b", WaveshareEPaper2P9InBV3),
@ -104,6 +119,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),
}
@ -126,12 +142,12 @@ def validate_full_update_every_only_types_ac(value):
CONFIG_SCHEMA = cv.All(
display.FULL_DISPLAY_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WaveshareEPaper),
cv.GenerateID(): cv.declare_id(WaveshareEPaperBase),
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_MODEL): cv.one_of(*MODELS, lower=True),
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema,
cv.Optional(CONF_FULL_UPDATE_EVERY): cv.uint32_t,
cv.Optional(CONF_FULL_UPDATE_EVERY): cv.int_range(min=1, max=4294967295),
cv.Optional(CONF_RESET_DURATION): cv.All(
cv.positive_time_period_milliseconds,
cv.Range(max=core.TimePeriod(milliseconds=500)),

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

@ -83,7 +83,7 @@ static const uint8_t PARTIAL_UPDATE_LUT_TTGO_B1[LUT_SIZE_TTGO_B1] = {
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
void WaveshareEPaper::setup_pins_() {
void WaveshareEPaperBase::setup_pins_() {
this->init_internal_(this->get_buffer_length_());
this->dc_pin_->setup(); // OUTPUT
this->dc_pin_->digital_write(false);
@ -98,19 +98,31 @@ void WaveshareEPaper::setup_pins_() {
this->reset_();
}
float WaveshareEPaper::get_setup_priority() const { return setup_priority::PROCESSOR; }
void WaveshareEPaper::command(uint8_t value) {
float WaveshareEPaperBase::get_setup_priority() const { return setup_priority::PROCESSOR; }
void WaveshareEPaperBase::command(uint8_t value) {
this->start_command_();
this->write_byte(value);
this->end_command_();
}
void WaveshareEPaper::data(uint8_t value) {
void WaveshareEPaperBase::data(uint8_t value) {
this->start_data_();
this->write_byte(value);
this->end_data_();
}
bool WaveshareEPaper::wait_until_idle_() {
if (this->busy_pin_ == nullptr) {
// write a command followed by one or more bytes of data.
// The command is the first byte, length is the total including cmd.
void WaveshareEPaperBase::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 WaveshareEPaperBase::wait_until_idle_() {
if (this->busy_pin_ == nullptr || !this->busy_pin_->digital_read()) {
return true;
}
@ -120,11 +132,11 @@ bool WaveshareEPaper::wait_until_idle_() {
ESP_LOGE(TAG, "Timeout while displaying image!");
return false;
}
delay(10);
delay(1);
}
return true;
}
void WaveshareEPaper::update() {
void WaveshareEPaperBase::update() {
this->do_update_();
this->display();
}
@ -147,20 +159,51 @@ void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, Color color
this->buffer_[pos] &= ~(0x80 >> subpos);
}
}
uint32_t WaveshareEPaper::get_buffer_length_() {
return this->get_width_controller() * this->get_height_internal() / 8u;
} // just a black buffer
uint32_t WaveshareEPaperBWR::get_buffer_length_() {
return this->get_width_controller() * this->get_height_internal() / 4u;
} // black and red buffer
void WaveshareEPaperBWR::fill(Color color) {
this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color);
}
void WaveshareEPaper::start_command_() {
void HOT WaveshareEPaperBWR::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0)
return;
const uint32_t buf_half_len = this->get_buffer_length_() / 2u;
const uint32_t pos = (x + y * this->get_width_internal()) / 8u;
const uint8_t subpos = x & 0x07;
// flip logic
if (color.is_on()) {
this->buffer_[pos] |= 0x80 >> subpos;
} else {
this->buffer_[pos] &= ~(0x80 >> subpos);
}
// draw red pixels only, if the color contains red only
if (((color.red > 0) && (color.green == 0) && (color.blue == 0))) {
this->buffer_[pos + buf_half_len] |= 0x80 >> subpos;
} else {
this->buffer_[pos + buf_half_len] &= ~(0x80 >> subpos);
}
}
void WaveshareEPaperBase::start_command_() {
this->dc_pin_->digital_write(false);
this->enable();
}
void WaveshareEPaper::end_command_() { this->disable(); }
void WaveshareEPaper::start_data_() {
void WaveshareEPaperBase::end_command_() { this->disable(); }
void WaveshareEPaperBase::start_data_() {
this->dc_pin_->digital_write(true);
this->enable();
}
void WaveshareEPaper::end_data_() { this->disable(); }
void WaveshareEPaper::on_safe_shutdown() { this->deep_sleep(); }
void WaveshareEPaperBase::end_data_() { this->disable(); }
void WaveshareEPaperBase::on_safe_shutdown() { this->deep_sleep(); }
// ========================================================
// Type A
@ -481,7 +524,7 @@ uint32_t WaveshareEPaperTypeA::idle_timeout_() {
case TTGO_EPAPER_2_13_IN_B1:
return 2500;
default:
return WaveshareEPaper::idle_timeout_();
return WaveshareEPaperBase::idle_timeout_();
}
}
@ -687,6 +730,246 @@ void WaveshareEPaper2P7InV2::dump_config() {
LOG_UPDATE_INTERVAL(this);
}
// ========================================================
// 2.7inch_e-paper_b
// ========================================================
// Datasheet:
// - https://www.waveshare.com/w/upload/d/d8/2.7inch-e-paper-b-specification.pdf
// - https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_2in7b.c
static const uint8_t LUT_VCOM_DC_2_7B[44] = {0x00, 0x00, 0x00, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x0A,
0x00, 0x00, 0x08, 0x00, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x00, 0x0A,
0x0A, 0x00, 0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00,
0x03, 0x0E, 0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01};
static const uint8_t LUT_WHITE_TO_WHITE_2_7B[42] = {0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x40, 0x0A, 0x0A, 0x00, 0x00,
0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x80, 0x0A, 0x0A, 0x00,
0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, 0x03, 0x0E,
0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01};
static const uint8_t LUT_BLACK_TO_WHITE_2_7B[42] = {0xA0, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x0A, 0x00, 0x00,
0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x90, 0x0A, 0x0A, 0x00,
0x00, 0x08, 0xB0, 0x04, 0x10, 0x00, 0x00, 0x05, 0xB0, 0x03, 0x0E,
0x00, 0x00, 0x0A, 0xC0, 0x23, 0x00, 0x00, 0x00, 0x01};
static const uint8_t LUT_WHITE_TO_BLACK_2_7B[] = {0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x20, 0x0A, 0x0A, 0x00, 0x00,
0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x10, 0x0A, 0x0A, 0x00,
0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, 0x03, 0x0E,
0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01};
static const uint8_t LUT_BLACK_TO_BLACK_2_7B[42] = {0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x40, 0x0A, 0x0A, 0x00, 0x00,
0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x80, 0x0A, 0x0A, 0x00,
0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, 0x03, 0x0E,
0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01};
void WaveshareEPaper2P7InB::initialize() {
this->reset_();
// command power on
this->command(0x04);
this->wait_until_idle_();
delay(10);
// Command panel setting
this->command(0x00);
this->data(0xAF); // KW-BF KWR-AF BWROTP 0f
// command pll control
this->command(0x30);
this->data(0x3A); // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ
// command power setting
this->command(0x01);
this->data(0x03); // VDS_EN, VDG_EN
this->data(0x00); // VCOM_HV, VGHL_LV[1], VGHL_LV[0]
this->data(0x2B); // VDH
this->data(0x2B); // VDL
this->data(0x09); // VDHR
// command booster soft start
this->command(0x06);
this->data(0x07);
this->data(0x07);
this->data(0x17);
// Power optimization - ???
this->command(0xF8);
this->data(0x60);
this->data(0xA5);
this->command(0xF8);
this->data(0x89);
this->data(0xA5);
this->command(0xF8);
this->data(0x90);
this->data(0x00);
this->command(0xF8);
this->data(0x93);
this->data(0x2A);
this->command(0xF8);
this->data(0x73);
this->data(0x41);
// COMMAND VCM DC SETTING
this->command(0x82);
this->data(0x12);
// VCOM_AND_DATA_INTERVAL_SETTING
this->command(0x50);
this->data(0x87); // define by OTP
delay(2);
// COMMAND LUT FOR VCOM
this->command(0x20);
for (uint8_t i : LUT_VCOM_DC_2_7B)
this->data(i);
// COMMAND LUT WHITE TO WHITE
this->command(0x21);
for (uint8_t i : LUT_WHITE_TO_WHITE_2_7B)
this->data(i);
// COMMAND LUT BLACK TO WHITE
this->command(0x22);
for (uint8_t i : LUT_BLACK_TO_WHITE_2_7B)
this->data(i);
// COMMAND LUT WHITE TO BLACK
this->command(0x23);
for (uint8_t i : LUT_WHITE_TO_BLACK_2_7B) {
this->data(i);
}
// COMMAND LUT BLACK TO BLACK
this->command(0x24);
for (uint8_t i : LUT_BLACK_TO_BLACK_2_7B) {
this->data(i);
}
delay(2);
}
void HOT WaveshareEPaper2P7InB::display() {
uint32_t buf_len_half = this->get_buffer_length_() >> 1;
this->initialize();
// TCON_RESOLUTION
this->command(0x61);
this->data(this->get_width_internal() >> 8);
this->data(this->get_width_internal() & 0xff); // 176
this->data(this->get_height_internal() >> 8);
this->data(this->get_height_internal() & 0xff); // 264
// COMMAND DATA START TRANSMISSION 1 (BLACK)
this->command(0x10);
delay(2);
for (uint32_t i = 0; i < buf_len_half; i++) {
this->data(this->buffer_[i]);
}
this->command(0x11);
delay(2);
// COMMAND DATA START TRANSMISSION 2 (RED)
this->command(0x13);
delay(2);
for (uint32_t i = buf_len_half; i < buf_len_half * 2u; i++) {
this->data(this->buffer_[i]);
}
this->command(0x11);
delay(2);
// COMMAND DISPLAY REFRESH
this->command(0x12);
this->wait_until_idle_();
this->deep_sleep();
}
int WaveshareEPaper2P7InB::get_width_internal() { return 176; }
int WaveshareEPaper2P7InB::get_height_internal() { return 264; }
void WaveshareEPaper2P7InB::dump_config() {
LOG_DISPLAY("", "Waveshare E-Paper", this);
ESP_LOGCONFIG(TAG, " Model: 2.7in B");
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);
}
// ========================================================
// 2.7inch_e-paper_b_v2
// ========================================================
// Datasheet:
// - https://www.waveshare.com/w/upload/7/7b/2.7inch-e-paper-b-v2-specification.pdf
// - https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_2in7b_V2.c
void WaveshareEPaper2P7InBV2::initialize() {
this->reset_();
this->wait_until_idle_();
this->command(0x12);
this->wait_until_idle_();
this->command(0x00);
this->data(0x27);
this->data(0x01);
this->data(0x00);
this->command(0x11);
this->data(0x03);
// self.SetWindows(0, 0, self.width-1, self.height-1)
// SetWindows(self, Xstart, Ystart, Xend, Yend):
uint32_t xend = this->get_width_internal() - 1;
uint32_t yend = this->get_height_internal() - 1;
this->command(0x44);
this->data(0x00);
this->data((xend >> 3) & 0xff);
this->command(0x45);
this->data(0x00);
this->data(0x00);
this->data(yend & 0xff);
this->data((yend >> 8) & 0xff);
// SetCursor(self, Xstart, Ystart):
this->command(0x4E);
this->data(0x00);
this->command(0x4F);
this->data(0x00);
this->data(0x00);
}
void HOT WaveshareEPaper2P7InBV2::display() {
uint32_t buf_len = this->get_buffer_length_();
// COMMAND DATA START TRANSMISSION 1 (BLACK)
this->command(0x24);
delay(2);
for (uint32_t i = 0; i < buf_len; i++) {
this->data(this->buffer_[i]);
}
delay(2);
// COMMAND DATA START TRANSMISSION 2 (RED)
this->command(0x26);
delay(2);
for (uint32_t i = 0; i < buf_len; i++) {
this->data(this->buffer_[i]);
}
delay(2);
this->command(0x20);
this->wait_until_idle_();
}
int WaveshareEPaper2P7InBV2::get_width_internal() { return 176; }
int WaveshareEPaper2P7InBV2::get_height_internal() { return 264; }
void WaveshareEPaper2P7InBV2::dump_config() {
LOG_DISPLAY("", "Waveshare E-Paper", this);
ESP_LOGCONFIG(TAG, " Model: 2.7in B V2");
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);
}
// ========================================================
// 2.90in Type B (LUT from OTP)
// Datasheet:
@ -1443,6 +1726,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 +1761,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 +1907,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 +2501,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 +2548,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 +2563,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

@ -7,9 +7,9 @@
namespace esphome {
namespace waveshare_epaper {
class WaveshareEPaper : public display::DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_2MHZ> {
class WaveshareEPaperBase : public display::DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_2MHZ> {
public:
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
float get_setup_priority() const override;
@ -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;
@ -26,8 +27,6 @@ class WaveshareEPaper : public display::DisplayBuffer,
void update() override;
void fill(Color color) override;
void setup() override {
this->setup_pins_();
this->initialize();
@ -35,11 +34,7 @@ class WaveshareEPaper : public display::DisplayBuffer,
void on_safe_shutdown() override;
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; }
protected:
void draw_absolute_pixel_internal(int x, int y, Color color) override;
bool wait_until_idle_();
void setup_pins_();
@ -49,13 +44,13 @@ 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);
}
}
virtual int get_width_controller() { return this->get_width_internal(); };
uint32_t get_buffer_length_();
virtual uint32_t get_buffer_length_() = 0; // NOLINT(readability-identifier-naming)
uint32_t reset_duration_{200};
void start_command_();
@ -69,6 +64,28 @@ class WaveshareEPaper : public display::DisplayBuffer,
virtual uint32_t idle_timeout_() { return 1000u; } // NOLINT(readability-identifier-naming)
};
class WaveshareEPaper : public WaveshareEPaperBase {
public:
void fill(Color color) override;
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; }
protected:
void draw_absolute_pixel_internal(int x, int y, Color color) override;
uint32_t get_buffer_length_() override;
};
class WaveshareEPaperBWR : public WaveshareEPaperBase {
public:
void fill(Color color) override;
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
protected:
void draw_absolute_pixel_internal(int x, int y, Color color) override;
uint32_t get_buffer_length_() override;
};
enum WaveshareEPaperTypeAModel {
WAVESHARE_EPAPER_1_54_IN = 0,
WAVESHARE_EPAPER_1_54_IN_V2,
@ -133,6 +150,8 @@ class WaveshareEPaperTypeA : public WaveshareEPaper {
enum WaveshareEPaperTypeBModel {
WAVESHARE_EPAPER_2_7_IN = 0,
WAVESHARE_EPAPER_2_7_IN_B,
WAVESHARE_EPAPER_2_7_IN_B_V2,
WAVESHARE_EPAPER_4_2_IN,
WAVESHARE_EPAPER_4_2_IN_B_V2,
WAVESHARE_EPAPER_7_5_IN,
@ -154,6 +173,68 @@ class WaveshareEPaper2P7In : public WaveshareEPaper {
this->data(0xA5); // check byte
}
protected:
int get_width_internal() override;
int get_height_internal() override;
};
class WaveshareEPaper2P7InB : public WaveshareEPaperBWR {
public:
void initialize() override;
void display() override;
void dump_config() override;
void deep_sleep() override {
// COMMAND VCOM_AND_DATA_INTERVAL_SETTING
this->command(0x50);
// COMMAND POWER OFF
this->command(0x02);
this->wait_until_idle_();
// COMMAND DEEP SLEEP
this->command(0x07); // deep sleep
this->data(0xA5); // check byte
}
protected:
int get_width_internal() override;
int get_height_internal() override;
};
class WaveshareEPaper2P7InBV2 : public WaveshareEPaperBWR {
public:
void initialize() override;
void display() override;
void dump_config() override;
void deep_sleep() override {
// COMMAND DEEP SLEEP
this->command(0x10);
this->data(0x01);
}
protected:
int get_width_internal() override;
int get_height_internal() override;
};
class GDEY029T94 : public WaveshareEPaper {
public:
void initialize() override;
void display() override;
void dump_config() override;
void deep_sleep() override {
// COMMAND DEEP SLEEP
this->command(0x07);
this->data(0xA5); // check byte
}
protected:
int get_width_internal() override;
@ -176,26 +257,6 @@ class WaveshareEPaper2P7InV2 : public WaveshareEPaper {
int get_height_internal() override;
};
class GDEY029T94 : public WaveshareEPaper {
public:
void initialize() override;
void display() override;
void dump_config() override;
void deep_sleep() override {
// COMMAND DEEP SLEEP
this->command(0x07);
this->data(0xA5); // check byte
}
protected:
int get_width_internal() override;
int get_height_internal() override;
};
class GDEW0154M09 : public WaveshareEPaper {
public:
void initialize() override;
@ -614,5 +675,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

@ -8,6 +8,8 @@ wled_ns = cg.esphome_ns.namespace("wled")
WLEDLightEffect = wled_ns.class_("WLEDLightEffect", AddressableLightEffect)
CONFIG_SCHEMA = cv.All(cv.Schema({}), cv.only_with_arduino)
CONF_SYNC_GROUP_MASK = "sync_group_mask"
CONF_BLANK_ON_START = "blank_on_start"
@register_addressable_effect(
@ -16,10 +18,13 @@ CONFIG_SCHEMA = cv.All(cv.Schema({}), cv.only_with_arduino)
"WLED",
{
cv.Optional(CONF_PORT, default=21324): cv.port,
cv.Optional(CONF_SYNC_GROUP_MASK, default=0): cv.int_range(min=0, max=255),
cv.Optional(CONF_BLANK_ON_START, default=True): cv.boolean,
},
)
async def wled_light_effect_to_code(config, effect_id):
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
cg.add(effect.set_port(config[CONF_PORT]))
cg.add(effect.set_sync_group_mask(config[CONF_SYNC_GROUP_MASK]))
cg.add(effect.set_blank_on_start(config[CONF_BLANK_ON_START]))
return effect

View file

@ -13,6 +13,10 @@
#include <WiFiUdp.h>
#endif
#ifdef USE_BK72XX
#include <WiFiUdp.h>
#endif
namespace esphome {
namespace wled {
@ -29,7 +33,11 @@ WLEDLightEffect::WLEDLightEffect(const std::string &name) : AddressableLightEffe
void WLEDLightEffect::start() {
AddressableLightEffect::start();
blank_at_ = 0;
if (this->blank_on_start_) {
this->blank_at_ = 0;
} else {
this->blank_at_ = UINT32_MAX;
}
}
void WLEDLightEffect::stop() {
@ -101,8 +109,11 @@ bool WLEDLightEffect::parse_frame_(light::AddressableLight &it, const uint8_t *p
if (!parse_drgb_frame_(it, payload, size))
return false;
} else {
if (!parse_notifier_frame_(it, payload, size))
if (!parse_notifier_frame_(it, payload, size)) {
return false;
} else {
timeout = UINT8_MAX;
}
}
break;
@ -143,8 +154,32 @@ bool WLEDLightEffect::parse_frame_(light::AddressableLight &it, const uint8_t *p
}
bool WLEDLightEffect::parse_notifier_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
// Packet needs to be empty
return size == 0;
// Receive at least RGBW and Brightness for all LEDs from WLED Sync Notification
// https://kno.wled.ge/interfaces/udp-notifier/
// https://github.com/Aircoookie/WLED/blob/main/wled00/udp.cpp
if (size < 34) {
return false;
}
uint8_t payload_sync_group_mask = payload[34];
if ((payload_sync_group_mask & this->sync_group_mask_) != this->sync_group_mask_) {
ESP_LOGD(TAG, "sync group mask does not match");
return false;
}
uint8_t bri = payload[0];
uint8_t r = esp_scale8(payload[1], bri);
uint8_t g = esp_scale8(payload[2], bri);
uint8_t b = esp_scale8(payload[3], bri);
uint8_t w = esp_scale8(payload[8], bri);
for (auto &&led : it) {
led.set(Color(r, g, b, w));
}
return true;
}
bool WLEDLightEffect::parse_warls_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {

View file

@ -21,6 +21,8 @@ class WLEDLightEffect : public light::AddressableLightEffect {
void stop() override;
void apply(light::AddressableLight &it, const Color &current_color) override;
void set_port(uint16_t port) { this->port_ = port; }
void set_sync_group_mask(uint8_t mask) { this->sync_group_mask_ = mask; }
void set_blank_on_start(bool blank) { this->blank_on_start_ = blank; }
protected:
void blank_all_leds_(light::AddressableLight &it);
@ -35,6 +37,8 @@ class WLEDLightEffect : public light::AddressableLightEffect {
std::unique_ptr<UDP> udp_;
uint32_t blank_at_{0};
uint32_t dropped_{0};
uint8_t sync_group_mask_{0};
bool blank_on_start_{true};
};
} // namespace wled

View file

@ -112,6 +112,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"
@ -1080,6 +1082,7 @@ DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds"
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS = "volatile_organic_compounds_parts"
DEVICE_CLASS_VOLTAGE = "voltage"
DEVICE_CLASS_VOLUME = "volume"
DEVICE_CLASS_VOLUME_FLOW_RATE = "volume_flow_rate"
DEVICE_CLASS_VOLUME_STORAGE = "volume_storage"
DEVICE_CLASS_WATER = "water"
DEVICE_CLASS_WEIGHT = "weight"

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

@ -1,8 +1,9 @@
#pragma once
#include <string>
#include <functional>
#include <cmath>
#include <cstdint>
#include <functional>
#include <string>
#include "esphome/core/optional.h"

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

@ -11,6 +11,12 @@
#include <cstdio>
#include <cstring>
#ifdef USE_HOST
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <unistd.h>
#endif
#if defined(USE_ESP8266)
#include <osapi.h>
#include <user_interface.h>
@ -415,7 +421,7 @@ std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) {
int8_t step_to_accuracy_decimals(float step) {
// use printf %g to find number of digits based on temperature step
char buf[32];
sprintf(buf, "%.5g", step);
snprintf(buf, sizeof buf, "%.5g", step);
std::string str{buf};
size_t dot_pos = str.find('.');
@ -551,7 +557,10 @@ void HighFrequencyLoopRequester::stop() {
bool HighFrequencyLoopRequester::is_high_frequency() { return num_requests > 0; }
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
#if defined(USE_ESP32)
#if defined(USE_HOST)
static const uint8_t esphome_host_mac_address[6] = USE_ESPHOME_HOST_MAC_ADDRESS;
memcpy(mac, esphome_host_mac_address, sizeof(esphome_host_mac_address));
#elif defined(USE_ESP32)
#if defined(CONFIG_SOC_IEEE802154_SUPPORTED) || defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC)
// When CONFIG_SOC_IEEE802154_SUPPORTED is defined, esp_efuse_mac_get_default
// returns the 802.15.4 EUI-64 address. Read directly from eFuse instead.
@ -569,6 +578,8 @@ void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parame
WiFi.macAddress(mac);
#elif defined(USE_LIBRETINY)
WiFi.macAddress(mac);
#else
// this should be an error, but that messes with CI checks. #error No mac address method defined
#endif
}
std::string get_mac_address() {

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

@ -1,5 +1,6 @@
#pragma once
#include <cstdint>
#include <cstdlib>
#include <ctime>
#include <string>

View file

@ -478,7 +478,7 @@ def variable(
:param type_: Manually define a type for the variable, only use this when it's not possible
to do so during config validation phase (for example because of template arguments).
:returns The new variable as a MockObj.
:return: The new variable as a MockObj.
"""
assert isinstance(id_, ID)
rhs = safe_exp(rhs)
@ -526,7 +526,7 @@ def new_variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj
:param type_: Manually define a type for the variable, only use this when it's not possible
to do so during config validation phase (for example because of template arguments).
:returns The new variable as a MockObj.
:return: The new variable as a MockObj.
"""
assert isinstance(id_, ID)
rhs = safe_exp(rhs)
@ -549,7 +549,7 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
:param type_: Manually define a type for the variable, only use this when it's not possible
to do so during config validation phase (for example because of template arguments).
:returns The new variable as a MockObj.
:return: The new variable as a MockObj.
"""
rhs = safe_exp(rhs)
obj = MockObj(id_, "->")
@ -570,7 +570,7 @@ def new_Pvariable(id_: ID, *args: SafeExpType) -> Pvariable:
:param id_: The ID used to declare the variable (also specifies the type).
:param args: The values to pass to the constructor.
:returns The new variable as a MockObj.
:return: The new variable as a MockObj.
"""
if args and isinstance(args[0], TemplateArguments):
id_ = id_.copy()

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

@ -7,6 +7,7 @@ import logging
import math
import os
import uuid
from io import TextIOWrapper
from typing import Any
import yaml
@ -19,7 +20,7 @@ except ImportError:
FastestAvailableSafeLoader = PurePythonLoader
from esphome import core
from esphome.config_helpers import Extend, Remove, read_config_file
from esphome.config_helpers import Extend, Remove
from esphome.core import (
CORE,
DocumentRange,
@ -418,19 +419,26 @@ def load_yaml(fname: str, clear_secrets: bool = True) -> Any:
def _load_yaml_internal(fname: str) -> Any:
"""Load a YAML file."""
content = read_config_file(fname)
try:
return _load_yaml_internal_with_type(ESPHomeLoader, fname, content)
except EsphomeError:
# Loading failed, so we now load with the Python loader which has more
# readable exceptions
return _load_yaml_internal_with_type(ESPHomePurePythonLoader, fname, content)
with open(fname, encoding="utf-8") as f_handle:
try:
return _load_yaml_internal_with_type(ESPHomeLoader, fname, f_handle)
except EsphomeError:
# Loading failed, so we now load with the Python loader which has more
# readable exceptions
# Rewind the stream so we can try again
f_handle.seek(0, 0)
return _load_yaml_internal_with_type(
ESPHomePurePythonLoader, fname, f_handle
)
except (UnicodeDecodeError, OSError) as err:
raise EsphomeError(f"Error reading file {fname}: {err}") from err
def _load_yaml_internal_with_type(
loader_type: type[ESPHomeLoader] | type[ESPHomePurePythonLoader],
fname: str,
content: str,
content: TextIOWrapper,
) -> Any:
"""Load a YAML file."""
loader = loader_type(content)

View file

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

View file

@ -1,4 +1,5 @@
async_timeout==4.0.3; python_version <= "3.10"
cryptography==42.0.2
voluptuous==0.14.1
PyYAML==6.0.1
paho-mqtt==1.6.1
@ -8,11 +9,11 @@ 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
aioesphomeapi==21.0.1
aioesphomeapi==21.0.2
zeroconf==0.131.0
python-magic==0.4.27

View file

@ -1,3 +1,2 @@
pillow==10.2.0
cairosvg==2.7.1
cryptography==41.0.4

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,13 @@
uart:
- id: uart_a01nyub
tx_pin:
number: 4
rx_pin:
number: 5
baud_rate: 9600
sensor:
- platform: a01nyub
id: a01nyub_sensor
name: a01nyub Distance
uart_id: uart_a01nyub

View file

@ -0,0 +1,13 @@
uart:
- id: uart_a01nyub
tx_pin:
number: 4
rx_pin:
number: 5
baud_rate: 9600
sensor:
- platform: a01nyub
id: a01nyub_sensor
name: a01nyub Distance
uart_id: uart_a01nyub

View file

@ -0,0 +1,13 @@
uart:
- id: uart_a01nyub
tx_pin:
number: 17
rx_pin:
number: 16
baud_rate: 9600
sensor:
- platform: a01nyub
id: a01nyub_sensor
name: a01nyub Distance
uart_id: uart_a01nyub

View file

@ -0,0 +1,13 @@
uart:
- id: uart_a01nyub
tx_pin:
number: 17
rx_pin:
number: 16
baud_rate: 9600
sensor:
- platform: a01nyub
id: a01nyub_sensor
name: a01nyub Distance
uart_id: uart_a01nyub

View file

@ -0,0 +1,13 @@
uart:
- id: uart_a01nyub
tx_pin:
number: 4
rx_pin:
number: 5
baud_rate: 9600
sensor:
- platform: a01nyub
id: a01nyub_sensor
name: a01nyub Distance
uart_id: uart_a01nyub

View file

@ -0,0 +1,13 @@
uart:
- id: uart_a01nyub
tx_pin:
number: 4
rx_pin:
number: 5
baud_rate: 9600
sensor:
- platform: a01nyub
id: a01nyub_sensor
name: a01nyub Distance
uart_id: uart_a01nyub

View file

@ -0,0 +1,13 @@
uart:
- id: uart_a02yyuw
tx_pin:
number: 4
rx_pin:
number: 5
baud_rate: 9600
sensor:
- platform: a02yyuw
id: a02yyuw_sensor
name: a02yyuw Distance
uart_id: uart_a02yyuw

View file

@ -0,0 +1,13 @@
uart:
- id: uart_a02yyuw
tx_pin:
number: 4
rx_pin:
number: 5
baud_rate: 9600
sensor:
- platform: a02yyuw
id: a02yyuw_sensor
name: a02yyuw Distance
uart_id: uart_a02yyuw

View file

@ -0,0 +1,13 @@
uart:
- id: uart_a02yyuw
tx_pin:
number: 17
rx_pin:
number: 16
baud_rate: 9600
sensor:
- platform: a02yyuw
id: a02yyuw_sensor
name: a02yyuw Distance
uart_id: uart_a02yyuw

Some files were not shown because too many files have changed in this diff Show more