mirror of https://github.com/esphome/esphome.git
Merge branch 'dev' of https://github.com/esphome/esphome into bms
This commit is contained in:
commit
1f05286ef4
|
@ -36,7 +36,7 @@ runs:
|
|||
|
||||
- name: Build and push to ghcr by digest
|
||||
id: build-ghcr
|
||||
uses: docker/build-push-action@v5.2.0
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
|
@ -67,7 +67,7 @@ runs:
|
|||
|
||||
- name: Build and push to dockerhub by digest
|
||||
id: build-dockerhub
|
||||
uses: docker/build-push-action@v5.2.0
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
|
|
|
@ -22,7 +22,7 @@ runs:
|
|||
python-version: ${{ inputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.0.1
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
name: API Proto CI
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "esphome/components/api/api.proto"
|
||||
- "esphome/components/api/api_pb2.cpp"
|
||||
- "esphome/components/api/api_pb2.h"
|
||||
- "esphome/components/api/api_pb2_service.cpp"
|
||||
- "esphome/components/api/api_pb2_service.h"
|
||||
- "script/api_protobuf/api_protobuf.py"
|
||||
- ".github/workflows/ci-api-proto.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check generated files
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.0.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install apt dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt-cache show protobuf-compiler
|
||||
sudo apt install -y protobuf-compiler
|
||||
protoc --version
|
||||
- name: Install python dependencies
|
||||
run: pip install aioesphomeapi -c requirements.txt -r requirements_dev.txt
|
||||
- name: Generate files
|
||||
run: script/api_protobuf/api_protobuf.py
|
||||
- name: Check for changes
|
||||
run: |
|
||||
if ! git diff --quiet; then
|
||||
echo "## Job Failed" | tee -a $GITHUB_STEP_SUMMARY
|
||||
echo "You have altered the generated proto files but they do not match what is expected." | tee -a $GITHUB_STEP_SUMMARY
|
||||
echo "Please run 'script/api_protobuf/api_protobuf.py' and commit the changes." | tee -a $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
- if: failure()
|
||||
name: Review PR
|
||||
uses: actions/github-script@v7.0.1
|
||||
with:
|
||||
script: |
|
||||
await github.rest.pulls.createReview({
|
||||
pull_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
event: 'REQUEST_CHANGES',
|
||||
body: 'You have altered the generated proto files but they do not match what is expected.\nPlease run "script/api_protobuf/api_protobuf.py" and commit the changes.'
|
||||
})
|
||||
- if: success()
|
||||
name: Dismiss review
|
||||
uses: actions/github-script@v7.0.1
|
||||
with:
|
||||
script: |
|
||||
let reviews = await github.rest.pulls.listReviews({
|
||||
pull_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo
|
||||
});
|
||||
for (let review of reviews.data) {
|
||||
if (review.user.login === 'github-actions[bot]' && review.state === 'CHANGES_REQUESTED') {
|
||||
await github.rest.pulls.dismissReview({
|
||||
pull_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
review_id: review.id,
|
||||
message: 'Files now match the expected proto files.'
|
||||
});
|
||||
}
|
||||
}
|
|
@ -46,7 +46,7 @@ jobs:
|
|||
with:
|
||||
python-version: "3.9"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.1.0
|
||||
uses: docker/setup-buildx-action@v3.2.0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ jobs:
|
|||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.0.1
|
||||
uses: actions/cache@v4.0.2
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
|
@ -367,7 +367,7 @@ jobs:
|
|||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Cache platformio
|
||||
uses: actions/cache@v4.0.1
|
||||
uses: actions/cache@v4.0.2
|
||||
with:
|
||||
path: ~/.platformio
|
||||
# yamllint disable-line rule:line-length
|
||||
|
@ -398,6 +398,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- common
|
||||
if: github.event_name == 'pull_request'
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
|
@ -406,10 +407,14 @@ jobs:
|
|||
with:
|
||||
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
|
||||
fetch-depth: 500
|
||||
- name: Fetch dev branch
|
||||
- name: Get target branch
|
||||
id: target-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
|
||||
echo "branch=${{ github.event.pull_request.base.ref }}" >> $GITHUB_OUTPUT
|
||||
- name: Fetch ${{ steps.target-branch.outputs.branch }} branch
|
||||
run: |
|
||||
git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +refs/heads/${{ steps.target-branch.outputs.branch }}:refs/remotes/origin/${{ steps.target-branch.outputs.branch }}
|
||||
git merge-base refs/remotes/origin/${{ steps.target-branch.outputs.branch }} HEAD
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
@ -419,7 +424,7 @@ jobs:
|
|||
id: set-matrix
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
echo "matrix=$(script/list-components.py --changed | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
|
||||
echo "matrix=$(script/list-components.py --changed --branch ${{ steps.target-branch.outputs.branch }} | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
|
||||
|
||||
test-build-components:
|
||||
name: Component test ${{ matrix.file }}
|
||||
|
@ -427,7 +432,7 @@ jobs:
|
|||
needs:
|
||||
- common
|
||||
- list-components
|
||||
if: ${{ needs.list-components.outputs.matrix != '[]' && needs.list-components.outputs.matrix != '' }}
|
||||
if: ${{ github.event_name == 'pull_request' && needs.list-components.outputs.matrix != '[]' && needs.list-components.outputs.matrix != '' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 2
|
||||
|
|
|
@ -85,18 +85,18 @@ jobs:
|
|||
python-version: "3.9"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.1.0
|
||||
uses: docker/setup-buildx-action@v3.2.0
|
||||
- name: Set up QEMU
|
||||
if: matrix.platform != 'linux/amd64'
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
@ -163,17 +163,17 @@ jobs:
|
|||
name: digests-${{ matrix.image.target }}-${{ matrix.registry }}
|
||||
path: /tmp/digests
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.1.0
|
||||
uses: docker/setup-buildx-action@v3.2.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
if: matrix.registry == 'dockerhub'
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
if: matrix.registry == 'ghcr'
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
|
|
@ -37,7 +37,7 @@ jobs:
|
|||
python ./script/sync-device_class.py
|
||||
|
||||
- name: Commit changes
|
||||
uses: peter-evans/create-pull-request@v6.0.1
|
||||
uses: peter-evans/create-pull-request@v6.0.2
|
||||
with:
|
||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||
committer: esphomebot <esphome@nabucasa.com>
|
||||
|
|
|
@ -346,6 +346,7 @@ esphome/components/st7789v/* @kbx81
|
|||
esphome/components/st7920/* @marsjan155
|
||||
esphome/components/substitutions/* @esphome/core
|
||||
esphome/components/sun/* @OttoWinter
|
||||
esphome/components/sun_gtil2/* @Mat931
|
||||
esphome/components/switch/* @esphome/core
|
||||
esphome/components/t6615/* @tylermenezes
|
||||
esphome/components/tca9548a/* @andreashergert1984
|
||||
|
|
|
@ -13,29 +13,29 @@ void AdE7953I2c::dump_config() {
|
|||
ade7953_base::ADE7953::dump_config();
|
||||
}
|
||||
bool AdE7953I2c::ade_write_8(uint16_t reg, uint8_t value) {
|
||||
std::vector<uint8_t> data(3);
|
||||
data.push_back(reg >> 8);
|
||||
data.push_back(reg >> 0);
|
||||
data.push_back(value);
|
||||
return this->write(data.data(), data.size()) != i2c::ERROR_OK;
|
||||
uint8_t data[3];
|
||||
data[0] = reg >> 8;
|
||||
data[1] = reg >> 0;
|
||||
data[2] = value;
|
||||
return this->write(data, 3) != i2c::ERROR_OK;
|
||||
}
|
||||
bool AdE7953I2c::ade_write_16(uint16_t reg, uint16_t value) {
|
||||
std::vector<uint8_t> data(4);
|
||||
data.push_back(reg >> 8);
|
||||
data.push_back(reg >> 0);
|
||||
data.push_back(value >> 8);
|
||||
data.push_back(value >> 0);
|
||||
return this->write(data.data(), data.size()) != i2c::ERROR_OK;
|
||||
uint8_t data[4];
|
||||
data[0] = reg >> 8;
|
||||
data[1] = reg >> 0;
|
||||
data[2] = value >> 8;
|
||||
data[3] = value >> 0;
|
||||
return this->write(data, 4) != i2c::ERROR_OK;
|
||||
}
|
||||
bool AdE7953I2c::ade_write_32(uint16_t reg, uint32_t value) {
|
||||
std::vector<uint8_t> data(6);
|
||||
data.push_back(reg >> 8);
|
||||
data.push_back(reg >> 0);
|
||||
data.push_back(value >> 24);
|
||||
data.push_back(value >> 16);
|
||||
data.push_back(value >> 8);
|
||||
data.push_back(value >> 0);
|
||||
return this->write(data.data(), data.size()) != i2c::ERROR_OK;
|
||||
uint8_t data[6];
|
||||
data[0] = reg >> 8;
|
||||
data[1] = reg >> 0;
|
||||
data[2] = value >> 24;
|
||||
data[3] = value >> 16;
|
||||
data[4] = value >> 8;
|
||||
data[5] = value >> 0;
|
||||
return this->write(data, 6) != i2c::ERROR_OK;
|
||||
}
|
||||
bool AdE7953I2c::ade_read_8(uint16_t reg, uint8_t *value) {
|
||||
uint8_t reg_data[2];
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#include "aht10.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace aht10 {
|
||||
|
@ -27,7 +26,7 @@ static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00};
|
|||
static const uint8_t AHT10_SOFTRESET_CMD[] = {0xBA};
|
||||
|
||||
static const uint8_t AHT10_DEFAULT_DELAY = 5; // ms, for initialization and temperature measurement
|
||||
static const uint8_t AHT10_HUMIDITY_DELAY = 30; // ms
|
||||
static const uint8_t AHT10_READ_DELAY = 80; // ms, time to wait for conversion result
|
||||
static const uint8_t AHT10_SOFTRESET_DELAY = 30; // ms
|
||||
|
||||
static const uint8_t AHT10_ATTEMPTS = 3; // safety margin, normally 3 attempts are enough: 3*30=90ms
|
||||
|
@ -83,74 +82,77 @@ void AHT10Component::setup() {
|
|||
ESP_LOGV(TAG, "AHT10 initialization");
|
||||
}
|
||||
|
||||
void AHT10Component::update() {
|
||||
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->status_set_warning();
|
||||
void AHT10Component::restart_read_() {
|
||||
if (this->read_count_ == AHT10_ATTEMPTS) {
|
||||
this->read_count_ = 0;
|
||||
this->status_set_error("Measurements reading timed-out!");
|
||||
return;
|
||||
}
|
||||
this->read_count_++;
|
||||
this->set_timeout(AHT10_READ_DELAY, [this]() { this->read_data_(); });
|
||||
}
|
||||
|
||||
void AHT10Component::read_data_() {
|
||||
uint8_t data[6];
|
||||
uint8_t delay_ms = AHT10_DEFAULT_DELAY;
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
delay_ms = AHT10_HUMIDITY_DELAY;
|
||||
bool success = false;
|
||||
for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
|
||||
ESP_LOGVV(TAG, "Attempt %d at %6" PRIu32, i, millis());
|
||||
delay(delay_ms);
|
||||
if (this->read(data, 6) != i2c::ERROR_OK) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
|
||||
ESP_LOGD(TAG, "AHT10 is busy, waiting...");
|
||||
} else if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) {
|
||||
// Unrealistic humidity (0x0)
|
||||
if (this->humidity_sensor_ == nullptr) {
|
||||
ESP_LOGVV(TAG, "ATH10 Unrealistic humidity (0x0), but humidity is not required");
|
||||
break;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "ATH10 Unrealistic humidity (0x0), retrying...");
|
||||
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// data is valid, we can break the loop
|
||||
ESP_LOGVV(TAG, "Answer at %6" PRIu32, millis());
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!success || (data[0] & 0x80) == 0x80) {
|
||||
ESP_LOGE(TAG, "Measurements reading timed-out!");
|
||||
this->status_set_warning();
|
||||
if (this->read_count_ > 1)
|
||||
ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
|
||||
if (this->read(data, 6) != i2c::ERROR_OK) {
|
||||
this->status_set_warning("AHT10 read failed, retrying soon");
|
||||
this->restart_read_();
|
||||
return;
|
||||
}
|
||||
|
||||
if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
|
||||
ESP_LOGD(TAG, "AHT10 is busy, waiting...");
|
||||
this->restart_read_();
|
||||
return;
|
||||
}
|
||||
if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) {
|
||||
// Unrealistic humidity (0x0)
|
||||
if (this->humidity_sensor_ == nullptr) {
|
||||
ESP_LOGV(TAG, "ATH10 Unrealistic humidity (0x0), but humidity is not required");
|
||||
} else {
|
||||
ESP_LOGD(TAG, "ATH10 Unrealistic humidity (0x0), retrying...");
|
||||
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
||||
this->status_set_warning("Communication with AHT10 failed!");
|
||||
}
|
||||
this->restart_read_();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this->read_count_ > 1)
|
||||
ESP_LOGD(TAG, "Success at %ums", (unsigned) (millis() - this->start_time_));
|
||||
uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
|
||||
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
|
||||
|
||||
float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f;
|
||||
float humidity;
|
||||
if (raw_humidity == 0) { // unrealistic value
|
||||
humidity = NAN;
|
||||
} else {
|
||||
humidity = (float) raw_humidity * 100.0f / 1048576.0f;
|
||||
}
|
||||
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f;
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
}
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
float humidity;
|
||||
if (raw_humidity == 0) { // unrealistic value
|
||||
humidity = NAN;
|
||||
} else {
|
||||
humidity = (float) raw_humidity * 100.0f / 1048576.0f;
|
||||
}
|
||||
if (std::isnan(humidity)) {
|
||||
ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
|
||||
}
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
}
|
||||
this->status_clear_warning();
|
||||
this->read_count_ = 0;
|
||||
}
|
||||
void AHT10Component::update() {
|
||||
if (this->read_count_ != 0)
|
||||
return;
|
||||
this->start_time_ = millis();
|
||||
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
||||
this->status_set_warning("Communication with AHT10 failed!");
|
||||
return;
|
||||
}
|
||||
this->restart_read_();
|
||||
}
|
||||
|
||||
float AHT10Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
|
|
@ -26,6 +26,10 @@ class AHT10Component : public PollingComponent, public i2c::I2CDevice {
|
|||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
AHT10Variant variant_{};
|
||||
unsigned read_count_{};
|
||||
void read_data_();
|
||||
void restart_read_();
|
||||
uint32_t start_time_{};
|
||||
};
|
||||
|
||||
} // namespace aht10
|
||||
|
|
|
@ -2,8 +2,10 @@ import base64
|
|||
import secrets
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import re
|
||||
|
||||
import requests
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
@ -11,7 +13,6 @@ import esphome.final_validate as fv
|
|||
from esphome import git
|
||||
from esphome.components.packages import validate_source_shorthand
|
||||
from esphome.const import CONF_REF, CONF_WIFI, CONF_ESPHOME, CONF_PROJECT
|
||||
from esphome.wizard import wizard_file
|
||||
from esphome.yaml_util import dump
|
||||
|
||||
dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import")
|
||||
|
@ -94,75 +95,74 @@ def import_config(
|
|||
if p.exists():
|
||||
raise FileExistsError
|
||||
|
||||
if project_name == "esphome.web":
|
||||
if "esp32c3" in import_url:
|
||||
board = "esp32-c3-devkitm-1"
|
||||
platform = "ESP32"
|
||||
elif "esp32s2" in import_url:
|
||||
board = "esp32-s2-saola-1"
|
||||
platform = "ESP32"
|
||||
elif "esp32s3" in import_url:
|
||||
board = "esp32-s3-devkitc-1"
|
||||
platform = "ESP32"
|
||||
elif "esp32" in import_url:
|
||||
board = "esp32dev"
|
||||
platform = "ESP32"
|
||||
elif "esp8266" in import_url:
|
||||
board = "esp01_1m"
|
||||
platform = "ESP8266"
|
||||
elif "pico-w" in import_url:
|
||||
board = "pico-w"
|
||||
platform = "RP2040"
|
||||
git_file = git.GitFile.from_shorthand(import_url)
|
||||
|
||||
kwargs = {
|
||||
"name": name,
|
||||
"friendly_name": friendly_name,
|
||||
"platform": platform,
|
||||
"board": board,
|
||||
"ssid": "!secret wifi_ssid",
|
||||
"psk": "!secret wifi_password",
|
||||
if git_file.query and "full_config" in git_file.query:
|
||||
url = git_file.raw_url
|
||||
try:
|
||||
req = requests.get(url, timeout=30)
|
||||
req.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise ValueError(f"Error while fetching {url}: {e}") from e
|
||||
|
||||
contents = req.text
|
||||
yaml = YAML()
|
||||
loaded_yaml = yaml.load(contents)
|
||||
if (
|
||||
"name_add_mac_suffix" in loaded_yaml["esphome"]
|
||||
and loaded_yaml["esphome"]["name_add_mac_suffix"]
|
||||
):
|
||||
loaded_yaml["esphome"]["name_add_mac_suffix"] = False
|
||||
name_val = loaded_yaml["esphome"]["name"]
|
||||
sub_pattern = re.compile(r"\$\{?([a-zA-Z-_]+)\}?")
|
||||
if match := sub_pattern.match(name_val):
|
||||
name_sub = match.group(1)
|
||||
if name_sub in loaded_yaml["substitutions"]:
|
||||
loaded_yaml["substitutions"][name_sub] = name
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Name substitution {name_sub} not found in substitutions"
|
||||
)
|
||||
else:
|
||||
loaded_yaml["esphome"]["name"] = name
|
||||
if friendly_name is not None:
|
||||
friendly_name_val = loaded_yaml["esphome"]["friendly_name"]
|
||||
if match := sub_pattern.match(friendly_name_val):
|
||||
friendly_name_sub = match.group(1)
|
||||
if friendly_name_sub in loaded_yaml["substitutions"]:
|
||||
loaded_yaml["substitutions"][friendly_name_sub] = friendly_name
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Friendly name substitution {friendly_name_sub} not found in substitutions"
|
||||
)
|
||||
else:
|
||||
loaded_yaml["esphome"]["friendly_name"] = friendly_name
|
||||
|
||||
with p.open("w", encoding="utf8") as f:
|
||||
yaml.dump(loaded_yaml, f)
|
||||
else:
|
||||
with p.open("w", encoding="utf8") as f:
|
||||
f.write(contents)
|
||||
|
||||
else:
|
||||
substitutions = {"name": name}
|
||||
esphome_core = {"name": "${name}", "name_add_mac_suffix": False}
|
||||
if friendly_name:
|
||||
substitutions["friendly_name"] = friendly_name
|
||||
esphome_core["friendly_name"] = "${friendly_name}"
|
||||
config = {
|
||||
"substitutions": substitutions,
|
||||
"packages": {project_name: import_url},
|
||||
"esphome": esphome_core,
|
||||
}
|
||||
if encryption:
|
||||
noise_psk = secrets.token_bytes(32)
|
||||
key = base64.b64encode(noise_psk).decode()
|
||||
kwargs["api_encryption_key"] = key
|
||||
config["api"] = {"encryption": {"key": key}}
|
||||
|
||||
p.write_text(
|
||||
wizard_file(**kwargs),
|
||||
encoding="utf8",
|
||||
)
|
||||
else:
|
||||
git_file = git.GitFile.from_shorthand(import_url)
|
||||
output = dump(config)
|
||||
|
||||
if git_file.query and "full_config" in git_file.query:
|
||||
url = git_file.raw_url
|
||||
try:
|
||||
req = requests.get(url, timeout=30)
|
||||
req.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise ValueError(f"Error while fetching {url}: {e}") from e
|
||||
if network == CONF_WIFI:
|
||||
output += WIFI_CONFIG
|
||||
|
||||
p.write_text(req.text, encoding="utf8")
|
||||
|
||||
else:
|
||||
substitutions = {"name": name}
|
||||
esphome_core = {"name": "${name}", "name_add_mac_suffix": False}
|
||||
if friendly_name:
|
||||
substitutions["friendly_name"] = friendly_name
|
||||
esphome_core["friendly_name"] = "${friendly_name}"
|
||||
config = {
|
||||
"substitutions": substitutions,
|
||||
"packages": {project_name: import_url},
|
||||
"esphome": esphome_core,
|
||||
}
|
||||
if encryption:
|
||||
noise_psk = secrets.token_bytes(32)
|
||||
key = base64.b64encode(noise_psk).decode()
|
||||
config["api"] = {"encryption": {"key": key}}
|
||||
|
||||
output = dump(config)
|
||||
|
||||
if network == CONF_WIFI:
|
||||
output += WIFI_CONFIG
|
||||
|
||||
p.write_text(output, encoding="utf8")
|
||||
p.write_text(output, encoding="utf8")
|
||||
|
|
|
@ -81,7 +81,7 @@ void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) {
|
|||
#endif
|
||||
|
||||
#if defined(USE_ESP32)
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
|
||||
void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; }
|
||||
|
||||
|
@ -121,7 +121,7 @@ void DeepSleepComponent::begin_sleep(bool manual) {
|
|||
App.run_safe_shutdown_hooks();
|
||||
|
||||
#if defined(USE_ESP32)
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
if (this->sleep_duration_.has_value())
|
||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
||||
if (this->wakeup_pin_ != nullptr) {
|
||||
|
@ -140,7 +140,7 @@ void DeepSleepComponent::begin_sleep(bool manual) {
|
|||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ESP32_VARIANT_ESP32C3
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
if (this->sleep_duration_.has_value())
|
||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
||||
if (this->wakeup_pin_ != nullptr) {
|
||||
|
|
|
@ -36,6 +36,21 @@ void HOT Display::line(int x1, int y1, int x2, int y2, Color color) {
|
|||
}
|
||||
}
|
||||
|
||||
void Display::line_at_angle(int x, int y, int angle, int length, Color color) {
|
||||
this->line_at_angle(x, y, angle, 0, length, color);
|
||||
}
|
||||
|
||||
void Display::line_at_angle(int x, int y, int angle, int start_radius, int stop_radius, Color color) {
|
||||
// Calculate start and end points
|
||||
int x1 = (start_radius * cos(angle * M_PI / 180)) + x;
|
||||
int y1 = (start_radius * sin(angle * M_PI / 180)) + y;
|
||||
int x2 = (stop_radius * cos(angle * M_PI / 180)) + x;
|
||||
int y2 = (stop_radius * sin(angle * M_PI / 180)) + y;
|
||||
|
||||
// Draw line
|
||||
this->line(x1, y1, x2, y2, color);
|
||||
}
|
||||
|
||||
void Display::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
|
||||
ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
|
||||
size_t line_stride = x_offset + w + x_pad; // length of each source line in pixels
|
||||
|
|
|
@ -258,6 +258,13 @@ class Display : public PollingComponent {
|
|||
/// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color.
|
||||
void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON);
|
||||
|
||||
/// Draw a straight line at the given angle based on the origin [x, y] for a specified length with the given color.
|
||||
void line_at_angle(int x, int y, int angle, int length, Color color = COLOR_ON);
|
||||
|
||||
/// Draw a straight line at the given angle based on the origin [x, y] from a specified start and stop radius with the
|
||||
/// given color.
|
||||
void line_at_angle(int x, int y, int angle, int start_radius, int stop_radius, Color color = COLOR_ON);
|
||||
|
||||
/// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color.
|
||||
void horizontal_line(int x, int y, int width, Color color = COLOR_ON);
|
||||
|
||||
|
|
|
@ -83,20 +83,22 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
|
|||
# The default/recommended arduino framework version
|
||||
# - https://github.com/esp8266/Arduino/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif8266
|
||||
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 0, 2)
|
||||
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 1, 2)
|
||||
# The platformio/espressif8266 version to use for arduino 2 framework versions
|
||||
# - https://github.com/platformio/platform-espressif8266/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266
|
||||
ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 3)
|
||||
# for arduino 3 framework versions
|
||||
ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 2, 0)
|
||||
# for arduino 4 framework versions
|
||||
ARDUINO_4_PLATFORM_VERSION = cv.Version(4, 2, 1)
|
||||
|
||||
|
||||
def _arduino_check_versions(value):
|
||||
value = value.copy()
|
||||
lookups = {
|
||||
"dev": (cv.Version(3, 0, 2), "https://github.com/esp8266/Arduino.git"),
|
||||
"latest": (cv.Version(3, 0, 2), None),
|
||||
"dev": (cv.Version(3, 1, 2), "https://github.com/esp8266/Arduino.git"),
|
||||
"latest": (cv.Version(3, 1, 2), None),
|
||||
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
|
||||
}
|
||||
|
||||
|
@ -116,7 +118,9 @@ def _arduino_check_versions(value):
|
|||
|
||||
platform_version = value.get(CONF_PLATFORM_VERSION)
|
||||
if platform_version is None:
|
||||
if version >= cv.Version(3, 0, 0):
|
||||
if version >= cv.Version(3, 1, 0):
|
||||
platform_version = _parse_platform_version(str(ARDUINO_4_PLATFORM_VERSION))
|
||||
elif version >= cv.Version(3, 0, 0):
|
||||
platform_version = _parse_platform_version(str(ARDUINO_3_PLATFORM_VERSION))
|
||||
elif version >= cv.Version(2, 5, 0):
|
||||
platform_version = _parse_platform_version(str(ARDUINO_2_PLATFORM_VERSION))
|
||||
|
|
|
@ -40,9 +40,9 @@ There are three documented parameters for modes:
|
|||
00 04 = Energy output mode
|
||||
This mode outputs detailed signal energy values for each gate and the target distance.
|
||||
The data format consist of the following.
|
||||
Header HH, Length LL, Persence PP, Distance DD, Range Gate GG, 16 Gate Energies EE, Footer FF
|
||||
HH HH HH HH LL LL PP DD DD GG GG EE EE .. 16x .. FF FF FF FF
|
||||
F4 F3 F2 F1 00 23 00 00 00 00 01 00 00 .. .. .. .. F8 F7 F6 F5
|
||||
Header HH, Length LL, Persence PP, Distance DD, 16 Gate Energies EE, Footer FF
|
||||
HH HH HH HH LL LL PP DD DD EE EE .. 16x .. FF FF FF FF
|
||||
F4 F3 F2 F1 23 00 00 00 00 00 00 .. .. .. .. F8 F7 F6 F5
|
||||
00 00 = debug output mode
|
||||
This mode outputs detailed values consisting of 20 Dopplers, 16 Ranges for a total 20 * 16 * 4 bytes
|
||||
The data format consist of the following.
|
||||
|
@ -211,10 +211,11 @@ void LD2420Component::factory_reset_action() {
|
|||
void LD2420Component::restart_module_action() {
|
||||
ESP_LOGCONFIG(TAG, "Restarting LD2420 module...");
|
||||
this->send_module_restart();
|
||||
delay_microseconds_safe(45000);
|
||||
this->set_config_mode(true);
|
||||
this->set_system_mode(system_mode_);
|
||||
this->set_config_mode(false);
|
||||
this->set_timeout(250, [this]() {
|
||||
this->set_config_mode(true);
|
||||
this->set_system_mode(system_mode_);
|
||||
this->set_config_mode(false);
|
||||
});
|
||||
ESP_LOGCONFIG(TAG, "LD2420 Restarted.");
|
||||
}
|
||||
|
||||
|
@ -527,18 +528,16 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
|||
this->write_byte(cmd_buffer[index]);
|
||||
}
|
||||
|
||||
delay_microseconds_safe(500); // give the module a moment to process it
|
||||
error = 0;
|
||||
if (frame.command == CMD_RESTART) {
|
||||
delay_microseconds_safe(25000); // Wait for the restart
|
||||
return 0; // restart does not reply exit now
|
||||
return 0; // restart does not reply exit now
|
||||
}
|
||||
|
||||
while (!this->cmd_reply_.ack) {
|
||||
while (available()) {
|
||||
this->readline_(read(), ack_buffer, sizeof(ack_buffer));
|
||||
}
|
||||
delay_microseconds_safe(250);
|
||||
delay_microseconds_safe(1450);
|
||||
if (loop_count <= 0) {
|
||||
error = LD2420_ERROR_TIMEOUT;
|
||||
retry--;
|
||||
|
|
|
@ -96,6 +96,12 @@ esp_err_t configure_timer_frequency(ledc_mode_t speed_mode, ledc_timer_t timer_n
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
constexpr int ledc_angle_to_htop(float angle, uint8_t bit_depth) {
|
||||
return static_cast<int>(angle * ((1U << bit_depth) - 1) / 360.);
|
||||
}
|
||||
#endif // USE_ESP_IDF
|
||||
|
||||
void LEDCOutput::write_state(float state) {
|
||||
if (!initialized_) {
|
||||
ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!");
|
||||
|
@ -117,7 +123,8 @@ void LEDCOutput::write_state(float state) {
|
|||
#ifdef USE_ESP_IDF
|
||||
auto speed_mode = get_speed_mode(channel_);
|
||||
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
|
||||
ledc_set_duty(speed_mode, chan_num, duty);
|
||||
int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_);
|
||||
ledc_set_duty_with_hpoint(speed_mode, chan_num, duty, hpoint);
|
||||
ledc_update_duty(speed_mode, chan_num);
|
||||
#endif
|
||||
}
|
||||
|
@ -143,8 +150,10 @@ void LEDCOutput::setup() {
|
|||
this->status_set_error();
|
||||
return;
|
||||
}
|
||||
int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_);
|
||||
|
||||
ESP_LOGV(TAG, "Configured frequency %f with a bit depth of %u bits", this->frequency_, this->bit_depth_);
|
||||
ESP_LOGV(TAG, "Angle of %.1f° results in hpoint %u", this->phase_angle_, hpoint);
|
||||
|
||||
ledc_channel_config_t chan_conf{};
|
||||
chan_conf.gpio_num = pin_->get_pin();
|
||||
|
@ -153,7 +162,7 @@ void LEDCOutput::setup() {
|
|||
chan_conf.intr_type = LEDC_INTR_DISABLE;
|
||||
chan_conf.timer_sel = timer_num;
|
||||
chan_conf.duty = inverted_ == pin_->is_inverted() ? 0 : (1U << bit_depth_);
|
||||
chan_conf.hpoint = 0;
|
||||
chan_conf.hpoint = hpoint;
|
||||
ledc_channel_config(&chan_conf);
|
||||
initialized_ = true;
|
||||
this->status_clear_error();
|
||||
|
@ -165,6 +174,7 @@ void LEDCOutput::dump_config() {
|
|||
LOG_PIN(" Pin ", this->pin_);
|
||||
ESP_LOGCONFIG(TAG, " LEDC Channel: %u", this->channel_);
|
||||
ESP_LOGCONFIG(TAG, " PWM Frequency: %.1f Hz", this->frequency_);
|
||||
ESP_LOGCONFIG(TAG, " Phase angle: %.1f°", this->phase_angle_);
|
||||
ESP_LOGCONFIG(TAG, " Bit depth: %u", this->bit_depth_);
|
||||
ESP_LOGV(TAG, " Max frequency for bit depth: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_));
|
||||
ESP_LOGV(TAG, " Min frequency for bit depth: %f",
|
||||
|
|
|
@ -19,6 +19,7 @@ class LEDCOutput : public output::FloatOutput, public Component {
|
|||
|
||||
void set_channel(uint8_t channel) { this->channel_ = channel; }
|
||||
void set_frequency(float frequency) { this->frequency_ = frequency; }
|
||||
void set_phase_angle(float angle) { this->phase_angle_ = angle; }
|
||||
/// Dynamically change frequency at runtime
|
||||
void update_frequency(float frequency) override;
|
||||
|
||||
|
@ -35,6 +36,7 @@ class LEDCOutput : public output::FloatOutput, public Component {
|
|||
InternalGPIOPin *pin_;
|
||||
uint8_t channel_{};
|
||||
uint8_t bit_depth_{};
|
||||
float phase_angle_{0.0f};
|
||||
float frequency_{};
|
||||
float duty_{0.0f};
|
||||
bool initialized_ = false;
|
||||
|
|
|
@ -3,6 +3,7 @@ from esphome.components import output
|
|||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import (
|
||||
CONF_PHASE_ANGLE,
|
||||
CONF_CHANNEL,
|
||||
CONF_FREQUENCY,
|
||||
CONF_ID,
|
||||
|
@ -46,6 +47,9 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
|||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency,
|
||||
cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15),
|
||||
cv.Optional(CONF_PHASE_ANGLE): cv.All(
|
||||
cv.only_with_esp_idf, cv.angle, cv.float_range(min=0.0, max=360.0)
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
@ -58,6 +62,8 @@ async def to_code(config):
|
|||
if CONF_CHANNEL in config:
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||
cg.add(var.set_frequency(config[CONF_FREQUENCY]))
|
||||
if CONF_PHASE_ANGLE in config:
|
||||
cg.add(var.set_phase_angle(config[CONF_PHASE_ANGLE]))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
|
|
|
@ -129,7 +129,7 @@ void Logger::pre_setup() {
|
|||
this->uart_num_ = UART_NUM_2;
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
case UART_SELECTION_USB_CDC:
|
||||
this->uart_num_ = -1;
|
||||
break;
|
||||
|
|
|
@ -93,11 +93,18 @@ int MicroWakeWord::read_microphone_() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
size_t bytes_written = this->ring_buffer_->write((void *) this->input_buffer_, bytes_read);
|
||||
if (bytes_written != bytes_read) {
|
||||
ESP_LOGW(TAG, "Failed to write some data to ring buffer (written=%d, expected=%d)", bytes_written, bytes_read);
|
||||
size_t bytes_free = this->ring_buffer_->free();
|
||||
|
||||
if (bytes_free < bytes_read) {
|
||||
ESP_LOGW(TAG,
|
||||
"Not enough free bytes in ring buffer to store incoming audio data (free bytes=%d, incoming bytes=%d). "
|
||||
"Resetting the ring buffer. Wake word detection accuracy will be reduced.",
|
||||
bytes_free, bytes_read);
|
||||
|
||||
this->ring_buffer_->reset();
|
||||
}
|
||||
return bytes_written;
|
||||
|
||||
return this->ring_buffer_->write((void *) this->input_buffer_, bytes_read);
|
||||
}
|
||||
|
||||
void MicroWakeWord::loop() {
|
||||
|
@ -206,12 +213,6 @@ bool MicroWakeWord::initialize_models() {
|
|||
return false;
|
||||
}
|
||||
|
||||
this->preprocessor_stride_buffer_ = audio_samples_allocator.allocate(HISTORY_SAMPLES_TO_KEEP);
|
||||
if (this->preprocessor_stride_buffer_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate the audio preprocessor's stride buffer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->preprocessor_model_ = tflite::GetModel(G_AUDIO_PREPROCESSOR_INT8_TFLITE);
|
||||
if (this->preprocessor_model_->version() != TFLITE_SCHEMA_VERSION) {
|
||||
ESP_LOGE(TAG, "Wake word's audio preprocessor model's schema is not supported");
|
||||
|
@ -225,7 +226,7 @@ bool MicroWakeWord::initialize_models() {
|
|||
}
|
||||
|
||||
static tflite::MicroMutableOpResolver<18> preprocessor_op_resolver;
|
||||
static tflite::MicroMutableOpResolver<14> streaming_op_resolver;
|
||||
static tflite::MicroMutableOpResolver<17> streaming_op_resolver;
|
||||
|
||||
if (!this->register_preprocessor_ops_(preprocessor_op_resolver))
|
||||
return false;
|
||||
|
@ -329,7 +330,6 @@ bool MicroWakeWord::detect_wake_word_() {
|
|||
}
|
||||
|
||||
// Perform inference
|
||||
uint32_t streaming_size = micros();
|
||||
float streaming_prob = this->perform_streaming_inference_();
|
||||
|
||||
// Add the most recent probability to the sliding window
|
||||
|
@ -357,6 +357,9 @@ bool MicroWakeWord::detect_wake_word_() {
|
|||
for (auto &prob : this->recent_streaming_probabilities_) {
|
||||
prob = 0;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Wake word sliding average probability is %.3f and most recent probability is %.3f",
|
||||
sliding_window_average, streaming_prob);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -371,23 +374,6 @@ void MicroWakeWord::set_sliding_window_average_size(size_t size) {
|
|||
bool MicroWakeWord::slice_available_() {
|
||||
size_t available = this->ring_buffer_->available();
|
||||
|
||||
size_t free = this->ring_buffer_->free();
|
||||
|
||||
if (free < NEW_SAMPLES_TO_GET * sizeof(int16_t)) {
|
||||
// If the ring buffer is within one audio slice of being full, then wake word detection will have issues.
|
||||
// If this is constantly occuring, then some possibilities why are
|
||||
// 1) there are too many other slow components configured
|
||||
// 2) the ESP32 isn't fast enough; e.g., an ESP32 is much slower than an ESP32-S3 at inferences.
|
||||
// 3) the model is too large
|
||||
// 4) the model uses operations that are not optimized
|
||||
ESP_LOGW(TAG,
|
||||
"Audio buffer is nearly full. Wake word detection may be less accurate and have slower reponse times. "
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
"microWakeWord is designed for the ESP32-S3. The current platform is too slow for this model."
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
return available > (NEW_SAMPLES_TO_GET * sizeof(int16_t));
|
||||
}
|
||||
|
||||
|
@ -396,13 +382,12 @@ bool MicroWakeWord::stride_audio_samples_(int16_t **audio_samples) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Copy 320 bytes (160 samples over 10 ms) into preprocessor_audio_buffer_ from history in
|
||||
// preprocessor_stride_buffer_
|
||||
memcpy((void *) (this->preprocessor_audio_buffer_), (void *) (this->preprocessor_stride_buffer_),
|
||||
// Copy the last 320 bytes (160 samples over 10 ms) from the audio buffer to the start of the audio buffer
|
||||
memcpy((void *) (this->preprocessor_audio_buffer_), (void *) (this->preprocessor_audio_buffer_ + NEW_SAMPLES_TO_GET),
|
||||
HISTORY_SAMPLES_TO_KEEP * sizeof(int16_t));
|
||||
|
||||
// Copy 640 bytes (320 samples over 20 ms) from the ring buffer
|
||||
// The first 320 bytes (160 samples over 10 ms) will be from history
|
||||
// Copy 640 bytes (320 samples over 20 ms) from the ring buffer into the audio buffer offset 320 bytes (160 samples
|
||||
// over 10 ms)
|
||||
size_t bytes_read = this->ring_buffer_->read((void *) (this->preprocessor_audio_buffer_ + HISTORY_SAMPLES_TO_KEEP),
|
||||
NEW_SAMPLES_TO_GET * sizeof(int16_t), pdMS_TO_TICKS(200));
|
||||
|
||||
|
@ -415,11 +400,6 @@ bool MicroWakeWord::stride_audio_samples_(int16_t **audio_samples) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Copy the last 320 bytes (160 samples over 10 ms) from the audio buffer into history stride buffer for the next
|
||||
// iteration
|
||||
memcpy((void *) (this->preprocessor_stride_buffer_), (void *) (this->preprocessor_audio_buffer_ + NEW_SAMPLES_TO_GET),
|
||||
HISTORY_SAMPLES_TO_KEEP * sizeof(int16_t));
|
||||
|
||||
*audio_samples = this->preprocessor_audio_buffer_;
|
||||
return true;
|
||||
}
|
||||
|
@ -480,7 +460,7 @@ bool MicroWakeWord::register_preprocessor_ops_(tflite::MicroMutableOpResolver<18
|
|||
return true;
|
||||
}
|
||||
|
||||
bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<14> &op_resolver) {
|
||||
bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<17> &op_resolver) {
|
||||
if (op_resolver.AddCallOnce() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddVarHandle() != kTfLiteOk)
|
||||
|
@ -509,6 +489,12 @@ bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<14> &
|
|||
return false;
|
||||
if (op_resolver.AddQuantize() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddDepthwiseConv2D() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddAveragePool2D() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddMaxPool2D() != kTfLiteOk)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -128,7 +128,6 @@ class MicroWakeWord : public Component {
|
|||
|
||||
// Stores audio fed into feature generator preprocessor
|
||||
int16_t *preprocessor_audio_buffer_;
|
||||
int16_t *preprocessor_stride_buffer_;
|
||||
|
||||
bool detected_{false};
|
||||
|
||||
|
@ -181,7 +180,7 @@ class MicroWakeWord : public Component {
|
|||
bool register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver);
|
||||
|
||||
/// @brief Returns true if successfully registered the streaming model's TensorFlow operations
|
||||
bool register_streaming_ops_(tflite::MicroMutableOpResolver<14> &op_resolver);
|
||||
bool register_streaming_ops_(tflite::MicroMutableOpResolver<17> &op_resolver);
|
||||
};
|
||||
|
||||
template<typename... Ts> class StartAction : public Action<Ts...>, public Parented<MicroWakeWord> {
|
||||
|
|
|
@ -187,11 +187,7 @@ void MQTTClientComponent::start_dnslookup_() {
|
|||
default:
|
||||
case ERR_ARG: {
|
||||
// error
|
||||
#if defined(USE_ESP8266)
|
||||
ESP_LOGW(TAG, "Error resolving MQTT broker IP address: %ld", err);
|
||||
#else
|
||||
ESP_LOGW(TAG, "Error resolving MQTT broker IP address: %d", err);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,9 @@ from esphome.components.esp32 import add_idf_sdkconfig_option
|
|||
from esphome.const import (
|
||||
CONF_ENABLE_IPV6,
|
||||
CONF_MIN_IPV6_ADDR_COUNT,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
|
@ -16,25 +19,30 @@ IPAddress = network_ns.class_("IPAddress")
|
|||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ENABLE_IPV6, default=False): cv.boolean,
|
||||
cv.SplitDefault(CONF_ENABLE_IPV6): cv.All(
|
||||
cv.boolean, cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040])
|
||||
),
|
||||
cv.Optional(CONF_MIN_IPV6_ADDR_COUNT, default=0): cv.positive_int,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_NETWORK_IPV6", config[CONF_ENABLE_IPV6])
|
||||
cg.add_define("USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT])
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6])
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_LWIP_IPV6_AUTOCONFIG", config[CONF_ENABLE_IPV6]
|
||||
if CONF_ENABLE_IPV6 in config:
|
||||
cg.add_define("USE_NETWORK_IPV6", config[CONF_ENABLE_IPV6])
|
||||
cg.add_define(
|
||||
"USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT]
|
||||
)
|
||||
else:
|
||||
if config[CONF_ENABLE_IPV6]:
|
||||
cg.add_build_flag("-DCONFIG_LWIP_IPV6")
|
||||
cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG")
|
||||
if CORE.is_rp2040:
|
||||
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6")
|
||||
if CORE.is_esp8266:
|
||||
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY")
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6])
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_LWIP_IPV6_AUTOCONFIG", config[CONF_ENABLE_IPV6]
|
||||
)
|
||||
else:
|
||||
if config[CONF_ENABLE_IPV6]:
|
||||
cg.add_build_flag("-DCONFIG_LWIP_IPV6")
|
||||
cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG")
|
||||
if CORE.is_rp2040:
|
||||
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6")
|
||||
if CORE.is_esp8266:
|
||||
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY")
|
||||
|
|
|
@ -82,16 +82,16 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
|||
/**
|
||||
* Set the picture of an image component.
|
||||
* @param component The component name.
|
||||
* @param value The picture name.
|
||||
* @param value The picture id.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_picture("pic", "4");
|
||||
* it.set_component_picture("pic", 4);
|
||||
* ```
|
||||
*
|
||||
* This will change the image of the component `pic` to the image with ID `4`.
|
||||
*/
|
||||
void set_component_picture(const char *component, const char *picture);
|
||||
void set_component_picture(const char *component, uint8_t picture_id);
|
||||
/**
|
||||
* Set the background color of a component.
|
||||
* @param component The component name.
|
||||
|
|
|
@ -197,8 +197,8 @@ void Nextion::disable_component_touch(const char *component) {
|
|||
this->add_no_result_to_queue_with_printf_("disable_component_touch", "tsw %s,0", component);
|
||||
}
|
||||
|
||||
void Nextion::set_component_picture(const char *component, const char *picture) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_picture", "%s.val=%s", component, picture);
|
||||
void Nextion::set_component_picture(const char *component, uint8_t picture_id) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_picture", "%s.pic=%d", component, picture_id);
|
||||
}
|
||||
|
||||
void Nextion::set_component_text(const char *component, const char *text) {
|
||||
|
|
|
@ -32,6 +32,7 @@ from esphome.const import (
|
|||
CONF_MAGNITUDE,
|
||||
CONF_WAND_ID,
|
||||
CONF_LEVEL,
|
||||
CONF_DELTA,
|
||||
)
|
||||
from esphome.core import coroutine
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
|
@ -792,6 +793,7 @@ async def pioneer_action(var, config, args):
|
|||
PRONTO_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_DATA): cv.string,
|
||||
cv.Optional(CONF_DELTA, default=-1): cv.int_,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -803,6 +805,7 @@ def pronto_binary_sensor(var, config):
|
|||
cg.StructInitializer(
|
||||
ProntoData,
|
||||
("data", config[CONF_DATA]),
|
||||
("delta", config[CONF_DELTA]),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "keeloq_protocol.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_base {
|
||||
|
||||
|
@ -34,7 +36,8 @@ transmitter and nutton command is decoded.
|
|||
void KeeloqProtocol::encode(RemoteTransmitData *dst, const KeeloqData &data) {
|
||||
uint32_t out_data = 0x0;
|
||||
|
||||
ESP_LOGD(TAG, "Send Keeloq: address=%07x command=%03x encrypted=%08x", data.address, data.command, data.encrypted);
|
||||
ESP_LOGD(TAG, "Send Keeloq: address=%07" PRIx32 " command=%03x encrypted=%08" PRIx32, data.address, data.command,
|
||||
data.encrypted);
|
||||
ESP_LOGV(TAG, "Send Keeloq: data bits (%d + %d)", NBITS_ENCRYPTED_DATA, NBITS_FIXED_DATA);
|
||||
|
||||
// Preamble = '01' x 12
|
||||
|
@ -181,7 +184,7 @@ optional<KeeloqData> KeeloqProtocol::decode(RemoteReceiveData src) {
|
|||
}
|
||||
|
||||
void KeeloqProtocol::dump(const KeeloqData &data) {
|
||||
ESP_LOGD(TAG, "Received Keeloq: address=0x%08X, command=0x%02x", data.address, data.command);
|
||||
ESP_LOGD(TAG, "Received Keeloq: address=0x%08" PRIx32 ", command=0x%02x", data.address, data.command);
|
||||
}
|
||||
|
||||
} // namespace remote_base
|
||||
|
|
|
@ -49,13 +49,13 @@ bool ProntoData::operator==(const ProntoData &rhs) const {
|
|||
for (std::vector<uint16_t>::size_type i = 0; i < data1.size() - 1; ++i) {
|
||||
int diff = data2[i] - data1[i];
|
||||
diff *= diff;
|
||||
if (diff > 9)
|
||||
if (rhs.delta == -1 && diff > 9)
|
||||
return false;
|
||||
|
||||
total_diff += diff;
|
||||
}
|
||||
|
||||
return total_diff <= data1.size() * 3;
|
||||
return total_diff <= (rhs.delta == -1 ? data1.size() * 3 : rhs.delta);
|
||||
}
|
||||
|
||||
// DO NOT EXPORT from this file
|
||||
|
@ -222,6 +222,7 @@ optional<ProntoData> ProntoProtocol::decode(RemoteReceiveData src) {
|
|||
prontodata += compensate_and_dump_sequence_(data, timebase);
|
||||
|
||||
out.data = prontodata;
|
||||
out.delta = -1;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ std::vector<uint16_t> encode_pronto(const std::string &str);
|
|||
|
||||
struct ProntoData {
|
||||
std::string data;
|
||||
int delta;
|
||||
|
||||
bool operator==(const ProntoData &rhs) const;
|
||||
};
|
||||
|
@ -40,10 +41,12 @@ DECLARE_REMOTE_PROTOCOL(Pronto)
|
|||
template<typename... Ts> class ProntoAction : public RemoteTransmitterActionBase<Ts...> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(std::string, data)
|
||||
TEMPLATABLE_VALUE(int, delta)
|
||||
|
||||
void encode(RemoteTransmitData *dst, Ts... x) override {
|
||||
ProntoData data{};
|
||||
data.data = this->data_.value(x...);
|
||||
data.delta = this->delta_.value(x...);
|
||||
ProntoProtocol().encode(dst, data);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ RemoteRMTChannel::RemoteRMTChannel(uint8_t mem_block_num) : mem_block_num_(mem_b
|
|||
}
|
||||
|
||||
void RemoteRMTChannel::config_rmt(rmt_config_t &rmt) {
|
||||
if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) >= RMT_CHANNEL_MAX) {
|
||||
if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) > RMT_CHANNEL_MAX) {
|
||||
this->mem_block_num_ = int(RMT_CHANNEL_MAX) - int(this->channel_);
|
||||
ESP_LOGW(TAG, "Not enough RMT memory blocks available, reduced to %i blocks.", this->mem_block_num_);
|
||||
}
|
||||
|
|
|
@ -14,13 +14,12 @@ from esphome.const import (
|
|||
CONF_PM_4_0,
|
||||
CONF_STORE_BASELINE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_AQI,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_NITROUS_OXIDE,
|
||||
DEVICE_CLASS_PM1,
|
||||
DEVICE_CLASS_PM10,
|
||||
DEVICE_CLASS_PM25,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||
ICON_CHEMICAL_WEAPON,
|
||||
ICON_RADIATOR,
|
||||
ICON_THERMOMETER,
|
||||
|
@ -132,13 +131,13 @@ CONFIG_SCHEMA = (
|
|||
cv.Optional(CONF_VOC): sensor.sensor_schema(
|
||||
icon=ICON_RADIATOR,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||
device_class=DEVICE_CLASS_AQI,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(GAS_SENSOR),
|
||||
cv.Optional(CONF_NOX): sensor.sensor_schema(
|
||||
icon=ICON_RADIATOR,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_NITROUS_OXIDE,
|
||||
device_class=DEVICE_CLASS_AQI,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(GAS_SENSOR),
|
||||
cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean,
|
||||
|
|
|
@ -54,9 +54,9 @@ void SenseAirComponent::update() {
|
|||
this->status_clear_warning();
|
||||
const uint8_t length = response[2];
|
||||
const uint16_t status = (uint16_t(response[3]) << 8) | response[4];
|
||||
const uint16_t ppm = (uint16_t(response[length + 1]) << 8) | response[length + 2];
|
||||
const int16_t ppm = int16_t((response[length + 1] << 8) | response[length + 2]);
|
||||
|
||||
ESP_LOGD(TAG, "SenseAir Received CO₂=%uppm Status=0x%02X", ppm, status);
|
||||
ESP_LOGD(TAG, "SenseAir Received CO₂=%dppm Status=0x%02X", ppm, status);
|
||||
if (this->co2_sensor_ != nullptr)
|
||||
this->co2_sensor_->publish_state(ppm);
|
||||
}
|
||||
|
|
|
@ -19,13 +19,28 @@ void Servo::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, " run duration: %" PRIu32 " ms", this->transition_length_);
|
||||
}
|
||||
|
||||
void Servo::setup() {
|
||||
float v;
|
||||
if (this->restore_) {
|
||||
this->rtc_ = global_preferences->make_preference<float>(global_servo_id);
|
||||
global_servo_id++;
|
||||
if (this->rtc_.load(&v)) {
|
||||
this->target_value_ = v;
|
||||
this->internal_write(v);
|
||||
this->state_ = STATE_ATTACHED;
|
||||
this->start_millis_ = millis();
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->detach();
|
||||
}
|
||||
|
||||
void Servo::loop() {
|
||||
// check if auto_detach_time_ is set and servo reached target
|
||||
if (this->auto_detach_time_ && this->state_ == STATE_TARGET_REACHED) {
|
||||
if (millis() - this->start_millis_ > this->auto_detach_time_) {
|
||||
this->detach();
|
||||
this->start_millis_ = 0;
|
||||
this->state_ = STATE_DETACHED;
|
||||
ESP_LOGD(TAG, "Servo detached on auto_detach_time");
|
||||
}
|
||||
}
|
||||
|
@ -54,8 +69,11 @@ void Servo::loop() {
|
|||
|
||||
void Servo::write(float value) {
|
||||
value = clamp(value, -1.0f, 1.0f);
|
||||
if (this->target_value_ == value)
|
||||
if ((this->state_ == STATE_DETACHED) && (this->target_value_ == value)) {
|
||||
this->internal_write(value);
|
||||
} else {
|
||||
this->save_level_(value);
|
||||
}
|
||||
this->target_value_ = value;
|
||||
this->source_value_ = this->current_value_;
|
||||
this->state_ = STATE_ATTACHED;
|
||||
|
@ -72,11 +90,18 @@ void Servo::internal_write(float value) {
|
|||
level = lerp(value, this->idle_level_, this->max_level_);
|
||||
}
|
||||
this->output_->set_level(level);
|
||||
if (this->target_value_ == this->current_value_) {
|
||||
this->save_level_(level);
|
||||
}
|
||||
this->current_value_ = value;
|
||||
}
|
||||
|
||||
void Servo::detach() {
|
||||
this->state_ = STATE_DETACHED;
|
||||
this->output_->set_level(0.0f);
|
||||
}
|
||||
|
||||
void Servo::save_level_(float v) {
|
||||
if (this->restore_)
|
||||
this->rtc_.save(&v);
|
||||
}
|
||||
|
||||
} // namespace servo
|
||||
} // namespace esphome
|
||||
|
|
|
@ -17,22 +17,8 @@ class Servo : public Component {
|
|||
void loop() override;
|
||||
void write(float value);
|
||||
void internal_write(float value);
|
||||
void detach() {
|
||||
this->output_->set_level(0.0f);
|
||||
this->save_level_(0.0f);
|
||||
}
|
||||
void setup() override {
|
||||
float v;
|
||||
if (this->restore_) {
|
||||
this->rtc_ = global_preferences->make_preference<float>(global_servo_id);
|
||||
global_servo_id++;
|
||||
if (this->rtc_.load(&v)) {
|
||||
this->output_->set_level(v);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->detach();
|
||||
}
|
||||
void detach();
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void set_min_level(float min_level) { min_level_ = min_level; }
|
||||
|
@ -42,8 +28,10 @@ class Servo : public Component {
|
|||
void set_auto_detach_time(uint32_t auto_detach_time) { auto_detach_time_ = auto_detach_time; }
|
||||
void set_transition_length(uint32_t transition_length) { transition_length_ = transition_length; }
|
||||
|
||||
bool has_reached_target() { return this->current_value_ == this->target_value_; }
|
||||
|
||||
protected:
|
||||
void save_level_(float v) { this->rtc_.save(&v); }
|
||||
void save_level_(float v);
|
||||
|
||||
output::FloatOutput *output_;
|
||||
float min_level_ = 0.0300f;
|
||||
|
|
|
@ -15,6 +15,7 @@ SM2135 = sm2135_ns.class_("SM2135", cg.Component)
|
|||
|
||||
CONF_RGB_CURRENT = "rgb_current"
|
||||
CONF_CW_CURRENT = "cw_current"
|
||||
CONF_SEPARATE_MODES = "separate_modes"
|
||||
|
||||
SM2135Current = sm2135_ns.enum("SM2135Current")
|
||||
|
||||
|
@ -51,6 +52,7 @@ CONFIG_SCHEMA = cv.Schema(
|
|||
cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_RGB_CURRENT, "20mA"): cv.enum(DRIVE_STRENGTHS_RGB),
|
||||
cv.Optional(CONF_CW_CURRENT, "10mA"): cv.enum(DRIVE_STRENGTHS_CW),
|
||||
cv.Optional(CONF_SEPARATE_MODES, default=True): cv.boolean,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
@ -66,3 +68,4 @@ async def to_code(config):
|
|||
|
||||
cg.add(var.set_rgb_current(config[CONF_RGB_CURRENT]))
|
||||
cg.add(var.set_cw_current(config[CONF_CW_CURRENT]))
|
||||
cg.add(var.set_separate_modes(config[CONF_SEPARATE_MODES]))
|
||||
|
|
|
@ -97,23 +97,32 @@ void SM2135::loop() {
|
|||
this->write_byte_(SM2135_ADDR_MC);
|
||||
this->write_byte_(current_mask_);
|
||||
|
||||
if (this->update_channel_ == 3 || this->update_channel_ == 4) {
|
||||
// No color so must be Cold/Warm
|
||||
if (this->separate_modes_) {
|
||||
if (this->update_channel_ == 3 || this->update_channel_ == 4) {
|
||||
// No color so must be Cold/Warm
|
||||
|
||||
this->write_byte_(SM2135_CW);
|
||||
this->sm2135_stop_();
|
||||
delay(1);
|
||||
this->sm2135_start_();
|
||||
this->write_byte_(SM2135_ADDR_C);
|
||||
this->write_byte_(this->pwm_amounts_[4]); // Warm
|
||||
this->write_byte_(this->pwm_amounts_[3]); // Cold
|
||||
this->write_byte_(SM2135_CW);
|
||||
this->sm2135_stop_();
|
||||
delay(1);
|
||||
this->sm2135_start_();
|
||||
this->write_byte_(SM2135_ADDR_C);
|
||||
this->write_byte_(this->pwm_amounts_[4]); // Warm
|
||||
this->write_byte_(this->pwm_amounts_[3]); // Cold
|
||||
} else {
|
||||
// Color
|
||||
|
||||
this->write_byte_(SM2135_RGB);
|
||||
this->write_byte_(this->pwm_amounts_[1]); // Green
|
||||
this->write_byte_(this->pwm_amounts_[0]); // Red
|
||||
this->write_byte_(this->pwm_amounts_[2]); // Blue
|
||||
}
|
||||
} else {
|
||||
// Color
|
||||
|
||||
this->write_byte_(SM2135_RGB);
|
||||
this->write_byte_(this->pwm_amounts_[1]); // Green
|
||||
this->write_byte_(this->pwm_amounts_[0]); // Red
|
||||
this->write_byte_(this->pwm_amounts_[2]); // Blue
|
||||
this->write_byte_(this->pwm_amounts_[4]); // Warm
|
||||
this->write_byte_(this->pwm_amounts_[3]); // Cold
|
||||
}
|
||||
|
||||
this->sm2135_stop_();
|
||||
|
|
|
@ -39,6 +39,8 @@ class SM2135 : public Component {
|
|||
this->current_mask_ = (this->rgb_current_ << 4) | this->cw_current_;
|
||||
}
|
||||
|
||||
void set_separate_modes(bool separate_modes) { this->separate_modes_ = separate_modes; }
|
||||
|
||||
void setup() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
@ -78,6 +80,7 @@ class SM2135 : public Component {
|
|||
uint8_t current_mask_;
|
||||
SM2135Current rgb_current_;
|
||||
SM2135Current cw_current_;
|
||||
bool separate_modes_;
|
||||
uint8_t update_channel_;
|
||||
std::vector<uint8_t> pwm_amounts_;
|
||||
bool update_{true};
|
||||
|
|
|
@ -268,6 +268,9 @@ SPI_SCHEMA = cv.All(
|
|||
*sum(get_hw_interface_list(), ["software", "hardware", "any"]),
|
||||
lower=True,
|
||||
),
|
||||
cv.Optional(CONF_DATA_PINS): cv.invalid(
|
||||
"'data_pins' should be used with 'type: quad' only"
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN),
|
||||
|
@ -287,6 +290,12 @@ SPI_QUAD_SCHEMA = cv.All(
|
|||
*sum(get_hw_interface_list(), ["hardware"]),
|
||||
lower=True,
|
||||
),
|
||||
cv.Optional(CONF_MISO_PIN): cv.invalid(
|
||||
"'miso_pin' should not be used with quad SPI"
|
||||
),
|
||||
cv.Optional(CONF_MOSI_PIN): cv.invalid(
|
||||
"'mosi_pin' should not be used with quad SPI"
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.only_on([PLATFORM_ESP32]),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "spi_device.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace spi_device {
|
||||
|
@ -18,9 +19,9 @@ void SPIDeviceComponent::dump_config() {
|
|||
LOG_PIN(" CS pin: ", this->cs_);
|
||||
ESP_LOGCONFIG(TAG, " Mode: %d", this->mode_);
|
||||
if (this->data_rate_ < 1000000) {
|
||||
ESP_LOGCONFIG(TAG, " Data rate: %dkHz", this->data_rate_ / 1000);
|
||||
ESP_LOGCONFIG(TAG, " Data rate: %" PRId32 "kHz", this->data_rate_ / 1000);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Data rate: %dMHz", this->data_rate_ / 1000000);
|
||||
ESP_LOGCONFIG(TAG, " Data rate: %" PRId32 "MHz", this->data_rate_ / 1000000);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@Mat931"]
|
||||
MULTI_CONF = True
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
CONF_SUN_GTIL2_ID = "sun_gtil2_id"
|
||||
|
||||
sun_gtil2_ns = cg.esphome_ns.namespace("sun_gtil2")
|
||||
|
||||
SunGTIL2Component = sun_gtil2_ns.class_("SunGTIL2", cg.Component, uart.UARTDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SunGTIL2Component),
|
||||
}
|
||||
).extend(uart.UART_DEVICE_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
|
@ -0,0 +1,87 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ICON_FLASH,
|
||||
UNIT_VOLT,
|
||||
ICON_THERMOMETER,
|
||||
UNIT_WATT,
|
||||
UNIT_CELSIUS,
|
||||
CONF_TEMPERATURE,
|
||||
)
|
||||
from . import SunGTIL2Component, CONF_SUN_GTIL2_ID
|
||||
|
||||
CONF_AC_VOLTAGE = "ac_voltage"
|
||||
CONF_DC_VOLTAGE = "dc_voltage"
|
||||
CONF_AC_POWER = "ac_power"
|
||||
CONF_DC_POWER = "dc_power"
|
||||
CONF_LIMITER_POWER = "limiter_power"
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_SUN_GTIL2_ID): cv.use_id(SunGTIL2Component),
|
||||
cv.Optional(CONF_AC_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
icon=ICON_FLASH,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
),
|
||||
cv.Optional(CONF_DC_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
icon=ICON_FLASH,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
),
|
||||
cv.Optional(CONF_AC_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
icon=ICON_FLASH,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
),
|
||||
cv.Optional(CONF_DC_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
icon=ICON_FLASH,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
),
|
||||
cv.Optional(CONF_LIMITER_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
icon=ICON_FLASH,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_SUN_GTIL2_ID])
|
||||
if ac_voltage_config := config.get(CONF_AC_VOLTAGE):
|
||||
sens = await sensor.new_sensor(ac_voltage_config)
|
||||
cg.add(hub.set_ac_voltage(sens))
|
||||
if dc_voltage_config := config.get(CONF_DC_VOLTAGE):
|
||||
sens = await sensor.new_sensor(dc_voltage_config)
|
||||
cg.add(hub.set_dc_voltage(sens))
|
||||
if ac_power_config := config.get(CONF_AC_POWER):
|
||||
sens = await sensor.new_sensor(ac_power_config)
|
||||
cg.add(hub.set_ac_power(sens))
|
||||
if dc_power_config := config.get(CONF_DC_POWER):
|
||||
sens = await sensor.new_sensor(dc_power_config)
|
||||
cg.add(hub.set_dc_power(sens))
|
||||
if limiter_power_config := config.get(CONF_LIMITER_POWER):
|
||||
sens = await sensor.new_sensor(limiter_power_config)
|
||||
cg.add(hub.set_limiter_power(sens))
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(hub.set_temperature(sens))
|
|
@ -0,0 +1,135 @@
|
|||
#include "sun_gtil2.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sun_gtil2 {
|
||||
|
||||
static const char *const TAG = "sun_gtil2";
|
||||
|
||||
static const double NTC_A = 0.0011591051055979914;
|
||||
static const double NTC_B = 0.00022878183547845582;
|
||||
static const double NTC_C = 1.0396291358342124e-07;
|
||||
static const float PULLUP_RESISTANCE = 10000.0f;
|
||||
static const uint16_t ADC_MAX = 1023; // ADC of the inverter controller, not the ESP
|
||||
|
||||
struct SunGTIL2Message {
|
||||
uint16_t sync;
|
||||
uint8_t ac_waveform[277];
|
||||
uint8_t frequency;
|
||||
uint16_t ac_voltage;
|
||||
uint16_t ac_power;
|
||||
uint16_t dc_voltage;
|
||||
uint8_t state;
|
||||
uint8_t unknown1;
|
||||
uint8_t unknown2;
|
||||
uint8_t unknown3;
|
||||
uint8_t limiter_mode;
|
||||
uint8_t unknown4;
|
||||
uint16_t temperature;
|
||||
uint32_t limiter_power;
|
||||
uint16_t dc_power;
|
||||
char serial_number[10];
|
||||
uint8_t unknown5;
|
||||
uint8_t end[39];
|
||||
} __attribute__((packed));
|
||||
|
||||
static const uint16_t MESSAGE_SIZE = sizeof(SunGTIL2Message);
|
||||
|
||||
static_assert(MESSAGE_SIZE == 350, "Expected the message size to be 350 bytes");
|
||||
|
||||
void SunGTIL2::setup() { this->rx_message_.reserve(MESSAGE_SIZE); }
|
||||
|
||||
void SunGTIL2::loop() {
|
||||
while (this->available()) {
|
||||
uint8_t c;
|
||||
this->read_byte(&c);
|
||||
this->handle_char_(c);
|
||||
}
|
||||
}
|
||||
|
||||
std::string SunGTIL2::state_to_string_(uint8_t state) {
|
||||
switch (state) {
|
||||
case 0x02:
|
||||
return "Starting voltage too low";
|
||||
case 0x07:
|
||||
return "Working";
|
||||
default:
|
||||
return str_sprintf("Unknown (0x%02x)", state);
|
||||
}
|
||||
}
|
||||
|
||||
float SunGTIL2::calculate_temperature_(uint16_t adc_value) {
|
||||
if (adc_value >= ADC_MAX || adc_value == 0) {
|
||||
return NAN;
|
||||
}
|
||||
|
||||
float ntc_resistance = PULLUP_RESISTANCE / ((static_cast<float>(ADC_MAX) / adc_value) - 1.0f);
|
||||
double lr = log(double(ntc_resistance));
|
||||
double v = NTC_A + NTC_B * lr + NTC_C * lr * lr * lr;
|
||||
return float(1.0 / v - 273.15);
|
||||
}
|
||||
|
||||
void SunGTIL2::handle_char_(uint8_t c) {
|
||||
if (this->rx_message_.size() > 1 || c == 0x07) {
|
||||
this->rx_message_.push_back(c);
|
||||
} else if (!this->rx_message_.empty()) {
|
||||
this->rx_message_.clear();
|
||||
}
|
||||
if (this->rx_message_.size() < MESSAGE_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
SunGTIL2Message msg;
|
||||
memcpy(&msg, this->rx_message_.data(), MESSAGE_SIZE);
|
||||
this->rx_message_.clear();
|
||||
|
||||
if (!((msg.end[0] == 0) && (msg.end[38] == 0x08)))
|
||||
return;
|
||||
|
||||
ESP_LOGVV(TAG, "Frequency raw value: %02x", msg.frequency);
|
||||
ESP_LOGVV(TAG, "Unknown values: %02x %02x %02x %02x %02x", msg.unknown1, msg.unknown2, msg.unknown3, msg.unknown4,
|
||||
msg.unknown5);
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
if (this->ac_voltage_ != nullptr)
|
||||
this->ac_voltage_->publish_state(__builtin_bswap16(msg.ac_voltage) / 10.0f);
|
||||
if (this->dc_voltage_ != nullptr)
|
||||
this->dc_voltage_->publish_state(__builtin_bswap16(msg.dc_voltage) / 8.0f);
|
||||
if (this->ac_power_ != nullptr)
|
||||
this->ac_power_->publish_state(__builtin_bswap16(msg.ac_power) / 10.0f);
|
||||
if (this->dc_power_ != nullptr)
|
||||
this->dc_power_->publish_state(__builtin_bswap16(msg.dc_power) / 10.0f);
|
||||
if (this->limiter_power_ != nullptr)
|
||||
this->limiter_power_->publish_state(static_cast<int32_t>(__builtin_bswap32(msg.limiter_power)) / 10.0f);
|
||||
if (this->temperature_ != nullptr)
|
||||
this->temperature_->publish_state(calculate_temperature_(__builtin_bswap16(msg.temperature)));
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->state_ != nullptr) {
|
||||
this->state_->publish_state(this->state_to_string_(msg.state));
|
||||
}
|
||||
if (this->serial_number_ != nullptr) {
|
||||
std::string serial_number;
|
||||
serial_number.assign(msg.serial_number, 10);
|
||||
this->serial_number_->publish_state(serial_number);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SunGTIL2::dump_config() {
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR("", "AC Voltage", this->ac_voltage_);
|
||||
LOG_SENSOR("", "DC Voltage", this->dc_voltage_);
|
||||
LOG_SENSOR("", "AC Power", this->ac_power_);
|
||||
LOG_SENSOR("", "DC Power", this->dc_power_);
|
||||
LOG_SENSOR("", "Limiter Power", this->limiter_power_);
|
||||
LOG_SENSOR("", "Temperature", this->temperature_);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
LOG_TEXT_SENSOR("", "State", this->state_);
|
||||
LOG_TEXT_SENSOR("", "Serial Number", this->serial_number_);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace sun_gtil2
|
||||
} // namespace esphome
|
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#endif
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sun_gtil2 {
|
||||
|
||||
class SunGTIL2 : public Component, public uart::UARTDevice {
|
||||
public:
|
||||
float get_setup_priority() const override { return setup_priority::LATE; }
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void set_ac_voltage(sensor::Sensor *sensor) { ac_voltage_ = sensor; }
|
||||
void set_dc_voltage(sensor::Sensor *sensor) { dc_voltage_ = sensor; }
|
||||
void set_ac_power(sensor::Sensor *sensor) { ac_power_ = sensor; }
|
||||
void set_dc_power(sensor::Sensor *sensor) { dc_power_ = sensor; }
|
||||
void set_limiter_power(sensor::Sensor *sensor) { limiter_power_ = sensor; }
|
||||
void set_temperature(sensor::Sensor *sensor) { temperature_ = sensor; }
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void set_state(text_sensor::TextSensor *text_sensor) { state_ = text_sensor; }
|
||||
void set_serial_number(text_sensor::TextSensor *text_sensor) { serial_number_ = text_sensor; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
std::string state_to_string_(uint8_t state);
|
||||
#ifdef USE_SENSOR
|
||||
sensor::Sensor *ac_voltage_{nullptr};
|
||||
sensor::Sensor *dc_voltage_{nullptr};
|
||||
sensor::Sensor *ac_power_{nullptr};
|
||||
sensor::Sensor *dc_power_{nullptr};
|
||||
sensor::Sensor *limiter_power_{nullptr};
|
||||
sensor::Sensor *temperature_{nullptr};
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
text_sensor::TextSensor *state_{nullptr};
|
||||
text_sensor::TextSensor *serial_number_{nullptr};
|
||||
#endif
|
||||
|
||||
float calculate_temperature_(uint16_t adc_value);
|
||||
void handle_char_(uint8_t c);
|
||||
std::vector<uint8_t> rx_message_;
|
||||
};
|
||||
|
||||
} // namespace sun_gtil2
|
||||
} // namespace esphome
|
|
@ -0,0 +1,31 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import CONF_STATE
|
||||
from . import SunGTIL2Component, CONF_SUN_GTIL2_ID
|
||||
|
||||
CONF_SERIAL_NUMBER = "serial_number"
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_SUN_GTIL2_ID): cv.use_id(SunGTIL2Component),
|
||||
cv.Optional(CONF_STATE): text_sensor.text_sensor_schema(
|
||||
text_sensor.TextSensor
|
||||
),
|
||||
cv.Optional(CONF_SERIAL_NUMBER): text_sensor.text_sensor_schema(
|
||||
text_sensor.TextSensor
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_SUN_GTIL2_ID])
|
||||
if state_config := config.get(CONF_STATE):
|
||||
sens = await text_sensor.new_text_sensor(state_config)
|
||||
cg.add(hub.set_state(sens))
|
||||
if serial_number_config := config.get(CONF_SERIAL_NUMBER):
|
||||
sens = await text_sensor.new_text_sensor(serial_number_config)
|
||||
cg.add(hub.set_serial_number(sens))
|
|
@ -7,15 +7,22 @@ from esphome.const import (
|
|||
CONF_SWITCH_DATAPOINT,
|
||||
CONF_SUPPORTS_COOL,
|
||||
CONF_SUPPORTS_HEAT,
|
||||
CONF_PRESET,
|
||||
CONF_SWING_MODE,
|
||||
CONF_FAN_MODE,
|
||||
CONF_TEMPERATURE,
|
||||
)
|
||||
from .. import tuya_ns, CONF_TUYA_ID, Tuya
|
||||
|
||||
DEPENDENCIES = ["tuya"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
|
||||
CONF_ACTIVE_STATE_DATAPOINT = "active_state_datapoint"
|
||||
CONF_ACTIVE_STATE_HEATING_VALUE = "active_state_heating_value"
|
||||
CONF_ACTIVE_STATE_COOLING_VALUE = "active_state_cooling_value"
|
||||
CONF_ACTIVE_STATE = "active_state"
|
||||
CONF_DATAPOINT = "datapoint"
|
||||
CONF_HEATING_VALUE = "heating_value"
|
||||
CONF_COOLING_VALUE = "cooling_value"
|
||||
CONF_DRYING_VALUE = "drying_value"
|
||||
CONF_FANONLY_VALUE = "fanonly_value"
|
||||
CONF_HEATING_STATE_PIN = "heating_state_pin"
|
||||
CONF_COOLING_STATE_PIN = "cooling_state_pin"
|
||||
CONF_TARGET_TEMPERATURE_DATAPOINT = "target_temperature_datapoint"
|
||||
|
@ -23,9 +30,17 @@ CONF_CURRENT_TEMPERATURE_DATAPOINT = "current_temperature_datapoint"
|
|||
CONF_TEMPERATURE_MULTIPLIER = "temperature_multiplier"
|
||||
CONF_CURRENT_TEMPERATURE_MULTIPLIER = "current_temperature_multiplier"
|
||||
CONF_TARGET_TEMPERATURE_MULTIPLIER = "target_temperature_multiplier"
|
||||
CONF_ECO_DATAPOINT = "eco_datapoint"
|
||||
CONF_ECO_TEMPERATURE = "eco_temperature"
|
||||
CONF_ECO = "eco"
|
||||
CONF_SLEEP = "sleep"
|
||||
CONF_SLEEP_DATAPOINT = "sleep_datapoint"
|
||||
CONF_REPORTS_FAHRENHEIT = "reports_fahrenheit"
|
||||
CONF_VERTICAL_DATAPOINT = "vertical_datapoint"
|
||||
CONF_HORIZONTAL_DATAPOINT = "horizontal_datapoint"
|
||||
CONF_LOW_VALUE = "low_value"
|
||||
CONF_MEDIUM_VALUE = "medium_value"
|
||||
CONF_MIDDLE_VALUE = "middle_value"
|
||||
CONF_HIGH_VALUE = "high_value"
|
||||
CONF_AUTO_VALUE = "auto_value"
|
||||
|
||||
TuyaClimate = tuya_ns.class_("TuyaClimate", climate.Climate, cg.Component)
|
||||
|
||||
|
@ -67,30 +82,73 @@ def validate_temperature_multipliers(value):
|
|||
return value
|
||||
|
||||
|
||||
def validate_active_state_values(value):
|
||||
if CONF_ACTIVE_STATE_DATAPOINT not in value:
|
||||
if CONF_ACTIVE_STATE_COOLING_VALUE in value:
|
||||
raise cv.Invalid(
|
||||
f"{CONF_ACTIVE_STATE_DATAPOINT} required if using "
|
||||
f"{CONF_ACTIVE_STATE_COOLING_VALUE}"
|
||||
)
|
||||
else:
|
||||
if value[CONF_SUPPORTS_COOL] and CONF_ACTIVE_STATE_COOLING_VALUE not in value:
|
||||
raise cv.Invalid(
|
||||
f"{CONF_ACTIVE_STATE_COOLING_VALUE} required if using "
|
||||
f"{CONF_ACTIVE_STATE_DATAPOINT} and device supports cooling"
|
||||
)
|
||||
def validate_cooling_values(value):
|
||||
if CONF_SUPPORTS_COOL in value:
|
||||
cooling_supported = value[CONF_SUPPORTS_COOL]
|
||||
if not cooling_supported and CONF_ACTIVE_STATE in value:
|
||||
active_state_config = value[CONF_ACTIVE_STATE]
|
||||
if (
|
||||
CONF_COOLING_VALUE in active_state_config
|
||||
or CONF_COOLING_STATE_PIN in value
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"Device does not support cooling, but {CONF_COOLING_VALUE} or {CONF_COOLING_STATE_PIN} specified."
|
||||
f" Please add '{CONF_SUPPORTS_COOL}: true' to your configuration."
|
||||
)
|
||||
elif cooling_supported and CONF_ACTIVE_STATE in value:
|
||||
active_state_config = value[CONF_ACTIVE_STATE]
|
||||
if (
|
||||
CONF_COOLING_VALUE not in active_state_config
|
||||
and CONF_COOLING_STATE_PIN not in value
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"Either {CONF_ACTIVE_STATE} {CONF_COOLING_VALUE} or {CONF_COOLING_STATE_PIN} is required if"
|
||||
f" {CONF_SUPPORTS_COOL}: true' is in your configuration."
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def validate_eco_values(value):
|
||||
if CONF_ECO_TEMPERATURE in value and CONF_ECO_DATAPOINT not in value:
|
||||
raise cv.Invalid(
|
||||
f"{CONF_ECO_DATAPOINT} required if using {CONF_ECO_TEMPERATURE}"
|
||||
)
|
||||
return value
|
||||
ACTIVE_STATES = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_HEATING_VALUE, default=1): cv.uint8_t,
|
||||
cv.Optional(CONF_COOLING_VALUE): cv.uint8_t,
|
||||
cv.Optional(CONF_DRYING_VALUE): cv.uint8_t,
|
||||
cv.Optional(CONF_FANONLY_VALUE): cv.uint8_t,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
PRESETS = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ECO): {
|
||||
cv.Required(CONF_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_TEMPERATURE): cv.temperature,
|
||||
},
|
||||
cv.Optional(CONF_SLEEP): {
|
||||
cv.Required(CONF_DATAPOINT): cv.uint8_t,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
FAN_MODES = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_AUTO_VALUE): cv.uint8_t,
|
||||
cv.Optional(CONF_LOW_VALUE): cv.uint8_t,
|
||||
cv.Optional(CONF_MEDIUM_VALUE): cv.uint8_t,
|
||||
cv.Optional(CONF_MIDDLE_VALUE): cv.uint8_t,
|
||||
cv.Optional(CONF_HIGH_VALUE): cv.uint8_t,
|
||||
}
|
||||
)
|
||||
|
||||
SWING_MODES = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_VERTICAL_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_HORIZONTAL_DATAPOINT): cv.uint8_t,
|
||||
},
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
climate.CLIMATE_SCHEMA.extend(
|
||||
{
|
||||
|
@ -99,9 +157,7 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean,
|
||||
cv.Optional(CONF_SUPPORTS_COOL, default=False): cv.boolean,
|
||||
cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_ACTIVE_STATE_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_ACTIVE_STATE_HEATING_VALUE, default=1): cv.uint8_t,
|
||||
cv.Optional(CONF_ACTIVE_STATE_COOLING_VALUE): cv.uint8_t,
|
||||
cv.Optional(CONF_ACTIVE_STATE): ACTIVE_STATES,
|
||||
cv.Optional(CONF_HEATING_STATE_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Optional(CONF_COOLING_STATE_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_DATAPOINT): cv.uint8_t,
|
||||
|
@ -109,17 +165,32 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Optional(CONF_TEMPERATURE_MULTIPLIER): cv.positive_float,
|
||||
cv.Optional(CONF_CURRENT_TEMPERATURE_MULTIPLIER): cv.positive_float,
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_MULTIPLIER): cv.positive_float,
|
||||
cv.Optional(CONF_ECO_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_ECO_TEMPERATURE): cv.temperature,
|
||||
cv.Optional(CONF_REPORTS_FAHRENHEIT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PRESET): PRESETS,
|
||||
cv.Optional(CONF_FAN_MODE): FAN_MODES,
|
||||
cv.Optional(CONF_SWING_MODE): SWING_MODES,
|
||||
cv.Optional("active_state_datapoint"): cv.invalid(
|
||||
"'active_state_datapoint' has been moved inside of the 'active_state' config block as 'datapoint'"
|
||||
),
|
||||
cv.Optional("active_state_heating_value"): cv.invalid(
|
||||
"'active_state_heating_value' has been moved inside of the 'active_state' config block as 'heating_value'"
|
||||
),
|
||||
cv.Optional("active_state_cooling_value"): cv.invalid(
|
||||
"'active_state_cooling_value' has been moved inside of the 'active_state' config block as 'cooling_value'"
|
||||
),
|
||||
cv.Optional("eco_datapoint"): cv.invalid(
|
||||
"'eco_datapoint' has been moved inside of the 'eco' config block under 'preset' as 'datapoint'"
|
||||
),
|
||||
cv.Optional("eco_temperature"): cv.invalid(
|
||||
"'eco_temperature' has been moved inside of the 'eco' config block under 'preset' as 'temperature'"
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.has_at_least_one_key(CONF_TARGET_TEMPERATURE_DATAPOINT, CONF_SWITCH_DATAPOINT),
|
||||
validate_temperature_multipliers,
|
||||
validate_active_state_values,
|
||||
cv.has_at_most_one_key(CONF_ACTIVE_STATE_DATAPOINT, CONF_HEATING_STATE_PIN),
|
||||
cv.has_at_most_one_key(CONF_ACTIVE_STATE_DATAPOINT, CONF_COOLING_STATE_PIN),
|
||||
validate_eco_values,
|
||||
validate_cooling_values,
|
||||
cv.has_at_most_one_key(CONF_ACTIVE_STATE, CONF_HEATING_STATE_PIN),
|
||||
cv.has_at_most_one_key(CONF_ACTIVE_STATE, CONF_COOLING_STATE_PIN),
|
||||
)
|
||||
|
||||
|
||||
|
@ -133,61 +204,78 @@ async def to_code(config):
|
|||
|
||||
cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT]))
|
||||
cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL]))
|
||||
if CONF_SWITCH_DATAPOINT in config:
|
||||
cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT]))
|
||||
if CONF_ACTIVE_STATE_DATAPOINT in config:
|
||||
cg.add(var.set_active_state_id(config[CONF_ACTIVE_STATE_DATAPOINT]))
|
||||
if CONF_ACTIVE_STATE_HEATING_VALUE in config:
|
||||
cg.add(
|
||||
var.set_active_state_heating_value(
|
||||
config[CONF_ACTIVE_STATE_HEATING_VALUE]
|
||||
)
|
||||
)
|
||||
if CONF_ACTIVE_STATE_COOLING_VALUE in config:
|
||||
cg.add(
|
||||
var.set_active_state_cooling_value(
|
||||
config[CONF_ACTIVE_STATE_COOLING_VALUE]
|
||||
)
|
||||
)
|
||||
if switch_datapoint := config.get(CONF_SWITCH_DATAPOINT):
|
||||
cg.add(var.set_switch_id(switch_datapoint))
|
||||
|
||||
if active_state_config := config.get(CONF_ACTIVE_STATE):
|
||||
cg.add(var.set_active_state_id(CONF_DATAPOINT))
|
||||
if (heating_value := active_state_config.get(CONF_HEATING_VALUE)) is not None:
|
||||
cg.add(var.set_active_state_heating_value(heating_value))
|
||||
if (cooling_value := active_state_config.get(CONF_COOLING_VALUE)) is not None:
|
||||
cg.add(var.set_active_state_cooling_value(cooling_value))
|
||||
if (drying_value := active_state_config.get(CONF_DRYING_VALUE)) is not None:
|
||||
cg.add(var.set_active_state_drying_value(drying_value))
|
||||
if (fanonly_value := active_state_config.get(CONF_FANONLY_VALUE)) is not None:
|
||||
cg.add(var.set_active_state_fanonly_value(fanonly_value))
|
||||
else:
|
||||
if CONF_HEATING_STATE_PIN in config:
|
||||
if heating_state_pin_config := config.get(CONF_HEATING_STATE_PIN):
|
||||
heating_state_pin = await cg.gpio_pin_expression(
|
||||
config[CONF_HEATING_STATE_PIN]
|
||||
config(heating_state_pin_config)
|
||||
)
|
||||
cg.add(var.set_heating_state_pin(heating_state_pin))
|
||||
if CONF_COOLING_STATE_PIN in config:
|
||||
if cooling_state_pin_config := config.get(CONF_COOLING_STATE_PIN):
|
||||
cooling_state_pin = await cg.gpio_pin_expression(
|
||||
config[CONF_COOLING_STATE_PIN]
|
||||
config(cooling_state_pin_config)
|
||||
)
|
||||
cg.add(var.set_cooling_state_pin(cooling_state_pin))
|
||||
if CONF_TARGET_TEMPERATURE_DATAPOINT in config:
|
||||
cg.add(var.set_target_temperature_id(config[CONF_TARGET_TEMPERATURE_DATAPOINT]))
|
||||
if CONF_CURRENT_TEMPERATURE_DATAPOINT in config:
|
||||
cg.add(
|
||||
var.set_current_temperature_id(config[CONF_CURRENT_TEMPERATURE_DATAPOINT])
|
||||
)
|
||||
if CONF_TEMPERATURE_MULTIPLIER in config:
|
||||
cg.add(
|
||||
var.set_target_temperature_multiplier(config[CONF_TEMPERATURE_MULTIPLIER])
|
||||
)
|
||||
cg.add(
|
||||
var.set_current_temperature_multiplier(config[CONF_TEMPERATURE_MULTIPLIER])
|
||||
)
|
||||
|
||||
if target_temperature_datapoint := config.get(CONF_TARGET_TEMPERATURE_DATAPOINT):
|
||||
cg.add(var.set_target_temperature_id(target_temperature_datapoint))
|
||||
if current_temperature_datapoint := config.get(CONF_CURRENT_TEMPERATURE_DATAPOINT):
|
||||
cg.add(var.set_current_temperature_id(current_temperature_datapoint))
|
||||
|
||||
if temperature_multiplier := config.get(CONF_TEMPERATURE_MULTIPLIER):
|
||||
cg.add(var.set_target_temperature_multiplier(temperature_multiplier))
|
||||
cg.add(var.set_current_temperature_multiplier(temperature_multiplier))
|
||||
else:
|
||||
cg.add(
|
||||
var.set_current_temperature_multiplier(
|
||||
config[CONF_CURRENT_TEMPERATURE_MULTIPLIER]
|
||||
if current_temperature_multiplier := config.get(
|
||||
CONF_CURRENT_TEMPERATURE_MULTIPLIER
|
||||
):
|
||||
cg.add(
|
||||
var.set_current_temperature_multiplier(current_temperature_multiplier)
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
var.set_target_temperature_multiplier(
|
||||
config[CONF_TARGET_TEMPERATURE_MULTIPLIER]
|
||||
)
|
||||
)
|
||||
if CONF_ECO_DATAPOINT in config:
|
||||
cg.add(var.set_eco_id(config[CONF_ECO_DATAPOINT]))
|
||||
if CONF_ECO_TEMPERATURE in config:
|
||||
cg.add(var.set_eco_temperature(config[CONF_ECO_TEMPERATURE]))
|
||||
if target_temperature_multiplier := config.get(
|
||||
CONF_TARGET_TEMPERATURE_MULTIPLIER
|
||||
):
|
||||
cg.add(var.set_target_temperature_multiplier(target_temperature_multiplier))
|
||||
|
||||
if config[CONF_REPORTS_FAHRENHEIT]:
|
||||
cg.add(var.set_reports_fahrenheit())
|
||||
|
||||
if preset_config := config.get(CONF_PRESET, {}):
|
||||
if eco_config := preset_config.get(CONF_ECO, {}):
|
||||
cg.add(var.set_eco_id(CONF_DATAPOINT))
|
||||
if eco_temperature := eco_config.get(CONF_TEMPERATURE):
|
||||
cg.add(var.set_eco_temperature(eco_temperature))
|
||||
if CONF_SLEEP in preset_config:
|
||||
cg.add(var.set_sleep_id(CONF_DATAPOINT))
|
||||
|
||||
if swing_mode_config := config.get(CONF_SWING_MODE):
|
||||
if swing_vertical_datapoint := swing_mode_config.get(CONF_VERTICAL_DATAPOINT):
|
||||
cg.add(var.set_swing_vertical_id(swing_vertical_datapoint))
|
||||
if swing_horizontal_datapoint := swing_mode_config.get(
|
||||
CONF_HORIZONTAL_DATAPOINT
|
||||
):
|
||||
cg.add(var.set_swing_horizontal_id(swing_horizontal_datapoint))
|
||||
if fan_mode_config := config.get(CONF_FAN_MODE):
|
||||
cg.add(var.set_fan_speed_id(CONF_DATAPOINT))
|
||||
if (fan_auto_value := fan_mode_config.get(CONF_AUTO_VALUE)) is not None:
|
||||
cg.add(var.set_fan_speed_auto_value(fan_auto_value))
|
||||
if (fan_low_value := fan_mode_config.get(CONF_LOW_VALUE)) is not None:
|
||||
cg.add(var.set_fan_speed_low_value(fan_low_value))
|
||||
if (fan_medium_value := fan_mode_config.get(CONF_MEDIUM_VALUE)) is not None:
|
||||
cg.add(var.set_fan_speed_medium_value(fan_medium_value))
|
||||
if (fan_middle_value := fan_mode_config.get(CONF_MIDDLE_VALUE)) is not None:
|
||||
cg.add(var.set_fan_speed_middle_value(fan_middle_value))
|
||||
if (fan_high_value := fan_mode_config.get(CONF_HIGH_VALUE)) is not None:
|
||||
cg.add(var.set_fan_speed_high_value(fan_high_value))
|
||||
|
|
|
@ -75,6 +75,41 @@ void TuyaClimate::setup() {
|
|||
this->publish_state();
|
||||
});
|
||||
}
|
||||
if (this->sleep_id_.has_value()) {
|
||||
this->parent_->register_listener(*this->sleep_id_, [this](const TuyaDatapoint &datapoint) {
|
||||
this->sleep_ = datapoint.value_bool;
|
||||
ESP_LOGV(TAG, "MCU reported sleep is: %s", ONOFF(this->sleep_));
|
||||
this->compute_preset_();
|
||||
this->compute_target_temperature_();
|
||||
this->publish_state();
|
||||
});
|
||||
}
|
||||
if (this->swing_vertical_id_.has_value()) {
|
||||
this->parent_->register_listener(*this->swing_vertical_id_, [this](const TuyaDatapoint &datapoint) {
|
||||
this->swing_vertical_ = datapoint.value_bool;
|
||||
ESP_LOGV(TAG, "MCU reported vertical swing is: %s", ONOFF(datapoint.value_bool));
|
||||
this->compute_swingmode_();
|
||||
this->publish_state();
|
||||
});
|
||||
}
|
||||
|
||||
if (this->swing_horizontal_id_.has_value()) {
|
||||
this->parent_->register_listener(*this->swing_horizontal_id_, [this](const TuyaDatapoint &datapoint) {
|
||||
this->swing_horizontal_ = datapoint.value_bool;
|
||||
ESP_LOGV(TAG, "MCU reported horizontal swing is: %s", ONOFF(datapoint.value_bool));
|
||||
this->compute_swingmode_();
|
||||
this->publish_state();
|
||||
});
|
||||
}
|
||||
|
||||
if (this->fan_speed_id_.has_value()) {
|
||||
this->parent_->register_listener(*this->fan_speed_id_, [this](const TuyaDatapoint &datapoint) {
|
||||
ESP_LOGV(TAG, "MCU reported Fan Speed Mode is: %u", datapoint.value_enum);
|
||||
this->fan_state_ = datapoint.value_enum;
|
||||
this->compute_fanmode_();
|
||||
this->publish_state();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void TuyaClimate::loop() {
|
||||
|
@ -110,8 +145,22 @@ void TuyaClimate::control(const climate::ClimateCall &call) {
|
|||
const bool switch_state = *call.get_mode() != climate::CLIMATE_MODE_OFF;
|
||||
ESP_LOGV(TAG, "Setting switch: %s", ONOFF(switch_state));
|
||||
this->parent_->set_boolean_datapoint_value(*this->switch_id_, switch_state);
|
||||
const climate::ClimateMode new_mode = *call.get_mode();
|
||||
|
||||
if (new_mode == climate::CLIMATE_MODE_HEAT && this->supports_heat_) {
|
||||
this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_heating_value_);
|
||||
} else if (new_mode == climate::CLIMATE_MODE_COOL && this->supports_cool_) {
|
||||
this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_cooling_value_);
|
||||
} else if (new_mode == climate::CLIMATE_MODE_DRY && this->active_state_drying_value_.has_value()) {
|
||||
this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_drying_value_);
|
||||
} else if (new_mode == climate::CLIMATE_MODE_FAN_ONLY && this->active_state_fanonly_value_.has_value()) {
|
||||
this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_fanonly_value_);
|
||||
}
|
||||
}
|
||||
|
||||
control_swing_mode_(call);
|
||||
control_fan_mode_(call);
|
||||
|
||||
if (call.get_target_temperature().has_value()) {
|
||||
float target_temperature = *call.get_target_temperature();
|
||||
if (this->reports_fahrenheit_)
|
||||
|
@ -129,6 +178,106 @@ void TuyaClimate::control(const climate::ClimateCall &call) {
|
|||
ESP_LOGV(TAG, "Setting eco: %s", ONOFF(eco));
|
||||
this->parent_->set_boolean_datapoint_value(*this->eco_id_, eco);
|
||||
}
|
||||
if (this->sleep_id_.has_value()) {
|
||||
const bool sleep = preset == climate::CLIMATE_PRESET_SLEEP;
|
||||
ESP_LOGV(TAG, "Setting sleep: %s", ONOFF(sleep));
|
||||
this->parent_->set_boolean_datapoint_value(*this->sleep_id_, sleep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TuyaClimate::control_swing_mode_(const climate::ClimateCall &call) {
|
||||
bool vertical_swing_changed = false;
|
||||
bool horizontal_swing_changed = false;
|
||||
|
||||
if (call.get_swing_mode().has_value()) {
|
||||
const auto swing_mode = *call.get_swing_mode();
|
||||
|
||||
switch (swing_mode) {
|
||||
case climate::CLIMATE_SWING_OFF:
|
||||
if (swing_vertical_ || swing_horizontal_) {
|
||||
this->swing_vertical_ = false;
|
||||
this->swing_horizontal_ = false;
|
||||
vertical_swing_changed = true;
|
||||
horizontal_swing_changed = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case climate::CLIMATE_SWING_BOTH:
|
||||
if (!swing_vertical_ || !swing_horizontal_) {
|
||||
this->swing_vertical_ = true;
|
||||
this->swing_horizontal_ = true;
|
||||
vertical_swing_changed = true;
|
||||
horizontal_swing_changed = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case climate::CLIMATE_SWING_VERTICAL:
|
||||
if (!swing_vertical_ || swing_horizontal_) {
|
||||
this->swing_vertical_ = true;
|
||||
this->swing_horizontal_ = false;
|
||||
vertical_swing_changed = true;
|
||||
horizontal_swing_changed = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case climate::CLIMATE_SWING_HORIZONTAL:
|
||||
if (swing_vertical_ || !swing_horizontal_) {
|
||||
this->swing_vertical_ = false;
|
||||
this->swing_horizontal_ = true;
|
||||
vertical_swing_changed = true;
|
||||
horizontal_swing_changed = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (vertical_swing_changed && this->swing_vertical_id_.has_value()) {
|
||||
ESP_LOGV(TAG, "Setting vertical swing: %s", ONOFF(swing_vertical_));
|
||||
this->parent_->set_boolean_datapoint_value(*this->swing_vertical_id_, swing_vertical_);
|
||||
}
|
||||
|
||||
if (horizontal_swing_changed && this->swing_horizontal_id_.has_value()) {
|
||||
ESP_LOGV(TAG, "Setting horizontal swing: %s", ONOFF(swing_horizontal_));
|
||||
this->parent_->set_boolean_datapoint_value(*this->swing_horizontal_id_, swing_horizontal_);
|
||||
}
|
||||
|
||||
// Publish the state after updating the swing mode
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
void TuyaClimate::control_fan_mode_(const climate::ClimateCall &call) {
|
||||
if (call.get_fan_mode().has_value()) {
|
||||
climate::ClimateFanMode fan_mode = *call.get_fan_mode();
|
||||
|
||||
uint8_t tuya_fan_speed;
|
||||
switch (fan_mode) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
tuya_fan_speed = *fan_speed_low_value_;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
tuya_fan_speed = *fan_speed_medium_value_;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MIDDLE:
|
||||
tuya_fan_speed = *fan_speed_middle_value_;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
tuya_fan_speed = *fan_speed_high_value_;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
tuya_fan_speed = *fan_speed_auto_value_;
|
||||
break;
|
||||
default:
|
||||
tuya_fan_speed = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (this->fan_speed_id_.has_value()) {
|
||||
this->parent_->set_enum_datapoint_value(*this->fan_speed_id_, tuya_fan_speed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,10 +289,46 @@ climate::ClimateTraits TuyaClimate::traits() {
|
|||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||
if (supports_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||
if (this->active_state_drying_value_.has_value())
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
|
||||
if (this->active_state_fanonly_value_.has_value())
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
||||
if (this->eco_id_.has_value()) {
|
||||
traits.add_supported_preset(climate::CLIMATE_PRESET_NONE);
|
||||
traits.add_supported_preset(climate::CLIMATE_PRESET_ECO);
|
||||
}
|
||||
if (this->sleep_id_.has_value()) {
|
||||
traits.add_supported_preset(climate::CLIMATE_PRESET_SLEEP);
|
||||
}
|
||||
if (this->sleep_id_.has_value() || this->eco_id_.has_value()) {
|
||||
traits.add_supported_preset(climate::CLIMATE_PRESET_NONE);
|
||||
}
|
||||
if (this->swing_vertical_id_.has_value() && this->swing_horizontal_id_.has_value()) {
|
||||
std::set<climate::ClimateSwingMode> supported_swing_modes = {
|
||||
climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL,
|
||||
climate::CLIMATE_SWING_HORIZONTAL};
|
||||
traits.set_supported_swing_modes(std::move(supported_swing_modes));
|
||||
} else if (this->swing_vertical_id_.has_value()) {
|
||||
std::set<climate::ClimateSwingMode> supported_swing_modes = {climate::CLIMATE_SWING_OFF,
|
||||
climate::CLIMATE_SWING_VERTICAL};
|
||||
traits.set_supported_swing_modes(std::move(supported_swing_modes));
|
||||
} else if (this->swing_horizontal_id_.has_value()) {
|
||||
std::set<climate::ClimateSwingMode> supported_swing_modes = {climate::CLIMATE_SWING_OFF,
|
||||
climate::CLIMATE_SWING_HORIZONTAL};
|
||||
traits.set_supported_swing_modes(std::move(supported_swing_modes));
|
||||
}
|
||||
|
||||
if (fan_speed_id_) {
|
||||
if (fan_speed_low_value_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_LOW);
|
||||
if (fan_speed_medium_value_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_MEDIUM);
|
||||
if (fan_speed_middle_value_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_MIDDLE);
|
||||
if (fan_speed_high_value_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_HIGH);
|
||||
if (fan_speed_auto_value_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_AUTO);
|
||||
}
|
||||
return traits;
|
||||
}
|
||||
|
||||
|
@ -166,16 +351,56 @@ void TuyaClimate::dump_config() {
|
|||
if (this->eco_id_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Eco has datapoint ID %u", *this->eco_id_);
|
||||
}
|
||||
if (this->sleep_id_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Sleep has datapoint ID %u", *this->sleep_id_);
|
||||
}
|
||||
if (this->swing_vertical_id_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Swing Vertical has datapoint ID %u", *this->swing_vertical_id_);
|
||||
}
|
||||
if (this->swing_horizontal_id_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Swing Horizontal has datapoint ID %u", *this->swing_horizontal_id_);
|
||||
}
|
||||
}
|
||||
|
||||
void TuyaClimate::compute_preset_() {
|
||||
if (this->eco_) {
|
||||
this->preset = climate::CLIMATE_PRESET_ECO;
|
||||
} else if (this->sleep_) {
|
||||
this->preset = climate::CLIMATE_PRESET_SLEEP;
|
||||
} else {
|
||||
this->preset = climate::CLIMATE_PRESET_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
void TuyaClimate::compute_swingmode_() {
|
||||
if (this->swing_vertical_ && this->swing_horizontal_) {
|
||||
this->swing_mode = climate::CLIMATE_SWING_BOTH;
|
||||
} else if (this->swing_vertical_) {
|
||||
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
|
||||
} else if (this->swing_horizontal_) {
|
||||
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
|
||||
} else {
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
void TuyaClimate::compute_fanmode_() {
|
||||
if (this->fan_speed_id_.has_value()) {
|
||||
// Use state from MCU datapoint
|
||||
if (this->fan_speed_auto_value_.has_value() && this->fan_state_ == this->fan_speed_auto_value_) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
} else if (this->fan_speed_high_value_.has_value() && this->fan_state_ == this->fan_speed_high_value_) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
} else if (this->fan_speed_medium_value_.has_value() && this->fan_state_ == this->fan_speed_medium_value_) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
} else if (this->fan_speed_middle_value_.has_value() && this->fan_state_ == this->fan_speed_middle_value_) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_MIDDLE;
|
||||
} else if (this->fan_speed_low_value_.has_value() && this->fan_state_ == this->fan_speed_low_value_) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TuyaClimate::compute_target_temperature_() {
|
||||
if (this->eco_ && this->eco_temperature_.has_value()) {
|
||||
this->target_temperature = *this->eco_temperature_;
|
||||
|
@ -202,16 +427,28 @@ void TuyaClimate::compute_state_() {
|
|||
if (this->supports_heat_ && this->active_state_heating_value_.has_value() &&
|
||||
this->active_state_ == this->active_state_heating_value_) {
|
||||
target_action = climate::CLIMATE_ACTION_HEATING;
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
} else if (this->supports_cool_ && this->active_state_cooling_value_.has_value() &&
|
||||
this->active_state_ == this->active_state_cooling_value_) {
|
||||
target_action = climate::CLIMATE_ACTION_COOLING;
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
} else if (this->active_state_drying_value_.has_value() &&
|
||||
this->active_state_ == this->active_state_drying_value_) {
|
||||
target_action = climate::CLIMATE_ACTION_DRYING;
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
} else if (this->active_state_fanonly_value_.has_value() &&
|
||||
this->active_state_ == this->active_state_fanonly_value_) {
|
||||
target_action = climate::CLIMATE_ACTION_FAN;
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
}
|
||||
} else if (this->heating_state_pin_ != nullptr || this->cooling_state_pin_ != nullptr) {
|
||||
// Use state from input pins
|
||||
if (this->heating_state_) {
|
||||
target_action = climate::CLIMATE_ACTION_HEATING;
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
} else if (this->cooling_state_) {
|
||||
target_action = climate::CLIMATE_ACTION_COOLING;
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
}
|
||||
} else {
|
||||
// Fallback to active state calc based on temp and hysteresis
|
||||
|
@ -219,8 +456,10 @@ void TuyaClimate::compute_state_() {
|
|||
if (std::abs(temp_diff) > this->hysteresis_) {
|
||||
if (this->supports_heat_ && temp_diff > 0) {
|
||||
target_action = climate::CLIMATE_ACTION_HEATING;
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
} else if (this->supports_cool_ && temp_diff < 0) {
|
||||
target_action = climate::CLIMATE_ACTION_COOLING;
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,22 @@ class TuyaClimate : public climate::Climate, public Component {
|
|||
void set_active_state_id(uint8_t state_id) { this->active_state_id_ = state_id; }
|
||||
void set_active_state_heating_value(uint8_t value) { this->active_state_heating_value_ = value; }
|
||||
void set_active_state_cooling_value(uint8_t value) { this->active_state_cooling_value_ = value; }
|
||||
void set_active_state_drying_value(uint8_t value) { this->active_state_drying_value_ = value; }
|
||||
void set_active_state_fanonly_value(uint8_t value) { this->active_state_fanonly_value_ = value; }
|
||||
void set_heating_state_pin(GPIOPin *pin) { this->heating_state_pin_ = pin; }
|
||||
void set_cooling_state_pin(GPIOPin *pin) { this->cooling_state_pin_ = pin; }
|
||||
void set_swing_vertical_id(uint8_t swing_vertical_id) { this->swing_vertical_id_ = swing_vertical_id; }
|
||||
void set_swing_horizontal_id(uint8_t swing_horizontal_id) { this->swing_horizontal_id_ = swing_horizontal_id; }
|
||||
void set_fan_speed_id(uint8_t fan_speed_id) { this->fan_speed_id_ = fan_speed_id; }
|
||||
void set_fan_speed_low_value(uint8_t fan_speed_low_value) { this->fan_speed_low_value_ = fan_speed_low_value; }
|
||||
void set_fan_speed_medium_value(uint8_t fan_speed_medium_value) {
|
||||
this->fan_speed_medium_value_ = fan_speed_medium_value;
|
||||
}
|
||||
void set_fan_speed_middle_value(uint8_t fan_speed_middle_value) {
|
||||
this->fan_speed_middle_value_ = fan_speed_middle_value;
|
||||
}
|
||||
void set_fan_speed_high_value(uint8_t fan_speed_high_value) { this->fan_speed_high_value_ = fan_speed_high_value; }
|
||||
void set_fan_speed_auto_value(uint8_t fan_speed_auto_value) { this->fan_speed_auto_value_ = fan_speed_auto_value; }
|
||||
void set_target_temperature_id(uint8_t target_temperature_id) {
|
||||
this->target_temperature_id_ = target_temperature_id;
|
||||
}
|
||||
|
@ -34,6 +48,7 @@ class TuyaClimate : public climate::Climate, public Component {
|
|||
}
|
||||
void set_eco_id(uint8_t eco_id) { this->eco_id_ = eco_id; }
|
||||
void set_eco_temperature(float eco_temperature) { this->eco_temperature_ = eco_temperature; }
|
||||
void set_sleep_id(uint8_t sleep_id) { this->sleep_id_ = sleep_id; }
|
||||
|
||||
void set_reports_fahrenheit() { this->reports_fahrenheit_ = true; }
|
||||
|
||||
|
@ -43,6 +58,12 @@ class TuyaClimate : public climate::Climate, public Component {
|
|||
/// Override control to change settings of the climate device.
|
||||
void control(const climate::ClimateCall &call) override;
|
||||
|
||||
/// Override control to change settings of swing mode.
|
||||
void control_swing_mode_(const climate::ClimateCall &call);
|
||||
|
||||
/// Override control to change settings of fan mode.
|
||||
void control_fan_mode_(const climate::ClimateCall &call);
|
||||
|
||||
/// Return the traits of this controller.
|
||||
climate::ClimateTraits traits() override;
|
||||
|
||||
|
@ -55,6 +76,12 @@ class TuyaClimate : public climate::Climate, public Component {
|
|||
/// Re-compute the state of this climate controller.
|
||||
void compute_state_();
|
||||
|
||||
/// Re-Compute the swing mode of this climate controller.
|
||||
void compute_swingmode_();
|
||||
|
||||
/// Re-Compute the fan mode of this climate controller.
|
||||
void compute_fanmode_();
|
||||
|
||||
/// Switch the climate device to the given climate mode.
|
||||
void switch_to_action_(climate::ClimateAction action);
|
||||
|
||||
|
@ -65,6 +92,8 @@ class TuyaClimate : public climate::Climate, public Component {
|
|||
optional<uint8_t> active_state_id_{};
|
||||
optional<uint8_t> active_state_heating_value_{};
|
||||
optional<uint8_t> active_state_cooling_value_{};
|
||||
optional<uint8_t> active_state_drying_value_{};
|
||||
optional<uint8_t> active_state_fanonly_value_{};
|
||||
GPIOPin *heating_state_pin_{nullptr};
|
||||
GPIOPin *cooling_state_pin_{nullptr};
|
||||
optional<uint8_t> target_temperature_id_{};
|
||||
|
@ -73,12 +102,25 @@ class TuyaClimate : public climate::Climate, public Component {
|
|||
float target_temperature_multiplier_{1.0f};
|
||||
float hysteresis_{1.0f};
|
||||
optional<uint8_t> eco_id_{};
|
||||
optional<uint8_t> sleep_id_{};
|
||||
optional<float> eco_temperature_{};
|
||||
uint8_t active_state_;
|
||||
uint8_t fan_state_;
|
||||
optional<uint8_t> swing_vertical_id_{};
|
||||
optional<uint8_t> swing_horizontal_id_{};
|
||||
optional<uint8_t> fan_speed_id_{};
|
||||
optional<uint8_t> fan_speed_low_value_{};
|
||||
optional<uint8_t> fan_speed_medium_value_{};
|
||||
optional<uint8_t> fan_speed_middle_value_{};
|
||||
optional<uint8_t> fan_speed_high_value_{};
|
||||
optional<uint8_t> fan_speed_auto_value_{};
|
||||
bool swing_vertical_{false};
|
||||
bool swing_horizontal_{false};
|
||||
bool heating_state_{false};
|
||||
bool cooling_state_{false};
|
||||
float manual_temperature_;
|
||||
bool eco_;
|
||||
bool sleep_;
|
||||
bool reports_fahrenheit_{false};
|
||||
};
|
||||
|
||||
|
|
|
@ -61,9 +61,11 @@ void UponorSmatrixComponent::loop() {
|
|||
|
||||
// Send packets during bus silence
|
||||
if ((now - this->last_rx_ > 300) && (now - this->last_poll_start_ < 9500) && (now - this->last_tx_ > 200)) {
|
||||
#ifdef USE_TIME
|
||||
// Only build time packet when bus is silent and queue is empty to make sure we can send it right away
|
||||
if (this->send_time_requested_ && this->tx_queue_.empty() && this->do_send_time_())
|
||||
this->send_time_requested_ = false;
|
||||
#endif
|
||||
// Send the next packet in the queue
|
||||
if (!this->tx_queue_.empty()) {
|
||||
auto packet = std::move(this->tx_queue_.front());
|
||||
|
@ -171,7 +173,9 @@ bool UponorSmatrixComponent::send(uint16_t device_address, const UponorSmatrixDa
|
|||
return false;
|
||||
|
||||
// Assemble packet for send queue. All fields are big-endian except for the little-endian checksum.
|
||||
std::vector<uint8_t> packet(6 + 3 * data_len);
|
||||
std::vector<uint8_t> packet;
|
||||
packet.reserve(6 + 3 * data_len);
|
||||
|
||||
packet.push_back(this->address_ >> 8);
|
||||
packet.push_back(this->address_ >> 0);
|
||||
packet.push_back(device_address >> 8);
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_TIME
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
#include "esphome/core/time.h"
|
||||
|
|
|
@ -44,6 +44,11 @@ def default_url(config):
|
|||
config[CONF_CSS_URL] = ""
|
||||
if not (CONF_JS_URL in config):
|
||||
config[CONF_JS_URL] = "https://oi.esphome.io/v2/www.js"
|
||||
if config[CONF_VERSION] == 3:
|
||||
if not (CONF_CSS_URL in config):
|
||||
config[CONF_CSS_URL] = ""
|
||||
if not (CONF_JS_URL in config):
|
||||
config[CONF_JS_URL] = "https://oi.esphome.io/v3/www.js"
|
||||
return config
|
||||
|
||||
|
||||
|
@ -64,7 +69,7 @@ CONFIG_SCHEMA = cv.All(
|
|||
{
|
||||
cv.GenerateID(): cv.declare_id(WebServer),
|
||||
cv.Optional(CONF_PORT, default=80): cv.port,
|
||||
cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, int=True),
|
||||
cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, 3, int=True),
|
||||
cv.Optional(CONF_CSS_URL): cv.string,
|
||||
cv.Optional(CONF_CSS_INCLUDE): cv.file_,
|
||||
cv.Optional(CONF_JS_URL): cv.string,
|
||||
|
@ -152,7 +157,7 @@ async def to_code(config):
|
|||
cg.add_define("USE_WEBSERVER")
|
||||
cg.add_define("USE_WEBSERVER_PORT", config[CONF_PORT])
|
||||
cg.add_define("USE_WEBSERVER_VERSION", version)
|
||||
if version == 2:
|
||||
if version >= 2:
|
||||
# Don't compress the index HTML as the data sizes are almost the same.
|
||||
add_resource_as_progmem("INDEX_HTML", build_index_html(config), compress=False)
|
||||
else:
|
||||
|
|
|
@ -358,7 +358,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
|||
stream->print(F("</article></body></html>"));
|
||||
request->send(stream);
|
||||
}
|
||||
#elif USE_WEBSERVER_VERSION == 2
|
||||
#elif USE_WEBSERVER_VERSION >= 2
|
||||
void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response =
|
||||
request->beginResponse_P(200, "text/html", ESPHOME_WEBSERVER_INDEX_HTML, ESPHOME_WEBSERVER_INDEX_HTML_SIZE);
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include <freertos/semphr.h>
|
||||
#endif
|
||||
|
||||
#if USE_WEBSERVER_VERSION == 2
|
||||
#if USE_WEBSERVER_VERSION >= 2
|
||||
extern const uint8_t ESPHOME_WEBSERVER_INDEX_HTML[] PROGMEM;
|
||||
extern const size_t ESPHOME_WEBSERVER_INDEX_HTML_SIZE;
|
||||
#endif
|
||||
|
|
|
@ -157,7 +157,7 @@ network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() {
|
|||
} else {
|
||||
addresses[0] = network::IPAddress(&ip.ip);
|
||||
}
|
||||
#if LWIP_IPV6
|
||||
#if USE_NETWORK_IPV6
|
||||
ip6_addr_t ipv6;
|
||||
err = tcpip_adapter_get_ip6_global(TCPIP_ADAPTER_IF_STA, &ipv6);
|
||||
if (err != ESP_OK) {
|
||||
|
@ -171,7 +171,7 @@ network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() {
|
|||
} else {
|
||||
addresses[2] = network::IPAddress(&ipv6);
|
||||
}
|
||||
#endif /* LWIP_IPV6 */
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
|
|
@ -21,10 +21,14 @@ extern "C" {
|
|||
#include <AddrList.h>
|
||||
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0)
|
||||
#include "LwipDhcpServer.h"
|
||||
#if USE_ARDUINO_VERSION_CODE < VERSION_CODE(3, 1, 0)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include "ESP8266WiFiAP.h"
|
||||
#define wifi_softap_set_dhcps_lease(lease) dhcpSoftAP.set_dhcps_lease(lease)
|
||||
#define wifi_softap_set_dhcps_lease_time(time) dhcpSoftAP.set_dhcps_lease_time(time)
|
||||
#define wifi_softap_set_dhcps_offer_option(offer, mode) dhcpSoftAP.set_dhcps_offer_option(offer, mode)
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
@ -721,7 +725,7 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
|
|||
return false;
|
||||
}
|
||||
|
||||
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0)
|
||||
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) && USE_ARDUINO_VERSION_CODE < VERSION_CODE(3, 1, 0)
|
||||
dhcpSoftAP.begin(&info);
|
||||
#endif
|
||||
|
||||
|
@ -745,12 +749,16 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
|
|||
return false;
|
||||
}
|
||||
|
||||
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 1, 0)
|
||||
ESP8266WiFiClass::softAPDhcpServer().setRouter(true); // send ROUTER option with netif's gateway IP
|
||||
#else
|
||||
uint8_t mode = 1;
|
||||
// bit0, 1 enables router information from ESP8266 SoftAP DHCP server.
|
||||
if (!wifi_softap_set_dhcps_offer_option(OFFER_ROUTER, &mode)) {
|
||||
ESP_LOGV(TAG, "wifi_softap_set_dhcps_offer_option failed!");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!wifi_softap_dhcps_start()) {
|
||||
ESP_LOGV(TAG, "Starting SoftAP DHCPS failed!");
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <utility>
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
|
@ -140,18 +141,35 @@ bool Component::is_ready() {
|
|||
(this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_SETUP;
|
||||
}
|
||||
bool Component::can_proceed() { return true; }
|
||||
bool Component::status_has_warning() { return this->component_state_ & STATUS_LED_WARNING; }
|
||||
bool Component::status_has_error() { return this->component_state_ & STATUS_LED_ERROR; }
|
||||
void Component::status_set_warning() {
|
||||
bool Component::status_has_warning() const { return this->component_state_ & STATUS_LED_WARNING; }
|
||||
bool Component::status_has_error() const { return this->component_state_ & STATUS_LED_ERROR; }
|
||||
void Component::status_set_warning(const char *message) {
|
||||
// Don't spam the log. This risks missing different warning messages though.
|
||||
if ((this->component_state_ & STATUS_LED_WARNING) != 0)
|
||||
return;
|
||||
this->component_state_ |= STATUS_LED_WARNING;
|
||||
App.app_state_ |= STATUS_LED_WARNING;
|
||||
ESP_LOGW(this->get_component_source(), "Warning set: %s", message);
|
||||
}
|
||||
void Component::status_set_error() {
|
||||
void Component::status_set_error(const char *message) {
|
||||
if ((this->component_state_ & STATUS_LED_ERROR) != 0)
|
||||
return;
|
||||
this->component_state_ |= STATUS_LED_ERROR;
|
||||
App.app_state_ |= STATUS_LED_ERROR;
|
||||
ESP_LOGE(this->get_component_source(), "Error set: %s", message);
|
||||
}
|
||||
void Component::status_clear_warning() {
|
||||
if ((this->component_state_ & STATUS_LED_WARNING) == 0)
|
||||
return;
|
||||
this->component_state_ &= ~STATUS_LED_WARNING;
|
||||
ESP_LOGW(this->get_component_source(), "Warning cleared");
|
||||
}
|
||||
void Component::status_clear_error() {
|
||||
if ((this->component_state_ & STATUS_LED_ERROR) == 0)
|
||||
return;
|
||||
this->component_state_ &= ~STATUS_LED_ERROR;
|
||||
ESP_LOGE(this->get_component_source(), "Error cleared");
|
||||
}
|
||||
void Component::status_clear_warning() { this->component_state_ &= ~STATUS_LED_WARNING; }
|
||||
void Component::status_clear_error() { this->component_state_ &= ~STATUS_LED_ERROR; }
|
||||
void Component::status_momentary_warning(const std::string &name, uint32_t length) {
|
||||
this->status_set_warning();
|
||||
this->set_timeout(name, length, [this]() { this->status_clear_warning(); });
|
||||
|
@ -211,8 +229,8 @@ WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {
|
|||
uint32_t now = millis();
|
||||
if (now - started_ > 50) {
|
||||
const char *src = component_ == nullptr ? "<null>" : component_->get_component_source();
|
||||
ESP_LOGW(TAG, "Component %s took a long time for an operation (%.2f s).", src, (now - started_) / 1e3f);
|
||||
ESP_LOGW(TAG, "Components should block for at most 20-30ms.");
|
||||
ESP_LOGW(TAG, "Component %s took a long time for an operation (%" PRIu32 " ms).", src, (now - started_));
|
||||
ESP_LOGW(TAG, "Components should block for at most 30 ms.");
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,13 +124,13 @@ class Component {
|
|||
|
||||
virtual bool can_proceed();
|
||||
|
||||
bool status_has_warning();
|
||||
bool status_has_warning() const;
|
||||
|
||||
bool status_has_error();
|
||||
bool status_has_error() const;
|
||||
|
||||
void status_set_warning();
|
||||
void status_set_warning(const char *message = "unspecified");
|
||||
|
||||
void status_set_error();
|
||||
void status_set_error(const char *message = "unspecified");
|
||||
|
||||
void status_clear_warning();
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@
|
|||
// ESP8266-specific feature flags
|
||||
#ifdef USE_ESP8266
|
||||
#define USE_ADC_SENSOR_VCC
|
||||
#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 0, 2)
|
||||
#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 1, 2)
|
||||
#define USE_ESP8266_PREFERENCES_FLASH
|
||||
#define USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||
#define USE_SOCKET_IMPL_LWIP_TCP
|
||||
|
|
|
@ -516,7 +516,8 @@ class ImportRequestHandler(BaseHandler):
|
|||
self.set_status(500)
|
||||
self.write("File already exists")
|
||||
return
|
||||
except ValueError:
|
||||
except ValueError as e:
|
||||
_LOGGER.error(e)
|
||||
self.set_status(422)
|
||||
self.write("Invalid package url")
|
||||
return
|
||||
|
@ -687,6 +688,11 @@ class MainRequestHandler(BaseHandler):
|
|||
@authenticated
|
||||
def get(self) -> None:
|
||||
begin = bool(self.get_argument("begin", False))
|
||||
if settings.using_password:
|
||||
# Simply accessing the xsrf_token sets the cookie for us
|
||||
self.xsrf_token # pylint: disable=pointless-statement
|
||||
else:
|
||||
self.clear_cookie("_xsrf")
|
||||
|
||||
self.render(
|
||||
"index.template.html",
|
||||
|
@ -1101,6 +1107,7 @@ def make_app(debug=get_bool_env(ENV_DEV)) -> tornado.web.Application:
|
|||
"log_function": log_function,
|
||||
"websocket_ping_interval": 30.0,
|
||||
"template_path": get_base_frontend_path(),
|
||||
"xsrf_cookies": settings.using_password,
|
||||
}
|
||||
rel = settings.relative_url
|
||||
return tornado.web.Application(
|
||||
|
|
|
@ -80,9 +80,9 @@ build_flags =
|
|||
; This are common settings for the ESP8266 using Arduino.
|
||||
[common:esp8266-arduino]
|
||||
extends = common:arduino
|
||||
platform = platformio/espressif8266@3.2.0
|
||||
platform = platformio/espressif8266@4.2.1
|
||||
platform_packages =
|
||||
platformio/framework-arduinoespressif8266@~3.30002.0
|
||||
platformio/framework-arduinoespressif8266@~3.30102.0
|
||||
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
|
|
|
@ -12,10 +12,11 @@ pyserial==3.5
|
|||
platformio==6.1.13 # When updating platformio, also update Dockerfile
|
||||
esptool==4.7.0
|
||||
click==8.1.7
|
||||
esphome-dashboard==20231107.0
|
||||
aioesphomeapi==23.1.1
|
||||
esphome-dashboard==20240319.0
|
||||
aioesphomeapi==23.2.0
|
||||
zeroconf==0.131.0
|
||||
python-magic==0.4.27
|
||||
ruamel.yaml==0.18.6 # dashboard_import
|
||||
|
||||
# esp-idf requires this, but doesn't bundle it by default
|
||||
# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24
|
||||
|
|
|
@ -8,6 +8,6 @@ pre-commit
|
|||
pytest==8.1.1
|
||||
pytest-cov==4.1.0
|
||||
pytest-mock==3.12.0
|
||||
pytest-asyncio==0.23.5.post1
|
||||
pytest-asyncio==0.23.6
|
||||
asyncmock==0.4.2
|
||||
hypothesis==6.92.1
|
||||
|
|
|
@ -70,11 +70,11 @@ def splitlines_no_ends(string):
|
|||
return [s.strip() for s in string.splitlines()]
|
||||
|
||||
|
||||
def changed_files():
|
||||
def changed_files(branch="dev"):
|
||||
check_remotes = ["upstream", "origin"]
|
||||
check_remotes.extend(splitlines_no_ends(get_output("git", "remote")))
|
||||
for remote in check_remotes:
|
||||
command = ["git", "merge-base", f"refs/remotes/{remote}/dev", "HEAD"]
|
||||
command = ["git", "merge-base", f"refs/remotes/{remote}/{branch}", "HEAD"]
|
||||
try:
|
||||
merge_base = splitlines_no_ends(get_output(*command))[0]
|
||||
break
|
||||
|
|
|
@ -120,13 +120,22 @@ def main():
|
|||
parser.add_argument(
|
||||
"-c", "--changed", action="store_true", help="Only run on changed files"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-b", "--branch", help="Branch to compare changed files against"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.branch and not args.changed:
|
||||
parser.error("--branch requires --changed")
|
||||
|
||||
files = git_ls_files()
|
||||
files = filter(filter_component_files, files)
|
||||
|
||||
if args.changed:
|
||||
changed = changed_files()
|
||||
if args.branch:
|
||||
changed = changed_files(args.branch)
|
||||
else:
|
||||
changed = changed_files()
|
||||
files = [f for f in files if f in changed]
|
||||
|
||||
components = extract_component_names_array_from_files_array(files)
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- homeassistant.event:
|
||||
event: esphome.button_pressed
|
||||
data:
|
||||
message: Button was pressed
|
||||
- homeassistant.service:
|
||||
service: notify.html5
|
||||
data:
|
||||
message: Button was pressed
|
||||
- homeassistant.tag_scanned: pulse
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- homeassistant.event:
|
||||
event: esphome.button_pressed
|
||||
data:
|
||||
message: Button was pressed
|
||||
- homeassistant.service:
|
||||
service: notify.html5
|
||||
data:
|
||||
message: Button was pressed
|
||||
- homeassistant.tag_scanned: pulse
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- homeassistant.event:
|
||||
event: esphome.button_pressed
|
||||
data:
|
||||
message: Button was pressed
|
||||
- homeassistant.service:
|
||||
service: notify.html5
|
||||
data:
|
||||
message: Button was pressed
|
||||
- homeassistant.tag_scanned: pulse
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- homeassistant.event:
|
||||
event: esphome.button_pressed
|
||||
data:
|
||||
message: Button was pressed
|
||||
- homeassistant.service:
|
||||
service: notify.html5
|
||||
data:
|
||||
message: Button was pressed
|
||||
- homeassistant.tag_scanned: pulse
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- homeassistant.event:
|
||||
event: esphome.button_pressed
|
||||
data:
|
||||
message: Button was pressed
|
||||
- homeassistant.service:
|
||||
service: notify.html5
|
||||
data:
|
||||
message: Button was pressed
|
||||
- homeassistant.tag_scanned: pulse
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- homeassistant.event:
|
||||
event: esphome.button_pressed
|
||||
data:
|
||||
message: Button was pressed
|
||||
- homeassistant.service:
|
||||
service: notify.html5
|
||||
data:
|
||||
message: Button was pressed
|
||||
- homeassistant.tag_scanned: pulse
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
|
|
@ -2,9 +2,12 @@ output:
|
|||
- platform: ledc
|
||||
id: light_output_1
|
||||
pin: 1
|
||||
channel: 0
|
||||
- platform: ledc
|
||||
id: light_output_2
|
||||
pin: 2
|
||||
channel: 1
|
||||
phase_angle: 180°
|
||||
|
||||
light:
|
||||
- platform: cwww
|
||||
|
|
|
@ -2,9 +2,12 @@ output:
|
|||
- platform: ledc
|
||||
id: light_output_1
|
||||
pin: 12
|
||||
channel: 0
|
||||
- platform: ledc
|
||||
id: light_output_2
|
||||
pin: 13
|
||||
channel: 1
|
||||
phase_angle: 180°
|
||||
|
||||
light:
|
||||
- platform: cwww
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
spi:
|
||||
- id: spi_main_lcd
|
||||
clk_pin: 16
|
||||
mosi_pin: 17
|
||||
miso_pin: 15
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
id: main_lcd
|
||||
model: ili9342
|
||||
cs_pin: 12
|
||||
dc_pin: 13
|
||||
reset_pin: 21
|
||||
lambda: |-
|
||||
// Draw an analog clock in the center of the screen
|
||||
int centerX = it.get_width() / 2;
|
||||
int centerY = it.get_height() / 2;
|
||||
int radius = min(it.get_width(), it.get_height()) / 4;
|
||||
|
||||
// Draw border
|
||||
it.circle(centerX, centerY, radius);
|
||||
|
||||
// Draw hour ticks
|
||||
for(int h = 0; h < 12; h++) {
|
||||
int hourAngle = (h * 30) - 90;
|
||||
|
||||
it.line_at_angle(centerX, centerY, hourAngle, radius - 10, radius);
|
||||
}
|
||||
|
||||
// Draw minute ticks
|
||||
for(int m = 0; m < 60; m++) {
|
||||
int minuteAngle = (m * 6) - 90;
|
||||
|
||||
it.line_at_angle(centerX, centerY, minuteAngle, radius - 5, radius);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
e131:
|
||||
|
||||
light:
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: led_matrix_32x8
|
||||
default_transition_length: 500ms
|
||||
chipset: ws2812
|
||||
rgb_order: GRB
|
||||
num_leds: 256
|
||||
pin: 2
|
||||
rmt_channel: 0
|
||||
effects:
|
||||
- e131:
|
||||
universe: 1
|
|
@ -0,0 +1,18 @@
|
|||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
e131:
|
||||
|
||||
light:
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: led_matrix_32x8
|
||||
default_transition_length: 500ms
|
||||
chipset: ws2812
|
||||
rgb_order: GRB
|
||||
num_leds: 256
|
||||
pin: 2
|
||||
rmt_channel: 0
|
||||
effects:
|
||||
- e131:
|
||||
universe: 1
|
|
@ -0,0 +1,18 @@
|
|||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
e131:
|
||||
|
||||
light:
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: led_matrix_32x8
|
||||
default_transition_length: 500ms
|
||||
chipset: ws2812
|
||||
rgb_order: GRB
|
||||
num_leds: 256
|
||||
pin: 2
|
||||
rmt_channel: 0
|
||||
effects:
|
||||
- e131:
|
||||
universe: 1
|
|
@ -0,0 +1,18 @@
|
|||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
e131:
|
||||
|
||||
light:
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: led_matrix_32x8
|
||||
default_transition_length: 500ms
|
||||
chipset: ws2812
|
||||
rgb_order: GRB
|
||||
num_leds: 256
|
||||
pin: 2
|
||||
rmt_channel: 0
|
||||
effects:
|
||||
- e131:
|
||||
universe: 1
|
|
@ -0,0 +1,17 @@
|
|||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
e131:
|
||||
|
||||
light:
|
||||
- platform: neopixelbus
|
||||
name: Neopixelbus Light
|
||||
pin: 1
|
||||
type: GRBW
|
||||
variant: SK6812
|
||||
method: ESP8266_UART0
|
||||
num_leds: 256
|
||||
effects:
|
||||
- e131:
|
||||
universe: 1
|
|
@ -0,0 +1,17 @@
|
|||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
e131:
|
||||
|
||||
light:
|
||||
- platform: rp2040_pio_led_strip
|
||||
id: led_strip
|
||||
pin: 2
|
||||
pio: 0
|
||||
num_leds: 256
|
||||
rgb_order: GRB
|
||||
chipset: WS2812
|
||||
effects:
|
||||
- e131:
|
||||
universe: 1
|
|
@ -0,0 +1,14 @@
|
|||
i2c:
|
||||
- id: i2c_ee895
|
||||
scl: 5
|
||||
sda: 4
|
||||
|
||||
sensor:
|
||||
- platform: ee895
|
||||
address: 0x5F
|
||||
co2:
|
||||
name: EE895 CO2
|
||||
temperature:
|
||||
name: EE895 Temperature
|
||||
pressure:
|
||||
name: EE895 Pressure
|
|
@ -0,0 +1,14 @@
|
|||
i2c:
|
||||
- id: i2c_ee895
|
||||
scl: 5
|
||||
sda: 4
|
||||
|
||||
sensor:
|
||||
- platform: ee895
|
||||
address: 0x5F
|
||||
co2:
|
||||
name: EE895 CO2
|
||||
temperature:
|
||||
name: EE895 Temperature
|
||||
pressure:
|
||||
name: EE895 Pressure
|
|
@ -0,0 +1,14 @@
|
|||
i2c:
|
||||
- id: i2c_ee895
|
||||
scl: 16
|
||||
sda: 17
|
||||
|
||||
sensor:
|
||||
- platform: ee895
|
||||
address: 0x5F
|
||||
co2:
|
||||
name: EE895 CO2
|
||||
temperature:
|
||||
name: EE895 Temperature
|
||||
pressure:
|
||||
name: EE895 Pressure
|
|
@ -0,0 +1,14 @@
|
|||
i2c:
|
||||
- id: i2c_ee895
|
||||
scl: 16
|
||||
sda: 17
|
||||
|
||||
sensor:
|
||||
- platform: ee895
|
||||
address: 0x5F
|
||||
co2:
|
||||
name: EE895 CO2
|
||||
temperature:
|
||||
name: EE895 Temperature
|
||||
pressure:
|
||||
name: EE895 Pressure
|
|
@ -0,0 +1,14 @@
|
|||
i2c:
|
||||
- id: i2c_ee895
|
||||
scl: 5
|
||||
sda: 4
|
||||
|
||||
sensor:
|
||||
- platform: ee895
|
||||
address: 0x5F
|
||||
co2:
|
||||
name: EE895 CO2
|
||||
temperature:
|
||||
name: EE895 Temperature
|
||||
pressure:
|
||||
name: EE895 Pressure
|
|
@ -0,0 +1,14 @@
|
|||
i2c:
|
||||
- id: i2c_ee895
|
||||
scl: 5
|
||||
sda: 4
|
||||
|
||||
sensor:
|
||||
- platform: ee895
|
||||
address: 0x5F
|
||||
co2:
|
||||
name: EE895 CO2
|
||||
temperature:
|
||||
name: EE895 Temperature
|
||||
pressure:
|
||||
name: EE895 Pressure
|
|
@ -0,0 +1,24 @@
|
|||
i2c:
|
||||
- id: i2c_ektf2232
|
||||
scl: 5
|
||||
sda: 4
|
||||
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
id: ssd1306_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: 3
|
||||
pages:
|
||||
- id: page1
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
|
||||
touchscreen:
|
||||
- platform: ektf2232
|
||||
interrupt_pin: 6
|
||||
rts_pin: 7
|
||||
display: ssd1306_display
|
||||
on_touch:
|
||||
- logger.log:
|
||||
format: Touch at (%d, %d)
|
||||
args: [touch.x, touch.y]
|
|
@ -0,0 +1,24 @@
|
|||
i2c:
|
||||
- id: i2c_ektf2232
|
||||
scl: 5
|
||||
sda: 4
|
||||
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
id: ssd1306_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: 3
|
||||
pages:
|
||||
- id: page1
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
|
||||
touchscreen:
|
||||
- platform: ektf2232
|
||||
interrupt_pin: 6
|
||||
rts_pin: 7
|
||||
display: ssd1306_display
|
||||
on_touch:
|
||||
- logger.log:
|
||||
format: Touch at (%d, %d)
|
||||
args: [touch.x, touch.y]
|
|
@ -0,0 +1,24 @@
|
|||
i2c:
|
||||
- id: i2c_ektf2232
|
||||
scl: 16
|
||||
sda: 17
|
||||
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
id: ssd1306_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: 13
|
||||
pages:
|
||||
- id: page1
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
|
||||
touchscreen:
|
||||
- platform: ektf2232
|
||||
interrupt_pin: 14
|
||||
rts_pin: 15
|
||||
display: ssd1306_display
|
||||
on_touch:
|
||||
- logger.log:
|
||||
format: Touch at (%d, %d)
|
||||
args: [touch.x, touch.y]
|
|
@ -0,0 +1,24 @@
|
|||
i2c:
|
||||
- id: i2c_ektf2232
|
||||
scl: 16
|
||||
sda: 17
|
||||
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
id: ssd1306_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: 13
|
||||
pages:
|
||||
- id: page1
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
|
||||
touchscreen:
|
||||
- platform: ektf2232
|
||||
interrupt_pin: 14
|
||||
rts_pin: 15
|
||||
display: ssd1306_display
|
||||
on_touch:
|
||||
- logger.log:
|
||||
format: Touch at (%d, %d)
|
||||
args: [touch.x, touch.y]
|
|
@ -0,0 +1,24 @@
|
|||
i2c:
|
||||
- id: i2c_ektf2232
|
||||
scl: 5
|
||||
sda: 4
|
||||
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
id: ssd1306_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: 3
|
||||
pages:
|
||||
- id: page1
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
|
||||
touchscreen:
|
||||
- platform: ektf2232
|
||||
interrupt_pin: 12
|
||||
rts_pin: 13
|
||||
display: ssd1306_display
|
||||
on_touch:
|
||||
- logger.log:
|
||||
format: Touch at (%d, %d)
|
||||
args: [touch.x, touch.y]
|
|
@ -0,0 +1,24 @@
|
|||
i2c:
|
||||
- id: i2c_ektf2232
|
||||
scl: 5
|
||||
sda: 4
|
||||
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
id: ssd1306_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: 3
|
||||
pages:
|
||||
- id: page1
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
|
||||
touchscreen:
|
||||
- platform: ektf2232
|
||||
interrupt_pin: 6
|
||||
rts_pin: 7
|
||||
display: ssd1306_display
|
||||
on_touch:
|
||||
- logger.log:
|
||||
format: Touch at (%d, %d)
|
||||
args: [touch.x, touch.y]
|
|
@ -0,0 +1,25 @@
|
|||
i2c:
|
||||
- id: i2c_emc2101
|
||||
scl: 5
|
||||
sda: 4
|
||||
|
||||
emc2101:
|
||||
pwm:
|
||||
resolution: 8
|
||||
|
||||
output:
|
||||
- platform: emc2101
|
||||
id: fan_duty_cycle
|
||||
|
||||
sensor:
|
||||
- platform: emc2101
|
||||
internal_temperature:
|
||||
id: internal_temperature_sensor
|
||||
name: Internal Temperature Sensor
|
||||
speed:
|
||||
id: speed_sensor
|
||||
name: Speed Sensor
|
||||
duty_cycle:
|
||||
id: duty_cycle_sensor
|
||||
name: Duty Cycle Sensor
|
||||
update_interval: 5s
|
|
@ -0,0 +1,25 @@
|
|||
i2c:
|
||||
- id: i2c_emc2101
|
||||
scl: 5
|
||||
sda: 4
|
||||
|
||||
emc2101:
|
||||
pwm:
|
||||
resolution: 8
|
||||
|
||||
output:
|
||||
- platform: emc2101
|
||||
id: fan_duty_cycle
|
||||
|
||||
sensor:
|
||||
- platform: emc2101
|
||||
internal_temperature:
|
||||
id: internal_temperature_sensor
|
||||
name: Internal Temperature Sensor
|
||||
speed:
|
||||
id: speed_sensor
|
||||
name: Speed Sensor
|
||||
duty_cycle:
|
||||
id: duty_cycle_sensor
|
||||
name: Duty Cycle Sensor
|
||||
update_interval: 5s
|
|
@ -0,0 +1,25 @@
|
|||
i2c:
|
||||
- id: i2c_emc2101
|
||||
scl: 16
|
||||
sda: 17
|
||||
|
||||
emc2101:
|
||||
pwm:
|
||||
resolution: 8
|
||||
|
||||
output:
|
||||
- platform: emc2101
|
||||
id: fan_duty_cycle
|
||||
|
||||
sensor:
|
||||
- platform: emc2101
|
||||
internal_temperature:
|
||||
id: internal_temperature_sensor
|
||||
name: Internal Temperature Sensor
|
||||
speed:
|
||||
id: speed_sensor
|
||||
name: Speed Sensor
|
||||
duty_cycle:
|
||||
id: duty_cycle_sensor
|
||||
name: Duty Cycle Sensor
|
||||
update_interval: 5s
|
|
@ -0,0 +1,25 @@
|
|||
i2c:
|
||||
- id: i2c_emc2101
|
||||
scl: 16
|
||||
sda: 17
|
||||
|
||||
emc2101:
|
||||
pwm:
|
||||
resolution: 8
|
||||
|
||||
output:
|
||||
- platform: emc2101
|
||||
id: fan_duty_cycle
|
||||
|
||||
sensor:
|
||||
- platform: emc2101
|
||||
internal_temperature:
|
||||
id: internal_temperature_sensor
|
||||
name: Internal Temperature Sensor
|
||||
speed:
|
||||
id: speed_sensor
|
||||
name: Speed Sensor
|
||||
duty_cycle:
|
||||
id: duty_cycle_sensor
|
||||
name: Duty Cycle Sensor
|
||||
update_interval: 5s
|
|
@ -0,0 +1,25 @@
|
|||
i2c:
|
||||
- id: i2c_emc2101
|
||||
scl: 5
|
||||
sda: 4
|
||||
|
||||
emc2101:
|
||||
pwm:
|
||||
resolution: 8
|
||||
|
||||
output:
|
||||
- platform: emc2101
|
||||
id: fan_duty_cycle
|
||||
|
||||
sensor:
|
||||
- platform: emc2101
|
||||
internal_temperature:
|
||||
id: internal_temperature_sensor
|
||||
name: Internal Temperature Sensor
|
||||
speed:
|
||||
id: speed_sensor
|
||||
name: Speed Sensor
|
||||
duty_cycle:
|
||||
id: duty_cycle_sensor
|
||||
name: Duty Cycle Sensor
|
||||
update_interval: 5s
|
|
@ -0,0 +1,25 @@
|
|||
i2c:
|
||||
- id: i2c_emc2101
|
||||
scl: 5
|
||||
sda: 4
|
||||
|
||||
emc2101:
|
||||
pwm:
|
||||
resolution: 8
|
||||
|
||||
output:
|
||||
- platform: emc2101
|
||||
id: fan_duty_cycle
|
||||
|
||||
sensor:
|
||||
- platform: emc2101
|
||||
internal_temperature:
|
||||
id: internal_temperature_sensor
|
||||
name: Internal Temperature Sensor
|
||||
speed:
|
||||
id: speed_sensor
|
||||
name: Speed Sensor
|
||||
duty_cycle:
|
||||
id: duty_cycle_sensor
|
||||
name: Duty Cycle Sensor
|
||||
update_interval: 5s
|
|
@ -0,0 +1,33 @@
|
|||
binary_sensor:
|
||||
- platform: template
|
||||
id: bin1
|
||||
lambda: |-
|
||||
if (millis() > 10000) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
id: template_switch1
|
||||
optimistic: true
|
||||
- platform: template
|
||||
id: template_switch2
|
||||
optimistic: true
|
||||
|
||||
cover:
|
||||
- platform: endstop
|
||||
id: endstop_cover
|
||||
name: Endstop Cover
|
||||
stop_action:
|
||||
- switch.turn_on: template_switch1
|
||||
open_endstop: bin1
|
||||
open_action:
|
||||
- switch.turn_on: template_switch1
|
||||
open_duration: 5min
|
||||
close_endstop: bin1
|
||||
close_action:
|
||||
- switch.turn_on: template_switch2
|
||||
close_duration: 4.5min
|
||||
max_duration: 10min
|
|
@ -0,0 +1,33 @@
|
|||
binary_sensor:
|
||||
- platform: template
|
||||
id: bin1
|
||||
lambda: |-
|
||||
if (millis() > 10000) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
id: template_switch1
|
||||
optimistic: true
|
||||
- platform: template
|
||||
id: template_switch2
|
||||
optimistic: true
|
||||
|
||||
cover:
|
||||
- platform: endstop
|
||||
id: endstop_cover
|
||||
name: Endstop Cover
|
||||
stop_action:
|
||||
- switch.turn_on: template_switch1
|
||||
open_endstop: bin1
|
||||
open_action:
|
||||
- switch.turn_on: template_switch1
|
||||
open_duration: 5min
|
||||
close_endstop: bin1
|
||||
close_action:
|
||||
- switch.turn_on: template_switch2
|
||||
close_duration: 4.5min
|
||||
max_duration: 10min
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue