mirror of
https://github.com/esphome/esphome.git
synced 2024-11-27 17:27:59 +01:00
Merge branch 'dev' into patch-1
This commit is contained in:
commit
d178b3c229
501 changed files with 11924 additions and 596 deletions
61
.github/workflows/ci.yml
vendored
61
.github/workflows/ci.yml
vendored
|
@ -45,7 +45,7 @@ jobs:
|
|||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.3.2
|
||||
uses: actions/cache@v4.0.0
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
|
@ -365,7 +365,7 @@ jobs:
|
|||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Cache platformio
|
||||
uses: actions/cache@v3.3.2
|
||||
uses: actions/cache@v4.0.0
|
||||
with:
|
||||
path: ~/.platformio
|
||||
# yamllint disable-line rule:line-length
|
||||
|
@ -392,6 +392,62 @@ jobs:
|
|||
# yamllint disable-line rule:line-length
|
||||
if: always()
|
||||
|
||||
list-components:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- common
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
|
||||
fetch-depth: 500
|
||||
- name: Fetch dev branch
|
||||
run: |
|
||||
git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +refs/heads/dev*:refs/remotes/origin/dev* +refs/tags/dev*:refs/tags/dev*
|
||||
git merge-base refs/remotes/origin/dev HEAD
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Find changed components
|
||||
id: set-matrix
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
echo "matrix=$(script/list-components.py --changed | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
|
||||
|
||||
test-build-components:
|
||||
name: Component test ${{ matrix.file }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- common
|
||||
- list-components
|
||||
if: ${{ needs.list-components.outputs.matrix != '[]' && needs.list-components.outputs.matrix != '' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 2
|
||||
matrix:
|
||||
file: ${{ fromJson(needs.list-components.outputs.matrix) }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: test_build_components -e config -c ${{ matrix.file }}
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
./script/test_build_components -e config -c ${{ matrix.file }}
|
||||
- name: test_build_components -e compile -c ${{ matrix.file }}
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
./script/test_build_components -e compile -c ${{ matrix.file }}
|
||||
|
||||
ci-status:
|
||||
name: CI Status
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -406,6 +462,7 @@ jobs:
|
|||
- pyupgrade
|
||||
- compile-tests
|
||||
- clang-tidy
|
||||
- test-build-components
|
||||
if: always()
|
||||
steps:
|
||||
- name: Success
|
||||
|
|
|
@ -71,6 +71,7 @@ esphome/components/cd74hc4067/* @asoehlke
|
|||
esphome/components/climate/* @esphome/core
|
||||
esphome/components/climate_ir/* @glmnet
|
||||
esphome/components/color_temperature/* @jesserockz
|
||||
esphome/components/combination/* @Cat-Ion @kahrendt
|
||||
esphome/components/coolix/* @glmnet
|
||||
esphome/components/copy/* @OttoWinter
|
||||
esphome/components/cover/* @esphome/core
|
||||
|
@ -137,6 +138,7 @@ esphome/components/heatpumpir/* @rob-deutsch
|
|||
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||
esphome/components/hm3301/* @freekode
|
||||
esphome/components/homeassistant/* @OttoWinter
|
||||
esphome/components/honeywell_hih_i2c/* @Benichou34
|
||||
esphome/components/honeywellabp/* @RubyBailey
|
||||
esphome/components/honeywellabp2_i2c/* @jpfaff
|
||||
esphome/components/host/* @esphome/core
|
||||
|
@ -160,7 +162,6 @@ esphome/components/integration/* @OttoWinter
|
|||
esphome/components/internal_temperature/* @Mat931
|
||||
esphome/components/interval/* @esphome/core
|
||||
esphome/components/json/* @OttoWinter
|
||||
esphome/components/kalman_combinator/* @Cat-Ion
|
||||
esphome/components/key_collector/* @ssieb
|
||||
esphome/components/key_provider/* @ssieb
|
||||
esphome/components/kuntze/* @ssieb
|
||||
|
@ -366,6 +367,7 @@ esphome/components/veml3235/* @kbx81
|
|||
esphome/components/version/* @esphome/core
|
||||
esphome/components/voice_assistant/* @jesserockz
|
||||
esphome/components/wake_on_lan/* @willwill2will54
|
||||
esphome/components/waveshare_epaper/* @clydebarrow
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/web_server_idf/* @dentra
|
||||
esphome/components/whirlpool/* @glmnet
|
||||
|
|
|
@ -81,7 +81,7 @@ RUN \
|
|||
fi; \
|
||||
pip3 install \
|
||||
--break-system-packages --no-cache-dir \
|
||||
platformio==6.1.11 \
|
||||
platformio==6.1.13 \
|
||||
# Change some platformio settings
|
||||
&& platformio settings set enable_telemetry No \
|
||||
&& platformio settings set check_platformio_interval 1000000 \
|
||||
|
|
|
@ -242,7 +242,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
|||
this->set_notify_(true);
|
||||
|
||||
#ifdef USE_TIME
|
||||
if (this->time_id_.has_value()) {
|
||||
if (this->time_id_ != nullptr) {
|
||||
this->send_local_time();
|
||||
}
|
||||
#endif
|
||||
|
@ -441,9 +441,8 @@ uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) {
|
|||
|
||||
#ifdef USE_TIME
|
||||
void BedJetHub::send_local_time() {
|
||||
if (this->time_id_.has_value()) {
|
||||
auto *time_id = *this->time_id_;
|
||||
ESPTime now = time_id->now();
|
||||
if (this->time_id_ != nullptr) {
|
||||
ESPTime now = this->time_id_->now();
|
||||
if (now.is_valid()) {
|
||||
this->set_clock(now.hour, now.minute);
|
||||
ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute);
|
||||
|
@ -454,10 +453,9 @@ void BedJetHub::send_local_time() {
|
|||
}
|
||||
|
||||
void BedJetHub::setup_time_() {
|
||||
if (this->time_id_.has_value()) {
|
||||
if (this->time_id_ != nullptr) {
|
||||
this->send_local_time();
|
||||
auto *time_id = *this->time_id_;
|
||||
time_id->add_on_time_sync_callback([this] { this->send_local_time(); });
|
||||
this->time_id_->add_on_time_sync_callback([this] { this->send_local_time(); });
|
||||
} else {
|
||||
ESP_LOGI(TAG, "`time_id` is not configured: will not sync BedJet clock.");
|
||||
}
|
||||
|
|
|
@ -141,7 +141,7 @@ class BedJetHub : public esphome::ble_client::BLEClientNode, public PollingCompo
|
|||
#ifdef USE_TIME
|
||||
/** Initializes time sync callbacks to support syncing current time to the BedJet. */
|
||||
void setup_time_();
|
||||
optional<time::RealTimeClock *> time_id_{};
|
||||
time::RealTimeClock *time_id_{nullptr};
|
||||
#endif
|
||||
|
||||
uint32_t timeout_{DEFAULT_STATUS_TIMEOUT};
|
||||
|
|
|
@ -265,8 +265,8 @@ float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) {
|
|||
int32_t const var2 = (((((adc >> 4) - t1) * ((adc >> 4) - t1)) >> 12) * t3) >> 14;
|
||||
*t_fine = var1 + var2;
|
||||
|
||||
float const temperature = (*t_fine * 5 + 128) >> 8;
|
||||
return temperature / 100.0f;
|
||||
float const temperature = (*t_fine * 5 + 128);
|
||||
return temperature / 25600.0f;
|
||||
}
|
||||
|
||||
float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) {
|
||||
|
|
|
@ -200,8 +200,8 @@ float BMP280Component::read_temperature_(int32_t *t_fine) {
|
|||
int32_t var2 = (((((adc >> 4) - t1) * ((adc >> 4) - t1)) >> 12) * t3) >> 14;
|
||||
*t_fine = var1 + var2;
|
||||
|
||||
float temperature = (*t_fine * 5 + 128) >> 8;
|
||||
return temperature / 100.0f;
|
||||
float temperature = (*t_fine * 5 + 128);
|
||||
return temperature / 25600.0f;
|
||||
}
|
||||
|
||||
float BMP280Component::read_pressure_(int32_t t_fine) {
|
||||
|
|
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))
|
|
@ -1,6 +1,8 @@
|
|||
#include "cse7766.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cinttypes>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
namespace esphome {
|
||||
namespace cse7766 {
|
||||
|
@ -68,20 +70,26 @@ bool CSE7766Component::check_byte_() {
|
|||
return true;
|
||||
}
|
||||
void CSE7766Component::parse_data_() {
|
||||
ESP_LOGVV(TAG, "CSE7766 Data: ");
|
||||
for (uint8_t i = 0; i < 23; i++) {
|
||||
ESP_LOGVV(TAG, " %u: 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", i + 1, BYTE_TO_BINARY(this->raw_data_[i]),
|
||||
this->raw_data_[i]);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Raw data:" << std::hex << std::uppercase << std::setfill('0');
|
||||
for (uint8_t i = 0; i < 23; i++) {
|
||||
ss << ' ' << std::setw(2) << static_cast<unsigned>(this->raw_data_[i]);
|
||||
}
|
||||
ESP_LOGVV(TAG, "%s", ss.str().c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
// Parse header
|
||||
uint8_t header1 = this->raw_data_[0];
|
||||
|
||||
if (header1 == 0xAA) {
|
||||
ESP_LOGE(TAG, "CSE7766 not calibrated!");
|
||||
return;
|
||||
}
|
||||
|
||||
bool power_cycle_exceeds_range = false;
|
||||
|
||||
if ((header1 & 0xF0) == 0xF0) {
|
||||
if (header1 & 0xD) {
|
||||
ESP_LOGE(TAG, "CSE7766 reports abnormal external circuit or chip damage: (0x%02X)", header1);
|
||||
|
@ -94,74 +102,106 @@ void CSE7766Component::parse_data_() {
|
|||
if (header1 & (1 << 0)) {
|
||||
ESP_LOGE(TAG, " Coefficient storage area is abnormal.");
|
||||
}
|
||||
|
||||
// Datasheet: voltage or current cycle exceeding range means invalid values
|
||||
return;
|
||||
}
|
||||
|
||||
power_cycle_exceeds_range = header1 & (1 << 1);
|
||||
}
|
||||
|
||||
uint32_t voltage_calib = this->get_24_bit_uint_(2);
|
||||
// Parse data frame
|
||||
uint32_t voltage_coeff = this->get_24_bit_uint_(2);
|
||||
uint32_t voltage_cycle = this->get_24_bit_uint_(5);
|
||||
uint32_t current_calib = this->get_24_bit_uint_(8);
|
||||
uint32_t current_coeff = this->get_24_bit_uint_(8);
|
||||
uint32_t current_cycle = this->get_24_bit_uint_(11);
|
||||
uint32_t power_calib = this->get_24_bit_uint_(14);
|
||||
uint32_t power_coeff = this->get_24_bit_uint_(14);
|
||||
uint32_t power_cycle = this->get_24_bit_uint_(17);
|
||||
|
||||
uint8_t adj = this->raw_data_[20];
|
||||
uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22];
|
||||
|
||||
bool have_power = adj & 0x10;
|
||||
bool have_current = adj & 0x20;
|
||||
bool have_voltage = adj & 0x40;
|
||||
|
||||
float voltage = 0.0f;
|
||||
if (have_voltage) {
|
||||
// voltage cycle of serial port outputted is a complete cycle;
|
||||
float voltage = voltage_calib / float(voltage_cycle);
|
||||
if (this->voltage_sensor_ != nullptr)
|
||||
voltage = voltage_coeff / float(voltage_cycle);
|
||||
if (this->voltage_sensor_ != nullptr) {
|
||||
this->voltage_sensor_->publish_state(voltage);
|
||||
}
|
||||
}
|
||||
|
||||
bool have_power = adj & 0x10;
|
||||
float power = 0.0f;
|
||||
|
||||
if (have_power) {
|
||||
// power cycle of serial port outputted is a complete cycle;
|
||||
// According to the user manual, power cycle exceeding range means the measured power is 0
|
||||
if (!power_cycle_exceeds_range) {
|
||||
power = power_calib / float(power_cycle);
|
||||
float energy = 0.0f;
|
||||
if (power_cycle_exceeds_range) {
|
||||
// Datasheet: power cycle exceeding range means active power is 0
|
||||
if (this->power_sensor_ != nullptr) {
|
||||
this->power_sensor_->publish_state(0.0f);
|
||||
}
|
||||
if (this->power_sensor_ != nullptr)
|
||||
} else if (have_power) {
|
||||
power = power_coeff / float(power_cycle);
|
||||
if (this->power_sensor_ != nullptr) {
|
||||
this->power_sensor_->publish_state(power);
|
||||
}
|
||||
|
||||
// Add CF pulses to the total energy only if we have Power coefficient to multiply by
|
||||
|
||||
uint32_t difference;
|
||||
if (this->cf_pulses_last_ == 0) {
|
||||
this->cf_pulses_last_ = cf_pulses;
|
||||
}
|
||||
|
||||
uint32_t cf_diff;
|
||||
if (cf_pulses < this->cf_pulses_last_) {
|
||||
difference = cf_pulses + (0x10000 - this->cf_pulses_last_);
|
||||
cf_diff = cf_pulses + (0x10000 - this->cf_pulses_last_);
|
||||
} else {
|
||||
difference = cf_pulses - this->cf_pulses_last_;
|
||||
cf_diff = cf_pulses - this->cf_pulses_last_;
|
||||
}
|
||||
this->cf_pulses_last_ = cf_pulses;
|
||||
this->energy_total_ += difference * float(power_calib) / 1000000.0f / 3600.0f;
|
||||
|
||||
energy = cf_diff * float(power_coeff) / 1000000.0f / 3600.0f;
|
||||
this->energy_total_ += energy;
|
||||
if (this->energy_sensor_ != nullptr)
|
||||
this->energy_sensor_->publish_state(this->energy_total_);
|
||||
} else if ((this->energy_sensor_ != nullptr) && !this->energy_sensor_->has_state()) {
|
||||
this->energy_sensor_->publish_state(0);
|
||||
}
|
||||
|
||||
if (adj & 0x20) {
|
||||
// indicates current cycle of serial port outputted is a complete cycle;
|
||||
float current = 0.0f;
|
||||
if (have_voltage && !have_power) {
|
||||
// Testing has shown that when we have voltage and current but not power, that means the power is 0.
|
||||
// We report a power of 0, which in turn means we should report a current of 0.
|
||||
if (this->power_sensor_ != nullptr)
|
||||
this->power_sensor_->publish_state(0);
|
||||
} else if (power != 0.0f) {
|
||||
current = current_calib / float(current_cycle);
|
||||
float current = 0.0f;
|
||||
float calculated_current = 0.0f;
|
||||
if (have_current) {
|
||||
// Assumption: if we don't have power measurement, then current is likely below 50mA
|
||||
if (have_power && voltage > 1.0f) {
|
||||
calculated_current = power / voltage;
|
||||
}
|
||||
if (this->current_sensor_ != nullptr)
|
||||
// Datasheet: minimum measured current is 50mA
|
||||
if (calculated_current > 0.05f) {
|
||||
current = current_coeff / float(current_cycle);
|
||||
}
|
||||
if (this->current_sensor_ != nullptr) {
|
||||
this->current_sensor_->publish_state(current);
|
||||
}
|
||||
}
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Parsed:";
|
||||
if (have_voltage) {
|
||||
ss << " V=" << voltage << "V";
|
||||
}
|
||||
if (have_current) {
|
||||
ss << " I=" << current * 1000.0f << "mA (~" << calculated_current * 1000.0f << "mA)";
|
||||
}
|
||||
if (have_power) {
|
||||
ss << " P=" << power << "W";
|
||||
}
|
||||
if (energy != 0.0f) {
|
||||
ss << " E=" << energy << "kWh (" << cf_pulses << ")";
|
||||
}
|
||||
ESP_LOGVV(TAG, "%s", ss.str().c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t CSE7766Component::get_24_bit_uint_(uint8_t start_index) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome import core
|
||||
from esphome.automation import maybe_simple_id
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.components import uart
|
||||
|
@ -101,7 +100,7 @@ def range_segment_list(input):
|
|||
|
||||
largest_distance = -1
|
||||
for distance in input:
|
||||
if isinstance(distance, core.Lambda):
|
||||
if isinstance(distance, cv.Lambda):
|
||||
continue
|
||||
m = cv.distance(distance)
|
||||
if m > 9:
|
||||
|
@ -128,14 +127,14 @@ MMWAVE_SETTINGS_SCHEMA = cv.Schema(
|
|||
cv.Optional(CONF_OUTPUT_LATENCY): {
|
||||
cv.Required(CONF_DELAY_AFTER_DETECT): cv.templatable(
|
||||
cv.All(
|
||||
cv.positive_time_period,
|
||||
cv.Range(max=core.TimePeriod(seconds=1638.375)),
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(max=cv.TimePeriod(seconds=1638.375)),
|
||||
)
|
||||
),
|
||||
cv.Required(CONF_DELAY_AFTER_DISAPPEAR): cv.templatable(
|
||||
cv.All(
|
||||
cv.positive_time_period,
|
||||
cv.Range(max=core.TimePeriod(seconds=1638.375)),
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(max=cv.TimePeriod(seconds=1638.375)),
|
||||
)
|
||||
),
|
||||
},
|
||||
|
|
|
@ -50,7 +50,7 @@ class DfrobotSen0395SettingsAction : public Action<Ts...>, public Parented<Dfrob
|
|||
float detect = this->delay_after_detect_.value(x...);
|
||||
float disappear = this->delay_after_disappear_.value(x...);
|
||||
if (detect >= 0 && disappear >= 0) {
|
||||
this->parent_->enqueue(make_unique<OutputLatencyCommand>(detect, disappear));
|
||||
this->parent_->enqueue(make_unique<SetLatencyCommand>(detect, disappear));
|
||||
}
|
||||
}
|
||||
if (this->start_after_power_on_.has_value()) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "commands.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "dfrobot_sen0395.h"
|
||||
|
@ -194,32 +196,22 @@ uint8_t DetRangeCfgCommand::on_message(std::string &message) {
|
|||
return 0; // Command not done yet.
|
||||
}
|
||||
|
||||
OutputLatencyCommand::OutputLatencyCommand(float delay_after_detection, float delay_after_disappear) {
|
||||
delay_after_detection = round(delay_after_detection / 0.025) * 0.025;
|
||||
delay_after_disappear = round(delay_after_disappear / 0.025) * 0.025;
|
||||
if (delay_after_detection < 0)
|
||||
delay_after_detection = 0;
|
||||
if (delay_after_detection > 1638.375)
|
||||
delay_after_detection = 1638.375;
|
||||
if (delay_after_disappear < 0)
|
||||
delay_after_disappear = 0;
|
||||
if (delay_after_disappear > 1638.375)
|
||||
delay_after_disappear = 1638.375;
|
||||
|
||||
this->delay_after_detection_ = delay_after_detection;
|
||||
this->delay_after_disappear_ = delay_after_disappear;
|
||||
|
||||
this->cmd_ = str_sprintf("outputLatency -1 %.0f %.0f", delay_after_detection / 0.025, delay_after_disappear / 0.025);
|
||||
SetLatencyCommand::SetLatencyCommand(float delay_after_detection, float delay_after_disappear) {
|
||||
delay_after_detection = std::round(delay_after_detection / 0.025f) * 0.025f;
|
||||
delay_after_disappear = std::round(delay_after_disappear / 0.025f) * 0.025f;
|
||||
this->delay_after_detection_ = clamp(delay_after_detection, 0.0f, 1638.375f);
|
||||
this->delay_after_disappear_ = clamp(delay_after_disappear, 0.0f, 1638.375f);
|
||||
this->cmd_ = str_sprintf("setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_);
|
||||
};
|
||||
|
||||
uint8_t OutputLatencyCommand::on_message(std::string &message) {
|
||||
uint8_t SetLatencyCommand::on_message(std::string &message) {
|
||||
if (message == "sensor is not stopped") {
|
||||
ESP_LOGE(TAG, "Cannot configure output latency. Sensor is not stopped!");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG, "Updated output latency config:");
|
||||
ESP_LOGI(TAG, "Signal that someone was detected is delayed by %.02fs.", this->delay_after_detection_);
|
||||
ESP_LOGI(TAG, "Signal that nobody is detected anymore is delayed by %.02fs.", this->delay_after_disappear_);
|
||||
ESP_LOGI(TAG, "Signal that someone was detected is delayed by %.03f s.", this->delay_after_detection_);
|
||||
ESP_LOGI(TAG, "Signal that nobody is detected anymore is delayed by %.03f s.", this->delay_after_disappear_);
|
||||
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
|
||||
return 1; // Command done
|
||||
}
|
||||
|
|
|
@ -62,9 +62,9 @@ class DetRangeCfgCommand : public Command {
|
|||
// TODO: Set min max values in component, so they can be published as sensor.
|
||||
};
|
||||
|
||||
class OutputLatencyCommand : public Command {
|
||||
class SetLatencyCommand : public Command {
|
||||
public:
|
||||
OutputLatencyCommand(float delay_after_detection, float delay_after_disappear);
|
||||
SetLatencyCommand(float delay_after_detection, float delay_after_disappear);
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -91,7 +91,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
|||
delayMicroseconds(40);
|
||||
} else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
|
||||
delayMicroseconds(2000);
|
||||
} else if (this->model_ == DHT_MODEL_AM2302) {
|
||||
} else if (this->model_ == DHT_MODEL_AM2120 || this->model_ == DHT_MODEL_AM2302) {
|
||||
delayMicroseconds(1000);
|
||||
} else {
|
||||
delayMicroseconds(800);
|
||||
|
|
|
@ -11,6 +11,7 @@ enum DHTModel {
|
|||
DHT_MODEL_AUTO_DETECT = 0,
|
||||
DHT_MODEL_DHT11,
|
||||
DHT_MODEL_DHT22,
|
||||
DHT_MODEL_AM2120,
|
||||
DHT_MODEL_AM2302,
|
||||
DHT_MODEL_RHT03,
|
||||
DHT_MODEL_SI7021,
|
||||
|
@ -27,6 +28,7 @@ class DHT : public PollingComponent {
|
|||
* - DHT_MODEL_AUTO_DETECT (default)
|
||||
* - DHT_MODEL_DHT11
|
||||
* - DHT_MODEL_DHT22
|
||||
* - DHT_MODEL_AM2120
|
||||
* - DHT_MODEL_AM2302
|
||||
* - DHT_MODEL_RHT03
|
||||
* - DHT_MODEL_SI7021
|
||||
|
|
|
@ -23,6 +23,7 @@ DHT_MODELS = {
|
|||
"AUTO_DETECT": DHTModel.DHT_MODEL_AUTO_DETECT,
|
||||
"DHT11": DHTModel.DHT_MODEL_DHT11,
|
||||
"DHT22": DHTModel.DHT_MODEL_DHT22,
|
||||
"AM2120": DHTModel.DHT_MODEL_AM2120,
|
||||
"AM2302": DHTModel.DHT_MODEL_AM2302,
|
||||
"RHT03": DHTModel.DHT_MODEL_RHT03,
|
||||
"SI7021": DHTModel.DHT_MODEL_SI7021,
|
||||
|
|
|
@ -142,9 +142,9 @@ void Display::filled_circle(int center_x, int center_y, int radius, Color color)
|
|||
} while (dx <= 0);
|
||||
}
|
||||
void HOT Display::triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
|
||||
this->line(x1, y1, x2, y2);
|
||||
this->line(x1, y1, x3, y3);
|
||||
this->line(x2, y2, x3, y3);
|
||||
this->line(x1, y1, x2, y2, color);
|
||||
this->line(x1, y1, x3, y3, color);
|
||||
this->line(x2, y2, x3, y3, color);
|
||||
}
|
||||
void Display::sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3) {
|
||||
if (*y1 > *y2) {
|
||||
|
|
|
@ -161,10 +161,12 @@ light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index
|
|||
break;
|
||||
}
|
||||
uint8_t multiplier = this->is_rgbw_ ? 4 : 3;
|
||||
return {this->buf_ + (index * multiplier) + r,
|
||||
this->buf_ + (index * multiplier) + g,
|
||||
this->buf_ + (index * multiplier) + b,
|
||||
this->is_rgbw_ ? this->buf_ + (index * multiplier) + 3 : nullptr,
|
||||
uint8_t white = this->is_wrgb_ ? 0 : 3;
|
||||
|
||||
return {this->buf_ + (index * multiplier) + r + this->is_wrgb_,
|
||||
this->buf_ + (index * multiplier) + g + this->is_wrgb_,
|
||||
this->buf_ + (index * multiplier) + b + this->is_wrgb_,
|
||||
this->is_rgbw_ || this->is_wrgb_ ? this->buf_ + (index * multiplier) + white : nullptr,
|
||||
&this->effect_data_[index],
|
||||
&this->correction_};
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
|||
int32_t size() const override { return this->num_leds_; }
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
if (this->is_rgbw_) {
|
||||
if (this->is_rgbw_ || this->is_wrgb_) {
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE});
|
||||
} else {
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB});
|
||||
|
@ -44,6 +44,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
|||
void set_pin(uint8_t pin) { this->pin_ = pin; }
|
||||
void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
|
||||
void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; }
|
||||
void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; }
|
||||
|
||||
/// Set a maximum refresh rate in µs as some lights do not like being updated too often.
|
||||
void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; }
|
||||
|
@ -72,6 +73,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
|||
uint8_t pin_;
|
||||
uint16_t num_leds_;
|
||||
bool is_rgbw_;
|
||||
bool is_wrgb_;
|
||||
|
||||
rmt_item32_t bit0_, bit1_;
|
||||
RGBOrder rgb_order_;
|
||||
|
|
|
@ -52,6 +52,7 @@ CHIPSETS = {
|
|||
|
||||
|
||||
CONF_IS_RGBW = "is_rgbw"
|
||||
CONF_IS_WRGB = "is_wrgb"
|
||||
CONF_BIT0_HIGH = "bit0_high"
|
||||
CONF_BIT0_LOW = "bit0_low"
|
||||
CONF_BIT1_HIGH = "bit1_high"
|
||||
|
@ -90,6 +91,7 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
|
||||
cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
|
||||
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
|
||||
cv.Optional(CONF_IS_WRGB, default=False): cv.boolean,
|
||||
cv.Inclusive(
|
||||
CONF_BIT0_HIGH,
|
||||
"custom",
|
||||
|
@ -145,6 +147,7 @@ async def to_code(config):
|
|||
|
||||
cg.add(var.set_rgb_order(config[CONF_RGB_ORDER]))
|
||||
cg.add(var.set_is_rgbw(config[CONF_IS_RGBW]))
|
||||
cg.add(var.set_is_wrgb(config[CONF_IS_WRGB]))
|
||||
|
||||
cg.add(
|
||||
var.set_rmt_channel(
|
||||
|
|
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))
|
|
@ -4,8 +4,10 @@ from esphome.const import (
|
|||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_HOST,
|
||||
CONF_MAC_ADDRESS,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.helpers import IS_MACOS
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
|
@ -14,7 +16,6 @@ from .const import KEY_HOST
|
|||
# force import gpio to register pin schema
|
||||
from .gpio import host_pin_to_code # noqa
|
||||
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
AUTO_LOAD = ["network"]
|
||||
|
||||
|
@ -28,12 +29,21 @@ def set_core_data(config):
|
|||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema({}),
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_MAC_ADDRESS, default="98:35:69:ab:f6:79"): cv.mac_address,
|
||||
}
|
||||
),
|
||||
set_core_data,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_build_flag("-DUSE_HOST")
|
||||
cg.add_define("USE_ESPHOME_HOST_MAC_ADDRESS", config[CONF_MAC_ADDRESS].parts)
|
||||
cg.add_build_flag("-std=c++17")
|
||||
cg.add_build_flag("-lsodium")
|
||||
if IS_MACOS:
|
||||
cg.add_build_flag("-L/opt/homebrew/lib")
|
||||
cg.add_define("ESPHOME_BOARD", "host")
|
||||
cg.add_platformio_option("platform", "platformio/native")
|
||||
|
|
|
@ -66,6 +66,7 @@ MODELS = {
|
|||
"ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay),
|
||||
"S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay),
|
||||
"S3BOX_LITE": ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay),
|
||||
"WAVESHARE_RES_3_5": ili9xxx_ns.class_("WAVESHARERES35", ILI9XXXDisplay),
|
||||
}
|
||||
|
||||
COLOR_ORDERS = {
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
namespace esphome {
|
||||
namespace ili9xxx {
|
||||
|
||||
static const char *const TAG = "ili9xxx";
|
||||
static const uint16_t SPI_SETUP_US = 100; // estimated fixed overhead in microseconds for an SPI write
|
||||
static const uint16_t SPI_MAX_BLOCK_SIZE = 4092; // Max size of continuous SPI transfer
|
||||
|
||||
|
@ -17,13 +16,7 @@ static inline void put16_be(uint8_t *buf, uint16_t value) {
|
|||
buf[1] = value;
|
||||
}
|
||||
|
||||
void ILI9XXXDisplay::setup() {
|
||||
ESP_LOGD(TAG, "Setting up ILI9xxx");
|
||||
|
||||
this->setup_pins_();
|
||||
this->init_lcd_();
|
||||
|
||||
this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF);
|
||||
void ILI9XXXDisplay::set_madctl() {
|
||||
// custom x/y transform and color order
|
||||
uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB;
|
||||
if (this->swap_xy_)
|
||||
|
@ -32,8 +25,19 @@ void ILI9XXXDisplay::setup() {
|
|||
mad |= MADCTL_MX;
|
||||
if (this->mirror_y_)
|
||||
mad |= MADCTL_MY;
|
||||
this->send_command(ILI9XXX_MADCTL, &mad, 1);
|
||||
this->command(ILI9XXX_MADCTL);
|
||||
this->data(mad);
|
||||
esph_log_d(TAG, "Wrote MADCTL 0x%02X", mad);
|
||||
}
|
||||
|
||||
void ILI9XXXDisplay::setup() {
|
||||
ESP_LOGD(TAG, "Setting up ILI9xxx");
|
||||
|
||||
this->setup_pins_();
|
||||
this->init_lcd_();
|
||||
|
||||
this->set_madctl();
|
||||
this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF);
|
||||
this->x_low_ = this->width_;
|
||||
this->y_low_ = this->height_;
|
||||
this->x_high_ = 0;
|
||||
|
@ -89,6 +93,7 @@ void ILI9XXXDisplay::dump_config() {
|
|||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Color order: %s", this->color_order_ == display::COLOR_ORDER_BGR ? "BGR" : "RGB");
|
||||
ESP_LOGCONFIG(TAG, " Swap_xy: %s", YESNO(this->swap_xy_));
|
||||
ESP_LOGCONFIG(TAG, " Mirror_x: %s", YESNO(this->mirror_x_));
|
||||
ESP_LOGCONFIG(TAG, " Mirror_y: %s", YESNO(this->mirror_y_));
|
||||
|
@ -196,7 +201,6 @@ void ILI9XXXDisplay::display_() {
|
|||
uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE];
|
||||
// check if something was displayed
|
||||
if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) {
|
||||
ESP_LOGV(TAG, "Nothing to display");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -211,14 +215,13 @@ void ILI9XXXDisplay::display_() {
|
|||
size_t mw_time = (w * h * 16) / mhz + w * h * 2 / ILI9XXX_TRANSFER_BUFFER_SIZE * SPI_SETUP_US;
|
||||
ESP_LOGV(TAG,
|
||||
"Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, "
|
||||
"height:%d, mode=%d, 18bit=%d, sw_time=%dus, mw_time=%dus)",
|
||||
"height:%zu, mode=%d, 18bit=%d, sw_time=%zuus, mw_time=%zuus)",
|
||||
this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, this->buffer_color_mode_,
|
||||
this->is_18bitdisplay_, sw_time, mw_time);
|
||||
auto now = millis();
|
||||
this->enable();
|
||||
if (this->buffer_color_mode_ == BITS_16 && !this->is_18bitdisplay_ && sw_time < mw_time) {
|
||||
// 16 bit mode maps directly to display format
|
||||
ESP_LOGV(TAG, "Doing single write of %d bytes", this->width_ * h * 2);
|
||||
ESP_LOGV(TAG, "Doing single write of %zu bytes", this->width_ * h * 2);
|
||||
set_addr_window_(0, this->y_low_, this->width_ - 1, this->y_high_);
|
||||
this->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2);
|
||||
} else {
|
||||
|
@ -267,7 +270,7 @@ void ILI9XXXDisplay::display_() {
|
|||
this->write_array(transfer_buffer, idx);
|
||||
}
|
||||
}
|
||||
this->disable();
|
||||
this->end_data_();
|
||||
ESP_LOGV(TAG, "Data write took %dms", (unsigned) (millis() - now));
|
||||
// invalidate watermarks
|
||||
this->x_low_ = this->width_;
|
||||
|
@ -290,7 +293,6 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons
|
|||
return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
|
||||
x_pad);
|
||||
}
|
||||
this->enable();
|
||||
this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1);
|
||||
// x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
|
||||
if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
|
||||
|
@ -302,7 +304,7 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons
|
|||
this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2);
|
||||
}
|
||||
}
|
||||
this->disable();
|
||||
this->end_data_();
|
||||
}
|
||||
|
||||
// should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color
|
||||
|
@ -328,20 +330,6 @@ void ILI9XXXDisplay::send_command(uint8_t command_byte, const uint8_t *data_byte
|
|||
this->end_data_();
|
||||
}
|
||||
|
||||
uint8_t ILI9XXXDisplay::read_command(uint8_t command_byte, uint8_t index) {
|
||||
uint8_t data = 0x10 + index;
|
||||
this->send_command(0xD9, &data, 1); // Set Index Register
|
||||
uint8_t result;
|
||||
this->start_command_();
|
||||
this->write_byte(command_byte);
|
||||
this->start_data_();
|
||||
do {
|
||||
result = this->read_byte();
|
||||
} while (index--);
|
||||
this->end_data_();
|
||||
return result;
|
||||
}
|
||||
|
||||
void ILI9XXXDisplay::start_command_() {
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->enable();
|
||||
|
@ -357,9 +345,9 @@ void ILI9XXXDisplay::end_data_() { this->disable(); }
|
|||
void ILI9XXXDisplay::reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
delay(20);
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(10);
|
||||
delay(20);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -369,7 +357,7 @@ void ILI9XXXDisplay::init_lcd_() {
|
|||
while ((cmd = *addr++) > 0) {
|
||||
x = *addr++;
|
||||
num_args = x & 0x7F;
|
||||
send_command(cmd, addr, num_args);
|
||||
this->send_command(cmd, addr, num_args);
|
||||
addr += num_args;
|
||||
if (x & 0x80)
|
||||
delay(150); // NOLINT
|
||||
|
@ -377,24 +365,23 @@ void ILI9XXXDisplay::init_lcd_() {
|
|||
}
|
||||
|
||||
// Tell the display controller where we want to draw pixels.
|
||||
// when called, the SPI should have already been enabled, only the D/C pin will be toggled here.
|
||||
void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
|
||||
uint8_t buf[4];
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_byte(ILI9XXX_CASET); // Column address set
|
||||
put16_be(buf, x1 + this->offset_x_);
|
||||
put16_be(buf + 2, x2 + this->offset_x_);
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->write_array(buf, sizeof buf);
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_byte(ILI9XXX_PASET); // Row address set
|
||||
put16_be(buf, y1 + this->offset_y_);
|
||||
put16_be(buf + 2, y2 + this->offset_y_);
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->write_array(buf, sizeof buf);
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_byte(ILI9XXX_RAMWR); // Write to RAM
|
||||
this->dc_pin_->digital_write(true);
|
||||
x1 += this->offset_x_;
|
||||
x2 += this->offset_x_;
|
||||
y1 += this->offset_y_;
|
||||
y2 += this->offset_y_;
|
||||
this->command(ILI9XXX_CASET);
|
||||
this->data(x1 >> 8);
|
||||
this->data(x1 & 0xFF);
|
||||
this->data(x2 >> 8);
|
||||
this->data(x2 & 0xFF);
|
||||
this->command(ILI9XXX_PASET); // Page address set
|
||||
this->data(y1 >> 8);
|
||||
this->data(y1 & 0xFF);
|
||||
this->data(y2 >> 8);
|
||||
this->data(y2 & 0xFF);
|
||||
this->command(ILI9XXX_RAMWR); // Write to RAM
|
||||
this->start_data_();
|
||||
}
|
||||
|
||||
void ILI9XXXDisplay::invert_colors(bool invert) {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
namespace esphome {
|
||||
namespace ili9xxx {
|
||||
|
||||
static const char *const TAG = "ili9xxx";
|
||||
const size_t ILI9XXX_TRANSFER_BUFFER_SIZE = 126; // ensure this is divisible by 6
|
||||
|
||||
enum ILI9XXXColorMode {
|
||||
|
@ -32,6 +33,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
|||
while ((cmd = *addr++) != 0) {
|
||||
num_args = *addr++ & 0x7F;
|
||||
bits = *addr;
|
||||
esph_log_d(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, bits);
|
||||
switch (cmd) {
|
||||
case ILI9XXX_MADCTL: {
|
||||
this->swap_xy_ = (bits & MADCTL_MV) != 0;
|
||||
|
@ -68,10 +70,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
|||
this->offset_y_ = offset_y;
|
||||
}
|
||||
void invert_colors(bool invert);
|
||||
void command(uint8_t value);
|
||||
void data(uint8_t value);
|
||||
virtual void command(uint8_t value);
|
||||
virtual void data(uint8_t value);
|
||||
void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes);
|
||||
uint8_t read_command(uint8_t command_byte, uint8_t index);
|
||||
void set_color_order(display::ColorOrder color_order) { this->color_order_ = color_order; }
|
||||
void set_swap_xy(bool swap_xy) { this->swap_xy_ = swap_xy; }
|
||||
void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; }
|
||||
|
@ -92,6 +93,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
|||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||
void setup_pins_();
|
||||
|
||||
virtual void set_madctl();
|
||||
void display_();
|
||||
void init_lcd_();
|
||||
void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2);
|
||||
|
@ -127,7 +129,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
|||
bool need_update_ = false;
|
||||
bool is_18bitdisplay_ = false;
|
||||
bool pre_invertcolors_ = false;
|
||||
display::ColorOrder color_order_{};
|
||||
display::ColorOrder color_order_{display::COLOR_ORDER_BGR};
|
||||
bool swap_xy_{};
|
||||
bool mirror_x_{};
|
||||
bool mirror_y_{};
|
||||
|
@ -181,10 +183,48 @@ class ILI9XXXILI9486 : public ILI9XXXDisplay {
|
|||
ILI9XXXILI9486() : ILI9XXXDisplay(INITCMD_ILI9486, 480, 320, false) {}
|
||||
};
|
||||
|
||||
//----------- ILI9XXX_35_TFT rotated display --------------
|
||||
class ILI9XXXILI9488 : public ILI9XXXDisplay {
|
||||
public:
|
||||
ILI9XXXILI9488() : ILI9XXXDisplay(INITCMD_ILI9488, 480, 320, true) {}
|
||||
ILI9XXXILI9488(const uint8_t *seq = INITCMD_ILI9488) : ILI9XXXDisplay(seq, 480, 320, true) {}
|
||||
|
||||
protected:
|
||||
void set_madctl() override {
|
||||
uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB;
|
||||
uint8_t dfun = 0x22;
|
||||
this->width_ = 320;
|
||||
this->height_ = 480;
|
||||
if (!(this->swap_xy_ || this->mirror_x_ || this->mirror_y_)) {
|
||||
// no transforms
|
||||
} else if (this->mirror_y_ && this->mirror_x_) {
|
||||
// rotate 180
|
||||
dfun = 0x42;
|
||||
} else if (this->swap_xy_) {
|
||||
this->width_ = 480;
|
||||
this->height_ = 320;
|
||||
mad |= 0x20;
|
||||
if (this->mirror_x_) {
|
||||
dfun = 0x02;
|
||||
} else {
|
||||
dfun = 0x62;
|
||||
}
|
||||
}
|
||||
this->command(ILI9XXX_DFUNCTR);
|
||||
this->data(0);
|
||||
this->data(dfun);
|
||||
this->command(ILI9XXX_MADCTL);
|
||||
this->data(mad);
|
||||
}
|
||||
};
|
||||
//----------- Waveshare 3.5 Res Touch - ILI9488 interfaced via 16 bit shift register to parallel */
|
||||
class WAVESHARERES35 : public ILI9XXXILI9488 {
|
||||
public:
|
||||
WAVESHARERES35() : ILI9XXXILI9488(INITCMD_WAVESHARE_RES_3_5) {}
|
||||
void data(uint8_t value) override {
|
||||
this->start_data_();
|
||||
this->write_byte(0);
|
||||
this->write_byte(value);
|
||||
this->end_data_();
|
||||
}
|
||||
};
|
||||
|
||||
//----------- ILI9XXX_35_TFT origin colors rotated display --------------
|
||||
|
|
|
@ -141,7 +141,8 @@ static const uint8_t PROGMEM INITCMD_ILI9486[] = {
|
|||
0x00 // End of list
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_ILI9488[] = {
|
||||
|
||||
static const uint8_t INITCMD_ILI9488[] = {
|
||||
ILI9XXX_GMCTRP1,15, 0x0f, 0x24, 0x1c, 0x0a, 0x0f, 0x08, 0x43, 0x88, 0x32, 0x0f, 0x10, 0x06, 0x0f, 0x07, 0x00,
|
||||
ILI9XXX_GMCTRN1,15, 0x0F, 0x38, 0x30, 0x09, 0x0f, 0x0f, 0x4e, 0x77, 0x3c, 0x07, 0x10, 0x05, 0x23, 0x1b, 0x00,
|
||||
|
||||
|
@ -153,28 +154,27 @@ static const uint8_t PROGMEM INITCMD_ILI9488[] = {
|
|||
ILI9XXX_FRMCTR1, 1, 0xA0, // Frame rate = 60Hz
|
||||
ILI9XXX_INVCTR, 1, 0x02, // Display Inversion Control = 2dot
|
||||
|
||||
ILI9XXX_DFUNCTR, 2, 0x02, 0x02, // Nomal scan
|
||||
|
||||
0xE9, 1, 0x00, // Set Image Functio. Disable 24 bit data
|
||||
|
||||
ILI9XXX_ADJCTL3, 4, 0xA9, 0x51, 0x2C, 0x82, // Adjust Control 3
|
||||
|
||||
ILI9XXX_MADCTL, 1, 0x28,
|
||||
//ILI9XXX_PIXFMT, 1, 0x55, // Interface Pixel Format = 16bit
|
||||
ILI9XXX_PIXFMT, 1, 0x66, //ILI9488 only supports 18-bit pixel format in 4/3 wire SPI mode
|
||||
|
||||
|
||||
|
||||
// 5 frames
|
||||
//ILI9XXX_ETMOD, 1, 0xC6, //
|
||||
|
||||
|
||||
ILI9XXX_SLPOUT, 0x80, // Exit sleep mode
|
||||
//ILI9XXX_INVON , 0,
|
||||
ILI9XXX_DISPON, 0x80, // Set display on
|
||||
0x00 // end
|
||||
};
|
||||
|
||||
static const uint8_t INITCMD_WAVESHARE_RES_3_5[] = {
|
||||
ILI9XXX_PWCTR3, 1, 0x33,
|
||||
ILI9XXX_VMCTR1, 3, 0x00, 0x1e, 0x80,
|
||||
ILI9XXX_FRMCTR1, 1, 0xA0,
|
||||
ILI9XXX_GMCTRP1, 15, 0x0, 0x13, 0x18, 0x04, 0x0F, 0x06, 0x3a, 0x56, 0x4d, 0x03, 0x0a, 0x06, 0x30, 0x3e, 0x0f,
|
||||
ILI9XXX_GMCTRN1, 15, 0x0, 0x13, 0x18, 0x01, 0x11, 0x06, 0x38, 0x34, 0x4d, 0x06, 0x0d, 0x0b, 0x31, 0x37, 0x0f,
|
||||
ILI9XXX_PIXFMT, 1, 0x55,
|
||||
ILI9XXX_SLPOUT, 0x80, // slpout, delay
|
||||
ILI9XXX_DISPON, 0,
|
||||
0x00 // End of list
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_ILI9488_A[] = {
|
||||
ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F,
|
||||
ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F,
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
CODEOWNERS = ["@Cat-Ion"]
|
|
@ -1,82 +0,0 @@
|
|||
#include "kalman_combinator.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
|
||||
namespace esphome {
|
||||
namespace kalman_combinator {
|
||||
|
||||
void KalmanCombinatorComponent::dump_config() {
|
||||
ESP_LOGCONFIG("kalman_combinator", "Kalman Combinator:");
|
||||
ESP_LOGCONFIG("kalman_combinator", " Update variance: %f per ms", this->update_variance_value_);
|
||||
ESP_LOGCONFIG("kalman_combinator", " Sensors:");
|
||||
for (const auto &sensor : this->sensors_) {
|
||||
auto &entity = *sensor.first;
|
||||
ESP_LOGCONFIG("kalman_combinator", " - %s", entity.get_name().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void KalmanCombinatorComponent::setup() {
|
||||
for (const auto &sensor : this->sensors_) {
|
||||
const auto stddev = sensor.second;
|
||||
sensor.first->add_on_state_callback([this, stddev](float x) -> void { this->correct_(x, stddev(x)); });
|
||||
}
|
||||
}
|
||||
|
||||
void KalmanCombinatorComponent::add_source(Sensor *sensor, std::function<float(float)> const &stddev) {
|
||||
this->sensors_.emplace_back(sensor, stddev);
|
||||
}
|
||||
|
||||
void KalmanCombinatorComponent::add_source(Sensor *sensor, float stddev) {
|
||||
this->add_source(sensor, std::function<float(float)>{[stddev](float x) -> float { return stddev; }});
|
||||
}
|
||||
|
||||
void KalmanCombinatorComponent::update_variance_() {
|
||||
uint32_t now = millis();
|
||||
|
||||
// Variance increases by update_variance_ each millisecond
|
||||
auto dt = now - this->last_update_;
|
||||
auto dv = this->update_variance_value_ * dt;
|
||||
this->variance_ += dv;
|
||||
this->last_update_ = now;
|
||||
}
|
||||
|
||||
void KalmanCombinatorComponent::correct_(float value, float stddev) {
|
||||
if (std::isnan(value) || std::isinf(stddev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::isnan(this->state_) || std::isinf(this->variance_)) {
|
||||
this->state_ = value;
|
||||
this->variance_ = stddev * stddev;
|
||||
if (this->std_dev_sensor_ != nullptr) {
|
||||
this->std_dev_sensor_->publish_state(stddev);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this->update_variance_();
|
||||
|
||||
// Combine two gaussian distributions mu1+-var1, mu2+-var2 to a new one around mu
|
||||
// Use the value with the smaller variance as mu1 to prevent precision errors
|
||||
const bool this_first = this->variance_ < (stddev * stddev);
|
||||
const float mu1 = this_first ? this->state_ : value;
|
||||
const float mu2 = this_first ? value : this->state_;
|
||||
|
||||
const float var1 = this_first ? this->variance_ : stddev * stddev;
|
||||
const float var2 = this_first ? stddev * stddev : this->variance_;
|
||||
|
||||
const float mu = mu1 + var1 * (mu2 - mu1) / (var1 + var2);
|
||||
const float var = var1 - (var1 * var1) / (var1 + var2);
|
||||
|
||||
// Update and publish state
|
||||
this->state_ = mu;
|
||||
this->variance_ = var;
|
||||
|
||||
this->publish_state(mu);
|
||||
if (this->std_dev_sensor_ != nullptr) {
|
||||
this->std_dev_sensor_->publish_state(std::sqrt(var));
|
||||
}
|
||||
}
|
||||
} // namespace kalman_combinator
|
||||
} // namespace esphome
|
|
@ -1,46 +0,0 @@
|
|||
#pragma once
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace kalman_combinator {
|
||||
|
||||
class KalmanCombinatorComponent : public Component, public sensor::Sensor {
|
||||
public:
|
||||
KalmanCombinatorComponent() = default;
|
||||
|
||||
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
|
||||
|
||||
void dump_config() override;
|
||||
void setup() override;
|
||||
|
||||
void add_source(Sensor *sensor, std::function<float(float)> const &stddev);
|
||||
void add_source(Sensor *sensor, float stddev);
|
||||
void set_process_std_dev(float process_std_dev) {
|
||||
this->update_variance_value_ = process_std_dev * process_std_dev * 0.001f;
|
||||
}
|
||||
void set_std_dev_sensor(Sensor *sensor) { this->std_dev_sensor_ = sensor; }
|
||||
|
||||
private:
|
||||
void update_variance_();
|
||||
void correct_(float value, float stddev);
|
||||
|
||||
// Source sensors and their error functions
|
||||
std::vector<std::pair<Sensor *, std::function<float(float)>>> sensors_;
|
||||
|
||||
// Optional sensor for publishing the current error
|
||||
sensor::Sensor *std_dev_sensor_{nullptr};
|
||||
|
||||
// Tick of the last update
|
||||
uint32_t last_update_{0};
|
||||
// Change of the variance, per ms
|
||||
float update_variance_value_{0.f};
|
||||
|
||||
// Best guess for the state and its variance
|
||||
float state_{NAN};
|
||||
float variance_{INFINITY};
|
||||
};
|
||||
} // namespace kalman_combinator
|
||||
} // namespace esphome
|
|
@ -1,90 +1,6 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_SOURCE,
|
||||
CONF_ACCURACY_DECIMALS,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_ICON,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
|
||||
CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid(
|
||||
"The kalman_combinator sensor has moved.\nPlease use the combination platform instead with type: kalman.\n"
|
||||
"See https://esphome.io/components/sensor/combination.html"
|
||||
)
|
||||
from esphome.core.entity_helpers import inherit_property_from
|
||||
|
||||
kalman_combinator_ns = cg.esphome_ns.namespace("kalman_combinator")
|
||||
KalmanCombinatorComponent = kalman_combinator_ns.class_(
|
||||
"KalmanCombinatorComponent", cg.Component, sensor.Sensor
|
||||
)
|
||||
|
||||
CONF_ERROR = "error"
|
||||
CONF_SOURCES = "sources"
|
||||
CONF_PROCESS_STD_DEV = "process_std_dev"
|
||||
CONF_STD_DEV = "std_dev"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(KalmanCombinatorComponent)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float,
|
||||
cv.Required(CONF_SOURCES): cv.ensure_list(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_ERROR): cv.templatable(cv.positive_float),
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_STD_DEV): sensor.sensor_schema(),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# Inherit some sensor values from the first source, for both the state and the error value
|
||||
properties_to_inherit = [
|
||||
CONF_ACCURACY_DECIMALS,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_ICON,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
# CONF_STATE_CLASS could also be inherited, but might lead to unexpected behaviour with "total_increasing"
|
||||
]
|
||||
inherit_schema_for_state = [
|
||||
inherit_property_from(property, [CONF_SOURCES, 0, CONF_SOURCE])
|
||||
for property in properties_to_inherit
|
||||
]
|
||||
inherit_schema_for_std_dev = [
|
||||
inherit_property_from([CONF_STD_DEV, property], [CONF_SOURCES, 0, CONF_SOURCE])
|
||||
for property in properties_to_inherit
|
||||
]
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
CONFIG_SCHEMA.extend(
|
||||
{cv.Required(CONF_ID): cv.use_id(KalmanCombinatorComponent)},
|
||||
extra=cv.ALLOW_EXTRA,
|
||||
),
|
||||
*inherit_schema_for_state,
|
||||
*inherit_schema_for_std_dev,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await sensor.register_sensor(var, config)
|
||||
|
||||
cg.add(var.set_process_std_dev(config[CONF_PROCESS_STD_DEV]))
|
||||
for source_conf in config[CONF_SOURCES]:
|
||||
source = await cg.get_variable(source_conf[CONF_SOURCE])
|
||||
error = await cg.templatable(
|
||||
source_conf[CONF_ERROR],
|
||||
[(float, "x")],
|
||||
cg.float_,
|
||||
)
|
||||
cg.add(var.add_source(source, error))
|
||||
|
||||
if CONF_STD_DEV in config:
|
||||
sens = await sensor.new_sensor(config[CONF_STD_DEV])
|
||||
cg.add(var.set_std_dev_sensor(sens))
|
||||
|
|
|
@ -120,6 +120,7 @@ void LightState::loop() {
|
|||
// Apply transformer (if any)
|
||||
if (this->transformer_ != nullptr) {
|
||||
auto values = this->transformer_->apply();
|
||||
this->is_transformer_active_ = true;
|
||||
if (values.has_value()) {
|
||||
this->current_values = *values;
|
||||
this->output_->update_state(this);
|
||||
|
@ -131,6 +132,7 @@ void LightState::loop() {
|
|||
this->current_values = this->transformer_->get_target_values();
|
||||
|
||||
this->transformer_->stop();
|
||||
this->is_transformer_active_ = false;
|
||||
this->transformer_ = nullptr;
|
||||
this->target_state_reached_callback_.call();
|
||||
}
|
||||
|
@ -214,6 +216,8 @@ void LightState::current_values_as_ct(float *color_temperature, float *white_bri
|
|||
this->gamma_correct_);
|
||||
}
|
||||
|
||||
bool LightState::is_transformer_active() { return this->is_transformer_active_; }
|
||||
|
||||
void LightState::start_effect_(uint32_t effect_index) {
|
||||
this->stop_effect_();
|
||||
if (effect_index == 0)
|
||||
|
@ -263,6 +267,7 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b
|
|||
}
|
||||
|
||||
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) {
|
||||
this->is_transformer_active_ = false;
|
||||
this->transformer_ = nullptr;
|
||||
this->current_values = target;
|
||||
if (set_remote_values) {
|
||||
|
|
|
@ -144,6 +144,17 @@ class LightState : public EntityBase, public Component {
|
|||
|
||||
void current_values_as_ct(float *color_temperature, float *white_brightness);
|
||||
|
||||
/**
|
||||
* Indicator if a transformer (e.g. transition) is active. This is useful
|
||||
* for effects e.g. at the start of the apply() method, add a check like:
|
||||
*
|
||||
* if (this->state_->is_transformer_active()) {
|
||||
* // Something is already running.
|
||||
* return;
|
||||
* }
|
||||
*/
|
||||
bool is_transformer_active();
|
||||
|
||||
protected:
|
||||
friend LightOutput;
|
||||
friend LightCall;
|
||||
|
@ -203,6 +214,9 @@ class LightState : public EntityBase, public Component {
|
|||
LightRestoreMode restore_mode_;
|
||||
/// List of effects for this light.
|
||||
std::vector<LightEffect *> effects_;
|
||||
|
||||
// for effects, true if a transformer (transition) is active.
|
||||
bool is_transformer_active_ = false;
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace lightwaverf {
|
||||
|
||||
|
|
|
@ -212,6 +212,14 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
|
|||
return;
|
||||
#endif
|
||||
#ifdef USE_HOST
|
||||
time_t rawtime;
|
||||
struct tm *timeinfo;
|
||||
char buffer[80];
|
||||
|
||||
time(&rawtime);
|
||||
timeinfo = localtime(&rawtime);
|
||||
strftime(buffer, sizeof buffer, "[%H:%M:%S]", timeinfo);
|
||||
fputs(buffer, stdout);
|
||||
puts(msg);
|
||||
#endif
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ from esphome.const import (
|
|||
CONF_BIRTH_MESSAGE,
|
||||
CONF_BROKER,
|
||||
CONF_CERTIFICATE_AUTHORITY,
|
||||
CONF_CLIENT_CERTIFICATE,
|
||||
CONF_CLIENT_CERTIFICATE_KEY,
|
||||
CONF_CLIENT_ID,
|
||||
CONF_COMMAND_TOPIC,
|
||||
CONF_COMMAND_RETAIN,
|
||||
|
@ -199,6 +201,12 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Optional(CONF_CERTIFICATE_AUTHORITY): cv.All(
|
||||
cv.string, cv.only_with_esp_idf
|
||||
),
|
||||
cv.Inclusive(CONF_CLIENT_CERTIFICATE, "cert-key-pair"): cv.All(
|
||||
cv.string, cv.only_on_esp32
|
||||
),
|
||||
cv.Inclusive(CONF_CLIENT_CERTIFICATE_KEY, "cert-key-pair"): cv.All(
|
||||
cv.string, cv.only_on_esp32
|
||||
),
|
||||
cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32_idf=False): cv.All(
|
||||
cv.boolean, cv.only_with_esp_idf
|
||||
),
|
||||
|
@ -378,6 +386,9 @@ async def to_code(config):
|
|||
if CONF_CERTIFICATE_AUTHORITY in config:
|
||||
cg.add(var.set_ca_certificate(config[CONF_CERTIFICATE_AUTHORITY]))
|
||||
cg.add(var.set_skip_cert_cn_check(config[CONF_SKIP_CERT_CN_CHECK]))
|
||||
if CONF_CLIENT_CERTIFICATE in config:
|
||||
cg.add(var.set_cl_certificate(config[CONF_CLIENT_CERTIFICATE]))
|
||||
cg.add(var.set_cl_key(config[CONF_CLIENT_CERTIFICATE_KEY]))
|
||||
|
||||
# prevent error -0x428e
|
||||
# See https://github.com/espressif/esp-idf/issues/139
|
||||
|
|
|
@ -45,6 +45,11 @@ bool MQTTBackendESP32::initialize_() {
|
|||
mqtt_cfg_.cert_pem = ca_certificate_.value().c_str();
|
||||
mqtt_cfg_.skip_cert_common_name_check = skip_cert_cn_check_;
|
||||
mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_SSL;
|
||||
|
||||
if (this->cl_certificate_.has_value() && this->cl_key_.has_value()) {
|
||||
mqtt_cfg_.client_cert_pem = this->cl_certificate_.value().c_str();
|
||||
mqtt_cfg_.client_key_pem = this->cl_key_.value().c_str();
|
||||
}
|
||||
} else {
|
||||
mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_TCP;
|
||||
}
|
||||
|
@ -79,6 +84,11 @@ bool MQTTBackendESP32::initialize_() {
|
|||
mqtt_cfg_.broker.verification.certificate = ca_certificate_.value().c_str();
|
||||
mqtt_cfg_.broker.verification.skip_cert_common_name_check = skip_cert_cn_check_;
|
||||
mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_SSL;
|
||||
|
||||
if (this->cl_certificate_.has_value() && this->cl_key_.has_value()) {
|
||||
mqtt_cfg_.credentials.authentication.certificate = this->cl_certificate_.value().c_str();
|
||||
mqtt_cfg_.credentials.authentication.key = this->cl_key_.value().c_str();
|
||||
}
|
||||
} else {
|
||||
mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_TCP;
|
||||
}
|
||||
|
|
|
@ -124,6 +124,8 @@ class MQTTBackendESP32 final : public MQTTBackend {
|
|||
void loop() final;
|
||||
|
||||
void set_ca_certificate(const std::string &cert) { ca_certificate_ = cert; }
|
||||
void set_cl_certificate(const std::string &cert) { cl_certificate_ = cert; }
|
||||
void set_cl_key(const std::string &key) { cl_key_ = key; }
|
||||
void set_skip_cert_cn_check(bool skip_check) { skip_cert_cn_check_ = skip_check; }
|
||||
|
||||
protected:
|
||||
|
@ -154,6 +156,8 @@ class MQTTBackendESP32 final : public MQTTBackend {
|
|||
uint16_t keep_alive_;
|
||||
bool clean_session_;
|
||||
optional<std::string> ca_certificate_;
|
||||
optional<std::string> cl_certificate_;
|
||||
optional<std::string> cl_key_;
|
||||
bool skip_cert_cn_check_{false};
|
||||
|
||||
// callbacks
|
||||
|
|
|
@ -146,6 +146,8 @@ class MQTTClientComponent : public Component {
|
|||
#endif
|
||||
#ifdef USE_ESP32
|
||||
void set_ca_certificate(const char *cert) { this->mqtt_backend_.set_ca_certificate(cert); }
|
||||
void set_cl_certificate(const char *cert) { this->mqtt_backend_.set_cl_certificate(cert); }
|
||||
void set_cl_key(const char *key) { this->mqtt_backend_.set_cl_key(key); }
|
||||
void set_skip_cert_cn_check(bool skip_check) { this->mqtt_backend_.set_skip_cert_cn_check(skip_check); }
|
||||
#endif
|
||||
const Availability &get_availability();
|
||||
|
|
|
@ -14,6 +14,13 @@
|
|||
#include <IPAddress.h>
|
||||
#endif /* USE_ADRDUINO */
|
||||
|
||||
#ifdef USE_HOST
|
||||
#include <arpa/inet.h>
|
||||
using ip_addr_t = in_addr;
|
||||
using ip4_addr_t = in_addr;
|
||||
#define ipaddr_aton(x, y) inet_aton((x), (y))
|
||||
#endif
|
||||
|
||||
#if USE_ESP32_FRAMEWORK_ARDUINO
|
||||
#define arduino_ns Arduino_h
|
||||
#elif USE_LIBRETINY
|
||||
|
@ -32,6 +39,14 @@ namespace network {
|
|||
|
||||
struct IPAddress {
|
||||
public:
|
||||
#ifdef USE_HOST
|
||||
IPAddress() { ip_addr_.s_addr = 0; }
|
||||
IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) {
|
||||
this->ip_addr_.s_addr = htonl((first << 24) | (second << 16) | (third << 8) | fourth);
|
||||
}
|
||||
IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); }
|
||||
IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; }
|
||||
#else
|
||||
IPAddress() { ip_addr_set_zero(&ip_addr_); }
|
||||
IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) {
|
||||
IP_ADDR4(&ip_addr_, first, second, third, fourth);
|
||||
|
@ -107,6 +122,7 @@ struct IPAddress {
|
|||
}
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
ip_addr_t ip_addr_;
|
||||
|
|
|
@ -24,7 +24,7 @@ int Nextion::upload_range(const std::string &url, int range_start) {
|
|||
ESP_LOGVV(TAG, "url: %s", url.c_str());
|
||||
uint range_size = this->tft_size_ - range_start;
|
||||
ESP_LOGVV(TAG, "tft_size_: %i", this->tft_size_);
|
||||
ESP_LOGV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
int range_end = (range_start == 0) ? std::min(this->tft_size_, 16383) : this->tft_size_;
|
||||
if (range_size <= 0 or range_end <= range_start) {
|
||||
ESP_LOGE(TAG, "Invalid range");
|
||||
|
@ -67,12 +67,13 @@ int Nextion::upload_range(const std::string &url, int range_start) {
|
|||
|
||||
int total_read_len = 0, read_len;
|
||||
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
ESP_LOGV(TAG, "Allocate buffer");
|
||||
uint8_t *buffer = new uint8_t[4096];
|
||||
std::string recv_string;
|
||||
if (buffer == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for buffer");
|
||||
ESP_LOGV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Memory for buffer allocated successfully");
|
||||
|
||||
|
@ -86,15 +87,14 @@ int Nextion::upload_range(const std::string &url, int range_start) {
|
|||
ESP_LOGVV(TAG, "Write to UART successful");
|
||||
this->recv_ret_string_(recv_string, 5000, true);
|
||||
this->content_length_ -= read_len;
|
||||
ESP_LOGD(TAG, "Uploaded %0.2f %%, remaining %d bytes",
|
||||
100.0 * (this->tft_size_ - this->content_length_) / this->tft_size_, this->content_length_);
|
||||
if (recv_string[0] != 0x05) { // 0x05 == "ok"
|
||||
ESP_LOGD(TAG, "Uploaded %0.2f %%, remaining %d bytes, heap is %" PRIu32 " bytes",
|
||||
100.0 * (this->tft_size_ - this->content_length_) / this->tft_size_, this->content_length_,
|
||||
esp_get_free_heap_size());
|
||||
|
||||
if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request
|
||||
ESP_LOGD(
|
||||
TAG, "recv_string [%s]",
|
||||
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
|
||||
}
|
||||
// handle partial upload request
|
||||
if (recv_string[0] == 0x08 && recv_string.size() == 5) {
|
||||
uint32_t result = 0;
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j);
|
||||
|
@ -103,13 +103,37 @@ int Nextion::upload_range(const std::string &url, int range_start) {
|
|||
ESP_LOGI(TAG, "Nextion reported new range %" PRIu32, result);
|
||||
this->content_length_ = this->tft_size_ - result;
|
||||
// Deallocate the buffer when done
|
||||
ESP_LOGV(TAG, "Deallocate buffer");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
delete[] buffer;
|
||||
ESP_LOGVV(TAG, "Memory for buffer deallocated");
|
||||
esp_http_client_cleanup(client);
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
ESP_LOGV(TAG, "Close http client");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
ESP_LOGVV(TAG, "Client closed");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
return result;
|
||||
}
|
||||
} else if (recv_string[0] != 0x05) { // 0x05 == "ok"
|
||||
ESP_LOGE(
|
||||
TAG, "Invalid response from Nextion: [%s]",
|
||||
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
|
||||
ESP_LOGV(TAG, "Deallocate buffer");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
delete[] buffer;
|
||||
ESP_LOGVV(TAG, "Memory for buffer deallocated");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
ESP_LOGV(TAG, "Close http client");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
ESP_LOGVV(TAG, "Client closed");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
return -1;
|
||||
}
|
||||
|
||||
recv_string.clear();
|
||||
} else if (read_len == 0) {
|
||||
ESP_LOGV(TAG, "End of HTTP response reached");
|
||||
|
@ -121,11 +145,18 @@ int Nextion::upload_range(const std::string &url, int range_start) {
|
|||
}
|
||||
|
||||
// Deallocate the buffer when done
|
||||
ESP_LOGV(TAG, "Deallocate buffer");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
delete[] buffer;
|
||||
ESP_LOGVV(TAG, "Memory for buffer deallocated");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
}
|
||||
esp_http_client_cleanup(client);
|
||||
ESP_LOGV(TAG, "Close http client");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
ESP_LOGVV(TAG, "Client closed");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
return range_end + 1;
|
||||
}
|
||||
|
||||
|
@ -159,7 +190,7 @@ bool Nextion::upload_tft() {
|
|||
|
||||
// Initialize the HTTP client with the configuration
|
||||
ESP_LOGV(TAG, "Initializing HTTP client");
|
||||
ESP_LOGV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
esp_http_client_handle_t http = esp_http_client_init(&config);
|
||||
if (!http) {
|
||||
ESP_LOGE(TAG, "Failed to initialize HTTP client.");
|
||||
|
@ -168,7 +199,7 @@ bool Nextion::upload_tft() {
|
|||
|
||||
// Perform the HTTP request
|
||||
ESP_LOGV(TAG, "Check if the client could connect");
|
||||
ESP_LOGV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
esp_err_t err = esp_http_client_perform(http);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err));
|
||||
|
@ -177,14 +208,22 @@ bool Nextion::upload_tft() {
|
|||
}
|
||||
|
||||
// Check the HTTP Status Code
|
||||
ESP_LOGV(TAG, "Check the HTTP Status Code");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
int status_code = esp_http_client_get_status_code(http);
|
||||
ESP_LOGV(TAG, "HTTP Status Code: %d", status_code);
|
||||
size_t tft_file_size = esp_http_client_get_content_length(http);
|
||||
ESP_LOGD(TAG, "TFT file size: %zu", tft_file_size);
|
||||
|
||||
ESP_LOGD(TAG, "Close HTTP connection");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
esp_http_client_close(http);
|
||||
esp_http_client_cleanup(http);
|
||||
ESP_LOGVV(TAG, "Connection closed");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
|
||||
if (tft_file_size < 4096) {
|
||||
ESP_LOGE(TAG, "File size check failed. Size: %zu", tft_file_size);
|
||||
esp_http_client_cleanup(http);
|
||||
return this->upload_end(false);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "File size check passed. Proceeding...");
|
||||
|
@ -193,8 +232,10 @@ bool Nextion::upload_tft() {
|
|||
this->tft_size_ = tft_file_size;
|
||||
|
||||
ESP_LOGD(TAG, "Updating Nextion");
|
||||
// The Nextion will ignore the update command if it is sleeping
|
||||
|
||||
// The Nextion will ignore the update command if it is sleeping
|
||||
ESP_LOGV(TAG, "Wake-up Nextion");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
this->send_command_("sleep=0");
|
||||
this->set_backlight_brightness(1.0);
|
||||
vTaskDelay(pdMS_TO_TICKS(250)); // NOLINT
|
||||
|
@ -207,26 +248,31 @@ bool Nextion::upload_tft() {
|
|||
sprintf(command, "whmi-wris %d,%" PRIu32 ",1", this->content_length_, this->parent_->get_baud_rate());
|
||||
|
||||
// Clear serial receive buffer
|
||||
ESP_LOGV(TAG, "Clear serial receive buffer");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
uint8_t d;
|
||||
while (this->available()) {
|
||||
this->read_byte(&d);
|
||||
};
|
||||
|
||||
ESP_LOGV(TAG, "Send update instruction: %s", command);
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
this->send_command_(command);
|
||||
|
||||
std::string response;
|
||||
ESP_LOGV(TAG, "Waiting for upgrade response");
|
||||
this->recv_ret_string_(response, 2048, true); // This can take some time to return
|
||||
this->recv_ret_string_(response, 5000, true); // This can take some time to return
|
||||
|
||||
// The Nextion display will, if it's ready to accept data, send a 0x05 byte.
|
||||
ESP_LOGD(TAG, "Upgrade response is [%s]",
|
||||
format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str());
|
||||
ESP_LOGD(TAG, "Upgrade response is [%s] - %zu bytes",
|
||||
format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str(),
|
||||
response.length());
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
|
||||
if (response.find(0x05) != std::string::npos) {
|
||||
ESP_LOGV(TAG, "Preparation for tft update done");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Preparation for tft update failed %d \"%s\"", response[0], response.c_str());
|
||||
esp_http_client_cleanup(http);
|
||||
return this->upload_end(false);
|
||||
}
|
||||
|
||||
|
@ -234,12 +280,12 @@ bool Nextion::upload_tft() {
|
|||
content_length_, esp_get_free_heap_size());
|
||||
|
||||
ESP_LOGV(TAG, "Starting transfer by chunks loop");
|
||||
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||
int result = 0;
|
||||
while (content_length_ > 0) {
|
||||
result = upload_range(this->tft_url_.c_str(), result);
|
||||
if (result < 0) {
|
||||
ESP_LOGE(TAG, "Error updating Nextion!");
|
||||
esp_http_client_cleanup(http);
|
||||
return this->upload_end(false);
|
||||
}
|
||||
App.feed_wdt();
|
||||
|
@ -248,9 +294,6 @@ bool Nextion::upload_tft() {
|
|||
|
||||
ESP_LOGD(TAG, "Successfully updated Nextion!");
|
||||
|
||||
ESP_LOGD(TAG, "Close HTTP connection");
|
||||
esp_http_client_close(http);
|
||||
esp_http_client_cleanup(http);
|
||||
return upload_end(true);
|
||||
}
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ from esphome.const import (
|
|||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_VOLUME,
|
||||
DEVICE_CLASS_VOLUME_FLOW_RATE,
|
||||
DEVICE_CLASS_VOLUME_STORAGE,
|
||||
DEVICE_CLASS_WATER,
|
||||
DEVICE_CLASS_WEIGHT,
|
||||
|
@ -117,6 +118,7 @@ DEVICE_CLASSES = [
|
|||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_VOLUME,
|
||||
DEVICE_CLASS_VOLUME_FLOW_RATE,
|
||||
DEVICE_CLASS_VOLUME_STORAGE,
|
||||
DEVICE_CLASS_WATER,
|
||||
DEVICE_CLASS_WEIGHT,
|
||||
|
|
|
@ -12,6 +12,7 @@ from esphome.const import (
|
|||
CONF_TRIGGER_ID,
|
||||
CONF_OTA,
|
||||
KEY_PAST_SAFE_MODE,
|
||||
CONF_VERSION,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
|
@ -41,6 +42,7 @@ CONFIG_SCHEMA = cv.Schema(
|
|||
{
|
||||
cv.GenerateID(): cv.declare_id(OTAComponent),
|
||||
cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, int=True),
|
||||
cv.SplitDefault(
|
||||
CONF_PORT,
|
||||
esp8266=8266,
|
||||
|
@ -93,6 +95,7 @@ async def to_code(config):
|
|||
if CONF_PASSWORD in config:
|
||||
cg.add(var.set_auth_password(config[CONF_PASSWORD]))
|
||||
cg.add_define("USE_OTA_PASSWORD")
|
||||
cg.add_define("USE_OTA_VERSION", config[CONF_VERSION])
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
|
|
|
@ -20,8 +20,7 @@ namespace esphome {
|
|||
namespace ota {
|
||||
|
||||
static const char *const TAG = "ota";
|
||||
|
||||
static const uint8_t OTA_VERSION_1_0 = 1;
|
||||
static constexpr u_int16_t OTA_BLOCK_SIZE = 8192;
|
||||
|
||||
OTAComponent *global_ota_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
|
@ -101,6 +100,7 @@ void OTAComponent::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, " Using Password.");
|
||||
}
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG, " OTA version: %d.", USE_OTA_VERSION);
|
||||
if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1 &&
|
||||
this->safe_mode_rtc_value_ != esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) {
|
||||
ESP_LOGW(TAG, "Last Boot was an unhandled reset, will proceed to safe mode in %" PRIu32 " restarts",
|
||||
|
@ -132,6 +132,9 @@ void OTAComponent::handle_() {
|
|||
uint8_t ota_features;
|
||||
std::unique_ptr<OTABackend> backend;
|
||||
(void) ota_features;
|
||||
#if USE_OTA_VERSION == 2
|
||||
size_t size_acknowledged = 0;
|
||||
#endif
|
||||
|
||||
if (client_ == nullptr) {
|
||||
struct sockaddr_storage source_addr;
|
||||
|
@ -168,7 +171,7 @@ void OTAComponent::handle_() {
|
|||
|
||||
// Send OK and version - 2 bytes
|
||||
buf[0] = OTA_RESPONSE_OK;
|
||||
buf[1] = OTA_VERSION_1_0;
|
||||
buf[1] = USE_OTA_VERSION;
|
||||
this->writeall_(buf, 2);
|
||||
|
||||
backend = make_ota_backend();
|
||||
|
@ -312,6 +315,13 @@ void OTAComponent::handle_() {
|
|||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||
}
|
||||
total += read;
|
||||
#if USE_OTA_VERSION == 2
|
||||
while (size_acknowledged + OTA_BLOCK_SIZE <= total || (total == ota_size && size_acknowledged < ota_size)) {
|
||||
buf[0] = OTA_RESPONSE_CHUNK_OK;
|
||||
this->writeall_(buf, 1);
|
||||
size_acknowledged += OTA_BLOCK_SIZE;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t now = millis();
|
||||
if (now - last_progress > 1000) {
|
||||
|
|
|
@ -10,31 +10,32 @@ namespace esphome {
|
|||
namespace ota {
|
||||
|
||||
enum OTAResponseTypes {
|
||||
OTA_RESPONSE_OK = 0,
|
||||
OTA_RESPONSE_REQUEST_AUTH = 1,
|
||||
OTA_RESPONSE_OK = 0x00,
|
||||
OTA_RESPONSE_REQUEST_AUTH = 0x01,
|
||||
|
||||
OTA_RESPONSE_HEADER_OK = 64,
|
||||
OTA_RESPONSE_AUTH_OK = 65,
|
||||
OTA_RESPONSE_UPDATE_PREPARE_OK = 66,
|
||||
OTA_RESPONSE_BIN_MD5_OK = 67,
|
||||
OTA_RESPONSE_RECEIVE_OK = 68,
|
||||
OTA_RESPONSE_UPDATE_END_OK = 69,
|
||||
OTA_RESPONSE_SUPPORTS_COMPRESSION = 70,
|
||||
OTA_RESPONSE_HEADER_OK = 0x40,
|
||||
OTA_RESPONSE_AUTH_OK = 0x41,
|
||||
OTA_RESPONSE_UPDATE_PREPARE_OK = 0x42,
|
||||
OTA_RESPONSE_BIN_MD5_OK = 0x43,
|
||||
OTA_RESPONSE_RECEIVE_OK = 0x44,
|
||||
OTA_RESPONSE_UPDATE_END_OK = 0x45,
|
||||
OTA_RESPONSE_SUPPORTS_COMPRESSION = 0x46,
|
||||
OTA_RESPONSE_CHUNK_OK = 0x47,
|
||||
|
||||
OTA_RESPONSE_ERROR_MAGIC = 128,
|
||||
OTA_RESPONSE_ERROR_UPDATE_PREPARE = 129,
|
||||
OTA_RESPONSE_ERROR_AUTH_INVALID = 130,
|
||||
OTA_RESPONSE_ERROR_WRITING_FLASH = 131,
|
||||
OTA_RESPONSE_ERROR_UPDATE_END = 132,
|
||||
OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 133,
|
||||
OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 134,
|
||||
OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 135,
|
||||
OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 136,
|
||||
OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137,
|
||||
OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 138,
|
||||
OTA_RESPONSE_ERROR_MD5_MISMATCH = 139,
|
||||
OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 140,
|
||||
OTA_RESPONSE_ERROR_UNKNOWN = 255,
|
||||
OTA_RESPONSE_ERROR_MAGIC = 0x80,
|
||||
OTA_RESPONSE_ERROR_UPDATE_PREPARE = 0x81,
|
||||
OTA_RESPONSE_ERROR_AUTH_INVALID = 0x82,
|
||||
OTA_RESPONSE_ERROR_WRITING_FLASH = 0x83,
|
||||
OTA_RESPONSE_ERROR_UPDATE_END = 0x84,
|
||||
OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 0x85,
|
||||
OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 0x86,
|
||||
OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 0x87,
|
||||
OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 0x88,
|
||||
OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 0x89,
|
||||
OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 0x8A,
|
||||
OTA_RESPONSE_ERROR_MD5_MISMATCH = 0x8B,
|
||||
OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 0x8C,
|
||||
OTA_RESPONSE_ERROR_UNKNOWN = 0xFF,
|
||||
};
|
||||
|
||||
enum OTAState { OTA_COMPLETED = 0, OTA_STARTED, OTA_IN_PROGRESS, OTA_ERROR };
|
||||
|
|
|
@ -82,6 +82,7 @@ from esphome.const import (
|
|||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_VOLUME,
|
||||
DEVICE_CLASS_VOLUME_FLOW_RATE,
|
||||
DEVICE_CLASS_VOLUME_STORAGE,
|
||||
DEVICE_CLASS_WATER,
|
||||
DEVICE_CLASS_WEIGHT,
|
||||
|
@ -141,6 +142,7 @@ DEVICE_CLASSES = [
|
|||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_VOLUME,
|
||||
DEVICE_CLASS_VOLUME_FLOW_RATE,
|
||||
DEVICE_CLASS_VOLUME_STORAGE,
|
||||
DEVICE_CLASS_WATER,
|
||||
DEVICE_CLASS_WEIGHT,
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace sntp {
|
|||
static const char *const TAG = "sntp";
|
||||
|
||||
void SNTPComponent::setup() {
|
||||
#ifndef USE_HOST
|
||||
ESP_LOGCONFIG(TAG, "Setting up SNTP...");
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
if (sntp_enabled()) {
|
||||
|
@ -48,6 +49,7 @@ void SNTPComponent::setup() {
|
|||
#endif
|
||||
|
||||
sntp_init();
|
||||
#endif
|
||||
}
|
||||
void SNTPComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "SNTP Time:");
|
||||
|
@ -57,7 +59,7 @@ void SNTPComponent::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
|
||||
}
|
||||
void SNTPComponent::update() {
|
||||
#ifndef USE_ESP_IDF
|
||||
#if !defined(USE_ESP_IDF) && !defined(USE_HOST)
|
||||
// force resync
|
||||
if (sntp_enabled()) {
|
||||
sntp_stop();
|
||||
|
|
|
@ -87,7 +87,7 @@ class BSDSocketImpl : public Socket {
|
|||
int listen(int backlog) override { return ::listen(fd_, backlog); }
|
||||
ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); }
|
||||
ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override {
|
||||
#if defined(USE_ESP32)
|
||||
#if defined(USE_ESP32) || defined(USE_HOST)
|
||||
return ::recvfrom(this->fd_, buf, len, 0, addr, addr_len);
|
||||
#else
|
||||
return ::lwip_recvfrom(this->fd_, buf, len, 0, addr, addr_len);
|
||||
|
|
|
@ -29,12 +29,15 @@ from esphome.const import (
|
|||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
CONF_ALLOW_OTHER_USES,
|
||||
CONF_DATA_PINS,
|
||||
)
|
||||
from esphome.core import coroutine_with_priority, CORE
|
||||
|
||||
CODEOWNERS = ["@esphome/core", "@clydebarrow"]
|
||||
spi_ns = cg.esphome_ns.namespace("spi")
|
||||
SPIComponent = spi_ns.class_("SPIComponent", cg.Component)
|
||||
QuadSPIComponent = spi_ns.class_("QuadSPIComponent", cg.Component)
|
||||
SPIDevice = spi_ns.class_("SPIDevice")
|
||||
SPIDataRate = spi_ns.enum("SPIDataRate")
|
||||
SPIMode = spi_ns.enum("SPIMode")
|
||||
|
@ -190,12 +193,9 @@ def get_hw_spi(config, available):
|
|||
def validate_spi_config(config):
|
||||
available = list(range(len(get_hw_interface_list())))
|
||||
for spi in config:
|
||||
# map pin number to schema
|
||||
spi[CONF_CLK_PIN] = pins.gpio_output_pin_schema(spi[CONF_CLK_PIN])
|
||||
interface = spi[CONF_INTERFACE]
|
||||
if spi[CONF_FORCE_SW]:
|
||||
if interface == "any":
|
||||
spi[CONF_INTERFACE] = interface = "software"
|
||||
elif interface != "software":
|
||||
raise cv.Invalid("force_sw is deprecated - use interface: software")
|
||||
if interface == "software":
|
||||
pass
|
||||
elif interface == "any":
|
||||
|
@ -229,6 +229,8 @@ def validate_spi_config(config):
|
|||
spi, spi[CONF_INTERFACE_INDEX]
|
||||
):
|
||||
raise cv.Invalid("Invalid pin selections for hardware SPI interface")
|
||||
if CONF_DATA_PINS in spi and CONF_INTERFACE_INDEX not in spi:
|
||||
raise cv.Invalid("Quad mode requires a hardware interface")
|
||||
|
||||
return config
|
||||
|
||||
|
@ -249,14 +251,26 @@ def get_spi_interface(index):
|
|||
return "new SPIClass(HSPI)"
|
||||
|
||||
|
||||
# Do not use a pin schema for the number, as that will trigger a pin reuse error due to duplication of the
|
||||
# clock pin in the standard and quad schemas.
|
||||
clk_pin_validator = cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_NUMBER): cv.Any(cv.int_, cv.string),
|
||||
cv.Optional(CONF_ALLOW_OTHER_USES): cv.boolean,
|
||||
},
|
||||
key=CONF_NUMBER,
|
||||
)
|
||||
|
||||
SPI_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SPIComponent),
|
||||
cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_CLK_PIN): clk_pin_validator,
|
||||
cv.Optional(CONF_MISO_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Optional(CONF_MOSI_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_FORCE_SW, default=False): cv.boolean,
|
||||
cv.Optional(CONF_FORCE_SW): cv.invalid(
|
||||
"force_sw is deprecated - use interface: software"
|
||||
),
|
||||
cv.Optional(CONF_INTERFACE, default="any"): cv.one_of(
|
||||
*sum(get_hw_interface_list(), ["software", "hardware", "any"]),
|
||||
lower=True,
|
||||
|
@ -267,8 +281,34 @@ SPI_SCHEMA = cv.All(
|
|||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]),
|
||||
)
|
||||
|
||||
SPI_QUAD_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(QuadSPIComponent),
|
||||
cv.Required(CONF_CLK_PIN): clk_pin_validator,
|
||||
cv.Required(CONF_DATA_PINS): cv.All(
|
||||
cv.ensure_list(pins.internal_gpio_output_pin_number),
|
||||
cv.Length(min=4, max=4),
|
||||
),
|
||||
cv.Optional(CONF_INTERFACE, default="hardware"): cv.one_of(
|
||||
*sum(get_hw_interface_list(), ["hardware"]),
|
||||
lower=True,
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.only_with_esp_idf,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.ensure_list(SPI_SCHEMA),
|
||||
# Order is important. SPI_SCHEMA is the default.
|
||||
cv.ensure_list(
|
||||
cv.Any(
|
||||
SPI_SCHEMA,
|
||||
SPI_QUAD_SCHEMA,
|
||||
msg="Standard SPI requires mosi_pin and/or miso_pin; quad SPI requires data_pins only."
|
||||
+ " A clock pin is always required",
|
||||
),
|
||||
),
|
||||
validate_spi_config,
|
||||
)
|
||||
|
||||
|
@ -277,43 +317,46 @@ CONFIG_SCHEMA = cv.All(
|
|||
async def to_code(configs):
|
||||
cg.add_define("USE_SPI")
|
||||
cg.add_global(spi_ns.using)
|
||||
if CORE.using_arduino:
|
||||
cg.add_library("SPI", None)
|
||||
for spi in configs:
|
||||
var = cg.new_Pvariable(spi[CONF_ID])
|
||||
await cg.register_component(var, spi)
|
||||
|
||||
clk = await cg.gpio_pin_expression(spi[CONF_CLK_PIN])
|
||||
cg.add(var.set_clk(clk))
|
||||
if CONF_MISO_PIN in spi:
|
||||
miso = await cg.gpio_pin_expression(spi[CONF_MISO_PIN])
|
||||
cg.add(var.set_miso(miso))
|
||||
if CONF_MOSI_PIN in spi:
|
||||
mosi = await cg.gpio_pin_expression(spi[CONF_MOSI_PIN])
|
||||
cg.add(var.set_mosi(mosi))
|
||||
if CONF_INTERFACE_INDEX in spi:
|
||||
index = spi[CONF_INTERFACE_INDEX]
|
||||
cg.add(var.set_interface(cg.RawExpression(get_spi_interface(index))))
|
||||
if miso := spi.get(CONF_MISO_PIN):
|
||||
cg.add(var.set_miso(await cg.gpio_pin_expression(miso)))
|
||||
if mosi := spi.get(CONF_MOSI_PIN):
|
||||
cg.add(var.set_mosi(await cg.gpio_pin_expression(mosi)))
|
||||
if data_pins := spi.get(CONF_DATA_PINS):
|
||||
cg.add(var.set_data_pins(data_pins))
|
||||
if (index := spi.get(CONF_INTERFACE_INDEX)) is not None:
|
||||
interface = get_spi_interface(index)
|
||||
cg.add(var.set_interface(cg.RawExpression(interface)))
|
||||
cg.add(
|
||||
var.set_interface_name(
|
||||
re.sub(
|
||||
r"\W", "", get_spi_interface(index).replace("new SPIClass", "")
|
||||
)
|
||||
re.sub(r"\W", "", interface.replace("new SPIClass", ""))
|
||||
)
|
||||
)
|
||||
|
||||
if CORE.using_arduino:
|
||||
cg.add_library("SPI", None)
|
||||
|
||||
|
||||
def spi_device_schema(
|
||||
cs_pin_required=True, default_data_rate=cv.UNDEFINED, default_mode=cv.UNDEFINED
|
||||
cs_pin_required=True,
|
||||
default_data_rate=cv.UNDEFINED,
|
||||
default_mode=cv.UNDEFINED,
|
||||
quad=False,
|
||||
):
|
||||
"""Create a schema for an SPI device.
|
||||
:param cs_pin_required: If true, make the CS_PIN required in the config.
|
||||
:param default_data_rate: Optional data_rate to use as default
|
||||
:param default_mode Optional. The default SPI mode to use.
|
||||
:param quad If set, will require an SPI component configured as quad data bits.
|
||||
:return: The SPI device schema, `extend` this in your config schema.
|
||||
"""
|
||||
schema = {
|
||||
cv.GenerateID(CONF_SPI_ID): cv.use_id(SPIComponent),
|
||||
cv.GenerateID(CONF_SPI_ID): cv.use_id(
|
||||
QuadSPIComponent if quad else SPIComponent
|
||||
),
|
||||
cv.Optional(CONF_DATA_RATE, default=default_data_rate): SPI_DATA_RATE_SCHEMA,
|
||||
cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum(
|
||||
SPI_MODE_OPTIONS, upper=True
|
||||
|
|
|
@ -49,7 +49,8 @@ void SPIComponent::setup() {
|
|||
}
|
||||
|
||||
if (this->using_hw_) {
|
||||
this->spi_bus_ = SPIComponent::get_bus(this->interface_, this->clk_pin_, this->sdo_pin_, this->sdi_pin_);
|
||||
this->spi_bus_ =
|
||||
SPIComponent::get_bus(this->interface_, this->clk_pin_, this->sdo_pin_, this->sdi_pin_, this->data_pins_);
|
||||
if (this->spi_bus_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Unable to allocate SPI interface");
|
||||
this->mark_failed();
|
||||
|
@ -68,6 +69,9 @@ void SPIComponent::dump_config() {
|
|||
LOG_PIN(" CLK Pin: ", this->clk_pin_)
|
||||
LOG_PIN(" SDI Pin: ", this->sdi_pin_)
|
||||
LOG_PIN(" SDO Pin: ", this->sdo_pin_)
|
||||
for (size_t i = 0; i != this->data_pins_.size(); i++) {
|
||||
ESP_LOGCONFIG(TAG, " Data pin %u: GPIO%d", i, this->data_pins_[i]);
|
||||
}
|
||||
if (this->spi_bus_->is_hw()) {
|
||||
ESP_LOGCONFIG(TAG, " Using HW SPI: %s", this->interface_name_);
|
||||
} else {
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
|
@ -208,6 +209,10 @@ class SPIDelegate {
|
|||
esph_log_e("spi_device", "variable length write not implemented");
|
||||
}
|
||||
|
||||
virtual void write_cmd_addr_data(size_t cmd_bits, uint32_t cmd, size_t addr_bits, uint32_t address,
|
||||
const uint8_t *data, size_t length, uint8_t bus_width) {
|
||||
esph_log_e("spi_device", "write_cmd_addr_data not implemented");
|
||||
}
|
||||
// write 16 bits
|
||||
virtual void write16(uint16_t data) {
|
||||
if (this->bit_order_ == BIT_ORDER_MSB_FIRST) {
|
||||
|
@ -331,6 +336,7 @@ class SPIComponent : public Component {
|
|||
void set_miso(GPIOPin *sdi) { this->sdi_pin_ = sdi; }
|
||||
|
||||
void set_mosi(GPIOPin *sdo) { this->sdo_pin_ = sdo; }
|
||||
void set_data_pins(std::vector<uint8_t> pins) { this->data_pins_ = std::move(pins); }
|
||||
|
||||
void set_interface(SPIInterface interface) {
|
||||
this->interface_ = interface;
|
||||
|
@ -348,15 +354,19 @@ class SPIComponent : public Component {
|
|||
GPIOPin *clk_pin_{nullptr};
|
||||
GPIOPin *sdi_pin_{nullptr};
|
||||
GPIOPin *sdo_pin_{nullptr};
|
||||
std::vector<uint8_t> data_pins_{};
|
||||
|
||||
SPIInterface interface_{};
|
||||
bool using_hw_{false};
|
||||
const char *interface_name_{nullptr};
|
||||
SPIBus *spi_bus_{};
|
||||
std::map<SPIClient *, SPIDelegate *> devices_;
|
||||
|
||||
static SPIBus *get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi);
|
||||
static SPIBus *get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi,
|
||||
const std::vector<uint8_t> &data_pins);
|
||||
};
|
||||
|
||||
using QuadSPIComponent = SPIComponent;
|
||||
/**
|
||||
* Base class for SPIDevice, un-templated.
|
||||
*/
|
||||
|
@ -422,18 +432,49 @@ class SPIDevice : public SPIClient {
|
|||
|
||||
void read_array(uint8_t *data, size_t length) { return this->delegate_->read_array(data, length); }
|
||||
|
||||
/**
|
||||
* Write a single data item, up to 32 bits.
|
||||
* @param data The data
|
||||
* @param num_bits The number of bits to write. The lower num_bits of data will be sent.
|
||||
*/
|
||||
void write(uint16_t data, size_t num_bits) { this->delegate_->write(data, num_bits); };
|
||||
|
||||
/* Write command, address and data. Command and address will be written as single-bit SPI,
|
||||
* data phase can be multiple bit (currently only 1 or 4)
|
||||
* @param cmd_bits Number of bits to write in the command phase
|
||||
* @param cmd The command value to write
|
||||
* @param addr_bits Number of bits to write in addr phase
|
||||
* @param address Address data
|
||||
* @param data Plain data bytes
|
||||
* @param length Number of data bytes
|
||||
* @param bus_width The number of data lines to use for the data phase.
|
||||
*/
|
||||
void write_cmd_addr_data(size_t cmd_bits, uint32_t cmd, size_t addr_bits, uint32_t address, const uint8_t *data,
|
||||
size_t length, uint8_t bus_width = 1) {
|
||||
this->delegate_->write_cmd_addr_data(cmd_bits, cmd, addr_bits, address, data, length, bus_width);
|
||||
}
|
||||
|
||||
void write_byte(uint8_t data) { this->delegate_->write_array(&data, 1); }
|
||||
|
||||
/**
|
||||
* Write the array data, replace with received data.
|
||||
* @param data
|
||||
* @param length
|
||||
*/
|
||||
void transfer_array(uint8_t *data, size_t length) { this->delegate_->transfer(data, length); }
|
||||
|
||||
uint8_t transfer_byte(uint8_t data) { return this->delegate_->transfer(data); }
|
||||
|
||||
// the driver will byte-swap if required.
|
||||
/** Write 16 bit data. The driver will byte-swap if required.
|
||||
*/
|
||||
void write_byte16(uint16_t data) { this->delegate_->write16(data); }
|
||||
|
||||
// avoid use of this if possible. It's inefficient and ugly.
|
||||
/**
|
||||
* Write an array of data as 16 bit values, byte-swapping if required. Use of this should be avoided as
|
||||
* it is horribly slow.
|
||||
* @param data
|
||||
* @param length
|
||||
*/
|
||||
void write_array16(const uint16_t *data, size_t length) { this->delegate_->write_array16(data, length); }
|
||||
|
||||
void enable() { this->delegate_->begin_transaction(); }
|
||||
|
|
|
@ -85,7 +85,8 @@ class SPIBusHw : public SPIBus {
|
|||
bool is_hw() override { return true; }
|
||||
};
|
||||
|
||||
SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) {
|
||||
SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi,
|
||||
const std::vector<uint8_t> &data_pins) {
|
||||
return new SPIBusHw(clk, sdo, sdi, interface);
|
||||
}
|
||||
|
||||
|
|
|
@ -104,6 +104,60 @@ class SPIDelegateHw : public SPIDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write command, address and data
|
||||
* @param cmd_bits Number of bits to write in the command phase
|
||||
* @param cmd The command value to write
|
||||
* @param addr_bits Number of bits to write in addr phase
|
||||
* @param address Address data
|
||||
* @param data Remaining data bytes
|
||||
* @param length Number of data bytes
|
||||
* @param bus_width The number of data lines to use
|
||||
*/
|
||||
void write_cmd_addr_data(size_t cmd_bits, uint32_t cmd, size_t addr_bits, uint32_t address, const uint8_t *data,
|
||||
size_t length, uint8_t bus_width) override {
|
||||
spi_transaction_ext_t desc = {};
|
||||
if (length == 0 && cmd_bits == 0 && addr_bits == 0) {
|
||||
esph_log_w(TAG, "Nothing to transfer");
|
||||
return;
|
||||
}
|
||||
desc.base.flags = SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_DUMMY;
|
||||
if (bus_width == 4) {
|
||||
desc.base.flags |= SPI_TRANS_MODE_QIO;
|
||||
} else if (bus_width == 8) {
|
||||
desc.base.flags |= SPI_TRANS_MODE_OCT;
|
||||
}
|
||||
desc.command_bits = cmd_bits;
|
||||
desc.address_bits = addr_bits;
|
||||
desc.dummy_bits = 0;
|
||||
desc.base.rxlength = 0;
|
||||
desc.base.cmd = cmd;
|
||||
desc.base.addr = address;
|
||||
do {
|
||||
size_t chunk_size = std::min(length, MAX_TRANSFER_SIZE);
|
||||
if (data != nullptr && chunk_size != 0) {
|
||||
desc.base.length = chunk_size * 8;
|
||||
desc.base.tx_buffer = data;
|
||||
length -= chunk_size;
|
||||
data += chunk_size;
|
||||
} else {
|
||||
length = 0;
|
||||
desc.base.length = 0;
|
||||
}
|
||||
esp_err_t err = spi_device_polling_start(this->handle_, (spi_transaction_t *) &desc, portMAX_DELAY);
|
||||
if (err == ESP_OK) {
|
||||
err = spi_device_polling_end(this->handle_, portMAX_DELAY);
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Transmit failed - err %X", err);
|
||||
return;
|
||||
}
|
||||
// if more data is to be sent, skip the command and address phases.
|
||||
desc.command_bits = 0;
|
||||
desc.address_bits = 0;
|
||||
} while (length != 0);
|
||||
}
|
||||
|
||||
void transfer(uint8_t *ptr, size_t length) override { this->transfer(ptr, ptr, length); }
|
||||
|
||||
uint8_t transfer(uint8_t data) override {
|
||||
|
@ -142,13 +196,27 @@ class SPIDelegateHw : public SPIDelegate {
|
|||
|
||||
class SPIBusHw : public SPIBus {
|
||||
public:
|
||||
SPIBusHw(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, SPIInterface channel) : SPIBus(clk, sdo, sdi), channel_(channel) {
|
||||
SPIBusHw(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, SPIInterface channel, std::vector<uint8_t> data_pins)
|
||||
: SPIBus(clk, sdo, sdi), channel_(channel) {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = Utility::get_pin_no(sdo);
|
||||
buscfg.miso_io_num = Utility::get_pin_no(sdi);
|
||||
buscfg.sclk_io_num = Utility::get_pin_no(clk);
|
||||
buscfg.quadwp_io_num = -1;
|
||||
buscfg.quadhd_io_num = -1;
|
||||
buscfg.flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_SCLK;
|
||||
if (data_pins.empty()) {
|
||||
buscfg.mosi_io_num = Utility::get_pin_no(sdo);
|
||||
buscfg.miso_io_num = Utility::get_pin_no(sdi);
|
||||
buscfg.quadwp_io_num = -1;
|
||||
buscfg.quadhd_io_num = -1;
|
||||
} else {
|
||||
buscfg.data0_io_num = data_pins[0];
|
||||
buscfg.data1_io_num = data_pins[1];
|
||||
buscfg.data2_io_num = data_pins[2];
|
||||
buscfg.data3_io_num = data_pins[3];
|
||||
buscfg.data4_io_num = -1;
|
||||
buscfg.data5_io_num = -1;
|
||||
buscfg.data6_io_num = -1;
|
||||
buscfg.data7_io_num = -1;
|
||||
buscfg.flags |= SPICOMMON_BUSFLAG_QUAD;
|
||||
}
|
||||
buscfg.max_transfer_sz = MAX_TRANSFER_SIZE;
|
||||
auto err = spi_bus_initialize(channel, &buscfg, SPI_DMA_CH_AUTO);
|
||||
if (err != ESP_OK)
|
||||
|
@ -166,8 +234,9 @@ class SPIBusHw : public SPIBus {
|
|||
bool is_hw() override { return true; }
|
||||
};
|
||||
|
||||
SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) {
|
||||
return new SPIBusHw(clk, sdo, sdi, interface);
|
||||
SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi,
|
||||
const std::vector<uint8_t> &data_pins) {
|
||||
return new SPIBusHw(clk, sdo, sdi, interface, data_pins);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -28,7 +28,7 @@ class Filter {
|
|||
* @param value The new value.
|
||||
* @return An optional string, the new value that should be pushed out.
|
||||
*/
|
||||
virtual optional<std::string> new_value(std::string value);
|
||||
virtual optional<std::string> new_value(std::string value) = 0;
|
||||
|
||||
/// Initialize this filter, please note this can be called more than once.
|
||||
virtual void initialize(TextSensor *parent, Filter *next);
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
#include "real_time_clock.h"
|
||||
#include "esphome/core/log.h"
|
||||
#ifdef USE_HOST
|
||||
#include <sys/time.h>
|
||||
#else
|
||||
#include "lwip/opt.h"
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
#include "sys/time.h"
|
||||
#endif
|
||||
|
@ -25,7 +29,7 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
|
|||
.tv_sec = static_cast<time_t>(epoch), .tv_usec = 0,
|
||||
};
|
||||
ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch);
|
||||
timezone tz = {0, 0};
|
||||
struct timezone tz = {0, 0};
|
||||
int ret = settimeofday(&timev, &tz);
|
||||
if (ret == EINVAL) {
|
||||
// Some ESP8266 frameworks abort when timezone parameter is not NULL
|
||||
|
|
|
@ -23,8 +23,8 @@ static const int MAX_RETRIES = 5;
|
|||
|
||||
void Tuya::setup() {
|
||||
this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
|
||||
if (this->status_pin_.has_value()) {
|
||||
this->status_pin_.value()->digital_write(false);
|
||||
if (this->status_pin_ != nullptr) {
|
||||
this->status_pin_->digital_write(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,9 +70,7 @@ void Tuya::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, " GPIO Configuration: status: pin %d, reset: pin %d", this->status_pin_reported_,
|
||||
this->reset_pin_reported_);
|
||||
}
|
||||
if (this->status_pin_.has_value()) {
|
||||
LOG_PIN(" Status Pin: ", this->status_pin_.value());
|
||||
}
|
||||
LOG_PIN(" Status Pin: ", this->status_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Product: '%s'", this->product_.c_str());
|
||||
}
|
||||
|
||||
|
@ -194,7 +192,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
|||
this->init_state_ = TuyaInitState::INIT_DATAPOINT;
|
||||
this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
|
||||
bool is_pin_equals =
|
||||
this->status_pin_.has_value() && this->status_pin_.value()->get_pin() == this->status_pin_reported_;
|
||||
this->status_pin_ != nullptr && this->status_pin_->get_pin() == this->status_pin_reported_;
|
||||
// Configure status pin toggling (if reported and configured) or WIFI_STATE periodic send
|
||||
if (is_pin_equals) {
|
||||
ESP_LOGV(TAG, "Configured status pin %i", this->status_pin_reported_);
|
||||
|
@ -244,13 +242,12 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
|||
break;
|
||||
case TuyaCommandType::LOCAL_TIME_QUERY:
|
||||
#ifdef USE_TIME
|
||||
if (this->time_id_.has_value()) {
|
||||
if (this->time_id_ != nullptr) {
|
||||
this->send_local_time_();
|
||||
|
||||
if (!this->time_sync_callback_registered_) {
|
||||
// tuya mcu supports time, so we let them know when our time changed
|
||||
auto *time_id = *this->time_id_;
|
||||
time_id->add_on_time_sync_callback([this] { this->send_local_time_(); });
|
||||
this->time_id_->add_on_time_sync_callback([this] { this->send_local_time_(); });
|
||||
this->time_sync_callback_registered_ = true;
|
||||
}
|
||||
} else
|
||||
|
@ -463,7 +460,7 @@ void Tuya::send_empty_command_(TuyaCommandType command) {
|
|||
|
||||
void Tuya::set_status_pin_() {
|
||||
bool is_network_ready = network::is_connected() && remote_is_connected();
|
||||
this->status_pin_.value()->digital_write(is_network_ready);
|
||||
this->status_pin_->digital_write(is_network_ready);
|
||||
}
|
||||
|
||||
uint8_t Tuya::get_wifi_status_code_() {
|
||||
|
@ -511,8 +508,7 @@ void Tuya::send_wifi_status_() {
|
|||
#ifdef USE_TIME
|
||||
void Tuya::send_local_time_() {
|
||||
std::vector<uint8_t> payload;
|
||||
auto *time_id = *this->time_id_;
|
||||
ESPTime now = time_id->now();
|
||||
ESPTime now = this->time_id_->now();
|
||||
if (now.is_valid()) {
|
||||
uint8_t year = now.year - 2000;
|
||||
uint8_t month = now.month;
|
||||
|
|
|
@ -130,14 +130,14 @@ class Tuya : public Component, public uart::UARTDevice {
|
|||
|
||||
#ifdef USE_TIME
|
||||
void send_local_time_();
|
||||
optional<time::RealTimeClock *> time_id_{};
|
||||
time::RealTimeClock *time_id_{nullptr};
|
||||
bool time_sync_callback_registered_{false};
|
||||
#endif
|
||||
TuyaInitState init_state_ = TuyaInitState::INIT_HEARTBEAT;
|
||||
bool init_failed_{false};
|
||||
int init_retries_{0};
|
||||
uint8_t protocol_version_ = -1;
|
||||
optional<InternalGPIOPin *> status_pin_{};
|
||||
InternalGPIOPin *status_pin_{nullptr};
|
||||
int status_pin_reported_ = -1;
|
||||
int reset_pin_reported_ = -1;
|
||||
uint32_t last_command_timestamp_ = 0;
|
||||
|
|
|
@ -17,7 +17,7 @@ static const char *const TAG = "voice_assistant";
|
|||
|
||||
static const size_t SAMPLE_RATE_HZ = 16000;
|
||||
static const size_t INPUT_BUFFER_SIZE = 32 * SAMPLE_RATE_HZ / 1000; // 32ms * 16kHz / 1000ms
|
||||
static const size_t BUFFER_SIZE = 1000 * SAMPLE_RATE_HZ / 1000; // 1s
|
||||
static const size_t BUFFER_SIZE = 1024 * SAMPLE_RATE_HZ / 1000;
|
||||
static const size_t SEND_BUFFER_SIZE = INPUT_BUFFER_SIZE * sizeof(int16_t);
|
||||
static const size_t RECEIVE_SIZE = 1024;
|
||||
static const size_t SPEAKER_BUFFER_SIZE = 16 * RECEIVE_SIZE;
|
||||
|
@ -231,10 +231,12 @@ void VoiceAssistant::loop() {
|
|||
}
|
||||
case State::STREAMING_MICROPHONE: {
|
||||
this->read_microphone_();
|
||||
if (this->ring_buffer_->available() >= SEND_BUFFER_SIZE) {
|
||||
this->ring_buffer_->read((void *) this->send_buffer_, SEND_BUFFER_SIZE, 0);
|
||||
this->socket_->sendto(this->send_buffer_, SEND_BUFFER_SIZE, 0, (struct sockaddr *) &this->dest_addr_,
|
||||
size_t available = this->ring_buffer_->available();
|
||||
while (available >= SEND_BUFFER_SIZE) {
|
||||
size_t read_bytes = this->ring_buffer_->read((void *) this->send_buffer_, SEND_BUFFER_SIZE, 0);
|
||||
this->socket_->sendto(this->send_buffer_, read_bytes, 0, (struct sockaddr *) &this->dest_addr_,
|
||||
sizeof(this->dest_addr_));
|
||||
available = this->ring_buffer_->available();
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@clydebarrow"]
|
|
@ -17,8 +17,12 @@ from esphome.const import (
|
|||
DEPENDENCIES = ["spi"]
|
||||
|
||||
waveshare_epaper_ns = cg.esphome_ns.namespace("waveshare_epaper")
|
||||
WaveshareEPaper = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer
|
||||
WaveshareEPaperBase = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaperBase", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer
|
||||
)
|
||||
WaveshareEPaper = waveshare_epaper_ns.class_("WaveshareEPaper", WaveshareEPaperBase)
|
||||
WaveshareEPaperBWR = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaperBWR", WaveshareEPaperBase
|
||||
)
|
||||
WaveshareEPaperTypeA = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaperTypeA", WaveshareEPaper
|
||||
|
@ -26,6 +30,12 @@ WaveshareEPaperTypeA = waveshare_epaper_ns.class_(
|
|||
WaveshareEPaper2P7In = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper2P7In", WaveshareEPaper
|
||||
)
|
||||
WaveshareEPaper2P7InB = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper2P7InB", WaveshareEPaperBWR
|
||||
)
|
||||
WaveshareEPaper2P7InBV2 = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper2P7InBV2", WaveshareEPaperBWR
|
||||
)
|
||||
WaveshareEPaper2P7InV2 = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper2P7InV2", WaveshareEPaper
|
||||
)
|
||||
|
@ -72,6 +82,9 @@ WaveshareEPaper7P5InHDB = waveshare_epaper_ns.class_(
|
|||
WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper2P13InDKE", WaveshareEPaper
|
||||
)
|
||||
WaveshareEPaper2P13InV3 = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper2P13InV3", WaveshareEPaper
|
||||
)
|
||||
GDEW0154M09 = waveshare_epaper_ns.class_("GDEW0154M09", WaveshareEPaper)
|
||||
|
||||
WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel")
|
||||
|
@ -89,6 +102,8 @@ MODELS = {
|
|||
"2.90inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN_V2),
|
||||
"gdey029t94": ("c", GDEY029T94),
|
||||
"2.70in": ("b", WaveshareEPaper2P7In),
|
||||
"2.70in-b": ("b", WaveshareEPaper2P7InB),
|
||||
"2.70in-bv2": ("b", WaveshareEPaper2P7InBV2),
|
||||
"2.70inv2": ("b", WaveshareEPaper2P7InV2),
|
||||
"2.90in-b": ("b", WaveshareEPaper2P9InB),
|
||||
"2.90in-bv3": ("b", WaveshareEPaper2P9InBV3),
|
||||
|
@ -104,6 +119,7 @@ MODELS = {
|
|||
"7.50inv2alt": ("b", WaveshareEPaper7P5InV2alt),
|
||||
"7.50in-hd-b": ("b", WaveshareEPaper7P5InHDB),
|
||||
"2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE),
|
||||
"2.13inv3": ("c", WaveshareEPaper2P13InV3),
|
||||
"1.54in-m5coreink-m09": ("c", GDEW0154M09),
|
||||
}
|
||||
|
||||
|
@ -126,12 +142,12 @@ def validate_full_update_every_only_types_ac(value):
|
|||
CONFIG_SCHEMA = cv.All(
|
||||
display.FULL_DISPLAY_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(WaveshareEPaper),
|
||||
cv.GenerateID(): cv.declare_id(WaveshareEPaperBase),
|
||||
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_MODEL): cv.one_of(*MODELS, lower=True),
|
||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Optional(CONF_FULL_UPDATE_EVERY): cv.uint32_t,
|
||||
cv.Optional(CONF_FULL_UPDATE_EVERY): cv.int_range(min=1, max=4294967295),
|
||||
cv.Optional(CONF_RESET_DURATION): cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(max=core.TimePeriod(milliseconds=500)),
|
||||
|
|
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
|
|
@ -83,7 +83,7 @@ static const uint8_t PARTIAL_UPDATE_LUT_TTGO_B1[LUT_SIZE_TTGO_B1] = {
|
|||
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
void WaveshareEPaper::setup_pins_() {
|
||||
void WaveshareEPaperBase::setup_pins_() {
|
||||
this->init_internal_(this->get_buffer_length_());
|
||||
this->dc_pin_->setup(); // OUTPUT
|
||||
this->dc_pin_->digital_write(false);
|
||||
|
@ -98,19 +98,31 @@ void WaveshareEPaper::setup_pins_() {
|
|||
|
||||
this->reset_();
|
||||
}
|
||||
float WaveshareEPaper::get_setup_priority() const { return setup_priority::PROCESSOR; }
|
||||
void WaveshareEPaper::command(uint8_t value) {
|
||||
float WaveshareEPaperBase::get_setup_priority() const { return setup_priority::PROCESSOR; }
|
||||
void WaveshareEPaperBase::command(uint8_t value) {
|
||||
this->start_command_();
|
||||
this->write_byte(value);
|
||||
this->end_command_();
|
||||
}
|
||||
void WaveshareEPaper::data(uint8_t value) {
|
||||
void WaveshareEPaperBase::data(uint8_t value) {
|
||||
this->start_data_();
|
||||
this->write_byte(value);
|
||||
this->end_data_();
|
||||
}
|
||||
bool WaveshareEPaper::wait_until_idle_() {
|
||||
if (this->busy_pin_ == nullptr) {
|
||||
|
||||
// write a command followed by one or more bytes of data.
|
||||
// The command is the first byte, length is the total including cmd.
|
||||
void WaveshareEPaperBase::cmd_data(const uint8_t *c_data, size_t length) {
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->enable();
|
||||
this->write_byte(c_data[0]);
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->write_array(c_data + 1, length - 1);
|
||||
this->disable();
|
||||
}
|
||||
|
||||
bool WaveshareEPaperBase::wait_until_idle_() {
|
||||
if (this->busy_pin_ == nullptr || !this->busy_pin_->digital_read()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -120,11 +132,11 @@ bool WaveshareEPaper::wait_until_idle_() {
|
|||
ESP_LOGE(TAG, "Timeout while displaying image!");
|
||||
return false;
|
||||
}
|
||||
delay(10);
|
||||
delay(1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void WaveshareEPaper::update() {
|
||||
void WaveshareEPaperBase::update() {
|
||||
this->do_update_();
|
||||
this->display();
|
||||
}
|
||||
|
@ -147,20 +159,51 @@ void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, Color color
|
|||
this->buffer_[pos] &= ~(0x80 >> subpos);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t WaveshareEPaper::get_buffer_length_() {
|
||||
return this->get_width_controller() * this->get_height_internal() / 8u;
|
||||
} // just a black buffer
|
||||
uint32_t WaveshareEPaperBWR::get_buffer_length_() {
|
||||
return this->get_width_controller() * this->get_height_internal() / 4u;
|
||||
} // black and red buffer
|
||||
|
||||
void WaveshareEPaperBWR::fill(Color color) {
|
||||
this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color);
|
||||
}
|
||||
void WaveshareEPaper::start_command_() {
|
||||
void HOT WaveshareEPaperBWR::draw_absolute_pixel_internal(int x, int y, Color color) {
|
||||
if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0)
|
||||
return;
|
||||
|
||||
const uint32_t buf_half_len = this->get_buffer_length_() / 2u;
|
||||
|
||||
const uint32_t pos = (x + y * this->get_width_internal()) / 8u;
|
||||
const uint8_t subpos = x & 0x07;
|
||||
// flip logic
|
||||
if (color.is_on()) {
|
||||
this->buffer_[pos] |= 0x80 >> subpos;
|
||||
} else {
|
||||
this->buffer_[pos] &= ~(0x80 >> subpos);
|
||||
}
|
||||
|
||||
// draw red pixels only, if the color contains red only
|
||||
if (((color.red > 0) && (color.green == 0) && (color.blue == 0))) {
|
||||
this->buffer_[pos + buf_half_len] |= 0x80 >> subpos;
|
||||
} else {
|
||||
this->buffer_[pos + buf_half_len] &= ~(0x80 >> subpos);
|
||||
}
|
||||
}
|
||||
|
||||
void WaveshareEPaperBase::start_command_() {
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->enable();
|
||||
}
|
||||
void WaveshareEPaper::end_command_() { this->disable(); }
|
||||
void WaveshareEPaper::start_data_() {
|
||||
void WaveshareEPaperBase::end_command_() { this->disable(); }
|
||||
void WaveshareEPaperBase::start_data_() {
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->enable();
|
||||
}
|
||||
void WaveshareEPaper::end_data_() { this->disable(); }
|
||||
void WaveshareEPaper::on_safe_shutdown() { this->deep_sleep(); }
|
||||
void WaveshareEPaperBase::end_data_() { this->disable(); }
|
||||
void WaveshareEPaperBase::on_safe_shutdown() { this->deep_sleep(); }
|
||||
|
||||
// ========================================================
|
||||
// Type A
|
||||
|
@ -481,7 +524,7 @@ uint32_t WaveshareEPaperTypeA::idle_timeout_() {
|
|||
case TTGO_EPAPER_2_13_IN_B1:
|
||||
return 2500;
|
||||
default:
|
||||
return WaveshareEPaper::idle_timeout_();
|
||||
return WaveshareEPaperBase::idle_timeout_();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -687,6 +730,246 @@ void WaveshareEPaper2P7InV2::dump_config() {
|
|||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// 2.7inch_e-paper_b
|
||||
// ========================================================
|
||||
// Datasheet:
|
||||
// - https://www.waveshare.com/w/upload/d/d8/2.7inch-e-paper-b-specification.pdf
|
||||
// - https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_2in7b.c
|
||||
|
||||
static const uint8_t LUT_VCOM_DC_2_7B[44] = {0x00, 0x00, 0x00, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x0A,
|
||||
0x00, 0x00, 0x08, 0x00, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x00, 0x0A,
|
||||
0x0A, 0x00, 0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00,
|
||||
0x03, 0x0E, 0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01};
|
||||
|
||||
static const uint8_t LUT_WHITE_TO_WHITE_2_7B[42] = {0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x40, 0x0A, 0x0A, 0x00, 0x00,
|
||||
0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x80, 0x0A, 0x0A, 0x00,
|
||||
0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, 0x03, 0x0E,
|
||||
0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01};
|
||||
|
||||
static const uint8_t LUT_BLACK_TO_WHITE_2_7B[42] = {0xA0, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x0A, 0x00, 0x00,
|
||||
0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x90, 0x0A, 0x0A, 0x00,
|
||||
0x00, 0x08, 0xB0, 0x04, 0x10, 0x00, 0x00, 0x05, 0xB0, 0x03, 0x0E,
|
||||
0x00, 0x00, 0x0A, 0xC0, 0x23, 0x00, 0x00, 0x00, 0x01};
|
||||
|
||||
static const uint8_t LUT_WHITE_TO_BLACK_2_7B[] = {0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x20, 0x0A, 0x0A, 0x00, 0x00,
|
||||
0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x10, 0x0A, 0x0A, 0x00,
|
||||
0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, 0x03, 0x0E,
|
||||
0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01};
|
||||
|
||||
static const uint8_t LUT_BLACK_TO_BLACK_2_7B[42] = {0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x40, 0x0A, 0x0A, 0x00, 0x00,
|
||||
0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x80, 0x0A, 0x0A, 0x00,
|
||||
0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, 0x03, 0x0E,
|
||||
0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01};
|
||||
|
||||
void WaveshareEPaper2P7InB::initialize() {
|
||||
this->reset_();
|
||||
|
||||
// command power on
|
||||
this->command(0x04);
|
||||
this->wait_until_idle_();
|
||||
delay(10);
|
||||
|
||||
// Command panel setting
|
||||
this->command(0x00);
|
||||
this->data(0xAF); // KW-BF KWR-AF BWROTP 0f
|
||||
// command pll control
|
||||
this->command(0x30);
|
||||
this->data(0x3A); // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ
|
||||
|
||||
// command power setting
|
||||
this->command(0x01);
|
||||
this->data(0x03); // VDS_EN, VDG_EN
|
||||
this->data(0x00); // VCOM_HV, VGHL_LV[1], VGHL_LV[0]
|
||||
this->data(0x2B); // VDH
|
||||
this->data(0x2B); // VDL
|
||||
this->data(0x09); // VDHR
|
||||
|
||||
// command booster soft start
|
||||
this->command(0x06);
|
||||
this->data(0x07);
|
||||
this->data(0x07);
|
||||
this->data(0x17);
|
||||
|
||||
// Power optimization - ???
|
||||
this->command(0xF8);
|
||||
this->data(0x60);
|
||||
this->data(0xA5);
|
||||
this->command(0xF8);
|
||||
this->data(0x89);
|
||||
this->data(0xA5);
|
||||
this->command(0xF8);
|
||||
this->data(0x90);
|
||||
this->data(0x00);
|
||||
this->command(0xF8);
|
||||
this->data(0x93);
|
||||
this->data(0x2A);
|
||||
this->command(0xF8);
|
||||
this->data(0x73);
|
||||
this->data(0x41);
|
||||
|
||||
// COMMAND VCM DC SETTING
|
||||
this->command(0x82);
|
||||
this->data(0x12);
|
||||
|
||||
// VCOM_AND_DATA_INTERVAL_SETTING
|
||||
this->command(0x50);
|
||||
this->data(0x87); // define by OTP
|
||||
|
||||
delay(2);
|
||||
// COMMAND LUT FOR VCOM
|
||||
this->command(0x20);
|
||||
for (uint8_t i : LUT_VCOM_DC_2_7B)
|
||||
this->data(i);
|
||||
// COMMAND LUT WHITE TO WHITE
|
||||
this->command(0x21);
|
||||
for (uint8_t i : LUT_WHITE_TO_WHITE_2_7B)
|
||||
this->data(i);
|
||||
// COMMAND LUT BLACK TO WHITE
|
||||
this->command(0x22);
|
||||
for (uint8_t i : LUT_BLACK_TO_WHITE_2_7B)
|
||||
this->data(i);
|
||||
// COMMAND LUT WHITE TO BLACK
|
||||
this->command(0x23);
|
||||
for (uint8_t i : LUT_WHITE_TO_BLACK_2_7B) {
|
||||
this->data(i);
|
||||
}
|
||||
// COMMAND LUT BLACK TO BLACK
|
||||
this->command(0x24);
|
||||
|
||||
for (uint8_t i : LUT_BLACK_TO_BLACK_2_7B) {
|
||||
this->data(i);
|
||||
}
|
||||
|
||||
delay(2);
|
||||
}
|
||||
|
||||
void HOT WaveshareEPaper2P7InB::display() {
|
||||
uint32_t buf_len_half = this->get_buffer_length_() >> 1;
|
||||
this->initialize();
|
||||
|
||||
// TCON_RESOLUTION
|
||||
this->command(0x61);
|
||||
this->data(this->get_width_internal() >> 8);
|
||||
this->data(this->get_width_internal() & 0xff); // 176
|
||||
this->data(this->get_height_internal() >> 8);
|
||||
this->data(this->get_height_internal() & 0xff); // 264
|
||||
|
||||
// COMMAND DATA START TRANSMISSION 1 (BLACK)
|
||||
this->command(0x10);
|
||||
delay(2);
|
||||
for (uint32_t i = 0; i < buf_len_half; i++) {
|
||||
this->data(this->buffer_[i]);
|
||||
}
|
||||
this->command(0x11);
|
||||
delay(2);
|
||||
|
||||
// COMMAND DATA START TRANSMISSION 2 (RED)
|
||||
this->command(0x13);
|
||||
delay(2);
|
||||
for (uint32_t i = buf_len_half; i < buf_len_half * 2u; i++) {
|
||||
this->data(this->buffer_[i]);
|
||||
}
|
||||
this->command(0x11);
|
||||
|
||||
delay(2);
|
||||
|
||||
// COMMAND DISPLAY REFRESH
|
||||
this->command(0x12);
|
||||
this->wait_until_idle_();
|
||||
|
||||
this->deep_sleep();
|
||||
}
|
||||
int WaveshareEPaper2P7InB::get_width_internal() { return 176; }
|
||||
int WaveshareEPaper2P7InB::get_height_internal() { return 264; }
|
||||
void WaveshareEPaper2P7InB::dump_config() {
|
||||
LOG_DISPLAY("", "Waveshare E-Paper", this);
|
||||
ESP_LOGCONFIG(TAG, " Model: 2.7in B");
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// 2.7inch_e-paper_b_v2
|
||||
// ========================================================
|
||||
// Datasheet:
|
||||
// - https://www.waveshare.com/w/upload/7/7b/2.7inch-e-paper-b-v2-specification.pdf
|
||||
// - https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_2in7b_V2.c
|
||||
|
||||
void WaveshareEPaper2P7InBV2::initialize() {
|
||||
this->reset_();
|
||||
|
||||
this->wait_until_idle_();
|
||||
this->command(0x12);
|
||||
this->wait_until_idle_();
|
||||
|
||||
this->command(0x00);
|
||||
this->data(0x27);
|
||||
this->data(0x01);
|
||||
this->data(0x00);
|
||||
|
||||
this->command(0x11);
|
||||
this->data(0x03);
|
||||
|
||||
// self.SetWindows(0, 0, self.width-1, self.height-1)
|
||||
// SetWindows(self, Xstart, Ystart, Xend, Yend):
|
||||
|
||||
uint32_t xend = this->get_width_internal() - 1;
|
||||
uint32_t yend = this->get_height_internal() - 1;
|
||||
this->command(0x44);
|
||||
this->data(0x00);
|
||||
this->data((xend >> 3) & 0xff);
|
||||
|
||||
this->command(0x45);
|
||||
this->data(0x00);
|
||||
this->data(0x00);
|
||||
this->data(yend & 0xff);
|
||||
this->data((yend >> 8) & 0xff);
|
||||
|
||||
// SetCursor(self, Xstart, Ystart):
|
||||
this->command(0x4E);
|
||||
this->data(0x00);
|
||||
this->command(0x4F);
|
||||
this->data(0x00);
|
||||
this->data(0x00);
|
||||
}
|
||||
|
||||
void HOT WaveshareEPaper2P7InBV2::display() {
|
||||
uint32_t buf_len = this->get_buffer_length_();
|
||||
// COMMAND DATA START TRANSMISSION 1 (BLACK)
|
||||
this->command(0x24);
|
||||
delay(2);
|
||||
for (uint32_t i = 0; i < buf_len; i++) {
|
||||
this->data(this->buffer_[i]);
|
||||
}
|
||||
delay(2);
|
||||
|
||||
// COMMAND DATA START TRANSMISSION 2 (RED)
|
||||
this->command(0x26);
|
||||
delay(2);
|
||||
for (uint32_t i = 0; i < buf_len; i++) {
|
||||
this->data(this->buffer_[i]);
|
||||
}
|
||||
|
||||
delay(2);
|
||||
|
||||
this->command(0x20);
|
||||
|
||||
this->wait_until_idle_();
|
||||
}
|
||||
int WaveshareEPaper2P7InBV2::get_width_internal() { return 176; }
|
||||
int WaveshareEPaper2P7InBV2::get_height_internal() { return 264; }
|
||||
void WaveshareEPaper2P7InBV2::dump_config() {
|
||||
LOG_DISPLAY("", "Waveshare E-Paper", this);
|
||||
ESP_LOGCONFIG(TAG, " Model: 2.7in B V2");
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// 2.90in Type B (LUT from OTP)
|
||||
// Datasheet:
|
||||
|
@ -1443,6 +1726,12 @@ void WaveshareEPaper7P5InBV2::initialize() {
|
|||
// COMMAND TCON SETTING
|
||||
this->command(0x60);
|
||||
this->data(0x22);
|
||||
|
||||
this->command(0x82);
|
||||
this->data(0x08);
|
||||
this->command(0x30);
|
||||
this->data(0x06);
|
||||
|
||||
// COMMAND RESOLUTION SETTING
|
||||
this->command(0x65);
|
||||
this->data(0x00);
|
||||
|
@ -1472,6 +1761,7 @@ void HOT WaveshareEPaper7P5InBV2::display() {
|
|||
this->command(0x12);
|
||||
delay(100); // NOLINT
|
||||
this->wait_until_idle_();
|
||||
this->deep_sleep();
|
||||
}
|
||||
int WaveshareEPaper7P5InBV2::get_width_internal() { return 800; }
|
||||
int WaveshareEPaper7P5InBV2::get_height_internal() { return 480; }
|
||||
|
@ -1617,7 +1907,7 @@ void HOT WaveshareEPaper7P5InBV3::display() {
|
|||
this->command(0x13); // Start Transmission
|
||||
delay(2);
|
||||
for (uint32_t i = 0; i < buf_len; i++) {
|
||||
this->data(this->buffer_[i]);
|
||||
this->data(~this->buffer_[i]);
|
||||
}
|
||||
|
||||
this->command(0x12); // Display Refresh
|
||||
|
@ -2211,8 +2501,9 @@ void HOT WaveshareEPaper2P13InDKE::display() {
|
|||
} else {
|
||||
// set up partial update
|
||||
this->command(0x32);
|
||||
for (uint8_t v : PART_UPDATE_LUT_TTGO_DKE)
|
||||
this->data(v);
|
||||
this->start_data_();
|
||||
this->write_array(PART_UPDATE_LUT_TTGO_DKE, sizeof(PART_UPDATE_LUT_TTGO_DKE));
|
||||
this->end_data_();
|
||||
this->command(0x3F);
|
||||
this->data(0x22);
|
||||
|
||||
|
@ -2257,12 +2548,10 @@ void HOT WaveshareEPaper2P13InDKE::display() {
|
|||
this->wait_until_idle_();
|
||||
|
||||
// data must be sent again on partial update
|
||||
delay(300); // NOLINT
|
||||
this->command(0x24);
|
||||
this->start_data_();
|
||||
this->write_array(this->buffer_, this->get_buffer_length_());
|
||||
this->end_data_();
|
||||
delay(300); // NOLINT
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Completed e-paper update.");
|
||||
|
@ -2274,6 +2563,7 @@ uint32_t WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; }
|
|||
void WaveshareEPaper2P13InDKE::dump_config() {
|
||||
LOG_DISPLAY("", "Waveshare E-Paper", this);
|
||||
ESP_LOGCONFIG(TAG, " Model: 2.13inDKE");
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
namespace esphome {
|
||||
namespace waveshare_epaper {
|
||||
|
||||
class WaveshareEPaper : public display::DisplayBuffer,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_2MHZ> {
|
||||
class WaveshareEPaperBase : public display::DisplayBuffer,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_2MHZ> {
|
||||
public:
|
||||
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
|
||||
float get_setup_priority() const override;
|
||||
|
@ -19,6 +19,7 @@ class WaveshareEPaper : public display::DisplayBuffer,
|
|||
|
||||
void command(uint8_t value);
|
||||
void data(uint8_t value);
|
||||
void cmd_data(const uint8_t *data, size_t length);
|
||||
|
||||
virtual void display() = 0;
|
||||
virtual void initialize() = 0;
|
||||
|
@ -26,8 +27,6 @@ class WaveshareEPaper : public display::DisplayBuffer,
|
|||
|
||||
void update() override;
|
||||
|
||||
void fill(Color color) override;
|
||||
|
||||
void setup() override {
|
||||
this->setup_pins_();
|
||||
this->initialize();
|
||||
|
@ -35,11 +34,7 @@ class WaveshareEPaper : public display::DisplayBuffer,
|
|||
|
||||
void on_safe_shutdown() override;
|
||||
|
||||
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; }
|
||||
|
||||
protected:
|
||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||
|
||||
bool wait_until_idle_();
|
||||
|
||||
void setup_pins_();
|
||||
|
@ -49,13 +44,13 @@ class WaveshareEPaper : public display::DisplayBuffer,
|
|||
this->reset_pin_->digital_write(false);
|
||||
delay(reset_duration_); // NOLINT
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(200); // NOLINT
|
||||
delay(20);
|
||||
}
|
||||
}
|
||||
|
||||
virtual int get_width_controller() { return this->get_width_internal(); };
|
||||
|
||||
uint32_t get_buffer_length_();
|
||||
virtual uint32_t get_buffer_length_() = 0; // NOLINT(readability-identifier-naming)
|
||||
uint32_t reset_duration_{200};
|
||||
|
||||
void start_command_();
|
||||
|
@ -69,6 +64,28 @@ class WaveshareEPaper : public display::DisplayBuffer,
|
|||
virtual uint32_t idle_timeout_() { return 1000u; } // NOLINT(readability-identifier-naming)
|
||||
};
|
||||
|
||||
class WaveshareEPaper : public WaveshareEPaperBase {
|
||||
public:
|
||||
void fill(Color color) override;
|
||||
|
||||
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; }
|
||||
|
||||
protected:
|
||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||
uint32_t get_buffer_length_() override;
|
||||
};
|
||||
|
||||
class WaveshareEPaperBWR : public WaveshareEPaperBase {
|
||||
public:
|
||||
void fill(Color color) override;
|
||||
|
||||
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
|
||||
|
||||
protected:
|
||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||
uint32_t get_buffer_length_() override;
|
||||
};
|
||||
|
||||
enum WaveshareEPaperTypeAModel {
|
||||
WAVESHARE_EPAPER_1_54_IN = 0,
|
||||
WAVESHARE_EPAPER_1_54_IN_V2,
|
||||
|
@ -133,6 +150,8 @@ class WaveshareEPaperTypeA : public WaveshareEPaper {
|
|||
|
||||
enum WaveshareEPaperTypeBModel {
|
||||
WAVESHARE_EPAPER_2_7_IN = 0,
|
||||
WAVESHARE_EPAPER_2_7_IN_B,
|
||||
WAVESHARE_EPAPER_2_7_IN_B_V2,
|
||||
WAVESHARE_EPAPER_4_2_IN,
|
||||
WAVESHARE_EPAPER_4_2_IN_B_V2,
|
||||
WAVESHARE_EPAPER_7_5_IN,
|
||||
|
@ -154,6 +173,68 @@ class WaveshareEPaper2P7In : public WaveshareEPaper {
|
|||
this->data(0xA5); // check byte
|
||||
}
|
||||
|
||||
protected:
|
||||
int get_width_internal() override;
|
||||
int get_height_internal() override;
|
||||
};
|
||||
|
||||
class WaveshareEPaper2P7InB : public WaveshareEPaperBWR {
|
||||
public:
|
||||
void initialize() override;
|
||||
|
||||
void display() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void deep_sleep() override {
|
||||
// COMMAND VCOM_AND_DATA_INTERVAL_SETTING
|
||||
this->command(0x50);
|
||||
// COMMAND POWER OFF
|
||||
this->command(0x02);
|
||||
this->wait_until_idle_();
|
||||
// COMMAND DEEP SLEEP
|
||||
this->command(0x07); // deep sleep
|
||||
this->data(0xA5); // check byte
|
||||
}
|
||||
|
||||
protected:
|
||||
int get_width_internal() override;
|
||||
int get_height_internal() override;
|
||||
};
|
||||
|
||||
class WaveshareEPaper2P7InBV2 : public WaveshareEPaperBWR {
|
||||
public:
|
||||
void initialize() override;
|
||||
|
||||
void display() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void deep_sleep() override {
|
||||
// COMMAND DEEP SLEEP
|
||||
this->command(0x10);
|
||||
this->data(0x01);
|
||||
}
|
||||
|
||||
protected:
|
||||
int get_width_internal() override;
|
||||
int get_height_internal() override;
|
||||
};
|
||||
|
||||
class GDEY029T94 : public WaveshareEPaper {
|
||||
public:
|
||||
void initialize() override;
|
||||
|
||||
void display() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void deep_sleep() override {
|
||||
// COMMAND DEEP SLEEP
|
||||
this->command(0x07);
|
||||
this->data(0xA5); // check byte
|
||||
}
|
||||
|
||||
protected:
|
||||
int get_width_internal() override;
|
||||
|
||||
|
@ -176,26 +257,6 @@ class WaveshareEPaper2P7InV2 : public WaveshareEPaper {
|
|||
int get_height_internal() override;
|
||||
};
|
||||
|
||||
class GDEY029T94 : public WaveshareEPaper {
|
||||
public:
|
||||
void initialize() override;
|
||||
|
||||
void display() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void deep_sleep() override {
|
||||
// COMMAND DEEP SLEEP
|
||||
this->command(0x07);
|
||||
this->data(0xA5); // check byte
|
||||
}
|
||||
|
||||
protected:
|
||||
int get_width_internal() override;
|
||||
|
||||
int get_height_internal() override;
|
||||
};
|
||||
|
||||
class GDEW0154M09 : public WaveshareEPaper {
|
||||
public:
|
||||
void initialize() override;
|
||||
|
@ -614,5 +675,39 @@ class WaveshareEPaper2P13InDKE : public WaveshareEPaper {
|
|||
uint32_t at_update_{0};
|
||||
};
|
||||
|
||||
class WaveshareEPaper2P13InV3 : public WaveshareEPaper {
|
||||
public:
|
||||
void display() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void deep_sleep() override {
|
||||
// COMMAND POWER DOWN
|
||||
this->command(0x10);
|
||||
this->data(0x01);
|
||||
// cannot wait until idle here, the device no longer responds
|
||||
}
|
||||
|
||||
void set_full_update_every(uint32_t full_update_every);
|
||||
|
||||
void setup() override;
|
||||
void initialize() override;
|
||||
|
||||
protected:
|
||||
int get_width_internal() override;
|
||||
int get_height_internal() override;
|
||||
uint32_t idle_timeout_() override;
|
||||
|
||||
void write_buffer_(uint8_t cmd, int top, int bottom);
|
||||
void set_window_(int t, int b);
|
||||
void send_reset_();
|
||||
void partial_update_();
|
||||
void full_update_();
|
||||
|
||||
uint32_t full_update_every_{30};
|
||||
uint32_t at_update_{0};
|
||||
bool is_busy_{false};
|
||||
void write_lut_(const uint8_t *lut);
|
||||
};
|
||||
} // namespace waveshare_epaper
|
||||
} // namespace esphome
|
||||
|
|
|
@ -55,6 +55,9 @@ void WiFiComponent::start() {
|
|||
uint32_t hash = this->has_sta() ? fnv1_hash(App.get_compilation_time()) : 88491487UL;
|
||||
|
||||
this->pref_ = global_preferences->make_preference<wifi::SavedWifiSettings>(hash, true);
|
||||
if (this->fast_connect_) {
|
||||
this->fast_connect_pref_ = global_preferences->make_preference<wifi::SavedWifiFastConnectSettings>(hash, false);
|
||||
}
|
||||
|
||||
SavedWifiSettings save{};
|
||||
if (this->pref_.load(&save)) {
|
||||
|
@ -78,6 +81,7 @@ void WiFiComponent::start() {
|
|||
|
||||
if (this->fast_connect_) {
|
||||
this->selected_ap_ = this->sta_[0];
|
||||
this->load_fast_connect_settings_();
|
||||
this->start_connecting(this->selected_ap_, false);
|
||||
} else {
|
||||
this->start_scanning();
|
||||
|
@ -604,6 +608,11 @@ void WiFiComponent::check_connecting_finished() {
|
|||
|
||||
this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTED;
|
||||
this->num_retried_ = 0;
|
||||
|
||||
if (this->fast_connect_) {
|
||||
this->save_fast_connect_settings_();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -705,6 +714,35 @@ bool WiFiComponent::is_esp32_improv_active_() {
|
|||
#endif
|
||||
}
|
||||
|
||||
void WiFiComponent::load_fast_connect_settings_() {
|
||||
SavedWifiFastConnectSettings fast_connect_save{};
|
||||
|
||||
if (this->fast_connect_pref_.load(&fast_connect_save)) {
|
||||
bssid_t bssid{};
|
||||
std::copy(fast_connect_save.bssid, fast_connect_save.bssid + 6, bssid.begin());
|
||||
this->selected_ap_.set_bssid(bssid);
|
||||
this->selected_ap_.set_channel(fast_connect_save.channel);
|
||||
|
||||
ESP_LOGD(TAG, "Loaded saved fast_connect wifi settings");
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiComponent::save_fast_connect_settings_() {
|
||||
bssid_t bssid = wifi_bssid();
|
||||
uint8_t channel = wifi_channel_();
|
||||
|
||||
if (bssid != this->selected_ap_.get_bssid() || channel != this->selected_ap_.get_channel()) {
|
||||
SavedWifiFastConnectSettings fast_connect_save{};
|
||||
|
||||
memcpy(fast_connect_save.bssid, bssid.data(), 6);
|
||||
fast_connect_save.channel = channel;
|
||||
|
||||
this->fast_connect_pref_.save(&fast_connect_save);
|
||||
|
||||
ESP_LOGD(TAG, "Saved fast_connect wifi settings");
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiAP::set_ssid(const std::string &ssid) { this->ssid_ = ssid; }
|
||||
void WiFiAP::set_bssid(bssid_t bssid) { this->bssid_ = bssid; }
|
||||
void WiFiAP::set_bssid(optional<bssid_t> bssid) { this->bssid_ = bssid; }
|
||||
|
|
|
@ -48,6 +48,11 @@ struct SavedWifiSettings {
|
|||
char password[65];
|
||||
} PACKED; // NOLINT
|
||||
|
||||
struct SavedWifiFastConnectSettings {
|
||||
uint8_t bssid[6];
|
||||
uint8_t channel;
|
||||
} PACKED; // NOLINT
|
||||
|
||||
enum WiFiComponentState {
|
||||
/** Nothing has been initialized yet. Internal AP, if configured, is disabled at this point. */
|
||||
WIFI_COMPONENT_STATE_OFF = 0,
|
||||
|
@ -334,6 +339,9 @@ class WiFiComponent : public Component {
|
|||
bool is_captive_portal_active_();
|
||||
bool is_esp32_improv_active_();
|
||||
|
||||
void load_fast_connect_settings_();
|
||||
void save_fast_connect_settings_();
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
static void wifi_event_callback(System_Event_t *event);
|
||||
void wifi_scan_done_callback_(void *arg, STATUS status);
|
||||
|
@ -381,6 +389,7 @@ class WiFiComponent : public Component {
|
|||
optional<float> output_power_;
|
||||
bool passive_scan_{false};
|
||||
ESPPreferenceObject pref_;
|
||||
ESPPreferenceObject fast_connect_pref_;
|
||||
bool has_saved_wifi_settings_{false};
|
||||
#ifdef USE_WIFI_11KV_SUPPORT
|
||||
bool btm_{false};
|
||||
|
|
|
@ -8,6 +8,8 @@ wled_ns = cg.esphome_ns.namespace("wled")
|
|||
WLEDLightEffect = wled_ns.class_("WLEDLightEffect", AddressableLightEffect)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(cv.Schema({}), cv.only_with_arduino)
|
||||
CONF_SYNC_GROUP_MASK = "sync_group_mask"
|
||||
CONF_BLANK_ON_START = "blank_on_start"
|
||||
|
||||
|
||||
@register_addressable_effect(
|
||||
|
@ -16,10 +18,13 @@ CONFIG_SCHEMA = cv.All(cv.Schema({}), cv.only_with_arduino)
|
|||
"WLED",
|
||||
{
|
||||
cv.Optional(CONF_PORT, default=21324): cv.port,
|
||||
cv.Optional(CONF_SYNC_GROUP_MASK, default=0): cv.int_range(min=0, max=255),
|
||||
cv.Optional(CONF_BLANK_ON_START, default=True): cv.boolean,
|
||||
},
|
||||
)
|
||||
async def wled_light_effect_to_code(config, effect_id):
|
||||
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||
cg.add(effect.set_port(config[CONF_PORT]))
|
||||
|
||||
cg.add(effect.set_sync_group_mask(config[CONF_SYNC_GROUP_MASK]))
|
||||
cg.add(effect.set_blank_on_start(config[CONF_BLANK_ON_START]))
|
||||
return effect
|
||||
|
|
|
@ -13,6 +13,10 @@
|
|||
#include <WiFiUdp.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_BK72XX
|
||||
#include <WiFiUdp.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace wled {
|
||||
|
||||
|
@ -29,7 +33,11 @@ WLEDLightEffect::WLEDLightEffect(const std::string &name) : AddressableLightEffe
|
|||
void WLEDLightEffect::start() {
|
||||
AddressableLightEffect::start();
|
||||
|
||||
blank_at_ = 0;
|
||||
if (this->blank_on_start_) {
|
||||
this->blank_at_ = 0;
|
||||
} else {
|
||||
this->blank_at_ = UINT32_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
void WLEDLightEffect::stop() {
|
||||
|
@ -101,8 +109,11 @@ bool WLEDLightEffect::parse_frame_(light::AddressableLight &it, const uint8_t *p
|
|||
if (!parse_drgb_frame_(it, payload, size))
|
||||
return false;
|
||||
} else {
|
||||
if (!parse_notifier_frame_(it, payload, size))
|
||||
if (!parse_notifier_frame_(it, payload, size)) {
|
||||
return false;
|
||||
} else {
|
||||
timeout = UINT8_MAX;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -143,8 +154,32 @@ bool WLEDLightEffect::parse_frame_(light::AddressableLight &it, const uint8_t *p
|
|||
}
|
||||
|
||||
bool WLEDLightEffect::parse_notifier_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
|
||||
// Packet needs to be empty
|
||||
return size == 0;
|
||||
// Receive at least RGBW and Brightness for all LEDs from WLED Sync Notification
|
||||
// https://kno.wled.ge/interfaces/udp-notifier/
|
||||
// https://github.com/Aircoookie/WLED/blob/main/wled00/udp.cpp
|
||||
|
||||
if (size < 34) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t payload_sync_group_mask = payload[34];
|
||||
|
||||
if ((payload_sync_group_mask & this->sync_group_mask_) != this->sync_group_mask_) {
|
||||
ESP_LOGD(TAG, "sync group mask does not match");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t bri = payload[0];
|
||||
uint8_t r = esp_scale8(payload[1], bri);
|
||||
uint8_t g = esp_scale8(payload[2], bri);
|
||||
uint8_t b = esp_scale8(payload[3], bri);
|
||||
uint8_t w = esp_scale8(payload[8], bri);
|
||||
|
||||
for (auto &&led : it) {
|
||||
led.set(Color(r, g, b, w));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WLEDLightEffect::parse_warls_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
|
||||
|
|
|
@ -21,6 +21,8 @@ class WLEDLightEffect : public light::AddressableLightEffect {
|
|||
void stop() override;
|
||||
void apply(light::AddressableLight &it, const Color ¤t_color) override;
|
||||
void set_port(uint16_t port) { this->port_ = port; }
|
||||
void set_sync_group_mask(uint8_t mask) { this->sync_group_mask_ = mask; }
|
||||
void set_blank_on_start(bool blank) { this->blank_on_start_ = blank; }
|
||||
|
||||
protected:
|
||||
void blank_all_leds_(light::AddressableLight &it);
|
||||
|
@ -35,6 +37,8 @@ class WLEDLightEffect : public light::AddressableLightEffect {
|
|||
std::unique_ptr<UDP> udp_;
|
||||
uint32_t blank_at_{0};
|
||||
uint32_t dropped_{0};
|
||||
uint8_t sync_group_mask_{0};
|
||||
bool blank_on_start_{true};
|
||||
};
|
||||
|
||||
} // namespace wled
|
||||
|
|
|
@ -112,6 +112,8 @@ CONF_CHANNELS = "channels"
|
|||
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
|
||||
CONF_CHIPSET = "chipset"
|
||||
CONF_CLEAR_IMPEDANCE = "clear_impedance"
|
||||
CONF_CLIENT_CERTIFICATE = "client_certificate"
|
||||
CONF_CLIENT_CERTIFICATE_KEY = "client_certificate_key"
|
||||
CONF_CLIENT_ID = "client_id"
|
||||
CONF_CLK_PIN = "clk_pin"
|
||||
CONF_CLOCK_PIN = "clock_pin"
|
||||
|
@ -1080,6 +1082,7 @@ DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds"
|
|||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS = "volatile_organic_compounds_parts"
|
||||
DEVICE_CLASS_VOLTAGE = "voltage"
|
||||
DEVICE_CLASS_VOLUME = "volume"
|
||||
DEVICE_CLASS_VOLUME_FLOW_RATE = "volume_flow_rate"
|
||||
DEVICE_CLASS_VOLUME_STORAGE = "volume_storage"
|
||||
DEVICE_CLASS_WATER = "water"
|
||||
DEVICE_CLASS_WEIGHT = "weight"
|
||||
|
|
|
@ -169,7 +169,7 @@ float Component::get_actual_setup_priority() const {
|
|||
void Component::set_setup_priority(float priority) { this->setup_priority_override_ = priority; }
|
||||
|
||||
bool Component::has_overridden_loop() const {
|
||||
#ifdef CLANG_TIDY
|
||||
#if defined(USE_HOST) || defined(CLANG_TIDY)
|
||||
bool loop_overridden = true;
|
||||
bool call_loop_overridden = true;
|
||||
#else
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/optional.h"
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#define USE_OTA
|
||||
#define USE_OTA_PASSWORD
|
||||
#define USE_OTA_STATE_CALLBACK
|
||||
#define USE_OTA_VERSION 1
|
||||
#define USE_OUTPUT
|
||||
#define USE_POWER_SUPPLY
|
||||
#define USE_QR_CODE
|
||||
|
|
|
@ -11,6 +11,12 @@
|
|||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef USE_HOST
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#if defined(USE_ESP8266)
|
||||
#include <osapi.h>
|
||||
#include <user_interface.h>
|
||||
|
@ -415,7 +421,7 @@ std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) {
|
|||
int8_t step_to_accuracy_decimals(float step) {
|
||||
// use printf %g to find number of digits based on temperature step
|
||||
char buf[32];
|
||||
sprintf(buf, "%.5g", step);
|
||||
snprintf(buf, sizeof buf, "%.5g", step);
|
||||
|
||||
std::string str{buf};
|
||||
size_t dot_pos = str.find('.');
|
||||
|
@ -551,7 +557,10 @@ void HighFrequencyLoopRequester::stop() {
|
|||
bool HighFrequencyLoopRequester::is_high_frequency() { return num_requests > 0; }
|
||||
|
||||
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
|
||||
#if defined(USE_ESP32)
|
||||
#if defined(USE_HOST)
|
||||
static const uint8_t esphome_host_mac_address[6] = USE_ESPHOME_HOST_MAC_ADDRESS;
|
||||
memcpy(mac, esphome_host_mac_address, sizeof(esphome_host_mac_address));
|
||||
#elif defined(USE_ESP32)
|
||||
#if defined(CONFIG_SOC_IEEE802154_SUPPORTED) || defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC)
|
||||
// When CONFIG_SOC_IEEE802154_SUPPORTED is defined, esp_efuse_mac_get_default
|
||||
// returns the 802.15.4 EUI-64 address. Read directly from eFuse instead.
|
||||
|
@ -569,6 +578,8 @@ void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parame
|
|||
WiFi.macAddress(mac);
|
||||
#elif defined(USE_LIBRETINY)
|
||||
WiFi.macAddress(mac);
|
||||
#else
|
||||
// this should be an error, but that messes with CI checks. #error No mac address method defined
|
||||
#endif
|
||||
}
|
||||
std::string get_mac_address() {
|
||||
|
|
|
@ -15,17 +15,18 @@ std::unique_ptr<RingBuffer> RingBuffer::create(size_t len) {
|
|||
std::unique_ptr<RingBuffer> rb = make_unique<RingBuffer>();
|
||||
|
||||
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||
rb->storage_ = allocator.allocate(len);
|
||||
rb->storage_ = allocator.allocate(len + 1);
|
||||
if (rb->storage_ == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rb->handle_ = xStreamBufferCreateStatic(len, 0, rb->storage_, &rb->structure_);
|
||||
rb->handle_ = xStreamBufferCreateStatic(len + 1, 0, rb->storage_, &rb->structure_);
|
||||
ESP_LOGD(TAG, "Created ring buffer with size %u", len);
|
||||
return rb;
|
||||
}
|
||||
|
||||
size_t RingBuffer::read(void *data, size_t size, TickType_t ticks_to_wait) {
|
||||
return xStreamBufferReceive(this->handle_, data, size, ticks_to_wait);
|
||||
size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) {
|
||||
return xStreamBufferReceive(this->handle_, data, len, ticks_to_wait);
|
||||
}
|
||||
|
||||
size_t RingBuffer::write(void *data, size_t len) {
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace esphome {
|
|||
|
||||
class RingBuffer {
|
||||
public:
|
||||
size_t read(void *data, size_t size, TickType_t ticks_to_wait = 0);
|
||||
size_t read(void *data, size_t len, TickType_t ticks_to_wait = 0);
|
||||
|
||||
size_t write(void *data, size_t len);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
|
|
|
@ -478,7 +478,7 @@ def variable(
|
|||
:param type_: Manually define a type for the variable, only use this when it's not possible
|
||||
to do so during config validation phase (for example because of template arguments).
|
||||
|
||||
:returns The new variable as a MockObj.
|
||||
:return: The new variable as a MockObj.
|
||||
"""
|
||||
assert isinstance(id_, ID)
|
||||
rhs = safe_exp(rhs)
|
||||
|
@ -526,7 +526,7 @@ def new_variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj
|
|||
:param type_: Manually define a type for the variable, only use this when it's not possible
|
||||
to do so during config validation phase (for example because of template arguments).
|
||||
|
||||
:returns The new variable as a MockObj.
|
||||
:return: The new variable as a MockObj.
|
||||
"""
|
||||
assert isinstance(id_, ID)
|
||||
rhs = safe_exp(rhs)
|
||||
|
@ -549,7 +549,7 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
|
|||
:param type_: Manually define a type for the variable, only use this when it's not possible
|
||||
to do so during config validation phase (for example because of template arguments).
|
||||
|
||||
:returns The new variable as a MockObj.
|
||||
:return: The new variable as a MockObj.
|
||||
"""
|
||||
rhs = safe_exp(rhs)
|
||||
obj = MockObj(id_, "->")
|
||||
|
@ -570,7 +570,7 @@ def new_Pvariable(id_: ID, *args: SafeExpType) -> Pvariable:
|
|||
:param id_: The ID used to declare the variable (also specifies the type).
|
||||
:param args: The values to pass to the constructor.
|
||||
|
||||
:returns The new variable as a MockObj.
|
||||
:return: The new variable as a MockObj.
|
||||
"""
|
||||
if args and isinstance(args[0], TemplateArguments):
|
||||
id_ = id_.copy()
|
||||
|
|
|
@ -12,32 +12,34 @@ import time
|
|||
from esphome.core import EsphomeError
|
||||
from esphome.helpers import is_ip_address, resolve_ip_address
|
||||
|
||||
RESPONSE_OK = 0
|
||||
RESPONSE_REQUEST_AUTH = 1
|
||||
RESPONSE_OK = 0x00
|
||||
RESPONSE_REQUEST_AUTH = 0x01
|
||||
|
||||
RESPONSE_HEADER_OK = 64
|
||||
RESPONSE_AUTH_OK = 65
|
||||
RESPONSE_UPDATE_PREPARE_OK = 66
|
||||
RESPONSE_BIN_MD5_OK = 67
|
||||
RESPONSE_RECEIVE_OK = 68
|
||||
RESPONSE_UPDATE_END_OK = 69
|
||||
RESPONSE_SUPPORTS_COMPRESSION = 70
|
||||
RESPONSE_HEADER_OK = 0x40
|
||||
RESPONSE_AUTH_OK = 0x41
|
||||
RESPONSE_UPDATE_PREPARE_OK = 0x42
|
||||
RESPONSE_BIN_MD5_OK = 0x43
|
||||
RESPONSE_RECEIVE_OK = 0x44
|
||||
RESPONSE_UPDATE_END_OK = 0x45
|
||||
RESPONSE_SUPPORTS_COMPRESSION = 0x46
|
||||
RESPONSE_CHUNK_OK = 0x47
|
||||
|
||||
RESPONSE_ERROR_MAGIC = 128
|
||||
RESPONSE_ERROR_UPDATE_PREPARE = 129
|
||||
RESPONSE_ERROR_AUTH_INVALID = 130
|
||||
RESPONSE_ERROR_WRITING_FLASH = 131
|
||||
RESPONSE_ERROR_UPDATE_END = 132
|
||||
RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 133
|
||||
RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 134
|
||||
RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 135
|
||||
RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 136
|
||||
RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137
|
||||
RESPONSE_ERROR_NO_UPDATE_PARTITION = 138
|
||||
RESPONSE_ERROR_MD5_MISMATCH = 139
|
||||
RESPONSE_ERROR_UNKNOWN = 255
|
||||
RESPONSE_ERROR_MAGIC = 0x80
|
||||
RESPONSE_ERROR_UPDATE_PREPARE = 0x81
|
||||
RESPONSE_ERROR_AUTH_INVALID = 0x82
|
||||
RESPONSE_ERROR_WRITING_FLASH = 0x83
|
||||
RESPONSE_ERROR_UPDATE_END = 0x84
|
||||
RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 0x85
|
||||
RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 0x86
|
||||
RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 0x87
|
||||
RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 0x88
|
||||
RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 0x89
|
||||
RESPONSE_ERROR_NO_UPDATE_PARTITION = 0x8A
|
||||
RESPONSE_ERROR_MD5_MISMATCH = 0x8B
|
||||
RESPONSE_ERROR_UNKNOWN = 0xFF
|
||||
|
||||
OTA_VERSION_1_0 = 1
|
||||
OTA_VERSION_2_0 = 2
|
||||
|
||||
MAGIC_BYTES = [0x6C, 0x26, 0xF7, 0x5C, 0x45]
|
||||
|
||||
|
@ -203,7 +205,8 @@ def perform_ota(
|
|||
send_check(sock, MAGIC_BYTES, "magic bytes")
|
||||
|
||||
_, version = receive_exactly(sock, 2, "version", RESPONSE_OK)
|
||||
if version != OTA_VERSION_1_0:
|
||||
_LOGGER.debug("Device support OTA version: %s", version)
|
||||
if version not in (OTA_VERSION_1_0, OTA_VERSION_2_0):
|
||||
raise OTAError(f"Unsupported OTA version {version}")
|
||||
|
||||
# Features
|
||||
|
@ -279,6 +282,8 @@ def perform_ota(
|
|||
|
||||
try:
|
||||
sock.sendall(chunk)
|
||||
if version >= OTA_VERSION_2_0:
|
||||
receive_exactly(sock, 1, "chunk OK", RESPONSE_CHUNK_OK)
|
||||
except OSError as err:
|
||||
sys.stderr.write("\n")
|
||||
raise OTAError(f"Error sending data: {err}") from err
|
||||
|
|
|
@ -3,6 +3,7 @@ from contextlib import suppress
|
|||
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
import tempfile
|
||||
|
@ -11,6 +12,10 @@ import re
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
IS_MACOS = platform.system() == "Darwin"
|
||||
IS_WINDOWS = platform.system() == "Windows"
|
||||
IS_LINUX = platform.system() == "Linux"
|
||||
|
||||
|
||||
def ensure_unique_string(preferred_string, current_strings):
|
||||
test_string = preferred_string
|
||||
|
|
|
@ -7,6 +7,7 @@ import logging
|
|||
import math
|
||||
import os
|
||||
import uuid
|
||||
from io import TextIOWrapper
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
@ -19,7 +20,7 @@ except ImportError:
|
|||
FastestAvailableSafeLoader = PurePythonLoader
|
||||
|
||||
from esphome import core
|
||||
from esphome.config_helpers import Extend, Remove, read_config_file
|
||||
from esphome.config_helpers import Extend, Remove
|
||||
from esphome.core import (
|
||||
CORE,
|
||||
DocumentRange,
|
||||
|
@ -418,19 +419,26 @@ def load_yaml(fname: str, clear_secrets: bool = True) -> Any:
|
|||
|
||||
def _load_yaml_internal(fname: str) -> Any:
|
||||
"""Load a YAML file."""
|
||||
content = read_config_file(fname)
|
||||
try:
|
||||
return _load_yaml_internal_with_type(ESPHomeLoader, fname, content)
|
||||
except EsphomeError:
|
||||
# Loading failed, so we now load with the Python loader which has more
|
||||
# readable exceptions
|
||||
return _load_yaml_internal_with_type(ESPHomePurePythonLoader, fname, content)
|
||||
with open(fname, encoding="utf-8") as f_handle:
|
||||
try:
|
||||
return _load_yaml_internal_with_type(ESPHomeLoader, fname, f_handle)
|
||||
except EsphomeError:
|
||||
# Loading failed, so we now load with the Python loader which has more
|
||||
# readable exceptions
|
||||
# Rewind the stream so we can try again
|
||||
f_handle.seek(0, 0)
|
||||
return _load_yaml_internal_with_type(
|
||||
ESPHomePurePythonLoader, fname, f_handle
|
||||
)
|
||||
except (UnicodeDecodeError, OSError) as err:
|
||||
raise EsphomeError(f"Error reading file {fname}: {err}") from err
|
||||
|
||||
|
||||
def _load_yaml_internal_with_type(
|
||||
loader_type: type[ESPHomeLoader] | type[ESPHomePurePythonLoader],
|
||||
fname: str,
|
||||
content: str,
|
||||
content: TextIOWrapper,
|
||||
) -> Any:
|
||||
"""Load a YAML file."""
|
||||
loader = loader_type(content)
|
||||
|
|
|
@ -388,3 +388,4 @@ lib_deps =
|
|||
build_flags =
|
||||
${common.build_flags}
|
||||
-DUSE_HOST
|
||||
-std=c++17
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
async_timeout==4.0.3; python_version <= "3.10"
|
||||
cryptography==42.0.2
|
||||
voluptuous==0.14.1
|
||||
PyYAML==6.0.1
|
||||
paho-mqtt==1.6.1
|
||||
|
@ -8,11 +9,11 @@ tornado==6.4
|
|||
tzlocal==5.2 # from time
|
||||
tzdata>=2021.1 # from time
|
||||
pyserial==3.5
|
||||
platformio==6.1.11 # When updating platformio, also update Dockerfile
|
||||
platformio==6.1.13 # When updating platformio, also update Dockerfile
|
||||
esptool==4.7.0
|
||||
click==8.1.7
|
||||
esphome-dashboard==20231107.0
|
||||
aioesphomeapi==21.0.1
|
||||
aioesphomeapi==21.0.2
|
||||
zeroconf==0.131.0
|
||||
python-magic==0.4.27
|
||||
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
pillow==10.2.0
|
||||
cairosvg==2.7.1
|
||||
cryptography==41.0.4
|
||||
|
|
|
@ -12,3 +12,4 @@ script/lint-cpp
|
|||
script/unit_test
|
||||
script/component_test
|
||||
script/test
|
||||
script/test_build_components
|
||||
|
|
153
script/list-components.py
Executable file
153
script/list-components.py
Executable file
|
@ -0,0 +1,153 @@
|
|||
#!/usr/bin/env python3
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
from helpers import git_ls_files, changed_files
|
||||
from esphome.loader import get_component, get_platform
|
||||
from esphome.core import CORE
|
||||
from esphome.const import (
|
||||
KEY_CORE,
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
)
|
||||
|
||||
|
||||
def filter_component_files(str):
|
||||
return str.startswith("esphome/components/") | str.startswith("tests/components/")
|
||||
|
||||
|
||||
def extract_component_names_array_from_files_array(files):
|
||||
components = []
|
||||
for file in files:
|
||||
file_parts = file.split("/")
|
||||
if len(file_parts) >= 4:
|
||||
component_name = file_parts[2]
|
||||
if component_name not in components:
|
||||
components.append(component_name)
|
||||
return components
|
||||
|
||||
|
||||
def add_item_to_components_graph(components_graph, parent, child):
|
||||
if not parent.startswith("__") and parent != child:
|
||||
if parent not in components_graph:
|
||||
components_graph[parent] = []
|
||||
if child not in components_graph[parent]:
|
||||
components_graph[parent].append(child)
|
||||
|
||||
|
||||
def create_components_graph():
|
||||
# The root directory of the repo
|
||||
root = Path(__file__).parent.parent
|
||||
components_dir = root / "esphome" / "components"
|
||||
# Fake some directory so that get_component works
|
||||
CORE.config_path = str(root)
|
||||
# Various configuration to capture different outcomes used by `AUTO_LOAD` function.
|
||||
TARGET_CONFIGURATIONS = [
|
||||
{KEY_TARGET_FRAMEWORK: None, KEY_TARGET_PLATFORM: None},
|
||||
{KEY_TARGET_FRAMEWORK: "arduino", KEY_TARGET_PLATFORM: None},
|
||||
{KEY_TARGET_FRAMEWORK: "esp-idf", KEY_TARGET_PLATFORM: None},
|
||||
{KEY_TARGET_FRAMEWORK: None, KEY_TARGET_PLATFORM: PLATFORM_ESP32},
|
||||
]
|
||||
CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0]
|
||||
|
||||
components_graph = {}
|
||||
|
||||
for path in components_dir.iterdir():
|
||||
if not path.is_dir():
|
||||
continue
|
||||
if not (path / "__init__.py").is_file():
|
||||
continue
|
||||
name = path.name
|
||||
comp = get_component(name)
|
||||
if comp is None:
|
||||
print(
|
||||
f"Cannot find component {name}. Make sure current path is pip installed ESPHome"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
for dependency in comp.dependencies:
|
||||
add_item_to_components_graph(components_graph, dependency, name)
|
||||
|
||||
for target_config in TARGET_CONFIGURATIONS:
|
||||
CORE.data[KEY_CORE] = target_config
|
||||
for auto_load in comp.auto_load:
|
||||
add_item_to_components_graph(components_graph, auto_load, name)
|
||||
# restore config
|
||||
CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0]
|
||||
|
||||
for platform_path in path.iterdir():
|
||||
platform_name = platform_path.stem
|
||||
platform = get_platform(platform_name, name)
|
||||
if platform is None:
|
||||
continue
|
||||
|
||||
add_item_to_components_graph(components_graph, platform_name, name)
|
||||
|
||||
for dependency in platform.dependencies:
|
||||
add_item_to_components_graph(components_graph, dependency, name)
|
||||
|
||||
for target_config in TARGET_CONFIGURATIONS:
|
||||
CORE.data[KEY_CORE] = target_config
|
||||
for auto_load in platform.auto_load:
|
||||
add_item_to_components_graph(components_graph, auto_load, name)
|
||||
# restore config
|
||||
CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0]
|
||||
|
||||
return components_graph
|
||||
|
||||
|
||||
def find_children_of_component(components_graph, component_name, depth=0):
|
||||
if component_name not in components_graph:
|
||||
return []
|
||||
|
||||
children = []
|
||||
|
||||
for child in components_graph[component_name]:
|
||||
children.append(child)
|
||||
if depth < 10:
|
||||
children.extend(
|
||||
find_children_of_component(components_graph, child, depth + 1)
|
||||
)
|
||||
# Remove duplicate values
|
||||
return list(set(children))
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-c", "--changed", action="store_true", help="Only run on changed files"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
files = git_ls_files()
|
||||
files = filter(filter_component_files, files)
|
||||
|
||||
if args.changed:
|
||||
changed = changed_files()
|
||||
files = [f for f in files if f in changed]
|
||||
|
||||
components = extract_component_names_array_from_files_array(files)
|
||||
|
||||
if args.changed:
|
||||
components_graph = create_components_graph()
|
||||
|
||||
all_changed_components = components.copy()
|
||||
for c in components:
|
||||
all_changed_components.extend(
|
||||
find_children_of_component(components_graph, c)
|
||||
)
|
||||
# Remove duplicate values
|
||||
all_changed_components = list(set(all_changed_components))
|
||||
|
||||
for c in sorted(all_changed_components):
|
||||
print(c)
|
||||
else:
|
||||
for c in sorted(components):
|
||||
print(c)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
85
script/test_build_components
Executable file
85
script/test_build_components
Executable file
|
@ -0,0 +1,85 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# Parse parameter:
|
||||
# - `e` - Parameter for `esphome` command. Default `compile`. Common alternative is `config`.
|
||||
# - `c` - Component folder name to test. Default `*`.
|
||||
esphome_command="compile"
|
||||
target_component="*"
|
||||
while getopts e:c: flag
|
||||
do
|
||||
case $flag in
|
||||
e) esphome_command=${OPTARG};;
|
||||
c) target_component=${OPTARG};;
|
||||
\?) echo "Usage: $0 [-e <config|compile|clean>] [-c <string>]" 1>&2; exit 1;;
|
||||
esac
|
||||
done
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if ! [ -d "./tests/test_build_components/build" ]; then
|
||||
mkdir ./tests/test_build_components/build
|
||||
fi
|
||||
|
||||
start_esphome() {
|
||||
# create dynamic yaml file in `build` folder.
|
||||
# `./tests/test_build_components/build/[target_component].[test_name].[target_platform].yaml`
|
||||
component_test_file="./tests/test_build_components/build/$target_component.$test_name.$target_platform.yaml"
|
||||
|
||||
cp $target_platform_file $component_test_file
|
||||
sed -i "s!\$component_test_file!../../.$f!g" $component_test_file
|
||||
|
||||
# Start esphome process
|
||||
echo "> [$target_component] [$test_name] [$target_platform]"
|
||||
echo "esphome -s component_name $target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file"
|
||||
# TODO: Validate escape of Command line substitution value
|
||||
esphome -s component_name $target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file
|
||||
}
|
||||
|
||||
# Find all test yaml files.
|
||||
# - `./tests/components/[target_component]/[test_name].[target_platform].yaml`
|
||||
# - `./tests/components/[target_component]/[test_name].all.yaml`
|
||||
for f in ./tests/components/$target_component/*.*.yaml; do
|
||||
[ -f "$f" ] || continue
|
||||
IFS='/' read -r -a folder_name <<< "$f"
|
||||
target_component="${folder_name[3]}"
|
||||
|
||||
IFS='.' read -r -a file_name <<< "${folder_name[4]}"
|
||||
test_name="${file_name[0]}"
|
||||
target_platform="${file_name[1]}"
|
||||
file_name_parts=${#file_name[@]}
|
||||
|
||||
if [ "$target_platform" = "all" ] || [ $file_name_parts = 2 ]; then
|
||||
# Test has *not* defined a specific target platform. Need to run tests for all possible target platforms.
|
||||
|
||||
for target_platform_file in ./tests/test_build_components/build_components_base.*.yaml; do
|
||||
IFS='/' read -r -a folder_name <<< "$target_platform_file"
|
||||
IFS='.' read -r -a file_name <<< "${folder_name[3]}"
|
||||
target_platform="${file_name[1]}"
|
||||
|
||||
start_esphome
|
||||
done
|
||||
|
||||
else
|
||||
# Test has defined a specific target platform.
|
||||
|
||||
# Validate we have a base test yaml for selected platform.
|
||||
# The target_platform is sourced from the following location.
|
||||
# 1. `./tests/test_build_components/build_components_base.[target_platform].yaml`
|
||||
# 2. `./tests/test_build_components/build_components_base.[target_platform]-ard.yaml`
|
||||
target_platform_file="./tests/test_build_components/build_components_base.$target_platform.yaml"
|
||||
if ! [ -f "$target_platform_file" ]; then
|
||||
# Try find arduino test framework as platform.
|
||||
target_platform_ard="$target_platform-ard"
|
||||
target_platform_file="./tests/test_build_components/build_components_base.$target_platform_ard.yaml"
|
||||
if ! [ -f "$target_platform_file" ]; then
|
||||
echo "No base test file [./tests/test_build_components/build_components_base.$target_platform.yaml, ./tests/build_components_base.$target_platform_ard.yaml] for component test [$f] found."
|
||||
exit 1
|
||||
fi
|
||||
target_platform=$target_platform_ard
|
||||
fi
|
||||
|
||||
start_esphome
|
||||
fi
|
||||
done
|
13
tests/components/a01nyub/test.esp32-c3-idf.yaml
Normal file
13
tests/components/a01nyub/test.esp32-c3-idf.yaml
Normal file
|
@ -0,0 +1,13 @@
|
|||
uart:
|
||||
- id: uart_a01nyub
|
||||
tx_pin:
|
||||
number: 4
|
||||
rx_pin:
|
||||
number: 5
|
||||
baud_rate: 9600
|
||||
|
||||
sensor:
|
||||
- platform: a01nyub
|
||||
id: a01nyub_sensor
|
||||
name: a01nyub Distance
|
||||
uart_id: uart_a01nyub
|
13
tests/components/a01nyub/test.esp32-c3.yaml
Normal file
13
tests/components/a01nyub/test.esp32-c3.yaml
Normal file
|
@ -0,0 +1,13 @@
|
|||
uart:
|
||||
- id: uart_a01nyub
|
||||
tx_pin:
|
||||
number: 4
|
||||
rx_pin:
|
||||
number: 5
|
||||
baud_rate: 9600
|
||||
|
||||
sensor:
|
||||
- platform: a01nyub
|
||||
id: a01nyub_sensor
|
||||
name: a01nyub Distance
|
||||
uart_id: uart_a01nyub
|
13
tests/components/a01nyub/test.esp32-idf.yaml
Normal file
13
tests/components/a01nyub/test.esp32-idf.yaml
Normal file
|
@ -0,0 +1,13 @@
|
|||
uart:
|
||||
- id: uart_a01nyub
|
||||
tx_pin:
|
||||
number: 17
|
||||
rx_pin:
|
||||
number: 16
|
||||
baud_rate: 9600
|
||||
|
||||
sensor:
|
||||
- platform: a01nyub
|
||||
id: a01nyub_sensor
|
||||
name: a01nyub Distance
|
||||
uart_id: uart_a01nyub
|
13
tests/components/a01nyub/test.esp32.yaml
Normal file
13
tests/components/a01nyub/test.esp32.yaml
Normal file
|
@ -0,0 +1,13 @@
|
|||
uart:
|
||||
- id: uart_a01nyub
|
||||
tx_pin:
|
||||
number: 17
|
||||
rx_pin:
|
||||
number: 16
|
||||
baud_rate: 9600
|
||||
|
||||
sensor:
|
||||
- platform: a01nyub
|
||||
id: a01nyub_sensor
|
||||
name: a01nyub Distance
|
||||
uart_id: uart_a01nyub
|
13
tests/components/a01nyub/test.esp8266.yaml
Normal file
13
tests/components/a01nyub/test.esp8266.yaml
Normal file
|
@ -0,0 +1,13 @@
|
|||
uart:
|
||||
- id: uart_a01nyub
|
||||
tx_pin:
|
||||
number: 4
|
||||
rx_pin:
|
||||
number: 5
|
||||
baud_rate: 9600
|
||||
|
||||
sensor:
|
||||
- platform: a01nyub
|
||||
id: a01nyub_sensor
|
||||
name: a01nyub Distance
|
||||
uart_id: uart_a01nyub
|
13
tests/components/a01nyub/test.rp2040.yaml
Normal file
13
tests/components/a01nyub/test.rp2040.yaml
Normal file
|
@ -0,0 +1,13 @@
|
|||
uart:
|
||||
- id: uart_a01nyub
|
||||
tx_pin:
|
||||
number: 4
|
||||
rx_pin:
|
||||
number: 5
|
||||
baud_rate: 9600
|
||||
|
||||
sensor:
|
||||
- platform: a01nyub
|
||||
id: a01nyub_sensor
|
||||
name: a01nyub Distance
|
||||
uart_id: uart_a01nyub
|
13
tests/components/a02yyuw/test.esp32-c3-idf.yaml
Normal file
13
tests/components/a02yyuw/test.esp32-c3-idf.yaml
Normal file
|
@ -0,0 +1,13 @@
|
|||
uart:
|
||||
- id: uart_a02yyuw
|
||||
tx_pin:
|
||||
number: 4
|
||||
rx_pin:
|
||||
number: 5
|
||||
baud_rate: 9600
|
||||
|
||||
sensor:
|
||||
- platform: a02yyuw
|
||||
id: a02yyuw_sensor
|
||||
name: a02yyuw Distance
|
||||
uart_id: uart_a02yyuw
|
13
tests/components/a02yyuw/test.esp32-c3.yaml
Normal file
13
tests/components/a02yyuw/test.esp32-c3.yaml
Normal file
|
@ -0,0 +1,13 @@
|
|||
uart:
|
||||
- id: uart_a02yyuw
|
||||
tx_pin:
|
||||
number: 4
|
||||
rx_pin:
|
||||
number: 5
|
||||
baud_rate: 9600
|
||||
|
||||
sensor:
|
||||
- platform: a02yyuw
|
||||
id: a02yyuw_sensor
|
||||
name: a02yyuw Distance
|
||||
uart_id: uart_a02yyuw
|
13
tests/components/a02yyuw/test.esp32-idf.yaml
Normal file
13
tests/components/a02yyuw/test.esp32-idf.yaml
Normal file
|
@ -0,0 +1,13 @@
|
|||
uart:
|
||||
- id: uart_a02yyuw
|
||||
tx_pin:
|
||||
number: 17
|
||||
rx_pin:
|
||||
number: 16
|
||||
baud_rate: 9600
|
||||
|
||||
sensor:
|
||||
- platform: a02yyuw
|
||||
id: a02yyuw_sensor
|
||||
name: a02yyuw Distance
|
||||
uart_id: uart_a02yyuw
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue