mirror of
https://github.com/esphome/esphome.git
synced 2025-01-05 20:31:44 +01:00
Merge branch 'dev' into add-graphical-layout-system
This commit is contained in:
commit
6fe40257b3
82 changed files with 2219 additions and 360 deletions
61
.github/workflows/ci.yml
vendored
61
.github/workflows/ci.yml
vendored
|
@ -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
|
||||
|
|
|
@ -71,6 +71,7 @@ esphome/components/cd74hc4067/* @asoehlke
|
|||
esphome/components/climate/* @esphome/core
|
||||
esphome/components/climate_ir/* @glmnet
|
||||
esphome/components/color_temperature/* @jesserockz
|
||||
esphome/components/combination/* @Cat-Ion @kahrendt
|
||||
esphome/components/coolix/* @glmnet
|
||||
esphome/components/copy/* @OttoWinter
|
||||
esphome/components/cover/* @esphome/core
|
||||
|
@ -138,6 +139,7 @@ esphome/components/heatpumpir/* @rob-deutsch
|
|||
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||
esphome/components/hm3301/* @freekode
|
||||
esphome/components/homeassistant/* @OttoWinter
|
||||
esphome/components/honeywell_hih_i2c/* @Benichou34
|
||||
esphome/components/honeywellabp/* @RubyBailey
|
||||
esphome/components/honeywellabp2_i2c/* @jpfaff
|
||||
esphome/components/host/* @esphome/core
|
||||
|
@ -161,7 +163,6 @@ esphome/components/integration/* @OttoWinter
|
|||
esphome/components/internal_temperature/* @Mat931
|
||||
esphome/components/interval/* @esphome/core
|
||||
esphome/components/json/* @OttoWinter
|
||||
esphome/components/kalman_combinator/* @Cat-Ion
|
||||
esphome/components/key_collector/* @ssieb
|
||||
esphome/components/key_provider/* @ssieb
|
||||
esphome/components/kuntze/* @ssieb
|
||||
|
@ -367,6 +368,7 @@ esphome/components/veml3235/* @kbx81
|
|||
esphome/components/version/* @esphome/core
|
||||
esphome/components/voice_assistant/* @jesserockz
|
||||
esphome/components/wake_on_lan/* @willwill2will54
|
||||
esphome/components/waveshare_epaper/* @clydebarrow
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/web_server_idf/* @dentra
|
||||
esphome/components/whirlpool/* @glmnet
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
0
esphome/components/combination/__init__.py
Normal file
0
esphome/components/combination/__init__.py
Normal file
262
esphome/components/combination/combination.cpp
Normal file
262
esphome/components/combination/combination.cpp
Normal 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
|
141
esphome/components/combination/combination.h
Normal file
141
esphome/components/combination/combination.h
Normal 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
|
176
esphome/components/combination/sensor.py
Normal file
176
esphome/components/combination/sensor.py
Normal 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))
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
2
esphome/components/honeywell_hih_i2c/__init__.py
Normal file
2
esphome/components/honeywell_hih_i2c/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
"""Support for Honeywell HumidIcon HIH"""
|
||||
CODEOWNERS = ["@Benichou34"]
|
97
esphome/components/honeywell_hih_i2c/honeywell_hih.cpp
Normal file
97
esphome/components/honeywell_hih_i2c/honeywell_hih.cpp
Normal 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
|
34
esphome/components/honeywell_hih_i2c/honeywell_hih.h
Normal file
34
esphome/components/honeywell_hih_i2c/honeywell_hih.h
Normal 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
|
56
esphome/components/honeywell_hih_i2c/sensor.py
Normal file
56
esphome/components/honeywell_hih_i2c/sensor.py
Normal 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))
|
|
@ -6,6 +6,7 @@ from esphome.const import (
|
|||
PLATFORM_HOST,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.helpers import IS_MACOS
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
|
@ -14,7 +15,6 @@ from .const import KEY_HOST
|
|||
# force import gpio to register pin schema
|
||||
from .gpio import host_pin_to_code # noqa
|
||||
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
AUTO_LOAD = ["network"]
|
||||
|
||||
|
@ -35,5 +35,9 @@ CONFIG_SCHEMA = cv.All(
|
|||
|
||||
async def to_code(config):
|
||||
cg.add_build_flag("-DUSE_HOST")
|
||||
cg.add_build_flag("-std=c++17")
|
||||
cg.add_build_flag("-lsodium")
|
||||
if IS_MACOS:
|
||||
cg.add_build_flag("-L/opt/homebrew/lib")
|
||||
cg.add_define("ESPHOME_BOARD", "host")
|
||||
cg.add_platformio_option("platform", "platformio/native")
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
CODEOWNERS = ["@Cat-Ion"]
|
|
@ -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
|
|
@ -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
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@clydebarrow"]
|
|
@ -72,6 +72,9 @@ WaveshareEPaper7P5InHDB = waveshare_epaper_ns.class_(
|
|||
WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper2P13InDKE", WaveshareEPaper
|
||||
)
|
||||
WaveshareEPaper2P13InV3 = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper2P13InV3", WaveshareEPaper
|
||||
)
|
||||
GDEW0154M09 = waveshare_epaper_ns.class_("GDEW0154M09", WaveshareEPaper)
|
||||
|
||||
WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel")
|
||||
|
@ -104,6 +107,7 @@ MODELS = {
|
|||
"7.50inv2alt": ("b", WaveshareEPaper7P5InV2alt),
|
||||
"7.50in-hd-b": ("b", WaveshareEPaper7P5InHDB),
|
||||
"2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE),
|
||||
"2.13inv3": ("c", WaveshareEPaper2P13InV3),
|
||||
"1.54in-m5coreink-m09": ("c", GDEW0154M09),
|
||||
}
|
||||
|
||||
|
|
186
esphome/components/waveshare_epaper/waveshare_213v3.cpp
Normal file
186
esphome/components/waveshare_epaper/waveshare_213v3.cpp
Normal 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
|
|
@ -109,8 +109,20 @@ void WaveshareEPaper::data(uint8_t value) {
|
|||
this->write_byte(value);
|
||||
this->end_data_();
|
||||
}
|
||||
|
||||
// write a command followed by one or more bytes of data.
|
||||
// The command is the first byte, length is the total including cmd.
|
||||
void WaveshareEPaper::cmd_data(const uint8_t *c_data, size_t length) {
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->enable();
|
||||
this->write_byte(c_data[0]);
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->write_array(c_data + 1, length - 1);
|
||||
this->disable();
|
||||
}
|
||||
|
||||
bool WaveshareEPaper::wait_until_idle_() {
|
||||
if (this->busy_pin_ == nullptr) {
|
||||
if (this->busy_pin_ == nullptr || !this->busy_pin_->digital_read()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -120,7 +132,7 @@ bool WaveshareEPaper::wait_until_idle_() {
|
|||
ESP_LOGE(TAG, "Timeout while displaying image!");
|
||||
return false;
|
||||
}
|
||||
delay(10);
|
||||
delay(1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1443,6 +1455,12 @@ void WaveshareEPaper7P5InBV2::initialize() {
|
|||
// COMMAND TCON SETTING
|
||||
this->command(0x60);
|
||||
this->data(0x22);
|
||||
|
||||
this->command(0x82);
|
||||
this->data(0x08);
|
||||
this->command(0x30);
|
||||
this->data(0x06);
|
||||
|
||||
// COMMAND RESOLUTION SETTING
|
||||
this->command(0x65);
|
||||
this->data(0x00);
|
||||
|
@ -1472,6 +1490,7 @@ void HOT WaveshareEPaper7P5InBV2::display() {
|
|||
this->command(0x12);
|
||||
delay(100); // NOLINT
|
||||
this->wait_until_idle_();
|
||||
this->deep_sleep();
|
||||
}
|
||||
int WaveshareEPaper7P5InBV2::get_width_internal() { return 800; }
|
||||
int WaveshareEPaper7P5InBV2::get_height_internal() { return 480; }
|
||||
|
@ -1617,7 +1636,7 @@ void HOT WaveshareEPaper7P5InBV3::display() {
|
|||
this->command(0x13); // Start Transmission
|
||||
delay(2);
|
||||
for (uint32_t i = 0; i < buf_len; i++) {
|
||||
this->data(this->buffer_[i]);
|
||||
this->data(~this->buffer_[i]);
|
||||
}
|
||||
|
||||
this->command(0x12); // Display Refresh
|
||||
|
@ -2211,8 +2230,9 @@ void HOT WaveshareEPaper2P13InDKE::display() {
|
|||
} else {
|
||||
// set up partial update
|
||||
this->command(0x32);
|
||||
for (uint8_t v : PART_UPDATE_LUT_TTGO_DKE)
|
||||
this->data(v);
|
||||
this->start_data_();
|
||||
this->write_array(PART_UPDATE_LUT_TTGO_DKE, sizeof(PART_UPDATE_LUT_TTGO_DKE));
|
||||
this->end_data_();
|
||||
this->command(0x3F);
|
||||
this->data(0x22);
|
||||
|
||||
|
@ -2257,12 +2277,10 @@ void HOT WaveshareEPaper2P13InDKE::display() {
|
|||
this->wait_until_idle_();
|
||||
|
||||
// data must be sent again on partial update
|
||||
delay(300); // NOLINT
|
||||
this->command(0x24);
|
||||
this->start_data_();
|
||||
this->write_array(this->buffer_, this->get_buffer_length_());
|
||||
this->end_data_();
|
||||
delay(300); // NOLINT
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Completed e-paper update.");
|
||||
|
@ -2274,6 +2292,7 @@ uint32_t WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; }
|
|||
void WaveshareEPaper2P13InDKE::dump_config() {
|
||||
LOG_DISPLAY("", "Waveshare E-Paper", this);
|
||||
ESP_LOGCONFIG(TAG, " Model: 2.13inDKE");
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||
|
|
|
@ -19,6 +19,7 @@ class WaveshareEPaper : public display::DisplayBuffer,
|
|||
|
||||
void command(uint8_t value);
|
||||
void data(uint8_t value);
|
||||
void cmd_data(const uint8_t *data, size_t length);
|
||||
|
||||
virtual void display() = 0;
|
||||
virtual void initialize() = 0;
|
||||
|
@ -49,7 +50,7 @@ class WaveshareEPaper : public display::DisplayBuffer,
|
|||
this->reset_pin_->digital_write(false);
|
||||
delay(reset_duration_); // NOLINT
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(200); // NOLINT
|
||||
delay(20);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -614,5 +615,39 @@ class WaveshareEPaper2P13InDKE : public WaveshareEPaper {
|
|||
uint32_t at_update_{0};
|
||||
};
|
||||
|
||||
class WaveshareEPaper2P13InV3 : public WaveshareEPaper {
|
||||
public:
|
||||
void display() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void deep_sleep() override {
|
||||
// COMMAND POWER DOWN
|
||||
this->command(0x10);
|
||||
this->data(0x01);
|
||||
// cannot wait until idle here, the device no longer responds
|
||||
}
|
||||
|
||||
void set_full_update_every(uint32_t full_update_every);
|
||||
|
||||
void setup() override;
|
||||
void initialize() override;
|
||||
|
||||
protected:
|
||||
int get_width_internal() override;
|
||||
int get_height_internal() override;
|
||||
uint32_t idle_timeout_() override;
|
||||
|
||||
void write_buffer_(uint8_t cmd, int top, int bottom);
|
||||
void set_window_(int t, int b);
|
||||
void send_reset_();
|
||||
void partial_update_();
|
||||
void full_update_();
|
||||
|
||||
uint32_t full_update_every_{30};
|
||||
uint32_t at_update_{0};
|
||||
bool is_busy_{false};
|
||||
void write_lut_(const uint8_t *lut);
|
||||
};
|
||||
} // namespace waveshare_epaper
|
||||
} // namespace esphome
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -113,6 +113,8 @@ CONF_CHANNELS = "channels"
|
|||
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
|
||||
CONF_CHIPSET = "chipset"
|
||||
CONF_CLEAR_IMPEDANCE = "clear_impedance"
|
||||
CONF_CLIENT_CERTIFICATE = "client_certificate"
|
||||
CONF_CLIENT_CERTIFICATE_KEY = "client_certificate_key"
|
||||
CONF_CLIENT_ID = "client_id"
|
||||
CONF_CLK_PIN = "clk_pin"
|
||||
CONF_CLOCK_PIN = "clock_pin"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -388,3 +388,4 @@ lib_deps =
|
|||
build_flags =
|
||||
${common.build_flags}
|
||||
-DUSE_HOST
|
||||
-std=c++17
|
||||
|
|
|
@ -8,7 +8,7 @@ tornado==6.4
|
|||
tzlocal==5.2 # from time
|
||||
tzdata>=2021.1 # from time
|
||||
pyserial==3.5
|
||||
platformio==6.1.11 # When updating platformio, also update Dockerfile
|
||||
platformio==6.1.13 # When updating platformio, also update Dockerfile
|
||||
esptool==4.7.0
|
||||
click==8.1.7
|
||||
esphome-dashboard==20231107.0
|
||||
|
|
|
@ -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
153
script/list-components.py
Executable 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
85
script/test_build_components
Executable 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
|
5
tests/components/adc/test.esp32-c3.yaml
Normal file
5
tests/components/adc/test.esp32-c3.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
sensor:
|
||||
- platform: adc
|
||||
id: my_sensor
|
||||
pin: 4
|
||||
attenuation: 11db
|
11
tests/components/adc/test.esp32-idf.yaml
Normal file
11
tests/components/adc/test.esp32-idf.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
sensor:
|
||||
- platform: adc
|
||||
pin: A0
|
||||
name: Living Room Brightness
|
||||
update_interval: "1:01"
|
||||
attenuation: 2.5db
|
||||
unit_of_measurement: "°C"
|
||||
icon: "mdi:water-percent"
|
||||
accuracy_decimals: 5
|
||||
setup_priority: -100
|
||||
force_update: true
|
5
tests/components/adc/test.esp32-s2.yaml
Normal file
5
tests/components/adc/test.esp32-s2.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
sensor:
|
||||
- platform: adc
|
||||
id: my_sensor
|
||||
pin: 1
|
||||
attenuation: 11db
|
5
tests/components/adc/test.esp32-s3.yaml
Normal file
5
tests/components/adc/test.esp32-s3.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
sensor:
|
||||
- platform: adc
|
||||
id: my_sensor
|
||||
pin: 1
|
||||
attenuation: 11db
|
11
tests/components/adc/test.esp32.yaml
Normal file
11
tests/components/adc/test.esp32.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
sensor:
|
||||
- platform: adc
|
||||
pin: A0
|
||||
name: Living Room Brightness
|
||||
update_interval: "1:01"
|
||||
attenuation: 2.5db
|
||||
unit_of_measurement: "°C"
|
||||
icon: "mdi:water-percent"
|
||||
accuracy_decimals: 5
|
||||
setup_priority: -100
|
||||
force_update: true
|
4
tests/components/adc/test.esp8266.yaml
Normal file
4
tests/components/adc/test.esp8266.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
sensor:
|
||||
- platform: adc
|
||||
id: my_sensor
|
||||
pin: VCC
|
4
tests/components/adc/test.rp2040.yaml
Normal file
4
tests/components/adc/test.rp2040.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
sensor:
|
||||
- platform: adc
|
||||
pin: VCC
|
||||
name: VSYS
|
16
tests/components/mopeka_std_check/test.esp32.yaml
Normal file
16
tests/components/mopeka_std_check/test.esp32.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
esp32_ble_tracker:
|
||||
|
||||
sensor:
|
||||
# Example using 11kg 100% propane tank.
|
||||
- platform: mopeka_std_check
|
||||
mac_address: D3:75:F2:DC:16:91
|
||||
tank_type: Europe_11kg
|
||||
temperature:
|
||||
name: "Propane test temp"
|
||||
level:
|
||||
name: "Propane test level"
|
||||
distance:
|
||||
name: "Propane test distance"
|
||||
battery_level:
|
||||
name: "Propane test battery level"
|
||||
|
127
tests/components/template/test.all.yaml
Normal file
127
tests/components/template/test.all.yaml
Normal file
|
@ -0,0 +1,127 @@
|
|||
sensor:
|
||||
- platform: template
|
||||
name: "Template Sensor"
|
||||
id: template_sens
|
||||
lambda: |-
|
||||
if (id(some_binary_sensor).state) {
|
||||
return 42.0;
|
||||
} else {
|
||||
return 0.0;
|
||||
}
|
||||
update_interval: 60s
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
- sensor.template.publish:
|
||||
id: template_sens
|
||||
state: 42.0
|
||||
|
||||
# Templated
|
||||
- sensor.template.publish:
|
||||
id: template_sens
|
||||
state: !lambda 'return 42.0;'
|
||||
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
id: some_binary_sensor
|
||||
name: "Garage Door Open"
|
||||
lambda: |-
|
||||
if (id(template_sens).state > 30) {
|
||||
// Garage Door is open.
|
||||
return true;
|
||||
} else {
|
||||
// Garage Door is closed.
|
||||
return false;
|
||||
}
|
||||
|
||||
output:
|
||||
- platform: template
|
||||
id: outputsplit
|
||||
type: float
|
||||
write_action:
|
||||
- logger.log: "write_action"
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
name: "Template Switch"
|
||||
lambda: |-
|
||||
if (id(some_binary_sensor).state) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
turn_on_action:
|
||||
- logger.log: "turn_on_action"
|
||||
turn_off_action:
|
||||
- logger.log: "turn_off_action"
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: "Template Button"
|
||||
on_press:
|
||||
- logger.log: Button Pressed
|
||||
|
||||
cover:
|
||||
- platform: template
|
||||
name: "Template Cover"
|
||||
lambda: |-
|
||||
if (id(some_binary_sensor).state) {
|
||||
return COVER_OPEN;
|
||||
} else {
|
||||
return COVER_CLOSED;
|
||||
}
|
||||
open_action:
|
||||
- logger.log: open_action
|
||||
close_action:
|
||||
- logger.log: close_action
|
||||
stop_action:
|
||||
- logger.log: stop_action
|
||||
optimistic: true
|
||||
|
||||
number:
|
||||
- platform: template
|
||||
name: "Template number"
|
||||
optimistic: true
|
||||
min_value: 0
|
||||
max_value: 100
|
||||
step: 1
|
||||
|
||||
select:
|
||||
- platform: template
|
||||
name: "Template select"
|
||||
optimistic: true
|
||||
options:
|
||||
- one
|
||||
- two
|
||||
- three
|
||||
initial_option: two
|
||||
|
||||
lock:
|
||||
- platform: template
|
||||
name: "Template Lock"
|
||||
lambda: |-
|
||||
if (id(some_binary_sensor).state) {
|
||||
return LOCK_STATE_LOCKED;
|
||||
} else {
|
||||
return LOCK_STATE_UNLOCKED;
|
||||
}
|
||||
lock_action:
|
||||
- logger.log: lock_action
|
||||
unlock_action:
|
||||
- logger.log: unlock_action
|
||||
open_action:
|
||||
- logger.log: open_action
|
||||
|
||||
text:
|
||||
- platform: template
|
||||
name: "Template text"
|
||||
optimistic: true
|
||||
min_length: 0
|
||||
max_length: 100
|
||||
mode: text
|
||||
|
||||
alarm_control_panel:
|
||||
- platform: template
|
||||
name: Alarm Panel
|
||||
codes:
|
||||
- "1234"
|
|
@ -863,6 +863,13 @@ sensor:
|
|||
oversampling: 8x
|
||||
update_interval: 15s
|
||||
i2c_id: i2c_bus
|
||||
- platform: honeywell_hih_i2c
|
||||
temperature:
|
||||
name: Living Room Temperature 7
|
||||
humidity:
|
||||
name: Living Room Humidity 7
|
||||
update_interval: 15s
|
||||
i2c_id: i2c_bus
|
||||
- platform: honeywellabp
|
||||
pressure:
|
||||
name: Honeywell pressure
|
||||
|
@ -964,7 +971,8 @@ sensor:
|
|||
name: Internal Ttemperature
|
||||
update_interval: 15s
|
||||
i2c_id: i2c_bus
|
||||
- platform: kalman_combinator
|
||||
- platform: combination
|
||||
type: kalman
|
||||
name: Kalman-filtered temperature
|
||||
process_std_dev: 0.00139
|
||||
sources:
|
||||
|
@ -973,6 +981,57 @@ sensor:
|
|||
return 0.4 + std::abs(x - 25) * 0.023;
|
||||
- source: scd4x_temperature
|
||||
error: 1.5
|
||||
- platform: combination
|
||||
type: linear
|
||||
name: Linearly combined temperatures
|
||||
sources:
|
||||
- source: scd30_temperature
|
||||
coeffecient: !lambda |-
|
||||
return 0.4 + std::abs(x - 25) * 0.023;
|
||||
- source: scd4x_temperature
|
||||
coeffecient: 1.5
|
||||
- platform: combination
|
||||
type: max
|
||||
name: Max of combined temperatures
|
||||
sources:
|
||||
- source: scd30_temperature
|
||||
- source: scd4x_temperature
|
||||
- platform: combination
|
||||
type: mean
|
||||
name: Mean of combined temperatures
|
||||
sources:
|
||||
- source: scd30_temperature
|
||||
- source: scd4x_temperature
|
||||
- platform: combination
|
||||
type: median
|
||||
name: Median of combined temperatures
|
||||
sources:
|
||||
- source: scd30_temperature
|
||||
- source: scd4x_temperature
|
||||
- platform: combination
|
||||
type: min
|
||||
name: Min of combined temperatures
|
||||
sources:
|
||||
- source: scd30_temperature
|
||||
- source: scd4x_temperature
|
||||
- platform: combination
|
||||
type: most_recently_updated
|
||||
name: Most recently updated of combined temperatures
|
||||
sources:
|
||||
- source: scd30_temperature
|
||||
- source: scd4x_temperature
|
||||
- platform: combination
|
||||
type: range
|
||||
name: Range of combined temperatures
|
||||
sources:
|
||||
- source: scd30_temperature
|
||||
- source: scd4x_temperature
|
||||
- platform: combination
|
||||
type: sum
|
||||
name: Sum of combined temperatures
|
||||
sources:
|
||||
- source: scd30_temperature
|
||||
- source: scd4x_temperature
|
||||
- platform: htu21d
|
||||
temperature:
|
||||
name: Living Room Temperature 6
|
||||
|
|
|
@ -49,6 +49,7 @@ spi:
|
|||
number: GPIO14
|
||||
|
||||
ota:
|
||||
version: 2
|
||||
|
||||
logger:
|
||||
|
||||
|
|
|
@ -693,7 +693,6 @@ display:
|
|||
greyscale: false
|
||||
partial_updating: false
|
||||
update_interval: 60s
|
||||
|
||||
display_data_1_pin:
|
||||
number: GPIO5
|
||||
allow_other_uses: true
|
||||
|
@ -742,6 +741,24 @@ display:
|
|||
vcom_pin:
|
||||
number: GPIO1
|
||||
allow_other_uses: true
|
||||
- platform: waveshare_epaper
|
||||
spi_id: spi_id_1
|
||||
cs_pin:
|
||||
number: GPIO23
|
||||
allow_other_uses: true
|
||||
dc_pin:
|
||||
number: GPIO23
|
||||
allow_other_uses: true
|
||||
busy_pin:
|
||||
number: GPIO23
|
||||
allow_other_uses: true
|
||||
reset_pin:
|
||||
number: GPIO23
|
||||
allow_other_uses: true
|
||||
model: 2.13inv3
|
||||
full_update_every: 30
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
|
||||
number:
|
||||
- platform: tuya
|
||||
|
|
|
@ -28,6 +28,15 @@ spi:
|
|||
allow_other_uses: false
|
||||
mosi_pin: GPIO6
|
||||
interface: any
|
||||
- id: quad_spi
|
||||
clk_pin: 47
|
||||
data_pins:
|
||||
-
|
||||
number: 40
|
||||
allow_other_uses: false
|
||||
- 41
|
||||
- 42
|
||||
- 43
|
||||
|
||||
spi_device:
|
||||
id: spidev
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
esphome:
|
||||
name: componenttestesp32ard
|
||||
friendly_name: $component_name
|
||||
|
||||
esp32:
|
||||
board: nodemcu-32s
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
logger:
|
||||
level: VERY_VERBOSE
|
||||
|
||||
packages:
|
||||
component_under_test: !include
|
||||
file: $component_test_file
|
||||
vars:
|
||||
component_name: $component_name
|
||||
test_name: $test_name
|
||||
target_platform: $target_platform
|
||||
component_test_file: $component_test_file
|
|
@ -0,0 +1,20 @@
|
|||
esphome:
|
||||
name: componenttestesp32c3ard
|
||||
friendly_name: $component_name
|
||||
|
||||
esp32:
|
||||
board: lolin_c3_mini
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
logger:
|
||||
level: VERY_VERBOSE
|
||||
|
||||
packages:
|
||||
component_under_test: !include
|
||||
file: $component_test_file
|
||||
vars:
|
||||
component_name: $component_name
|
||||
test_name: $test_name
|
||||
target_platform: $target_platform
|
||||
component_test_file: $component_test_file
|
|
@ -0,0 +1,20 @@
|
|||
esphome:
|
||||
name: componenttestesp32c3idf
|
||||
friendly_name: $component_name
|
||||
|
||||
esp32:
|
||||
board: lolin_c3_mini
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
logger:
|
||||
level: VERY_VERBOSE
|
||||
|
||||
packages:
|
||||
component_under_test: !include
|
||||
file: $component_test_file
|
||||
vars:
|
||||
component_name: $component_name
|
||||
test_name: $test_name
|
||||
target_platform: $target_platform
|
||||
component_test_file: $component_test_file
|
|
@ -0,0 +1,20 @@
|
|||
esphome:
|
||||
name: componenttestesp32idf
|
||||
friendly_name: $component_name
|
||||
|
||||
esp32:
|
||||
board: nodemcu-32s
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
logger:
|
||||
level: VERY_VERBOSE
|
||||
|
||||
packages:
|
||||
component_under_test: !include
|
||||
file: $component_test_file
|
||||
vars:
|
||||
component_name: $component_name
|
||||
test_name: $test_name
|
||||
target_platform: $target_platform
|
||||
component_test_file: $component_test_file
|
|
@ -0,0 +1,21 @@
|
|||
esphome:
|
||||
name: componenttestesp32s2ard
|
||||
friendly_name: $component_name
|
||||
|
||||
esp32:
|
||||
board: esp32-s2-saola-1
|
||||
variant: ESP32S2
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
logger:
|
||||
level: VERY_VERBOSE
|
||||
|
||||
packages:
|
||||
component_under_test: !include
|
||||
file: $component_test_file
|
||||
vars:
|
||||
component_name: $component_name
|
||||
test_name: $test_name
|
||||
target_platform: $target_platform
|
||||
component_test_file: $component_test_file
|
|
@ -0,0 +1,21 @@
|
|||
esphome:
|
||||
name: componenttestesp32s2ard
|
||||
friendly_name: $component_name
|
||||
|
||||
esp32:
|
||||
board: esp32-s2-saola-1
|
||||
variant: ESP32S2
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
logger:
|
||||
level: VERY_VERBOSE
|
||||
|
||||
packages:
|
||||
component_under_test: !include
|
||||
file: $component_test_file
|
||||
vars:
|
||||
component_name: $component_name
|
||||
test_name: $test_name
|
||||
target_platform: $target_platform
|
||||
component_test_file: $component_test_file
|
|
@ -0,0 +1,21 @@
|
|||
esphome:
|
||||
name: componenttestesp32s3ard
|
||||
friendly_name: $component_name
|
||||
|
||||
esp32:
|
||||
board: esp32s3box
|
||||
variant: ESP32S3
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
logger:
|
||||
level: VERY_VERBOSE
|
||||
|
||||
packages:
|
||||
component_under_test: !include
|
||||
file: $component_test_file
|
||||
vars:
|
||||
component_name: $component_name
|
||||
test_name: $test_name
|
||||
target_platform: $target_platform
|
||||
component_test_file: $component_test_file
|
|
@ -0,0 +1,21 @@
|
|||
esphome:
|
||||
name: componenttestesp32s3ard
|
||||
friendly_name: $component_name
|
||||
|
||||
esp32:
|
||||
board: esp32s3box
|
||||
variant: ESP32S3
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
logger:
|
||||
level: VERY_VERBOSE
|
||||
|
||||
packages:
|
||||
component_under_test: !include
|
||||
file: $component_test_file
|
||||
vars:
|
||||
component_name: $component_name
|
||||
test_name: $test_name
|
||||
target_platform: $target_platform
|
||||
component_test_file: $component_test_file
|
|
@ -0,0 +1,18 @@
|
|||
esphome:
|
||||
name: componenttestesp8266
|
||||
friendly_name: $component_name
|
||||
|
||||
esp8266:
|
||||
board: d1_mini
|
||||
|
||||
logger:
|
||||
level: VERY_VERBOSE
|
||||
|
||||
packages:
|
||||
component_under_test: !include
|
||||
file: $component_test_file
|
||||
vars:
|
||||
component_name: $component_name
|
||||
test_name: $test_name
|
||||
target_platform: $target_platform
|
||||
component_test_file: $component_test_file
|
|
@ -0,0 +1,21 @@
|
|||
esphome:
|
||||
name: componenttestrp2040
|
||||
friendly_name: $component_name
|
||||
|
||||
rp2040:
|
||||
board: rpipicow
|
||||
framework:
|
||||
# Waiting for https://github.com/platformio/platform-raspberrypi/pull/36
|
||||
platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git
|
||||
|
||||
logger:
|
||||
level: VERY_VERBOSE
|
||||
|
||||
packages:
|
||||
component_under_test: !include
|
||||
file: $component_test_file
|
||||
vars:
|
||||
component_name: $component_name
|
||||
test_name: $test_name
|
||||
target_platform: $target_platform
|
||||
component_test_file: $component_test_file
|
Loading…
Reference in a new issue