mirror of
https://github.com/esphome/esphome.git
synced 2025-01-07 05:11:43 +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 }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v3.3.2
|
uses: actions/cache@v4.0.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
|
@ -365,7 +365,7 @@ jobs:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
uses: actions/cache@v3.3.2
|
uses: actions/cache@v4.0.0
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
|
@ -392,6 +392,62 @@ jobs:
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
if: always()
|
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:
|
ci-status:
|
||||||
name: CI Status
|
name: CI Status
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -406,6 +462,7 @@ jobs:
|
||||||
- pyupgrade
|
- pyupgrade
|
||||||
- compile-tests
|
- compile-tests
|
||||||
- clang-tidy
|
- clang-tidy
|
||||||
|
- test-build-components
|
||||||
if: always()
|
if: always()
|
||||||
steps:
|
steps:
|
||||||
- name: Success
|
- name: Success
|
||||||
|
|
|
@ -71,6 +71,7 @@ esphome/components/cd74hc4067/* @asoehlke
|
||||||
esphome/components/climate/* @esphome/core
|
esphome/components/climate/* @esphome/core
|
||||||
esphome/components/climate_ir/* @glmnet
|
esphome/components/climate_ir/* @glmnet
|
||||||
esphome/components/color_temperature/* @jesserockz
|
esphome/components/color_temperature/* @jesserockz
|
||||||
|
esphome/components/combination/* @Cat-Ion @kahrendt
|
||||||
esphome/components/coolix/* @glmnet
|
esphome/components/coolix/* @glmnet
|
||||||
esphome/components/copy/* @OttoWinter
|
esphome/components/copy/* @OttoWinter
|
||||||
esphome/components/cover/* @esphome/core
|
esphome/components/cover/* @esphome/core
|
||||||
|
@ -138,6 +139,7 @@ esphome/components/heatpumpir/* @rob-deutsch
|
||||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||||
esphome/components/hm3301/* @freekode
|
esphome/components/hm3301/* @freekode
|
||||||
esphome/components/homeassistant/* @OttoWinter
|
esphome/components/homeassistant/* @OttoWinter
|
||||||
|
esphome/components/honeywell_hih_i2c/* @Benichou34
|
||||||
esphome/components/honeywellabp/* @RubyBailey
|
esphome/components/honeywellabp/* @RubyBailey
|
||||||
esphome/components/honeywellabp2_i2c/* @jpfaff
|
esphome/components/honeywellabp2_i2c/* @jpfaff
|
||||||
esphome/components/host/* @esphome/core
|
esphome/components/host/* @esphome/core
|
||||||
|
@ -161,7 +163,6 @@ esphome/components/integration/* @OttoWinter
|
||||||
esphome/components/internal_temperature/* @Mat931
|
esphome/components/internal_temperature/* @Mat931
|
||||||
esphome/components/interval/* @esphome/core
|
esphome/components/interval/* @esphome/core
|
||||||
esphome/components/json/* @OttoWinter
|
esphome/components/json/* @OttoWinter
|
||||||
esphome/components/kalman_combinator/* @Cat-Ion
|
|
||||||
esphome/components/key_collector/* @ssieb
|
esphome/components/key_collector/* @ssieb
|
||||||
esphome/components/key_provider/* @ssieb
|
esphome/components/key_provider/* @ssieb
|
||||||
esphome/components/kuntze/* @ssieb
|
esphome/components/kuntze/* @ssieb
|
||||||
|
@ -367,6 +368,7 @@ esphome/components/veml3235/* @kbx81
|
||||||
esphome/components/version/* @esphome/core
|
esphome/components/version/* @esphome/core
|
||||||
esphome/components/voice_assistant/* @jesserockz
|
esphome/components/voice_assistant/* @jesserockz
|
||||||
esphome/components/wake_on_lan/* @willwill2will54
|
esphome/components/wake_on_lan/* @willwill2will54
|
||||||
|
esphome/components/waveshare_epaper/* @clydebarrow
|
||||||
esphome/components/web_server_base/* @OttoWinter
|
esphome/components/web_server_base/* @OttoWinter
|
||||||
esphome/components/web_server_idf/* @dentra
|
esphome/components/web_server_idf/* @dentra
|
||||||
esphome/components/whirlpool/* @glmnet
|
esphome/components/whirlpool/* @glmnet
|
||||||
|
|
|
@ -81,7 +81,7 @@ RUN \
|
||||||
fi; \
|
fi; \
|
||||||
pip3 install \
|
pip3 install \
|
||||||
--break-system-packages --no-cache-dir \
|
--break-system-packages --no-cache-dir \
|
||||||
platformio==6.1.11 \
|
platformio==6.1.13 \
|
||||||
# Change some platformio settings
|
# Change some platformio settings
|
||||||
&& platformio settings set enable_telemetry No \
|
&& platformio settings set enable_telemetry No \
|
||||||
&& platformio settings set check_platformio_interval 1000000 \
|
&& 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);
|
this->set_notify_(true);
|
||||||
|
|
||||||
#ifdef USE_TIME
|
#ifdef USE_TIME
|
||||||
if (this->time_id_.has_value()) {
|
if (this->time_id_ != nullptr) {
|
||||||
this->send_local_time();
|
this->send_local_time();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -441,9 +441,8 @@ uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) {
|
||||||
|
|
||||||
#ifdef USE_TIME
|
#ifdef USE_TIME
|
||||||
void BedJetHub::send_local_time() {
|
void BedJetHub::send_local_time() {
|
||||||
if (this->time_id_.has_value()) {
|
if (this->time_id_ != nullptr) {
|
||||||
auto *time_id = *this->time_id_;
|
ESPTime now = this->time_id_->now();
|
||||||
ESPTime now = time_id->now();
|
|
||||||
if (now.is_valid()) {
|
if (now.is_valid()) {
|
||||||
this->set_clock(now.hour, now.minute);
|
this->set_clock(now.hour, now.minute);
|
||||||
ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", 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_() {
|
void BedJetHub::setup_time_() {
|
||||||
if (this->time_id_.has_value()) {
|
if (this->time_id_ != nullptr) {
|
||||||
this->send_local_time();
|
this->send_local_time();
|
||||||
auto *time_id = *this->time_id_;
|
this->time_id_->add_on_time_sync_callback([this] { this->send_local_time(); });
|
||||||
time_id->add_on_time_sync_callback([this] { this->send_local_time(); });
|
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGI(TAG, "`time_id` is not configured: will not sync BedJet clock.");
|
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
|
#ifdef USE_TIME
|
||||||
/** Initializes time sync callbacks to support syncing current time to the BedJet. */
|
/** Initializes time sync callbacks to support syncing current time to the BedJet. */
|
||||||
void setup_time_();
|
void setup_time_();
|
||||||
optional<time::RealTimeClock *> time_id_{};
|
time::RealTimeClock *time_id_{nullptr};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint32_t timeout_{DEFAULT_STATUS_TIMEOUT};
|
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);
|
delayMicroseconds(40);
|
||||||
} else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
|
} else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
|
||||||
delayMicroseconds(2000);
|
delayMicroseconds(2000);
|
||||||
} else if (this->model_ == DHT_MODEL_AM2302) {
|
} else if (this->model_ == DHT_MODEL_AM2120 || this->model_ == DHT_MODEL_AM2302) {
|
||||||
delayMicroseconds(1000);
|
delayMicroseconds(1000);
|
||||||
} else {
|
} else {
|
||||||
delayMicroseconds(800);
|
delayMicroseconds(800);
|
||||||
|
|
|
@ -11,6 +11,7 @@ enum DHTModel {
|
||||||
DHT_MODEL_AUTO_DETECT = 0,
|
DHT_MODEL_AUTO_DETECT = 0,
|
||||||
DHT_MODEL_DHT11,
|
DHT_MODEL_DHT11,
|
||||||
DHT_MODEL_DHT22,
|
DHT_MODEL_DHT22,
|
||||||
|
DHT_MODEL_AM2120,
|
||||||
DHT_MODEL_AM2302,
|
DHT_MODEL_AM2302,
|
||||||
DHT_MODEL_RHT03,
|
DHT_MODEL_RHT03,
|
||||||
DHT_MODEL_SI7021,
|
DHT_MODEL_SI7021,
|
||||||
|
@ -27,6 +28,7 @@ class DHT : public PollingComponent {
|
||||||
* - DHT_MODEL_AUTO_DETECT (default)
|
* - DHT_MODEL_AUTO_DETECT (default)
|
||||||
* - DHT_MODEL_DHT11
|
* - DHT_MODEL_DHT11
|
||||||
* - DHT_MODEL_DHT22
|
* - DHT_MODEL_DHT22
|
||||||
|
* - DHT_MODEL_AM2120
|
||||||
* - DHT_MODEL_AM2302
|
* - DHT_MODEL_AM2302
|
||||||
* - DHT_MODEL_RHT03
|
* - DHT_MODEL_RHT03
|
||||||
* - DHT_MODEL_SI7021
|
* - DHT_MODEL_SI7021
|
||||||
|
|
|
@ -23,6 +23,7 @@ DHT_MODELS = {
|
||||||
"AUTO_DETECT": DHTModel.DHT_MODEL_AUTO_DETECT,
|
"AUTO_DETECT": DHTModel.DHT_MODEL_AUTO_DETECT,
|
||||||
"DHT11": DHTModel.DHT_MODEL_DHT11,
|
"DHT11": DHTModel.DHT_MODEL_DHT11,
|
||||||
"DHT22": DHTModel.DHT_MODEL_DHT22,
|
"DHT22": DHTModel.DHT_MODEL_DHT22,
|
||||||
|
"AM2120": DHTModel.DHT_MODEL_AM2120,
|
||||||
"AM2302": DHTModel.DHT_MODEL_AM2302,
|
"AM2302": DHTModel.DHT_MODEL_AM2302,
|
||||||
"RHT03": DHTModel.DHT_MODEL_RHT03,
|
"RHT03": DHTModel.DHT_MODEL_RHT03,
|
||||||
"SI7021": DHTModel.DHT_MODEL_SI7021,
|
"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);
|
} while (dx <= 0);
|
||||||
}
|
}
|
||||||
void HOT Display::triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
|
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, x2, y2, color);
|
||||||
this->line(x1, y1, x3, y3);
|
this->line(x1, y1, x3, y3, color);
|
||||||
this->line(x2, y2, x3, y3);
|
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) {
|
void Display::sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3) {
|
||||||
if (*y1 > *y2) {
|
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,
|
PLATFORM_HOST,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
from esphome.helpers import IS_MACOS
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
|
||||||
|
@ -14,7 +15,6 @@ from .const import KEY_HOST
|
||||||
# force import gpio to register pin schema
|
# force import gpio to register pin schema
|
||||||
from .gpio import host_pin_to_code # noqa
|
from .gpio import host_pin_to_code # noqa
|
||||||
|
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
AUTO_LOAD = ["network"]
|
AUTO_LOAD = ["network"]
|
||||||
|
|
||||||
|
@ -35,5 +35,9 @@ CONFIG_SCHEMA = cv.All(
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_build_flag("-DUSE_HOST")
|
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_define("ESPHOME_BOARD", "host")
|
||||||
cg.add_platformio_option("platform", "platformio/native")
|
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
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor
|
|
||||||
from esphome.const import (
|
CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid(
|
||||||
CONF_ID,
|
"The kalman_combinator sensor has moved.\nPlease use the combination platform instead with type: kalman.\n"
|
||||||
CONF_SOURCE,
|
"See https://esphome.io/components/sensor/combination.html"
|
||||||
CONF_ACCURACY_DECIMALS,
|
|
||||||
CONF_DEVICE_CLASS,
|
|
||||||
CONF_ENTITY_CATEGORY,
|
|
||||||
CONF_ICON,
|
|
||||||
CONF_UNIT_OF_MEASUREMENT,
|
|
||||||
)
|
)
|
||||||
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_BIRTH_MESSAGE,
|
||||||
CONF_BROKER,
|
CONF_BROKER,
|
||||||
CONF_CERTIFICATE_AUTHORITY,
|
CONF_CERTIFICATE_AUTHORITY,
|
||||||
|
CONF_CLIENT_CERTIFICATE,
|
||||||
|
CONF_CLIENT_CERTIFICATE_KEY,
|
||||||
CONF_CLIENT_ID,
|
CONF_CLIENT_ID,
|
||||||
CONF_COMMAND_TOPIC,
|
CONF_COMMAND_TOPIC,
|
||||||
CONF_COMMAND_RETAIN,
|
CONF_COMMAND_RETAIN,
|
||||||
|
@ -199,6 +201,12 @@ CONFIG_SCHEMA = cv.All(
|
||||||
cv.Optional(CONF_CERTIFICATE_AUTHORITY): cv.All(
|
cv.Optional(CONF_CERTIFICATE_AUTHORITY): cv.All(
|
||||||
cv.string, cv.only_with_esp_idf
|
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.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32_idf=False): cv.All(
|
||||||
cv.boolean, cv.only_with_esp_idf
|
cv.boolean, cv.only_with_esp_idf
|
||||||
),
|
),
|
||||||
|
@ -378,6 +386,9 @@ async def to_code(config):
|
||||||
if CONF_CERTIFICATE_AUTHORITY in config:
|
if CONF_CERTIFICATE_AUTHORITY in config:
|
||||||
cg.add(var.set_ca_certificate(config[CONF_CERTIFICATE_AUTHORITY]))
|
cg.add(var.set_ca_certificate(config[CONF_CERTIFICATE_AUTHORITY]))
|
||||||
cg.add(var.set_skip_cert_cn_check(config[CONF_SKIP_CERT_CN_CHECK]))
|
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
|
# prevent error -0x428e
|
||||||
# See https://github.com/espressif/esp-idf/issues/139
|
# 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_.cert_pem = ca_certificate_.value().c_str();
|
||||||
mqtt_cfg_.skip_cert_common_name_check = skip_cert_cn_check_;
|
mqtt_cfg_.skip_cert_common_name_check = skip_cert_cn_check_;
|
||||||
mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_SSL;
|
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 {
|
} else {
|
||||||
mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_TCP;
|
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.certificate = ca_certificate_.value().c_str();
|
||||||
mqtt_cfg_.broker.verification.skip_cert_common_name_check = skip_cert_cn_check_;
|
mqtt_cfg_.broker.verification.skip_cert_common_name_check = skip_cert_cn_check_;
|
||||||
mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_SSL;
|
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 {
|
} else {
|
||||||
mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_TCP;
|
mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_TCP;
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,6 +124,8 @@ class MQTTBackendESP32 final : public MQTTBackend {
|
||||||
void loop() final;
|
void loop() final;
|
||||||
|
|
||||||
void set_ca_certificate(const std::string &cert) { ca_certificate_ = cert; }
|
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; }
|
void set_skip_cert_cn_check(bool skip_check) { skip_cert_cn_check_ = skip_check; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -154,6 +156,8 @@ class MQTTBackendESP32 final : public MQTTBackend {
|
||||||
uint16_t keep_alive_;
|
uint16_t keep_alive_;
|
||||||
bool clean_session_;
|
bool clean_session_;
|
||||||
optional<std::string> ca_certificate_;
|
optional<std::string> ca_certificate_;
|
||||||
|
optional<std::string> cl_certificate_;
|
||||||
|
optional<std::string> cl_key_;
|
||||||
bool skip_cert_cn_check_{false};
|
bool skip_cert_cn_check_{false};
|
||||||
|
|
||||||
// callbacks
|
// callbacks
|
||||||
|
|
|
@ -146,6 +146,8 @@ class MQTTClientComponent : public Component {
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
void set_ca_certificate(const char *cert) { this->mqtt_backend_.set_ca_certificate(cert); }
|
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); }
|
void set_skip_cert_cn_check(bool skip_check) { this->mqtt_backend_.set_skip_cert_cn_check(skip_check); }
|
||||||
#endif
|
#endif
|
||||||
const Availability &get_availability();
|
const Availability &get_availability();
|
||||||
|
|
|
@ -14,6 +14,13 @@
|
||||||
#include <IPAddress.h>
|
#include <IPAddress.h>
|
||||||
#endif /* USE_ADRDUINO */
|
#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
|
#if USE_ESP32_FRAMEWORK_ARDUINO
|
||||||
#define arduino_ns Arduino_h
|
#define arduino_ns Arduino_h
|
||||||
#elif USE_LIBRETINY
|
#elif USE_LIBRETINY
|
||||||
|
@ -32,6 +39,14 @@ namespace network {
|
||||||
|
|
||||||
struct IPAddress {
|
struct IPAddress {
|
||||||
public:
|
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() { ip_addr_set_zero(&ip_addr_); }
|
||||||
IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) {
|
IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) {
|
||||||
IP_ADDR4(&ip_addr_, first, second, third, fourth);
|
IP_ADDR4(&ip_addr_, first, second, third, fourth);
|
||||||
|
@ -107,6 +122,7 @@ struct IPAddress {
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ip_addr_t ip_addr_;
|
ip_addr_t ip_addr_;
|
||||||
|
|
|
@ -12,6 +12,7 @@ from esphome.const import (
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_OTA,
|
CONF_OTA,
|
||||||
KEY_PAST_SAFE_MODE,
|
KEY_PAST_SAFE_MODE,
|
||||||
|
CONF_VERSION,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
|
@ -41,6 +42,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(OTAComponent),
|
cv.GenerateID(): cv.declare_id(OTAComponent),
|
||||||
cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
|
cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, int=True),
|
||||||
cv.SplitDefault(
|
cv.SplitDefault(
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
esp8266=8266,
|
esp8266=8266,
|
||||||
|
@ -93,6 +95,7 @@ async def to_code(config):
|
||||||
if CONF_PASSWORD in config:
|
if CONF_PASSWORD in config:
|
||||||
cg.add(var.set_auth_password(config[CONF_PASSWORD]))
|
cg.add(var.set_auth_password(config[CONF_PASSWORD]))
|
||||||
cg.add_define("USE_OTA_PASSWORD")
|
cg.add_define("USE_OTA_PASSWORD")
|
||||||
|
cg.add_define("USE_OTA_VERSION", config[CONF_VERSION])
|
||||||
|
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,7 @@ namespace esphome {
|
||||||
namespace ota {
|
namespace ota {
|
||||||
|
|
||||||
static const char *const TAG = "ota";
|
static const char *const TAG = "ota";
|
||||||
|
static constexpr u_int16_t OTA_BLOCK_SIZE = 8192;
|
||||||
static const uint8_t OTA_VERSION_1_0 = 1;
|
|
||||||
|
|
||||||
OTAComponent *global_ota_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
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.");
|
ESP_LOGCONFIG(TAG, " Using Password.");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
ESP_LOGCONFIG(TAG, " OTA version: %d.", USE_OTA_VERSION);
|
||||||
if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1 &&
|
if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1 &&
|
||||||
this->safe_mode_rtc_value_ != esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) {
|
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",
|
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;
|
uint8_t ota_features;
|
||||||
std::unique_ptr<OTABackend> backend;
|
std::unique_ptr<OTABackend> backend;
|
||||||
(void) ota_features;
|
(void) ota_features;
|
||||||
|
#if USE_OTA_VERSION == 2
|
||||||
|
size_t size_acknowledged = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (client_ == nullptr) {
|
if (client_ == nullptr) {
|
||||||
struct sockaddr_storage source_addr;
|
struct sockaddr_storage source_addr;
|
||||||
|
@ -168,7 +171,7 @@ void OTAComponent::handle_() {
|
||||||
|
|
||||||
// Send OK and version - 2 bytes
|
// Send OK and version - 2 bytes
|
||||||
buf[0] = OTA_RESPONSE_OK;
|
buf[0] = OTA_RESPONSE_OK;
|
||||||
buf[1] = OTA_VERSION_1_0;
|
buf[1] = USE_OTA_VERSION;
|
||||||
this->writeall_(buf, 2);
|
this->writeall_(buf, 2);
|
||||||
|
|
||||||
backend = make_ota_backend();
|
backend = make_ota_backend();
|
||||||
|
@ -312,6 +315,13 @@ void OTAComponent::handle_() {
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
}
|
}
|
||||||
total += read;
|
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();
|
uint32_t now = millis();
|
||||||
if (now - last_progress > 1000) {
|
if (now - last_progress > 1000) {
|
||||||
|
|
|
@ -10,31 +10,32 @@ namespace esphome {
|
||||||
namespace ota {
|
namespace ota {
|
||||||
|
|
||||||
enum OTAResponseTypes {
|
enum OTAResponseTypes {
|
||||||
OTA_RESPONSE_OK = 0,
|
OTA_RESPONSE_OK = 0x00,
|
||||||
OTA_RESPONSE_REQUEST_AUTH = 1,
|
OTA_RESPONSE_REQUEST_AUTH = 0x01,
|
||||||
|
|
||||||
OTA_RESPONSE_HEADER_OK = 64,
|
OTA_RESPONSE_HEADER_OK = 0x40,
|
||||||
OTA_RESPONSE_AUTH_OK = 65,
|
OTA_RESPONSE_AUTH_OK = 0x41,
|
||||||
OTA_RESPONSE_UPDATE_PREPARE_OK = 66,
|
OTA_RESPONSE_UPDATE_PREPARE_OK = 0x42,
|
||||||
OTA_RESPONSE_BIN_MD5_OK = 67,
|
OTA_RESPONSE_BIN_MD5_OK = 0x43,
|
||||||
OTA_RESPONSE_RECEIVE_OK = 68,
|
OTA_RESPONSE_RECEIVE_OK = 0x44,
|
||||||
OTA_RESPONSE_UPDATE_END_OK = 69,
|
OTA_RESPONSE_UPDATE_END_OK = 0x45,
|
||||||
OTA_RESPONSE_SUPPORTS_COMPRESSION = 70,
|
OTA_RESPONSE_SUPPORTS_COMPRESSION = 0x46,
|
||||||
|
OTA_RESPONSE_CHUNK_OK = 0x47,
|
||||||
|
|
||||||
OTA_RESPONSE_ERROR_MAGIC = 128,
|
OTA_RESPONSE_ERROR_MAGIC = 0x80,
|
||||||
OTA_RESPONSE_ERROR_UPDATE_PREPARE = 129,
|
OTA_RESPONSE_ERROR_UPDATE_PREPARE = 0x81,
|
||||||
OTA_RESPONSE_ERROR_AUTH_INVALID = 130,
|
OTA_RESPONSE_ERROR_AUTH_INVALID = 0x82,
|
||||||
OTA_RESPONSE_ERROR_WRITING_FLASH = 131,
|
OTA_RESPONSE_ERROR_WRITING_FLASH = 0x83,
|
||||||
OTA_RESPONSE_ERROR_UPDATE_END = 132,
|
OTA_RESPONSE_ERROR_UPDATE_END = 0x84,
|
||||||
OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 133,
|
OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 0x85,
|
||||||
OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 134,
|
OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 0x86,
|
||||||
OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 135,
|
OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 0x87,
|
||||||
OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 136,
|
OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 0x88,
|
||||||
OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137,
|
OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 0x89,
|
||||||
OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 138,
|
OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 0x8A,
|
||||||
OTA_RESPONSE_ERROR_MD5_MISMATCH = 139,
|
OTA_RESPONSE_ERROR_MD5_MISMATCH = 0x8B,
|
||||||
OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 140,
|
OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 0x8C,
|
||||||
OTA_RESPONSE_ERROR_UNKNOWN = 255,
|
OTA_RESPONSE_ERROR_UNKNOWN = 0xFF,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum OTAState { OTA_COMPLETED = 0, OTA_STARTED, OTA_IN_PROGRESS, OTA_ERROR };
|
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); }
|
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 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 {
|
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);
|
return ::recvfrom(this->fd_, buf, len, 0, addr, addr_len);
|
||||||
#else
|
#else
|
||||||
return ::lwip_recvfrom(this->fd_, buf, len, 0, addr, addr_len);
|
return ::lwip_recvfrom(this->fd_, buf, len, 0, addr, addr_len);
|
||||||
|
|
|
@ -29,12 +29,15 @@ from esphome.const import (
|
||||||
PLATFORM_ESP32,
|
PLATFORM_ESP32,
|
||||||
PLATFORM_ESP8266,
|
PLATFORM_ESP8266,
|
||||||
PLATFORM_RP2040,
|
PLATFORM_RP2040,
|
||||||
|
CONF_ALLOW_OTHER_USES,
|
||||||
|
CONF_DATA_PINS,
|
||||||
)
|
)
|
||||||
from esphome.core import coroutine_with_priority, CORE
|
from esphome.core import coroutine_with_priority, CORE
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core", "@clydebarrow"]
|
CODEOWNERS = ["@esphome/core", "@clydebarrow"]
|
||||||
spi_ns = cg.esphome_ns.namespace("spi")
|
spi_ns = cg.esphome_ns.namespace("spi")
|
||||||
SPIComponent = spi_ns.class_("SPIComponent", cg.Component)
|
SPIComponent = spi_ns.class_("SPIComponent", cg.Component)
|
||||||
|
QuadSPIComponent = spi_ns.class_("QuadSPIComponent", cg.Component)
|
||||||
SPIDevice = spi_ns.class_("SPIDevice")
|
SPIDevice = spi_ns.class_("SPIDevice")
|
||||||
SPIDataRate = spi_ns.enum("SPIDataRate")
|
SPIDataRate = spi_ns.enum("SPIDataRate")
|
||||||
SPIMode = spi_ns.enum("SPIMode")
|
SPIMode = spi_ns.enum("SPIMode")
|
||||||
|
@ -190,12 +193,9 @@ def get_hw_spi(config, available):
|
||||||
def validate_spi_config(config):
|
def validate_spi_config(config):
|
||||||
available = list(range(len(get_hw_interface_list())))
|
available = list(range(len(get_hw_interface_list())))
|
||||||
for spi in config:
|
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]
|
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":
|
if interface == "software":
|
||||||
pass
|
pass
|
||||||
elif interface == "any":
|
elif interface == "any":
|
||||||
|
@ -229,6 +229,8 @@ def validate_spi_config(config):
|
||||||
spi, spi[CONF_INTERFACE_INDEX]
|
spi, spi[CONF_INTERFACE_INDEX]
|
||||||
):
|
):
|
||||||
raise cv.Invalid("Invalid pin selections for hardware SPI interface")
|
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
|
return config
|
||||||
|
|
||||||
|
@ -249,14 +251,26 @@ def get_spi_interface(index):
|
||||||
return "new SPIClass(HSPI)"
|
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(
|
SPI_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(SPIComponent),
|
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_MISO_PIN): pins.gpio_input_pin_schema,
|
||||||
cv.Optional(CONF_MOSI_PIN): pins.gpio_output_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(
|
cv.Optional(CONF_INTERFACE, default="any"): cv.one_of(
|
||||||
*sum(get_hw_interface_list(), ["software", "hardware", "any"]),
|
*sum(get_hw_interface_list(), ["software", "hardware", "any"]),
|
||||||
lower=True,
|
lower=True,
|
||||||
|
@ -267,8 +281,34 @@ SPI_SCHEMA = cv.All(
|
||||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]),
|
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(
|
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,
|
validate_spi_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -277,43 +317,46 @@ CONFIG_SCHEMA = cv.All(
|
||||||
async def to_code(configs):
|
async def to_code(configs):
|
||||||
cg.add_define("USE_SPI")
|
cg.add_define("USE_SPI")
|
||||||
cg.add_global(spi_ns.using)
|
cg.add_global(spi_ns.using)
|
||||||
|
if CORE.using_arduino:
|
||||||
|
cg.add_library("SPI", None)
|
||||||
for spi in configs:
|
for spi in configs:
|
||||||
var = cg.new_Pvariable(spi[CONF_ID])
|
var = cg.new_Pvariable(spi[CONF_ID])
|
||||||
await cg.register_component(var, spi)
|
await cg.register_component(var, spi)
|
||||||
|
|
||||||
clk = await cg.gpio_pin_expression(spi[CONF_CLK_PIN])
|
clk = await cg.gpio_pin_expression(spi[CONF_CLK_PIN])
|
||||||
cg.add(var.set_clk(clk))
|
cg.add(var.set_clk(clk))
|
||||||
if CONF_MISO_PIN in spi:
|
if miso := spi.get(CONF_MISO_PIN):
|
||||||
miso = await cg.gpio_pin_expression(spi[CONF_MISO_PIN])
|
cg.add(var.set_miso(await cg.gpio_pin_expression(miso)))
|
||||||
cg.add(var.set_miso(miso))
|
if mosi := spi.get(CONF_MOSI_PIN):
|
||||||
if CONF_MOSI_PIN in spi:
|
cg.add(var.set_mosi(await cg.gpio_pin_expression(mosi)))
|
||||||
mosi = await cg.gpio_pin_expression(spi[CONF_MOSI_PIN])
|
if data_pins := spi.get(CONF_DATA_PINS):
|
||||||
cg.add(var.set_mosi(mosi))
|
cg.add(var.set_data_pins(data_pins))
|
||||||
if CONF_INTERFACE_INDEX in spi:
|
if (index := spi.get(CONF_INTERFACE_INDEX)) is not None:
|
||||||
index = spi[CONF_INTERFACE_INDEX]
|
interface = get_spi_interface(index)
|
||||||
cg.add(var.set_interface(cg.RawExpression(get_spi_interface(index))))
|
cg.add(var.set_interface(cg.RawExpression(interface)))
|
||||||
cg.add(
|
cg.add(
|
||||||
var.set_interface_name(
|
var.set_interface_name(
|
||||||
re.sub(
|
re.sub(r"\W", "", interface.replace("new SPIClass", ""))
|
||||||
r"\W", "", get_spi_interface(index).replace("new SPIClass", "")
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if CORE.using_arduino:
|
|
||||||
cg.add_library("SPI", None)
|
|
||||||
|
|
||||||
|
|
||||||
def spi_device_schema(
|
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.
|
"""Create a schema for an SPI device.
|
||||||
:param cs_pin_required: If true, make the CS_PIN required in the config.
|
: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_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.
|
:return: The SPI device schema, `extend` this in your config schema.
|
||||||
"""
|
"""
|
||||||
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_DATA_RATE, default=default_data_rate): SPI_DATA_RATE_SCHEMA,
|
||||||
cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum(
|
cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum(
|
||||||
SPI_MODE_OPTIONS, upper=True
|
SPI_MODE_OPTIONS, upper=True
|
||||||
|
|
|
@ -49,7 +49,8 @@ void SPIComponent::setup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->using_hw_) {
|
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) {
|
if (this->spi_bus_ == nullptr) {
|
||||||
ESP_LOGE(TAG, "Unable to allocate SPI interface");
|
ESP_LOGE(TAG, "Unable to allocate SPI interface");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -68,6 +69,9 @@ void SPIComponent::dump_config() {
|
||||||
LOG_PIN(" CLK Pin: ", this->clk_pin_)
|
LOG_PIN(" CLK Pin: ", this->clk_pin_)
|
||||||
LOG_PIN(" SDI Pin: ", this->sdi_pin_)
|
LOG_PIN(" SDI Pin: ", this->sdi_pin_)
|
||||||
LOG_PIN(" SDO Pin: ", this->sdo_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()) {
|
if (this->spi_bus_->is_hw()) {
|
||||||
ESP_LOGCONFIG(TAG, " Using HW SPI: %s", this->interface_name_);
|
ESP_LOGCONFIG(TAG, " Using HW SPI: %s", this->interface_name_);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/application.h"
|
|
||||||
#include <vector>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
#ifdef USE_ARDUINO
|
||||||
|
|
||||||
|
@ -208,6 +209,10 @@ class SPIDelegate {
|
||||||
esph_log_e("spi_device", "variable length write not implemented");
|
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
|
// write 16 bits
|
||||||
virtual void write16(uint16_t data) {
|
virtual void write16(uint16_t data) {
|
||||||
if (this->bit_order_ == BIT_ORDER_MSB_FIRST) {
|
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_miso(GPIOPin *sdi) { this->sdi_pin_ = sdi; }
|
||||||
|
|
||||||
void set_mosi(GPIOPin *sdo) { this->sdo_pin_ = sdo; }
|
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) {
|
void set_interface(SPIInterface interface) {
|
||||||
this->interface_ = interface;
|
this->interface_ = interface;
|
||||||
|
@ -348,15 +354,19 @@ class SPIComponent : public Component {
|
||||||
GPIOPin *clk_pin_{nullptr};
|
GPIOPin *clk_pin_{nullptr};
|
||||||
GPIOPin *sdi_pin_{nullptr};
|
GPIOPin *sdi_pin_{nullptr};
|
||||||
GPIOPin *sdo_pin_{nullptr};
|
GPIOPin *sdo_pin_{nullptr};
|
||||||
|
std::vector<uint8_t> data_pins_{};
|
||||||
|
|
||||||
SPIInterface interface_{};
|
SPIInterface interface_{};
|
||||||
bool using_hw_{false};
|
bool using_hw_{false};
|
||||||
const char *interface_name_{nullptr};
|
const char *interface_name_{nullptr};
|
||||||
SPIBus *spi_bus_{};
|
SPIBus *spi_bus_{};
|
||||||
std::map<SPIClient *, SPIDelegate *> devices_;
|
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.
|
* 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); }
|
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); };
|
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); }
|
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); }
|
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); }
|
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); }
|
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 write_array16(const uint16_t *data, size_t length) { this->delegate_->write_array16(data, length); }
|
||||||
|
|
||||||
void enable() { this->delegate_->begin_transaction(); }
|
void enable() { this->delegate_->begin_transaction(); }
|
||||||
|
|
|
@ -85,7 +85,8 @@ class SPIBusHw : public SPIBus {
|
||||||
bool is_hw() override { return true; }
|
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);
|
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); }
|
void transfer(uint8_t *ptr, size_t length) override { this->transfer(ptr, ptr, length); }
|
||||||
|
|
||||||
uint8_t transfer(uint8_t data) override {
|
uint8_t transfer(uint8_t data) override {
|
||||||
|
@ -142,13 +196,27 @@ class SPIDelegateHw : public SPIDelegate {
|
||||||
|
|
||||||
class SPIBusHw : public SPIBus {
|
class SPIBusHw : public SPIBus {
|
||||||
public:
|
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 = {};
|
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.sclk_io_num = Utility::get_pin_no(clk);
|
||||||
buscfg.quadwp_io_num = -1;
|
buscfg.flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_SCLK;
|
||||||
buscfg.quadhd_io_num = -1;
|
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;
|
buscfg.max_transfer_sz = MAX_TRANSFER_SIZE;
|
||||||
auto err = spi_bus_initialize(channel, &buscfg, SPI_DMA_CH_AUTO);
|
auto err = spi_bus_initialize(channel, &buscfg, SPI_DMA_CH_AUTO);
|
||||||
if (err != ESP_OK)
|
if (err != ESP_OK)
|
||||||
|
@ -166,8 +234,9 @@ class SPIBusHw : public SPIBus {
|
||||||
bool is_hw() override { return true; }
|
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,
|
||||||
return new SPIBusHw(clk, sdo, sdi, interface);
|
const std::vector<uint8_t> &data_pins) {
|
||||||
|
return new SPIBusHw(clk, sdo, sdi, interface, data_pins);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
#include "real_time_clock.h"
|
#include "real_time_clock.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#ifdef USE_HOST
|
||||||
|
#include <sys/time.h>
|
||||||
|
#else
|
||||||
#include "lwip/opt.h"
|
#include "lwip/opt.h"
|
||||||
|
#endif
|
||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
#include "sys/time.h"
|
#include "sys/time.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -25,7 +29,7 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
|
||||||
.tv_sec = static_cast<time_t>(epoch), .tv_usec = 0,
|
.tv_sec = static_cast<time_t>(epoch), .tv_usec = 0,
|
||||||
};
|
};
|
||||||
ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch);
|
ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch);
|
||||||
timezone tz = {0, 0};
|
struct timezone tz = {0, 0};
|
||||||
int ret = settimeofday(&timev, &tz);
|
int ret = settimeofday(&timev, &tz);
|
||||||
if (ret == EINVAL) {
|
if (ret == EINVAL) {
|
||||||
// Some ESP8266 frameworks abort when timezone parameter is not NULL
|
// Some ESP8266 frameworks abort when timezone parameter is not NULL
|
||||||
|
|
|
@ -23,8 +23,8 @@ static const int MAX_RETRIES = 5;
|
||||||
|
|
||||||
void Tuya::setup() {
|
void Tuya::setup() {
|
||||||
this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
|
this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
|
||||||
if (this->status_pin_.has_value()) {
|
if (this->status_pin_ != nullptr) {
|
||||||
this->status_pin_.value()->digital_write(false);
|
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_,
|
ESP_LOGCONFIG(TAG, " GPIO Configuration: status: pin %d, reset: pin %d", this->status_pin_reported_,
|
||||||
this->reset_pin_reported_);
|
this->reset_pin_reported_);
|
||||||
}
|
}
|
||||||
if (this->status_pin_.has_value()) {
|
LOG_PIN(" Status Pin: ", this->status_pin_);
|
||||||
LOG_PIN(" Status Pin: ", this->status_pin_.value());
|
|
||||||
}
|
|
||||||
ESP_LOGCONFIG(TAG, " Product: '%s'", this->product_.c_str());
|
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->init_state_ = TuyaInitState::INIT_DATAPOINT;
|
||||||
this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
|
this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
|
||||||
bool is_pin_equals =
|
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
|
// Configure status pin toggling (if reported and configured) or WIFI_STATE periodic send
|
||||||
if (is_pin_equals) {
|
if (is_pin_equals) {
|
||||||
ESP_LOGV(TAG, "Configured status pin %i", this->status_pin_reported_);
|
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;
|
break;
|
||||||
case TuyaCommandType::LOCAL_TIME_QUERY:
|
case TuyaCommandType::LOCAL_TIME_QUERY:
|
||||||
#ifdef USE_TIME
|
#ifdef USE_TIME
|
||||||
if (this->time_id_.has_value()) {
|
if (this->time_id_ != nullptr) {
|
||||||
this->send_local_time_();
|
this->send_local_time_();
|
||||||
|
|
||||||
if (!this->time_sync_callback_registered_) {
|
if (!this->time_sync_callback_registered_) {
|
||||||
// tuya mcu supports time, so we let them know when our time changed
|
// tuya mcu supports time, so we let them know when our time changed
|
||||||
auto *time_id = *this->time_id_;
|
this->time_id_->add_on_time_sync_callback([this] { this->send_local_time_(); });
|
||||||
time_id->add_on_time_sync_callback([this] { this->send_local_time_(); });
|
|
||||||
this->time_sync_callback_registered_ = true;
|
this->time_sync_callback_registered_ = true;
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
|
@ -463,7 +460,7 @@ void Tuya::send_empty_command_(TuyaCommandType command) {
|
||||||
|
|
||||||
void Tuya::set_status_pin_() {
|
void Tuya::set_status_pin_() {
|
||||||
bool is_network_ready = network::is_connected() && remote_is_connected();
|
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_() {
|
uint8_t Tuya::get_wifi_status_code_() {
|
||||||
|
@ -511,8 +508,7 @@ void Tuya::send_wifi_status_() {
|
||||||
#ifdef USE_TIME
|
#ifdef USE_TIME
|
||||||
void Tuya::send_local_time_() {
|
void Tuya::send_local_time_() {
|
||||||
std::vector<uint8_t> payload;
|
std::vector<uint8_t> payload;
|
||||||
auto *time_id = *this->time_id_;
|
ESPTime now = this->time_id_->now();
|
||||||
ESPTime now = time_id->now();
|
|
||||||
if (now.is_valid()) {
|
if (now.is_valid()) {
|
||||||
uint8_t year = now.year - 2000;
|
uint8_t year = now.year - 2000;
|
||||||
uint8_t month = now.month;
|
uint8_t month = now.month;
|
||||||
|
|
|
@ -130,14 +130,14 @@ class Tuya : public Component, public uart::UARTDevice {
|
||||||
|
|
||||||
#ifdef USE_TIME
|
#ifdef USE_TIME
|
||||||
void send_local_time_();
|
void send_local_time_();
|
||||||
optional<time::RealTimeClock *> time_id_{};
|
time::RealTimeClock *time_id_{nullptr};
|
||||||
bool time_sync_callback_registered_{false};
|
bool time_sync_callback_registered_{false};
|
||||||
#endif
|
#endif
|
||||||
TuyaInitState init_state_ = TuyaInitState::INIT_HEARTBEAT;
|
TuyaInitState init_state_ = TuyaInitState::INIT_HEARTBEAT;
|
||||||
bool init_failed_{false};
|
bool init_failed_{false};
|
||||||
int init_retries_{0};
|
int init_retries_{0};
|
||||||
uint8_t protocol_version_ = -1;
|
uint8_t protocol_version_ = -1;
|
||||||
optional<InternalGPIOPin *> status_pin_{};
|
InternalGPIOPin *status_pin_{nullptr};
|
||||||
int status_pin_reported_ = -1;
|
int status_pin_reported_ = -1;
|
||||||
int reset_pin_reported_ = -1;
|
int reset_pin_reported_ = -1;
|
||||||
uint32_t last_command_timestamp_ = 0;
|
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 SAMPLE_RATE_HZ = 16000;
|
||||||
static const size_t INPUT_BUFFER_SIZE = 32 * SAMPLE_RATE_HZ / 1000; // 32ms * 16kHz / 1000ms
|
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 SEND_BUFFER_SIZE = INPUT_BUFFER_SIZE * sizeof(int16_t);
|
||||||
static const size_t RECEIVE_SIZE = 1024;
|
static const size_t RECEIVE_SIZE = 1024;
|
||||||
static const size_t SPEAKER_BUFFER_SIZE = 16 * RECEIVE_SIZE;
|
static const size_t SPEAKER_BUFFER_SIZE = 16 * RECEIVE_SIZE;
|
||||||
|
@ -231,10 +231,12 @@ void VoiceAssistant::loop() {
|
||||||
}
|
}
|
||||||
case State::STREAMING_MICROPHONE: {
|
case State::STREAMING_MICROPHONE: {
|
||||||
this->read_microphone_();
|
this->read_microphone_();
|
||||||
if (this->ring_buffer_->available() >= SEND_BUFFER_SIZE) {
|
size_t available = this->ring_buffer_->available();
|
||||||
this->ring_buffer_->read((void *) this->send_buffer_, SEND_BUFFER_SIZE, 0);
|
while (available >= SEND_BUFFER_SIZE) {
|
||||||
this->socket_->sendto(this->send_buffer_, SEND_BUFFER_SIZE, 0, (struct sockaddr *) &this->dest_addr_,
|
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_));
|
sizeof(this->dest_addr_));
|
||||||
|
available = this->ring_buffer_->available();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ["@clydebarrow"]
|
|
@ -72,6 +72,9 @@ WaveshareEPaper7P5InHDB = waveshare_epaper_ns.class_(
|
||||||
WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_(
|
WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_(
|
||||||
"WaveshareEPaper2P13InDKE", WaveshareEPaper
|
"WaveshareEPaper2P13InDKE", WaveshareEPaper
|
||||||
)
|
)
|
||||||
|
WaveshareEPaper2P13InV3 = waveshare_epaper_ns.class_(
|
||||||
|
"WaveshareEPaper2P13InV3", WaveshareEPaper
|
||||||
|
)
|
||||||
GDEW0154M09 = waveshare_epaper_ns.class_("GDEW0154M09", WaveshareEPaper)
|
GDEW0154M09 = waveshare_epaper_ns.class_("GDEW0154M09", WaveshareEPaper)
|
||||||
|
|
||||||
WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel")
|
WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel")
|
||||||
|
@ -104,6 +107,7 @@ MODELS = {
|
||||||
"7.50inv2alt": ("b", WaveshareEPaper7P5InV2alt),
|
"7.50inv2alt": ("b", WaveshareEPaper7P5InV2alt),
|
||||||
"7.50in-hd-b": ("b", WaveshareEPaper7P5InHDB),
|
"7.50in-hd-b": ("b", WaveshareEPaper7P5InHDB),
|
||||||
"2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE),
|
"2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE),
|
||||||
|
"2.13inv3": ("c", WaveshareEPaper2P13InV3),
|
||||||
"1.54in-m5coreink-m09": ("c", GDEW0154M09),
|
"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->write_byte(value);
|
||||||
this->end_data_();
|
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_() {
|
bool WaveshareEPaper::wait_until_idle_() {
|
||||||
if (this->busy_pin_ == nullptr) {
|
if (this->busy_pin_ == nullptr || !this->busy_pin_->digital_read()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +132,7 @@ bool WaveshareEPaper::wait_until_idle_() {
|
||||||
ESP_LOGE(TAG, "Timeout while displaying image!");
|
ESP_LOGE(TAG, "Timeout while displaying image!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
delay(10);
|
delay(1);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1443,6 +1455,12 @@ void WaveshareEPaper7P5InBV2::initialize() {
|
||||||
// COMMAND TCON SETTING
|
// COMMAND TCON SETTING
|
||||||
this->command(0x60);
|
this->command(0x60);
|
||||||
this->data(0x22);
|
this->data(0x22);
|
||||||
|
|
||||||
|
this->command(0x82);
|
||||||
|
this->data(0x08);
|
||||||
|
this->command(0x30);
|
||||||
|
this->data(0x06);
|
||||||
|
|
||||||
// COMMAND RESOLUTION SETTING
|
// COMMAND RESOLUTION SETTING
|
||||||
this->command(0x65);
|
this->command(0x65);
|
||||||
this->data(0x00);
|
this->data(0x00);
|
||||||
|
@ -1472,6 +1490,7 @@ void HOT WaveshareEPaper7P5InBV2::display() {
|
||||||
this->command(0x12);
|
this->command(0x12);
|
||||||
delay(100); // NOLINT
|
delay(100); // NOLINT
|
||||||
this->wait_until_idle_();
|
this->wait_until_idle_();
|
||||||
|
this->deep_sleep();
|
||||||
}
|
}
|
||||||
int WaveshareEPaper7P5InBV2::get_width_internal() { return 800; }
|
int WaveshareEPaper7P5InBV2::get_width_internal() { return 800; }
|
||||||
int WaveshareEPaper7P5InBV2::get_height_internal() { return 480; }
|
int WaveshareEPaper7P5InBV2::get_height_internal() { return 480; }
|
||||||
|
@ -1617,7 +1636,7 @@ void HOT WaveshareEPaper7P5InBV3::display() {
|
||||||
this->command(0x13); // Start Transmission
|
this->command(0x13); // Start Transmission
|
||||||
delay(2);
|
delay(2);
|
||||||
for (uint32_t i = 0; i < buf_len; i++) {
|
for (uint32_t i = 0; i < buf_len; i++) {
|
||||||
this->data(this->buffer_[i]);
|
this->data(~this->buffer_[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->command(0x12); // Display Refresh
|
this->command(0x12); // Display Refresh
|
||||||
|
@ -2211,8 +2230,9 @@ void HOT WaveshareEPaper2P13InDKE::display() {
|
||||||
} else {
|
} else {
|
||||||
// set up partial update
|
// set up partial update
|
||||||
this->command(0x32);
|
this->command(0x32);
|
||||||
for (uint8_t v : PART_UPDATE_LUT_TTGO_DKE)
|
this->start_data_();
|
||||||
this->data(v);
|
this->write_array(PART_UPDATE_LUT_TTGO_DKE, sizeof(PART_UPDATE_LUT_TTGO_DKE));
|
||||||
|
this->end_data_();
|
||||||
this->command(0x3F);
|
this->command(0x3F);
|
||||||
this->data(0x22);
|
this->data(0x22);
|
||||||
|
|
||||||
|
@ -2257,12 +2277,10 @@ void HOT WaveshareEPaper2P13InDKE::display() {
|
||||||
this->wait_until_idle_();
|
this->wait_until_idle_();
|
||||||
|
|
||||||
// data must be sent again on partial update
|
// data must be sent again on partial update
|
||||||
delay(300); // NOLINT
|
|
||||||
this->command(0x24);
|
this->command(0x24);
|
||||||
this->start_data_();
|
this->start_data_();
|
||||||
this->write_array(this->buffer_, this->get_buffer_length_());
|
this->write_array(this->buffer_, this->get_buffer_length_());
|
||||||
this->end_data_();
|
this->end_data_();
|
||||||
delay(300); // NOLINT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Completed e-paper update.");
|
ESP_LOGI(TAG, "Completed e-paper update.");
|
||||||
|
@ -2274,6 +2292,7 @@ uint32_t WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; }
|
||||||
void WaveshareEPaper2P13InDKE::dump_config() {
|
void WaveshareEPaper2P13InDKE::dump_config() {
|
||||||
LOG_DISPLAY("", "Waveshare E-Paper", this);
|
LOG_DISPLAY("", "Waveshare E-Paper", this);
|
||||||
ESP_LOGCONFIG(TAG, " Model: 2.13inDKE");
|
ESP_LOGCONFIG(TAG, " Model: 2.13inDKE");
|
||||||
|
LOG_PIN(" CS Pin: ", this->cs_);
|
||||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||||
|
|
|
@ -19,6 +19,7 @@ class WaveshareEPaper : public display::DisplayBuffer,
|
||||||
|
|
||||||
void command(uint8_t value);
|
void command(uint8_t value);
|
||||||
void data(uint8_t value);
|
void data(uint8_t value);
|
||||||
|
void cmd_data(const uint8_t *data, size_t length);
|
||||||
|
|
||||||
virtual void display() = 0;
|
virtual void display() = 0;
|
||||||
virtual void initialize() = 0;
|
virtual void initialize() = 0;
|
||||||
|
@ -49,7 +50,7 @@ class WaveshareEPaper : public display::DisplayBuffer,
|
||||||
this->reset_pin_->digital_write(false);
|
this->reset_pin_->digital_write(false);
|
||||||
delay(reset_duration_); // NOLINT
|
delay(reset_duration_); // NOLINT
|
||||||
this->reset_pin_->digital_write(true);
|
this->reset_pin_->digital_write(true);
|
||||||
delay(200); // NOLINT
|
delay(20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -614,5 +615,39 @@ class WaveshareEPaper2P13InDKE : public WaveshareEPaper {
|
||||||
uint32_t at_update_{0};
|
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 waveshare_epaper
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -55,6 +55,9 @@ void WiFiComponent::start() {
|
||||||
uint32_t hash = this->has_sta() ? fnv1_hash(App.get_compilation_time()) : 88491487UL;
|
uint32_t hash = this->has_sta() ? fnv1_hash(App.get_compilation_time()) : 88491487UL;
|
||||||
|
|
||||||
this->pref_ = global_preferences->make_preference<wifi::SavedWifiSettings>(hash, true);
|
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{};
|
SavedWifiSettings save{};
|
||||||
if (this->pref_.load(&save)) {
|
if (this->pref_.load(&save)) {
|
||||||
|
@ -78,6 +81,7 @@ void WiFiComponent::start() {
|
||||||
|
|
||||||
if (this->fast_connect_) {
|
if (this->fast_connect_) {
|
||||||
this->selected_ap_ = this->sta_[0];
|
this->selected_ap_ = this->sta_[0];
|
||||||
|
this->load_fast_connect_settings_();
|
||||||
this->start_connecting(this->selected_ap_, false);
|
this->start_connecting(this->selected_ap_, false);
|
||||||
} else {
|
} else {
|
||||||
this->start_scanning();
|
this->start_scanning();
|
||||||
|
@ -604,6 +608,11 @@ void WiFiComponent::check_connecting_finished() {
|
||||||
|
|
||||||
this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTED;
|
this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTED;
|
||||||
this->num_retried_ = 0;
|
this->num_retried_ = 0;
|
||||||
|
|
||||||
|
if (this->fast_connect_) {
|
||||||
|
this->save_fast_connect_settings_();
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -705,6 +714,35 @@ bool WiFiComponent::is_esp32_improv_active_() {
|
||||||
#endif
|
#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_ssid(const std::string &ssid) { this->ssid_ = ssid; }
|
||||||
void WiFiAP::set_bssid(bssid_t bssid) { this->bssid_ = bssid; }
|
void WiFiAP::set_bssid(bssid_t bssid) { this->bssid_ = bssid; }
|
||||||
void WiFiAP::set_bssid(optional<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];
|
char password[65];
|
||||||
} PACKED; // NOLINT
|
} PACKED; // NOLINT
|
||||||
|
|
||||||
|
struct SavedWifiFastConnectSettings {
|
||||||
|
uint8_t bssid[6];
|
||||||
|
uint8_t channel;
|
||||||
|
} PACKED; // NOLINT
|
||||||
|
|
||||||
enum WiFiComponentState {
|
enum WiFiComponentState {
|
||||||
/** Nothing has been initialized yet. Internal AP, if configured, is disabled at this point. */
|
/** Nothing has been initialized yet. Internal AP, if configured, is disabled at this point. */
|
||||||
WIFI_COMPONENT_STATE_OFF = 0,
|
WIFI_COMPONENT_STATE_OFF = 0,
|
||||||
|
@ -334,6 +339,9 @@ class WiFiComponent : public Component {
|
||||||
bool is_captive_portal_active_();
|
bool is_captive_portal_active_();
|
||||||
bool is_esp32_improv_active_();
|
bool is_esp32_improv_active_();
|
||||||
|
|
||||||
|
void load_fast_connect_settings_();
|
||||||
|
void save_fast_connect_settings_();
|
||||||
|
|
||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
static void wifi_event_callback(System_Event_t *event);
|
static void wifi_event_callback(System_Event_t *event);
|
||||||
void wifi_scan_done_callback_(void *arg, STATUS status);
|
void wifi_scan_done_callback_(void *arg, STATUS status);
|
||||||
|
@ -381,6 +389,7 @@ class WiFiComponent : public Component {
|
||||||
optional<float> output_power_;
|
optional<float> output_power_;
|
||||||
bool passive_scan_{false};
|
bool passive_scan_{false};
|
||||||
ESPPreferenceObject pref_;
|
ESPPreferenceObject pref_;
|
||||||
|
ESPPreferenceObject fast_connect_pref_;
|
||||||
bool has_saved_wifi_settings_{false};
|
bool has_saved_wifi_settings_{false};
|
||||||
#ifdef USE_WIFI_11KV_SUPPORT
|
#ifdef USE_WIFI_11KV_SUPPORT
|
||||||
bool btm_{false};
|
bool btm_{false};
|
||||||
|
|
|
@ -113,6 +113,8 @@ CONF_CHANNELS = "channels"
|
||||||
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
|
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
|
||||||
CONF_CHIPSET = "chipset"
|
CONF_CHIPSET = "chipset"
|
||||||
CONF_CLEAR_IMPEDANCE = "clear_impedance"
|
CONF_CLEAR_IMPEDANCE = "clear_impedance"
|
||||||
|
CONF_CLIENT_CERTIFICATE = "client_certificate"
|
||||||
|
CONF_CLIENT_CERTIFICATE_KEY = "client_certificate_key"
|
||||||
CONF_CLIENT_ID = "client_id"
|
CONF_CLIENT_ID = "client_id"
|
||||||
CONF_CLK_PIN = "clk_pin"
|
CONF_CLK_PIN = "clk_pin"
|
||||||
CONF_CLOCK_PIN = "clock_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; }
|
void Component::set_setup_priority(float priority) { this->setup_priority_override_ = priority; }
|
||||||
|
|
||||||
bool Component::has_overridden_loop() const {
|
bool Component::has_overridden_loop() const {
|
||||||
#ifdef CLANG_TIDY
|
#if defined(USE_HOST) || defined(CLANG_TIDY)
|
||||||
bool loop_overridden = true;
|
bool loop_overridden = true;
|
||||||
bool call_loop_overridden = true;
|
bool call_loop_overridden = true;
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
#define USE_OTA
|
#define USE_OTA
|
||||||
#define USE_OTA_PASSWORD
|
#define USE_OTA_PASSWORD
|
||||||
#define USE_OTA_STATE_CALLBACK
|
#define USE_OTA_STATE_CALLBACK
|
||||||
|
#define USE_OTA_VERSION 1
|
||||||
#define USE_OUTPUT
|
#define USE_OUTPUT
|
||||||
#define USE_POWER_SUPPLY
|
#define USE_POWER_SUPPLY
|
||||||
#define USE_QR_CODE
|
#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>();
|
std::unique_ptr<RingBuffer> rb = make_unique<RingBuffer>();
|
||||||
|
|
||||||
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||||
rb->storage_ = allocator.allocate(len);
|
rb->storage_ = allocator.allocate(len + 1);
|
||||||
if (rb->storage_ == nullptr) {
|
if (rb->storage_ == nullptr) {
|
||||||
return 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;
|
return rb;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t RingBuffer::read(void *data, size_t size, TickType_t ticks_to_wait) {
|
size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) {
|
||||||
return xStreamBufferReceive(this->handle_, data, size, ticks_to_wait);
|
return xStreamBufferReceive(this->handle_, data, len, ticks_to_wait);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t RingBuffer::write(void *data, size_t len) {
|
size_t RingBuffer::write(void *data, size_t len) {
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace esphome {
|
||||||
|
|
||||||
class RingBuffer {
|
class RingBuffer {
|
||||||
public:
|
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);
|
size_t write(void *data, size_t len);
|
||||||
|
|
||||||
|
|
|
@ -12,32 +12,34 @@ import time
|
||||||
from esphome.core import EsphomeError
|
from esphome.core import EsphomeError
|
||||||
from esphome.helpers import is_ip_address, resolve_ip_address
|
from esphome.helpers import is_ip_address, resolve_ip_address
|
||||||
|
|
||||||
RESPONSE_OK = 0
|
RESPONSE_OK = 0x00
|
||||||
RESPONSE_REQUEST_AUTH = 1
|
RESPONSE_REQUEST_AUTH = 0x01
|
||||||
|
|
||||||
RESPONSE_HEADER_OK = 64
|
RESPONSE_HEADER_OK = 0x40
|
||||||
RESPONSE_AUTH_OK = 65
|
RESPONSE_AUTH_OK = 0x41
|
||||||
RESPONSE_UPDATE_PREPARE_OK = 66
|
RESPONSE_UPDATE_PREPARE_OK = 0x42
|
||||||
RESPONSE_BIN_MD5_OK = 67
|
RESPONSE_BIN_MD5_OK = 0x43
|
||||||
RESPONSE_RECEIVE_OK = 68
|
RESPONSE_RECEIVE_OK = 0x44
|
||||||
RESPONSE_UPDATE_END_OK = 69
|
RESPONSE_UPDATE_END_OK = 0x45
|
||||||
RESPONSE_SUPPORTS_COMPRESSION = 70
|
RESPONSE_SUPPORTS_COMPRESSION = 0x46
|
||||||
|
RESPONSE_CHUNK_OK = 0x47
|
||||||
|
|
||||||
RESPONSE_ERROR_MAGIC = 128
|
RESPONSE_ERROR_MAGIC = 0x80
|
||||||
RESPONSE_ERROR_UPDATE_PREPARE = 129
|
RESPONSE_ERROR_UPDATE_PREPARE = 0x81
|
||||||
RESPONSE_ERROR_AUTH_INVALID = 130
|
RESPONSE_ERROR_AUTH_INVALID = 0x82
|
||||||
RESPONSE_ERROR_WRITING_FLASH = 131
|
RESPONSE_ERROR_WRITING_FLASH = 0x83
|
||||||
RESPONSE_ERROR_UPDATE_END = 132
|
RESPONSE_ERROR_UPDATE_END = 0x84
|
||||||
RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 133
|
RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 0x85
|
||||||
RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 134
|
RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 0x86
|
||||||
RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 135
|
RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 0x87
|
||||||
RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 136
|
RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 0x88
|
||||||
RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137
|
RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 0x89
|
||||||
RESPONSE_ERROR_NO_UPDATE_PARTITION = 138
|
RESPONSE_ERROR_NO_UPDATE_PARTITION = 0x8A
|
||||||
RESPONSE_ERROR_MD5_MISMATCH = 139
|
RESPONSE_ERROR_MD5_MISMATCH = 0x8B
|
||||||
RESPONSE_ERROR_UNKNOWN = 255
|
RESPONSE_ERROR_UNKNOWN = 0xFF
|
||||||
|
|
||||||
OTA_VERSION_1_0 = 1
|
OTA_VERSION_1_0 = 1
|
||||||
|
OTA_VERSION_2_0 = 2
|
||||||
|
|
||||||
MAGIC_BYTES = [0x6C, 0x26, 0xF7, 0x5C, 0x45]
|
MAGIC_BYTES = [0x6C, 0x26, 0xF7, 0x5C, 0x45]
|
||||||
|
|
||||||
|
@ -203,7 +205,8 @@ def perform_ota(
|
||||||
send_check(sock, MAGIC_BYTES, "magic bytes")
|
send_check(sock, MAGIC_BYTES, "magic bytes")
|
||||||
|
|
||||||
_, version = receive_exactly(sock, 2, "version", RESPONSE_OK)
|
_, 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}")
|
raise OTAError(f"Unsupported OTA version {version}")
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
@ -279,6 +282,8 @@ def perform_ota(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sock.sendall(chunk)
|
sock.sendall(chunk)
|
||||||
|
if version >= OTA_VERSION_2_0:
|
||||||
|
receive_exactly(sock, 1, "chunk OK", RESPONSE_CHUNK_OK)
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
sys.stderr.write("\n")
|
sys.stderr.write("\n")
|
||||||
raise OTAError(f"Error sending data: {err}") from err
|
raise OTAError(f"Error sending data: {err}") from err
|
||||||
|
|
|
@ -3,6 +3,7 @@ from contextlib import suppress
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Union
|
from typing import Union
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -11,6 +12,10 @@ import re
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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):
|
def ensure_unique_string(preferred_string, current_strings):
|
||||||
test_string = preferred_string
|
test_string = preferred_string
|
||||||
|
|
|
@ -388,3 +388,4 @@ lib_deps =
|
||||||
build_flags =
|
build_flags =
|
||||||
${common.build_flags}
|
${common.build_flags}
|
||||||
-DUSE_HOST
|
-DUSE_HOST
|
||||||
|
-std=c++17
|
||||||
|
|
|
@ -8,7 +8,7 @@ tornado==6.4
|
||||||
tzlocal==5.2 # from time
|
tzlocal==5.2 # from time
|
||||||
tzdata>=2021.1 # from time
|
tzdata>=2021.1 # from time
|
||||||
pyserial==3.5
|
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
|
esptool==4.7.0
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
esphome-dashboard==20231107.0
|
esphome-dashboard==20231107.0
|
||||||
|
|
|
@ -12,3 +12,4 @@ script/lint-cpp
|
||||||
script/unit_test
|
script/unit_test
|
||||||
script/component_test
|
script/component_test
|
||||||
script/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
|
oversampling: 8x
|
||||||
update_interval: 15s
|
update_interval: 15s
|
||||||
i2c_id: i2c_bus
|
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
|
- platform: honeywellabp
|
||||||
pressure:
|
pressure:
|
||||||
name: Honeywell pressure
|
name: Honeywell pressure
|
||||||
|
@ -964,7 +971,8 @@ sensor:
|
||||||
name: Internal Ttemperature
|
name: Internal Ttemperature
|
||||||
update_interval: 15s
|
update_interval: 15s
|
||||||
i2c_id: i2c_bus
|
i2c_id: i2c_bus
|
||||||
- platform: kalman_combinator
|
- platform: combination
|
||||||
|
type: kalman
|
||||||
name: Kalman-filtered temperature
|
name: Kalman-filtered temperature
|
||||||
process_std_dev: 0.00139
|
process_std_dev: 0.00139
|
||||||
sources:
|
sources:
|
||||||
|
@ -973,6 +981,57 @@ sensor:
|
||||||
return 0.4 + std::abs(x - 25) * 0.023;
|
return 0.4 + std::abs(x - 25) * 0.023;
|
||||||
- source: scd4x_temperature
|
- source: scd4x_temperature
|
||||||
error: 1.5
|
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
|
- platform: htu21d
|
||||||
temperature:
|
temperature:
|
||||||
name: Living Room Temperature 6
|
name: Living Room Temperature 6
|
||||||
|
|
|
@ -49,6 +49,7 @@ spi:
|
||||||
number: GPIO14
|
number: GPIO14
|
||||||
|
|
||||||
ota:
|
ota:
|
||||||
|
version: 2
|
||||||
|
|
||||||
logger:
|
logger:
|
||||||
|
|
||||||
|
|
|
@ -693,7 +693,6 @@ display:
|
||||||
greyscale: false
|
greyscale: false
|
||||||
partial_updating: false
|
partial_updating: false
|
||||||
update_interval: 60s
|
update_interval: 60s
|
||||||
|
|
||||||
display_data_1_pin:
|
display_data_1_pin:
|
||||||
number: GPIO5
|
number: GPIO5
|
||||||
allow_other_uses: true
|
allow_other_uses: true
|
||||||
|
@ -742,6 +741,24 @@ display:
|
||||||
vcom_pin:
|
vcom_pin:
|
||||||
number: GPIO1
|
number: GPIO1
|
||||||
allow_other_uses: true
|
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:
|
number:
|
||||||
- platform: tuya
|
- platform: tuya
|
||||||
|
|
|
@ -28,6 +28,15 @@ spi:
|
||||||
allow_other_uses: false
|
allow_other_uses: false
|
||||||
mosi_pin: GPIO6
|
mosi_pin: GPIO6
|
||||||
interface: any
|
interface: any
|
||||||
|
- id: quad_spi
|
||||||
|
clk_pin: 47
|
||||||
|
data_pins:
|
||||||
|
-
|
||||||
|
number: 40
|
||||||
|
allow_other_uses: false
|
||||||
|
- 41
|
||||||
|
- 42
|
||||||
|
- 43
|
||||||
|
|
||||||
spi_device:
|
spi_device:
|
||||||
id: spidev
|
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