This commit is contained in:
clydebarrow 2024-03-21 15:35:50 +11:00
commit 1f05286ef4
346 changed files with 6054 additions and 396 deletions

View File

@ -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

View File

@ -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

81
.github/workflows/ci-api-proto.yml vendored Normal file
View File

@ -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.'
});
}
}

View File

@ -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

View File

@ -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

View File

@ -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 }}

View File

@ -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>

View File

@ -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

View File

@ -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];

View File

@ -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; }

View File

@ -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

View File

@ -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")

View File

@ -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) {

View File

@ -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

View File

@ -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);

View File

@ -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))

View File

@ -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--;

View File

@ -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",

View File

@ -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;

View File

@ -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(

View File

@ -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;

View File

@ -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;
}

View File

@ -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> {

View File

@ -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;
}
}

View File

@ -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")

View File

@ -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.

View File

@ -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) {

View File

@ -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]),
)
)
)

View File

@ -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

View File

@ -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;
}

View File

@ -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);
}
};

View File

@ -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_);
}

View File

@ -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,

View File

@ -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);
}

View File

@ -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

View File

@ -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;

View File

@ -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]))

View File

@ -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_();

View File

@ -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};

View File

@ -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]),

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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))

View File

@ -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;
}
}
}

View File

@ -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};
};

View File

@ -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);

View File

@ -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"

View File

@ -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:

View File

@ -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);

View File

@ -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

View File

@ -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;
}

View File

@ -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!");

View File

@ -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.");
;
}
}

View File

@ -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();

View File

@ -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

View File

@ -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(

View File

@ -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 =

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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]

View File

@ -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]

View File

@ -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]

View File

@ -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]

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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